【DirectX 11 SDK 学习笔记】3D Transformation

Summary

在上一个的例子里面,将一个model sapce 中的立方体渲染到了屏幕上。在这个例子中,我们将扩展变换的概念并且演示通过这些变换可以做出来的简单的动画。

这个例子的最终显示结果是一个物体沿着轨道绕另一个物体旋转,非常适合说明变换和为了达到需要的效果该如何将变换结合在一起。

 

Transformation

3D图像中,transformation 经常被用来操作点和向量,也用来将他们从一个空间变换到另一个空间,Transformation 通过乘以一个矩阵这种方式来实现。在顶点上执行的三种典型的基础变换:平移(在空间中相对于原点的位置),旋转(方向和x,y,z轴的关系),缩放(和原点的距离)。除了那些,投影变换通常用来将view space 变换到projection spaceXNA 数学库中有很方便构建各种功能矩阵的API,像平移,旋转,缩放,world-to-view 变换,view-to-projection变换,等等。应用程序就可以在场景中使用这些矩阵来对顶点做变换。对于矩阵变换需要有最基本的了解,我们来简要的看一看下面的例子:

Translation

平移涉及到移动或者放置物体到空间中的指定点。在3D中,用来平移变换的矩阵格式如下:

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

矩阵里面的(a,b,c) 定义了移动的方向和距离。例如,将一个点沿X轴移动-5个单位长度,我们可以乘以下面的平移矩阵:

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

如果我们对一个处于原点的正方体运用该变换,结果是这个物体沿着x轴的负方向移动5个单位长度,变换后的结果,如图所示:

3D中,一个空间通常用一个原点和三个不同的轴XYZ定义,计算机图形学中常用的空间有:object space, world space, view space, projection space, and screen space.

Rotation

旋转变换指的是将一个顶点绕着一个经过原点的轴旋转。空间中有XYZ三个这样的轴。一个2D的例子或许是将向量[ 1 0 ]沿着逆时针方向旋转90°。旋转的结果是[ 0 1 ] 。用来沿着y轴逆时针旋转t°的的矩阵看起来这样:

    cosΐ  0  -sinΐ   0
     0    1     0    0
    sinΐ  0   cosΐ  0
     0    0     0    1

3显示了将立方体在原点处沿着y轴旋转45°之后的效果:

Scaling

缩放变换是指放大或者缩小向量在各个轴的分量。例如,一个向量可以沿着所有方向或者只是沿着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的效果:

Multiple Transformations

在一个向量上执行多种变换,我们只需要简单的将向量乘以第一个变换矩阵,然后在将结果再乘以第二个变换矩阵。因为向量和矩阵的乘法是可结合的,我们也可以先将所有的矩阵相乘,然后用向量乘以该矩阵,得到的结果是相同的~下图展示了将平移和旋转结合起来之后正方体的样子

Creating the Orbit

在这个例子中,我们将对两个物体做变换。第一个在平面中旋转,同时第二个物体将绕第一个物体旋转,他们有着各自的旋转轴。这两个立方体拥有各自不同的世界变换矩阵,并且这些矩阵在渲染每一帧的时候都会被重新使用。

这里有一些XNA 数学库中自带的函数,用来帮助创建旋转,平移和缩放矩阵。

  • 绕着XYZ轴旋转可以分别通过函数XMMatrixRotationXXMMatrixRotationYXMMatrixRotationZ来完成。这些函数创建绕着空间轴旋转的基础旋转矩阵绕着其他轴的复杂旋转可以通过一系列的基本旋转矩阵相乘来得到。
  • 平移矩阵通过调用XMMatrixTranslation 函数来创建。该函数通过不同参数创建一个对点做指定变换的平移矩阵。
  • 缩放变换矩阵通过XMMatrixScaling创建,只是沿着主坐标轴做缩放变换。如果需要对任意轴做缩放变换,可以用缩放矩阵和相应的旋转矩阵相乘来得到缩放矩阵。

第一个立方体在平面上自转,作为轨道的中心,并且这个立方体在相应的world matrix 作用下沿着y轴旋转。这些可以通过调用XMMatrixRotationY 来完成,像下面代码中所示。立方体在每一帧旋转固定的角度,应为我们假设这些立方体会持续旋转,所以旋转矩阵的值每一帧都会在之前的基础上增加。

    // 1st Cube: Rotate around the origin
    g_World1 = XMMatrixRotationY( t );

 

第二个正方体将围绕第一个正方体旋转,为了演示多重变换,要加上缩放因子,各自的旋转轴。正确使用公式的代码如下(参看注释)。第一个立方体会被缩小30%,然后沿其旋转轴旋转(这个例子中是Z)。为了模拟旋转轨迹,先将其平移离开原点,然后绕着Y轴旋转。预期效果能够通过这样达到,使用4个拥有各自信息的独立矩阵(mScale, mSpin, mTranslate, mOrbit),然后将他们相乘。

     // 2nd Cube:  Rotate around origin
    XMMATRIX mSpin = XMMatrixRotationZ( -t );
    XMMATRIX mOrbit = XMMatrixRotationY( -t * 2.0f );
    XMMATRIX mTranslate = XMMatrixTranslation( -4.0f, 0.0f, 0.0f );
    XMMATRIX mScale = XMMatrixScaling( 0.3f, 0.3f, 0.3f );
    g_World2 = mScale * mSpin * mTranslate * mOrbit;

很重要的一点就是这些操作是不可交换的。变换的执行顺序很关键。用这个顺序来试验一下,并且观察结果。

因为所有的变换函数能够通过参数来生成新的矩阵,而且,他的旋转量又必须增加。通过跟新 time 变量就可以完成。

    // Update our time
    t += XM_PI * 0.0125f;

在渲染调用之前,必须为shader更新constant buffer。注意world matrix 对每一个物体都是不一样的,所以每一个物体的变化都需要传递给shader。

    //
    // Update variables for the first cube
    //
    ConstantBuffer cb1;
    cb1.mWorld = XMMatrixTranspose( g_World1 );
    cb1.mView = XMMatrixTranspose( g_View );
    cb1.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb1, 0, 0 );

    //
    // Render the first cube
    //
    g_pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );
    g_pImmediateContext->VSSetConstantBuffers( 0, 1, &g_pConstantBuffer );
    g_pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );
    g_pImmediateContext->DrawIndexed( 36, 0, 0 );
    
     // Update variables for the second cube
    ConstantBuffer cb2;
    cb2.mWorld = XMMatrixTranspose( g_World2 );
    cb2.mView = XMMatrixTranspose( g_View );
    cb2.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb2, 0, 0 );
    //
    // Render the second cube
    //
    g_pImmediateContext->DrawIndexed( 36, 0, 0 );

The Depth Buffer

本例中还有很重要的一点,就是depth buffer。如果没有它,较小的旋转正方体在转到较大的正方体背后的时候,仍然会被绘制在中心矩阵的上面。depth buffer 能够让Direct3D追踪每一个即将绘制在屏幕上的像素的深度。在Direct3D 11中depth buffer的默认行为是检测每一个将要被绘制在屏幕上的像素,和这个screen-space 像素存储在depth buffer的值做比较。如果即将绘制像素的深度值小于等于depth buffer中的值,这个像素就会被绘制,并且将depth buffer中对应的值更新为刚刚绘制的像素的深度,另一方面,如果即将绘制的像素深度大于深度缓存中的值,这个像素就会被丢弃,像素缓存中对应的深度值不会更新。
下面的代码就是创建depth buffer(depthstencil 纹理)的samplecode。同时也创建了depth buffer对应的depthstencil view,这样Direct3D 11才会把它当做Detph Stencil texture来使用。
  // Create depth stencil texture
    D3D11_TEXTURE2D_DESC descDepth;
    ZeroMemory( &descDepth, sizeof(descDepth) );
    descDepth.Width = width;
    descDepth.Height = height;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    descDepth.SampleDesc.Count = 1;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D11_USAGE_DEFAULT;
    descDepth.BindFlags = D3D11_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
    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    ZeroMemory( &descDSV, sizeof(descDSV) );
    descDSV.Format = descDepth.Format;
    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
    hr = g_pd3dDevice->CreateDepthStencilView( g_pDepthStencil, &descDSV, &g_pDepthStencilView );
    if( FAILED(hr) )
        return hr;
为了能够使用刚刚创建的depth stencil buffer,还需要绑定到device,通过传递depth stencil view给 OMSetRenderTargets  的第三个参数即可。
    g_pImmediateContext->OMSetRenderTargets( 1, &g_pRenderTargetView, g_pDepthStencilView );
同render target一样,在渲染之前,我们要同样需要清空depth buffer。这可以保证之前帧中的深度信息不回跑到当前帧中来。教程中下面的code将depth buffer的值自动设置为最大值1.0
    //clear depth buffer to 1.0f
    g_pImmediateContext->ClearDepthStencilView(g_pDepthStencilView,D3D11_CLEAR_DEPTH,1.0f,0);  
问题:
结构体声明之后,一定要ZeroMemory 来初始化,不然在窗口一出来就会退出。
 
运行效果:
 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值