概览

在前面的教程中,我们将一个立方体从模型空间变换到了屏幕空间并进行了绘制。在本教程中,我们将拓展变换的概念,展示一个使用这些变换实现的简单动画。

这个教程的输出结果是一个物体绕着另一个物体旋转,用来展示变换以及如何组合这些变换达到所需的效果。后面的教程会建立在这个教程之上介绍新的概念。

源代码

(SDK root)\Samples\C++\Direct3D10\Tutorials\Tutorial05

变换

在3D图形学中,变换常用在顶点和矢量上,变换还用于将它们从一个空间变换到另一个。变换的操作是通过矩阵乘法实现的。典型的有三种基本变换:平移,旋转和缩放。此外,还有投影变换用于从视空间变换到投影空间。D3DX库包含许多可以便利地构建矩阵的API,这些矩阵可以用于平移、旋转、缩放,世界-视空间变换,视-投影空间变换等。应用程序可以使用这些矩阵对顶点进行变换。对矩阵有个基本的理解是必须的,下面我们来看几个例子。

平移

平移变换表示在空间中移动一段距离。在3D中,用于平移的矩阵有以下的形式:

1 0 0 0
0 1 0 0
0 0 1 0
a b c 1

其中矢量(a, b, c)定义了移动的方向和大小。例如,要将一个顶点沿X轴移动-5个单位(负X轴方向),我们可以将这个顶点乘以以下矩阵:

1 0 0 0
0 1 0 0
0 0 1 0
-5 0 0 1

如果我们将它施加在一个位于原点的立方体上,结果就是盒子沿负X轴移动5个单位,如图1所示。




图1. 平移效果


在3D中,空间通常是由一个原点和从原点出发的三条轴定义的。在计算机图形学中通常使用好几个空间:对象空间,世界空间,视空间,投影空间和屏幕空间。




图2. 定义在对象空间中的立方体


旋转

旋转表示绕一条过原点的轴转动顶点,这些轴就是X,Y和Z轴。2D中的一个例子就是将矢量[1 0]逆时针旋转90度,结果是矢量[0 1]。下面的矩阵用来绕Y轴顺时针旋转θ度:

cosθ 0 -sinθ 0
0 1 0 0
sinθ 0 cosθ 0
0 0 0 1

图3显示了将一个立方体绕Y轴旋转45度的效果。




图3. 绕Y轴旋转的效果


缩放

缩放表示沿轴方向放大或缩小矢量的分量大小。例如,一个矢量可以沿所有方向放大,也可以单独沿X轴方向缩小。通常使用的缩放矩阵如下:

p 0 0 0
0 q 0 0
0 0 r 0
0 0 0 1

其中p,q和r表示沿X,Y和Z方向的缩放因子。下图表示沿X轴放大2倍,沿Y轴缩小0.5倍的效果。




图4. 缩放效果


多重变换

要在一个矢量上施加多重变换,只需简单地首先乘以第一个变换矩阵,然后将结果乘以第二个变换矩阵,依此类推。因为矢量和矩阵乘法符合结合律,我们可以首先将所有矩阵相乘,然后 用矢量乘以结果矩阵也能获得相同的结果。下图展示了在一个立方体上组合旋转和平移变换的效果。




图5. 旋转和平移的效果


创建环绕轨道

本教程中,我们会对两个立方体进行变换。第一个立方体会在原位置旋转,而第二个立方体绕第一个旋转,同时绕自身轴旋转。这两个立方体都有各自的世界变换矩阵,这些变换在每一帧都会重新施加。

D3DX中有一些方法可以帮助你创建旋转、平移和缩放矩阵。

绕X、Y和Z的旋转可以使用D3DXMatrixRotationX,D3DXMatrixRotationY和D3DXMatrixRotationZ方法实现,它们创建了绕轴旋转的矩阵。绕其他轴的复杂旋转可以通过以上基本旋转的乘积实现。

平移使用的是D3DXMatrixTranslation方法,这个方法可以创建一个平移矩阵,移动的信息保存在传递的参数中。

缩放使用的是D3DXMatrixScaling方法。这个方法只会沿着3根基本轴缩放,如果想要沿任意轴缩放,那么需要乘以一个正确的旋转矩阵才能达到效果。

第一个立方体原地旋转,作为圆轨道的中心。这个立方体有一个绕Y轴的旋转,这是通过调用D3DXMatrixRotationY方法实现,。它每帧旋转一定的数量。因为需要立方体持续地旋转,旋转矩阵的参数是递增的。




1



2


// 1st Cube: Rotate around the origin



D3DXMatrixRotationY( &g_World1, t );



第二个立方体绕第一个旋转。为了介绍多重变换,添加了一个缩放因子和绕自身轴的旋转。公式就在代码的下面(在注释中)。首先这个立方体缩小到30%大小,然后绕自身轴旋转 (本例中为Z轴)。要模拟轨道的运动,需要先平移出原点然后绕Y轴旋转。要达到效果,可以使用4个独立变换的矩阵,然后将他们相乘。




1



2



3



4



5



6



7



8



9



10



11



12



13


// 2nd Cube: Rotate around origin



D3DXMATRIX mTranslate;



D3DXMATRIX mOrbit;



D3DXMATRIX mSpin;



D3DXMATRIX mScale;



D3DXMatrixRotationZ( &mSpin, -t );



D3DXMatrixRotationY( &mOrbit, -t*2.0f );



D3DXMatrixTranslation( &mTranslate, -4.0f, 0.0f, 0.0f );



D3DXMatrixScaling( &mScale, 0.3f, 0.3f, 0.3f );



D3DXMatrixMultiply( &g_World2, &mScale, &mSpin );



D3DXMatrixMultiply( &g_World2, &g_World2, &mTranslate );



D3DXMatrixMultiply( &g_World2, &g_World2, &mOrbit );



//g_World2 = mScale * mSpin * mTranslate * mOrbit;



以上操作不满足交换律,变换的顺序非常重要。

你可以改变一下变换的顺序观察导致的结果。

所有变换方法都会从参数中创建一个矩阵,旋转的值是递增的,这可以通过更新“time”变量做到。




1



2


// Update our time



t += D3DX_PI * 0.0125f;



在进行绘制前,technique需要为shader获取变量。本例中,世界矩阵、视矩阵和投影矩阵会被绑定到technique。注意两个立方体的世界矩阵不同,需要独立设置。




1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18



19



20



21



22



23



24



25


//



// Render the first cube



//



D3D10_TECHNIQUE_DESC techDesc;



g_pTechnique->GetDesc( &techDesc );



for ( UINT p = 0; p < techDesc.Passes; ++p )



{



g_pTechnique->GetPassByIndex( p )->Apply(0);



g_pd3dDevice->DrawIndexed( 36, 0, 0 );



}



 



//



// Update variables for the second cube



//



g_pWorldVariable->SetMatrix( ( float *)&g_World2 );



g_pViewVariable->SetMatrix( ( float *)&g_View );



g_pProjectionVariable->SetMatrix( ( float *)&g_Projection );



//



// Render the second cube



//



for ( UINT p = 0; p < techDesc.Passes; ++p )



{



g_pTechnique->GetPassByIndex( p )->Apply(0);



g_pd3dDevice->DrawIndexed( 36, 0, 0 );



}



深度缓存

本教程还有一个重要的补充是深度缓存。没有它,当小物体绕到大物体背后时,仍会绘制在大物体前面。深度缓存让Direct3D保存了平面上每个像素的深度。在Direct3D 10中,深度缓存的默认行为是将绘制在屏幕上的每个像素的深度与存储在深度缓冲中的深度进行比较。如果正在进行绘制的像素的深度小于等于存储在深度缓存中的,则绘制这个像素,并将深度缓存中的深度值更新为新绘制的这个像素的深度值。相反,如果正在绘制的像素的深度值大于深度缓存中的,则该像素就不进行绘制,深度缓存中的值保持不变。

下面的代码创建了一个深度缓存(一个DepthStencil纹理)。还创建了一个深度缓存的DepthStencilView,这样Direct3D 10就知道将它作为一个Depth Stencil纹理。




1



2



3



4



5



6



7



8



9



10



11



12



13



14



15



16



17



18



19



20



21



22



23



24


// Create depth stencil texture



D3D10_TEXTURE2D_DESC descDepth;



descDepth.Width = width;



descDepth.Height = height;



descDepth.MipLevels = 1;



descDepth.ArraySize = 1;



descDepth.Format = DXGI_FORMAT_D32_FLOAT;



descDepth.SampleDesc.Count = 1;



descDepth.SampleDesc.Quality = 0;



descDepth.Usage = D3D10_USAGE_DEFAULT;



descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;



descDepth.CPUAccessFlags = 0;



descDepth.MiscFlags = 0;



hr = g_pd3dDevice->CreateTexture2D( &descDepth, NULL, &g_pDepthStencil );



if ( FAILED(hr) )



return hr;



// Create the depth stencil view



D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;



descDSV.Format = descDepth.Format;



descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;



descDSV.Texture2D.MipSlice = 0;



hr = g_pd3dDevice->CreateDepthStencilView( g_pDepthStencil, &descDSV, &g_pDepthStencilView );



if ( FAILED(hr) )



return hr;



为了使用这个新创建的深度模版缓存,必须将它绑定到设备。这是通过将深度缓存视图作为OMSetRenderTargets方法的第三个参数实现的。




1


g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, g_pDepthStencilView);



因为处理的是渲染目标,我们还需要在绘制前清除深度缓存。这可以确保前一帧的深度值不会错误地抛弃当前帧的像素。下面的代码将深度缓存全部设置为最大值1.0。



1



2



3



4


//



// Clear the depth buffer to 1.0 (max depth)



//



g_pd3dDevice->ClearDepthStencilView( g_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );