用DirectX实现多视图渲染

 

什么是多视图

一般的3D程序都只有一个视图,对应整个窗口的客户区。多视图就是在一个窗口中放置多个视图,以便从不同的角度观察模型或者场景。很多图形软件都有这个功能,比如大家熟知的3DMax就有四个视图,分别是前视图,左视图,顶视图和透视图。还有一些游戏引擎也有类似的Demo,比如irrlicht引擎中的SplitScreen就是用多视图实现的,如下图。

什么是视口(viewport)?

用DirectX实现多视图有几种方法,可以使用多个Viewport,也可以使用多个Swap Chain,后者实现起来比较复杂,以后再做介绍,先看如何使用多个viewport来实现。那么到底什么是viewport呢?举个现实中的例子,假设你站在一个密封的房子里,这个房子只有一个很小的窗口,你现在就站在窗口前面,通过这个窗口你可以观察到外面的世界,那么这个窗口就相当于一个视口,而外面的世界就是3D中的场景。视口有以下几个属性,长度和宽度,为了确定窗口的位置,我们还需要一个左上角坐标。为了支持Z-Buffer,还需要两个深度值,分别是zMin, zMax,表示最小深度和最大深度。好了,这就是视口的定义。在D3D中,视口用下面的结构体来表示,X和Y表示视口的左上角坐标,Width和Height表示窗口的宽度和高度,MinZ和MaxZ表示Z-buffer的最小值和最大值。

复制代码
typedef struct D3DVIEWPORT9 {
    DWORD X;
    DWORD Y;
    DWORD Width;
    DWORD Height;
    float MinZ;
    float MaxZ;
} D3DVIEWPORT9, *LPD3DVIEWPORT9;
复制代码

实现多个视口渲染需要以下几个步骤。

  • 创建主窗口
  • 将主窗口分割为四个区域
  • 设置每个区域对应的视口并渲染

创建主窗口

复制代码
WNDCLASSEX winClass ;

winClass.lpszClassName = "MultiViewports";
winClass.cbSize = sizeof(WNDCLASSEX);
winClass.style = CS_HREDRAW | CS_VREDRAW;
winClass.lpfnWndProc = MsgProc;
winClass.hInstance = hInstance;
winClass.hIcon = NULL ;
winClass.hIconSm = NULL ;
winClass.hCursor = LoadCursor(NULL, IDC_ARROW) ;
winClass.hbrBackground = NULL ;
winClass.lpszMenuName = NULL ;
winClass.cbClsExtra = 0;
winClass.cbWndExtra = 0;

RegisterClassEx (&winClass) ;

HWND hWnd = CreateWindowEx(NULL,
                           winClass.lpszClassName, // window class name
                           "MultiViewports", // window caption
                           WS_OVERLAPPEDWINDOW, // window style
                           32, // initial x position
                           32, // initial y position
                           600, // initial window width
                           600, // initial window height
                           NULL, // parent window handle
                           NULL, // window menu handle
                           hInstance, // program instance handle
                           NULL) ; // creation parameters

// Create window failed
if(hWnd == NULL)
{
    MessageBoxA(hWnd, "Create Window failed!", "Error", 0) ;
    return -1 ;
}
复制代码

分割主窗口

首先通过GetClientRect函数获得窗口的尺寸,然后将其横竖一分为二,这样整个窗口被分割为四部分,分别对应四个视口区域。如下图。这一步并没有实际的代码对应,而是在创建视口的时候完成的。如果窗口的左上角坐标是(x, y), 长宽分别是width和height,那么对应的四个视口分别是

viewport1  =  { 0 0 , width  /   2 , height  /   2 0.0f 1.0f } ;
viewport2 
=  {width  /   2 0 , width  /   2 , height  /   2 0.0f 1.0f } ;
viewport3 
=  { 0 , height  /   2 , width  /   2 , height  /   2 0.0f 1.0f } ;
viewport4 
=  {width  /   2 , height  /   2 , width  /   2 , height  /   2 0.0f 1.0f } ;

设置视口并渲染

视口定义好以后,使用SetViewport函数进行设置,然后就可以绘制视口对应的场景了。由于一共需要设置四个视口,为了避免代码重复,这里设置一个Draw函数,用来绘制每个视口中的场景,该函数有两个参数,第一个是待绘制的视口,第二个是视口的背景颜色。每设置一个视口,就调用这个函数一次。

复制代码
void Draw(D3DVIEWPORT9* viewport, DWORD color)
{
    g_pd3dDevice->SetViewport(viewport) ;
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0 );

    // Begin the scene
    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
    {
        // Draw teapot
        g_pTeapotMesh->DrawSubset(0) ;

        // End the scene
        g_pd3dDevice->EndScene();
    }
}
复制代码

在Draw函数内部,使用D3D函数SetViewport来设置viewport,设置完成以后要立即绘制该视口对应的场景,倘若一次性设置四个视口,然后在绘制每个视口对应的场景,那么后面的场景就会覆盖前面的,无法达到预期效果,所以正确的顺序是

设置视口1

绘制视口1的场景

设置视口2

绘制视口2的场景

设置视口3

绘制视口3的场景

设置视口4

绘制视口4的场景

而下面这样则是不对的

设置视口1

设置视口2

设置视口3

设置视口4

绘制视口1的场景

绘制视口2的场景

绘制视口3的场景

绘制视口4的场景

注意Present函数每个frame调用一次即可,而不是每次设置viewport都调用,那样的话屏幕会闪烁。为了分别从不同角度观察模型,可以为每个视口单独设置camera,分别对应前视图,左视图,顶视图及透视图。

1. 设置前视图

复制代码
//  Setup camera, front view
D3DXVECTOR3 eyePt( 0.0f 0.0f - 5.0f ) ;
D3DXVECTOR3 lookAt(
0.0f 0.0f 0.0f ) ;
D3DXVECTOR3 upVec(
0.0f 1.0f 0.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;
//  Draw top-left viewport
D3DVIEWPORT9 viewport1  =  { 0 0 , vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport1,  0xffff0000 ) ;
复制代码

2. 设置左视图

复制代码
//  Setup camera, left view
eyePt  =  D3DXVECTOR3( - 5.0f 0.0f 0.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;

//  Draw top-right viewport
D3DVIEWPORT9 viewport2  =  {vpWidth,  0 , vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport2,  0xff00ff00 ) ;
复制代码

3. 设置顶视图

复制代码
//  Setup camera, top view
eyePt  =  D3DXVECTOR3( 0.0f 5.0f 0.0f ) ;
upVec 
=  D3DXVECTOR3( 0.0f 0.0f 1.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;

//  Draw bottom-left viewport
D3DVIEWPORT9 viewport3  =  { 0 , vpHeight, vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport3,  0xff0000ff ) ;
复制代码

4. 设置透视图

复制代码
//  Setup camera, perspective view
eyePt  =  D3DXVECTOR3( - 3.0f 3.0f - 3.0f ) ;
upVec 
=  D3DXVECTOR3( 1.0f 2.0f 1.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;

//  Draw bottom-right viewport
D3DVIEWPORT9 viewport4  =  {vpWidth, vpHeight, vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport4,  0xffffff00 ) ;
复制代码

渲染

//  Present the back-buffer contents to the display
g_pd3dDevice -> Present( NULL, NULL, NULL, NULL );

好了,看一下效果图

什么是多视图

一般的3D程序都只有一个视图,对应整个窗口的客户区。多视图就是在一个窗口中放置多个视图,以便从不同的角度观察模型或者场景。很多图形软件都有这个功能,比如大家熟知的3DMax就有四个视图,分别是前视图,左视图,顶视图和透视图。还有一些游戏引擎也有类似的Demo,比如irrlicht引擎中的SplitScreen就是用多视图实现的,如下图。

什么是视口(viewport)?

用DirectX实现多视图有几种方法,可以使用多个Viewport,也可以使用多个Swap Chain,后者实现起来比较复杂,以后再做介绍,先看如何使用多个viewport来实现。那么到底什么是viewport呢?举个现实中的例子,假设你站在一个密封的房子里,这个房子只有一个很小的窗口,你现在就站在窗口前面,通过这个窗口你可以观察到外面的世界,那么这个窗口就相当于一个视口,而外面的世界就是3D中的场景。视口有以下几个属性,长度和宽度,为了确定窗口的位置,我们还需要一个左上角坐标。为了支持Z-Buffer,还需要两个深度值,分别是zMin, zMax,表示最小深度和最大深度。好了,这就是视口的定义。在D3D中,视口用下面的结构体来表示,X和Y表示视口的左上角坐标,Width和Height表示窗口的宽度和高度,MinZ和MaxZ表示Z-buffer的最小值和最大值。

复制代码
typedef struct D3DVIEWPORT9 {
    DWORD X;
    DWORD Y;
    DWORD Width;
    DWORD Height;
    float MinZ;
    float MaxZ;
} D3DVIEWPORT9, *LPD3DVIEWPORT9;
复制代码

实现多个视口渲染需要以下几个步骤。

  • 创建主窗口
  • 将主窗口分割为四个区域
  • 设置每个区域对应的视口并渲染

创建主窗口

复制代码
WNDCLASSEX winClass ;

winClass.lpszClassName = "MultiViewports";
winClass.cbSize = sizeof(WNDCLASSEX);
winClass.style = CS_HREDRAW | CS_VREDRAW;
winClass.lpfnWndProc = MsgProc;
winClass.hInstance = hInstance;
winClass.hIcon = NULL ;
winClass.hIconSm = NULL ;
winClass.hCursor = LoadCursor(NULL, IDC_ARROW) ;
winClass.hbrBackground = NULL ;
winClass.lpszMenuName = NULL ;
winClass.cbClsExtra = 0;
winClass.cbWndExtra = 0;

RegisterClassEx (&winClass) ;

HWND hWnd = CreateWindowEx(NULL,
                           winClass.lpszClassName, // window class name
                           "MultiViewports", // window caption
                           WS_OVERLAPPEDWINDOW, // window style
                           32, // initial x position
                           32, // initial y position
                           600, // initial window width
                           600, // initial window height
                           NULL, // parent window handle
                           NULL, // window menu handle
                           hInstance, // program instance handle
                           NULL) ; // creation parameters

// Create window failed
if(hWnd == NULL)
{
    MessageBoxA(hWnd, "Create Window failed!", "Error", 0) ;
    return -1 ;
}
复制代码

分割主窗口

首先通过GetClientRect函数获得窗口的尺寸,然后将其横竖一分为二,这样整个窗口被分割为四部分,分别对应四个视口区域。如下图。这一步并没有实际的代码对应,而是在创建视口的时候完成的。如果窗口的左上角坐标是(x, y), 长宽分别是width和height,那么对应的四个视口分别是

viewport1  =  { 0 0 , width  /   2 , height  /   2 0.0f 1.0f } ;
viewport2 
=  {width  /   2 0 , width  /   2 , height  /   2 0.0f 1.0f } ;
viewport3 
=  { 0 , height  /   2 , width  /   2 , height  /   2 0.0f 1.0f } ;
viewport4 
=  {width  /   2 , height  /   2 , width  /   2 , height  /   2 0.0f 1.0f } ;

设置视口并渲染

视口定义好以后,使用SetViewport函数进行设置,然后就可以绘制视口对应的场景了。由于一共需要设置四个视口,为了避免代码重复,这里设置一个Draw函数,用来绘制每个视口中的场景,该函数有两个参数,第一个是待绘制的视口,第二个是视口的背景颜色。每设置一个视口,就调用这个函数一次。

复制代码
void Draw(D3DVIEWPORT9* viewport, DWORD color)
{
    g_pd3dDevice->SetViewport(viewport) ;
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0 );

    // Begin the scene
    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
    {
        // Draw teapot
        g_pTeapotMesh->DrawSubset(0) ;

        // End the scene
        g_pd3dDevice->EndScene();
    }
}
复制代码

在Draw函数内部,使用D3D函数SetViewport来设置viewport,设置完成以后要立即绘制该视口对应的场景,倘若一次性设置四个视口,然后在绘制每个视口对应的场景,那么后面的场景就会覆盖前面的,无法达到预期效果,所以正确的顺序是

设置视口1

绘制视口1的场景

设置视口2

绘制视口2的场景

设置视口3

绘制视口3的场景

设置视口4

绘制视口4的场景

而下面这样则是不对的

设置视口1

设置视口2

设置视口3

设置视口4

绘制视口1的场景

绘制视口2的场景

绘制视口3的场景

绘制视口4的场景

注意Present函数每个frame调用一次即可,而不是每次设置viewport都调用,那样的话屏幕会闪烁。为了分别从不同角度观察模型,可以为每个视口单独设置camera,分别对应前视图,左视图,顶视图及透视图。

1. 设置前视图

复制代码
//  Setup camera, front view
D3DXVECTOR3 eyePt( 0.0f 0.0f - 5.0f ) ;
D3DXVECTOR3 lookAt(
0.0f 0.0f 0.0f ) ;
D3DXVECTOR3 upVec(
0.0f 1.0f 0.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;
//  Draw top-left viewport
D3DVIEWPORT9 viewport1  =  { 0 0 , vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport1,  0xffff0000 ) ;
复制代码

2. 设置左视图

复制代码
//  Setup camera, left view
eyePt  =  D3DXVECTOR3( - 5.0f 0.0f 0.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;

//  Draw top-right viewport
D3DVIEWPORT9 viewport2  =  {vpWidth,  0 , vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport2,  0xff00ff00 ) ;
复制代码

3. 设置顶视图

复制代码
//  Setup camera, top view
eyePt  =  D3DXVECTOR3( 0.0f 5.0f 0.0f ) ;
upVec 
=  D3DXVECTOR3( 0.0f 0.0f 1.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;

//  Draw bottom-left viewport
D3DVIEWPORT9 viewport3  =  { 0 , vpHeight, vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport3,  0xff0000ff ) ;
复制代码

4. 设置透视图

复制代码
//  Setup camera, perspective view
eyePt  =  D3DXVECTOR3( - 3.0f 3.0f - 3.0f ) ;
upVec 
=  D3DXVECTOR3( 1.0f 2.0f 1.0f ) ;
SetupCamera(
& eyePt,  & lookAt,  & upVec) ;

//  Draw bottom-right viewport
D3DVIEWPORT9 viewport4  =  {vpWidth, vpHeight, vpWidth, vpHeight,  0.0f 1.0f } ;
Draw(
& viewport4,  0xffffff00 ) ;
复制代码

渲染

//  Present the back-buffer contents to the display
g_pd3dDevice -> Present( NULL, NULL, NULL, NULL );

好了,看一下效果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值