這裡,我們將示範如何在基本圖形上使用深度、透視、色彩和其他效果。
目標: 建立 3D 物件,並將基本頂點光源和著色套用至該物件。
先決條件
我們假設您熟悉C++。 您也需要圖形程序設計概念的基本體驗。
我們也假設您已完成 快速入門:設定 DirectX 資源,並顯示影像 和 建立著色器和繪圖基本類型。
完成時間: 20 分鐘。
操作說明
1. 定義立方體變數
首先,我們需要定義 SimpleCubeVertex 和 ConstantBuffer 這個立方體的結構。 這些結構會指定立方體的頂點位置和色彩,以及立方體的檢視方式。 我們會使用
struct SimpleCubeVertex
{
DirectX::XMFLOAT3 pos; // Position
DirectX::XMFLOAT3 color; // Color
};
struct ConstantBuffer
{
DirectX::XMFLOAT4X4 model;
DirectX::XMFLOAT4X4 view;
DirectX::XMFLOAT4X4 projection;
};
// This class defines the application as a whole.
ref class Direct3DTutorialFrameworkView : public IFrameworkView
{
private:
Platform::Agile<CoreWindow> m_window;
ComPtr<IDXGISwapChain1> m_swapChain;
ComPtr<ID3D11Device1> m_d3dDevice;
ComPtr<ID3D11DeviceContext1> m_d3dDeviceContext;
ComPtr<ID3D11RenderTargetView> m_renderTargetView;
ComPtr<ID3D11DepthStencilView> m_depthStencilView;
ComPtr<ID3D11Buffer> m_constantBuffer;
ConstantBuffer m_constantBufferData;
2.建立深度模板視圖
除了建立轉譯目標檢視之外,我們也會建立深度樣板檢視。 深度樣板檢視可讓 Direct3D 有效率地在離相機更遠的物件前面呈現更接近相機的物件。 在我們能夠為深度樣板緩衝區建立檢視之前,必須先創建深度樣板緩衝區。 我們會填入 D3D11_TEXTURE2D_DESC 來描述深度樣板緩衝區,然後呼叫 ID3D11Device::CreateTexture2D 來建立深度樣板緩衝區。 若要建立深度樣板檢視,我們會填入 D3D11_DEPTH_STENCIL_VIEW_DESC 來描述深度樣板檢視,並將深度樣板檢視描述和深度樣板緩衝區傳遞至 ID3D11Device::CreateDepthStencilView。
// Once the render target view is created, create a depth stencil view. This
// allows Direct3D to efficiently render objects closer to the camera in front
// of objects further from the camera.
D3D11_TEXTURE2D_DESC backBufferDesc = {0};
backBuffer->GetDesc(&backBufferDesc);
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = backBufferDesc.Width;
depthStencilDesc.Height = backBufferDesc.Height;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
ComPtr<ID3D11Texture2D> depthStencil;
DX::ThrowIfFailed(
m_d3dDevice->CreateTexture2D(
&depthStencilDesc,
nullptr,
&depthStencil
)
);
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
depthStencilViewDesc.Format = depthStencilDesc.Format;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Flags = 0;
depthStencilViewDesc.Texture2D.MipSlice = 0;
DX::ThrowIfFailed(
m_d3dDevice->CreateDepthStencilView(
depthStencil.Get(),
&depthStencilViewDesc,
&m_depthStencilView
)
);
3. 使用視窗更新視角
我們會根據視窗尺寸來更新常數緩衝器的透視投影參數。 我們會將參數修正為 70 度檢視欄位,深度範圍為 0.01 到 100。
// Finally, update the constant buffer perspective projection parameters
// to account for the size of the application window. In this sample,
// the parameters are fixed to a 70-degree field of view, with a depth
// range of 0.01 to 100. For a generalized camera class, see Lesson 5.
float xScale = 1.42814801f;
float yScale = 1.42814801f;
if (backBufferDesc.Width > backBufferDesc.Height)
{
xScale = yScale *
static_cast<float>(backBufferDesc.Height) /
static_cast<float>(backBufferDesc.Width);
}
else
{
yScale = xScale *
static_cast<float>(backBufferDesc.Width) /
static_cast<float>(backBufferDesc.Height);
}
m_constantBufferData.projection = DirectX::XMFLOAT4X4(
xScale, 0.0f, 0.0f, 0.0f,
0.0f, yScale, 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, -0.01f,
0.0f, 0.0f, -1.0f, 0.0f
);
4.使用色彩元素建立頂點和像素著色器
在此應用程式中,我們創建的頂點著色器和像素著色器比先前教學課程中所描述的更為複雜,建立著色器和繪製基本類型。 應用程式的頂點著色器會將每個頂點位置轉換成投影空間,並將頂點色彩傳遞至圖元著色器。
描述頂點著色器程式碼配置的 D3D11_INPUT_ELEMENT_DESC 的 結構陣列,包含兩個配置元素:一個元素定義頂點位置,另一個元素定義顏色。
我們會建立頂點、索引和常數緩衝區,以定義軌道立方體。
定義軌道立方體
- 首先,我們會定義立方體。 除了位置之外,我們會為每個頂點指派色彩。 這可讓像素著色器以不同的方式為每個臉部著色,以便區分臉部。
- 接下來,我們會使用 Cube 定義來描述頂點和索引緩衝區(D3D11_BUFFER_DESC 和 D3D11_SUBRESOURCE_DATA)。 我們針對每個緩衝區呼叫 ID3D11Device::CreateBuffer 一次。
- 接下來,我們會建立常數緩衝區 (D3D11_BUFFER_DESC),以將模型、檢視和投影矩陣傳遞至頂點著色器。 我們稍後可以使用常數緩衝區來旋轉立方體,並將透視投影套用至其上。 我們呼叫 ID3D11Device::CreateBuffer 來建立常數緩衝區。
- 接下來,我們指定相機位置 X = 0、Y = 1、Z = 2 的相應視圖變換。
- 最後,我們會宣告一個 度 變數,我們將用來透過每幀旋轉它來使立方體動畫化。
auto loadVSTask = DX::ReadDataAsync(L"SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(L"SimplePixelShader.cso");
auto createVSTask = loadVSTask.then([this](const std::vector<byte>& vertexShaderBytecode) {
ComPtr<ID3D11VertexShader> vertexShader;
DX::ThrowIfFailed(
m_d3dDevice->CreateVertexShader(
vertexShaderBytecode->Data,
vertexShaderBytecode->Length,
nullptr,
&vertexShader
)
);
// Create an input layout that matches the layout defined in the vertex shader code.
// For this lesson, this is simply a DirectX::XMFLOAT3 vector defining the vertex position, and
// a DirectX::XMFLOAT3 vector defining the vertex color.
const D3D11_INPUT_ELEMENT_DESC basicVertexLayoutDesc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
ComPtr<ID3D11InputLayout> inputLayout;
DX::ThrowIfFailed(
m_d3dDevice->CreateInputLayout(
basicVertexLayoutDesc,
ARRAYSIZE(basicVertexLayoutDesc),
vertexShaderBytecode->Data,
vertexShaderBytecode->Length,
&inputLayout
)
);
});
// Load the raw pixel shader bytecode from disk and create a pixel shader with it.
auto createPSTask = loadPSTask.then([this](const std::vector<byte>& pixelShaderBytecode) {
ComPtr<ID3D11PixelShader> pixelShader;
DX::ThrowIfFailed(
m_d3dDevice->CreatePixelShader(
pixelShaderBytecode->Data,
pixelShaderBytecode->Length,
nullptr,
&pixelShader
)
);
});
// Create vertex and index buffers that define a simple unit cube.
auto createCubeTask = (createPSTask && createVSTask).then([this] () {
// In the array below, which will be used to initialize the cube vertex buffers,
// each vertex is assigned a color in addition to a position. This will allow
// the pixel shader to color each face differently, enabling them to be distinguished.
SimpleCubeVertex cubeVertices[] =
{
{ float3(-0.5f, 0.5f, -0.5f), float3(0.0f, 1.0f, 0.0f) }, // +Y (top face)
{ float3( 0.5f, 0.5f, -0.5f), float3(1.0f, 1.0f, 0.0f) },
{ float3( 0.5f, 0.5f, 0.5f), float3(1.0f, 1.0f, 1.0f) },
{ float3(-0.5f, 0.5f, 0.5f), float3(0.0f, 1.0f, 1.0f) },
{ float3(-0.5f, -0.5f, 0.5f), float3(0.0f, 0.0f, 1.0f) }, // -Y (bottom face)
{ float3( 0.5f, -0.5f, 0.5f), float3(1.0f, 0.0f, 1.0f) },
{ float3( 0.5f, -0.5f, -0.5f), float3(1.0f, 0.0f, 0.0f) },
{ float3(-0.5f, -0.5f, -0.5f), float3(0.0f, 0.0f, 0.0f) },
};
unsigned short cubeIndices[] =
{
0, 1, 2,
0, 2, 3,
4, 5, 6,
4, 6, 7,
3, 2, 5,
3, 5, 4,
2, 1, 6,
2, 6, 5,
1, 7, 6,
1, 0, 7,
0, 3, 4,
0, 4, 7
};
D3D11_BUFFER_DESC vertexBufferDesc = {0};
vertexBufferDesc.ByteWidth = sizeof(SimpleCubeVertex) * ARRAYSIZE(cubeVertices);
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> vertexBuffer;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&vertexBuffer
)
);
D3D11_BUFFER_DESC indexBufferDesc;
indexBufferDesc.ByteWidth = sizeof(unsigned short) * ARRAYSIZE(cubeIndices);
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA indexBufferData;
indexBufferData.pSysMem = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> indexBuffer;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&indexBufferDesc,
&indexBufferData,
&indexBuffer
)
);
// Create a constant buffer for passing model, view, and projection matrices
// to the vertex shader. This will allow us to rotate the cube and apply
// a perspective projection to it.
D3D11_BUFFER_DESC constantBufferDesc = {0};
constantBufferDesc.ByteWidth = sizeof(m_constantBufferData);
constantBufferDesc.Usage = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = 0;
constantBufferDesc.MiscFlags = 0;
constantBufferDesc.StructureByteStride = 0;
DX::ThrowIfFailed(
m_d3dDevice->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer
)
);
// Specify the view transform corresponding to a camera position of
// X = 0, Y = 1, Z = 2. For a generalized camera class, see Lesson 5.
m_constantBufferData.view = DirectX::XMFLOAT4X4(
-1.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
0.00000000f, 0.89442718f, 0.44721359f, 0.00000000f,
0.00000000f, 0.44721359f, -0.89442718f, -2.23606800f,
0.00000000f, 0.00000000f, 0.00000000f, 1.00000000f
);
});
// This value will be used to animate the cube by rotating it every frame.
float degree = 0.0f;
5.旋轉和繪製立方體,並呈現渲染後的影像
我們進入無休止的迴圈,以持續呈現和顯示場景。 我們呼叫 rotationY 內嵌函式(BasicMath.h),並帶入旋轉量來設定在 Y 軸上旋轉 Cube 模型矩陣的值。 然後,我們呼叫 ID3D11DeviceContext::UpdateSubresource 來更新常數緩衝區並旋轉 Cube 模型。 我們呼叫 ID3D11DeviceContext::OMSetRenderTargets,將轉譯目標指定為輸出目標。 在此 OMSetRenderTargets 函式呼叫中,我們會傳遞深度模板檢視。 我們會呼叫 ID3D11DeviceContext::ClearRenderTargetView 將渲染目標清除為純藍色,並呼叫 ID3D11DeviceContext::ClearDepthStencilView 來清除深度緩衝區。
在無休止的迴圈中,我們也在藍色表面上繪製立方體。
繪製立方體
- 首先,我們會呼叫 ID3D11DeviceContext::IASetInputLayout,以描述頂點緩衝區數據如何串流至輸入組合器階段。
- 接下來,我們會呼叫 ID3D11DeviceContext::IASetVertexBuffers 和 ID3D11DeviceContext::IASetIndexBuffer,將頂點和索引緩衝區系結至輸入組合器階段。
- 接下來,我們呼叫 ID3D11DeviceContext::IASetPrimitiveTopology,並使用 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP 值,指定輸入組合器階段將頂點數據解讀為三角形帶狀。
- 接下來,我們會呼叫 ID3D11DeviceContext::VSSetShader,使用頂點著色器程式碼初始化頂點著色器階段,並呼叫 ID3D11DeviceContext::PSSetShader 使用像素著色器程式碼初始化像素著色器階段。
- 接下來,我們會呼叫 ID3D11DeviceContext::VSSetConstantBuffers 來設定頂點著色器管線階段所使用的常數緩衝區。
- 最後,我們呼叫 ID3D11DeviceContext::DrawIndexed 來繪製立方體,並將其提交至渲染管線。
我們呼叫 IDXGISwapChain::Present,將轉譯的影像呈現至視窗。
// Update the constant buffer to rotate the cube model.
m_constantBufferData.model = XMMatrixRotationY(-degree);
degree += 1.0f;
m_d3dDeviceContext->UpdateSubresource(
m_constantBuffer.Get(),
0,
nullptr,
&m_constantBufferData,
0,
0
);
// Specify the render target and depth stencil we created as the output target.
m_d3dDeviceContext->OMSetRenderTargets(
1,
m_renderTargetView.GetAddressOf(),
m_depthStencilView.Get()
);
// Clear the render target to a solid color, and reset the depth stencil.
const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
m_d3dDeviceContext->ClearRenderTargetView(
m_renderTargetView.Get(),
clearColor
);
m_d3dDeviceContext->ClearDepthStencilView(
m_depthStencilView.Get(),
D3D11_CLEAR_DEPTH,
1.0f,
0
);
m_d3dDeviceContext->IASetInputLayout(inputLayout.Get());
// Set the vertex and index buffers, and specify the way they define geometry.
UINT stride = sizeof(SimpleCubeVertex);
UINT offset = 0;
m_d3dDeviceContext->IASetVertexBuffers(
0,
1,
vertexBuffer.GetAddressOf(),
&stride,
&offset
);
m_d3dDeviceContext->IASetIndexBuffer(
indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0
);
m_d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Set the vertex and pixel shader stage state.
m_d3dDeviceContext->VSSetShader(
vertexShader.Get(),
nullptr,
0
);
m_d3dDeviceContext->VSSetConstantBuffers(
0,
1,
m_constantBuffer.GetAddressOf()
);
m_d3dDeviceContext->PSSetShader(
pixelShader.Get(),
nullptr,
0
);
// Draw the cube.
m_d3dDeviceContext->DrawIndexed(
ARRAYSIZE(cubeIndices),
0,
0
);
// Present the rendered image to the window. Because the maximum frame latency is set to 1,
// the render loop will generally be throttled to the screen refresh rate, typically around
// 60 Hz, by sleeping the application on Present until the screen is refreshed.
DX::ThrowIfFailed(
m_swapChain->Present(1, 0)
);
摘要和後續步驟
我們在基本圖形上使用深度、透視、色彩和其他效果。
接下來,我們會將紋理套用至基本類型。