概述
在上一個教程中,我們從模型空間到屏幕渲染了一個立方體。 在本教程中,我們將擴展轉換的概念並演示可以通過這些轉換實現的簡單動畫。
本教程的結果將是圍繞另一個軌道運行的對象。 展示轉換以及如何將它們組合以實現期望的效果將是有用的。 在我們介紹新概念時,未來的教程將在此基礎上構建。
資源目錄
(SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial05
轉型
在3D圖形中,變換通常用於對頂點和矢量進行操作。 它還用於將它們在一個空間中轉換爲另一個空間。 通過與矩陣相乘來執行變換。 通常有三種類型的原始變換可以在頂點上執行:平移(相對於原點位於空間中),旋轉(相對於x,y,z幀的方向)和縮放(距離 起源)。 除此之外,投影變換用於從視圖空間到投影空間。 XNA Math庫包含的API可以方便地構建矩陣,用於多種用途,例如平移,旋轉,縮放,世界到視圖轉換,視圖到投影轉換等。 然後,應用程序可以使用這些矩陣來轉換其場景中的頂點。 需要對矩陣變換有基本的瞭解。 我們將簡要介紹下面的一些示例。
平移
平移是指在空間中移動或移位一定距離。 在3D中,用於翻譯的矩陣具有形式。
1 0 0 0 0 1 0 0 0 0 1 0 a b c 1
其中(a,b,c)是定義移動方向和距離的向量。 例如,要沿X軸(負X方向)移動頂點-5單位,我們可以將其與此矩陣相乘:
1 0 0 0 0 1 0 0 0 0 1 0 -5 0 0 1
如果我們將此應用於以原點爲中心的立方體對象,則結果是該框向負X軸移動5個單位,如圖5所示,在應用平移之後。
圖1.平移的影響
在3D中,空間通常由原點和來自原點的三個唯一軸定義:X,Y和Z.計算機圖形中通常使用多個空間:對象空間,世界空間,視圖空間,投影空間和屏幕空間。
圖2.在對象空間中定義的立方體
旋轉
旋轉是指圍繞穿過原點的軸旋轉頂點。 三個這樣的軸是空間中的X,Y和Z軸。 2D中的示例是逆時針旋轉矢量[1 0] 90度。 旋轉的結果是向量[0 1]。 用於圍繞Y軸順時針旋轉1度的矩陣看起來像這樣:
cosΐ 0 -sinΐ 0 0 1 0 0 sinΐ 0 cosΐ 0 0 0 0 1
圖6顯示了圍繞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.旋轉和平移的效果
創建軌道
在本教程中,我們將轉換兩個多維數據集。 第一個將旋轉到位,而第二個將圍繞第一個旋轉,同時在其自己的軸上旋轉。 這兩個立方體將具有與其關聯的自己的世界變換矩陣,並且該矩陣將在渲染的每個幀中重新應用於該矩陣。
XNA Math中有一些函數可以幫助創建旋轉,平移和縮放矩陣。
- 圍繞X,Y和Z軸執行的旋轉分別使用函數XMMatrixRotationX,XMMatrixRotationY和XMMatrixRotationZ來完成。 它們創建圍繞主軸之一旋轉的基本旋轉矩陣。 圍繞其他軸的複雜旋轉可以通過將它們中的幾個相乘來完成。
- 可以通過調用XMMatrixTranslation函數來執行轉換。 此函數將創建一個矩陣,用於轉換參數指定的點。
- 使用XMMatrixScaling完成縮放。 它僅沿主軸縮放。 如果需要沿任意軸縮放,則可以將縮放矩陣與適當的旋轉矩陣相乘以實現該效果。
第一個立方體將旋轉到位,並作爲軌道的中心。 立方體沿Y軸旋轉,應用於相關的世界矩陣。 這是通過調用以下代碼中顯示的XMMatrixRotationY函數來完成的。 立方體每幀旋轉一定量。 由於立方體被假設爲連續旋轉,因此旋轉矩陣所基於的值隨每幀遞增。
// 1st Cube: Rotate around the origin g_World1 = XMMatrixRotationY( t );
第一個立方體將旋轉到位,並作爲軌道的中心。 立方體沿Y軸旋轉,應用於相關的世界矩陣。 這是通過調用以下代碼中顯示的XMMatrixRotationY函數來完成的。 立方體每幀旋轉一定量。 由於立方體被假設爲連續旋轉,因此旋轉矩陣所基於的值隨每幀遞增。
// 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;
需要注意的一點是,這些操作不是可交換的。 應用轉換的順序很重要。 試驗轉化順序並觀察結果。
由於所有變換函數都將根據參數創建新矩陣,因此它們旋轉的量必須遞增。 這是通過更新“時間”變量來完成的。
// Update our time t += XM_PI * 0.0125f;
在進行渲染調用之前,必須爲着色器更新常量緩衝區。 請注意,世界矩陣對於每個多維數據集都是唯一的,因此會爲每個傳遞給它的對象進行更改。
// // 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 );
深度緩衝區
本教程還有另外一個重要的補充,那就是深度緩衝區。 沒有它,較小的軌道立方體在圍繞後者的後部時仍會被繪製在較大的中心立方體的頂部。 深度緩衝區允許Direct3D跟蹤繪製到屏幕的每個像素的深度。 Direct3D 11中深度緩衝區的默認行爲是檢查屏幕上繪製的每個像素與屏幕空間像素的深度緩衝區中存儲的值。 如果正在渲染的像素的深度小於或等於深度緩衝器中已經存在的值,則繪製像素並且將深度緩衝器中的值更新爲新繪製的像素的深度。 另一方面,如果正在繪製的像素的深度大於深度緩衝器中已經存在的值,則丟棄該像素並且深度緩衝器中的深度值保持不變。
示例中的以下代碼創建深度緩衝區(DepthStencil紋理)。 它還創建深度緩衝區的DepthStencilView,以便Direct3D 11知道將其用作深度模板紋理。
// 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;
爲了使用這個新創建的深度模板緩衝區,教程必須將其綁定到設備。 這是通過將深度模板視圖傳遞給OMSetRenderTargets函數的第三個參數來完成的。
g_pImmediateContext->OMSetRenderTargets( 1, &g_pRenderTargetView, g_pDepthStencilView );
與渲染目標一樣,我們還必須在渲染之前清除深度緩衝區。 這可確保先前幀的深度值不會錯誤地丟棄當前幀中的像素。 在下面的代碼中,教程實際上是將深度緩衝區設置爲最大量(1.0)。
// // Clear the depth buffer to 1.0 (max depth) //