什么是多视图

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

今天我们来看看如何用DirectX实现这种功能。多视图如何落实到DirectX中就是多个Viewport,当然也有其他方法,比如多个子窗口,每个子窗口对应一个swapchain,这种实现比较复杂,以后再说。现在先看多个Viewport的情况。那么到底什么是viewport呢?大家先看一下中文意思,一般翻译为视口。举个现实中的例子,假设你站在一个密封的房子里,这个房子只有一个很小的窗口,你现在就站在窗口前面,通过这个窗口你可以观察到外面的世界,那么这个窗口就相当于一个视口,而外面的世界就是3D中的场景。视口有以下几个属性,长度和宽度,为了确定窗口的位置,我们还需要一个左上角坐标。为了支持Z-Buffer,还需要两个深度值,分别是zMin, zMax,表示最小深度和最大深度。好了,这就是视口的定义。在D3D中,视口用下面的结构体来表示。

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} ;

设置视口并渲染

视口设置好以后,就可以进行绘制了,这里设置一个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();
}
}

在D3D中可以通过函数SetViewport来设置viewport,设置完成以后要立即绘制该视口对应的场景,如果想将所有的视口设置完再统一绘制,那么后面的视口就会覆盖前面的,从而无法达到预期效果。注意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 );

好了,看一下效果图

== THE END ==

Happy Coding!!!

作者: zdd 发表于 2011-03-26 09:16 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架