Usare profondità ed effetti sulle primitive
Qui viene illustrato come usare profondità, prospettiva, colore e altri effetti sulle primitive.
Obiettivo: creare un oggetto 3D e applicare a esso la colorazione e l'illuminazione dei vertici di base.
Prerequisiti
Si presuppone che l'utente abbia familiarità con C++. È anche necessario avere familiarità con i concetti di programmazione grafica.
Si presuppone anche che l'utente abbia letto Avvio rapido: configurare le risorse DirectX e visualizzare un'immagine e Creare shader e primitive di disegno.
Tempo di completamento: 20 minuti.
Istruzioni
1. Definizione delle variabili del cubo
Innanzitutto, è necessario definire le strutture SimpleCubeVertex e ConstantBuffer per il cubo. Queste strutture specificano le posizioni e i colori dei vertici per il cubo e il modo in cui verrà visualizzato il cubo. Si dichiara ID3D11DepthStencilView e ID3D11Buffer con ComPtr e un'istanza di 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. Creazione di una visualizzazione depth stencil
Oltre a creare la visualizzazione della destinazione di rendering, si crea anche una visualizzazione depth stencil. La visualizzazione depth stencil consente a Direct3D di eseguire in modo efficiente il rendering degli oggetti più vicini alla fotocamera davanti agli oggetti più lontani dalla fotocamera. Prima di poter creare una visualizzazione su un buffer depth stencil, è necessario creare il buffer depth stencil. Si popola D3D11_TEXTURE2D_DESC per descrivere il buffer depth stencil e si chiama ID3D11Device::CreateTexture2D per creare il buffer depth stencil. Per creare la visualizzazione depth stencil, popolare D3D11_DEPTH_STENCIL_VIEW_DESC per descrivere la visualizzazione depth stencil e passare la descrizione della visualizzazione depth stencil e il buffer depth stencil a 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. Aggiornamento della prospettiva con la finestra
Si aggiornano i parametri di proiezione della prospettiva per il buffer costante in base alle dimensioni della finestra. Si correggono i parametri in base a un campo visivo a 70 gradi con un intervallo di profondità compreso tra 0,01 e 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. Creazione di vertex shader e pixel shader con elementi di colore
In questa app si creano vertex shader e pixel shader più complessi rispetto a quanto descritto nell'esercitazione precedente, Creare shader e primitive di disegno. Il vertex shader dell'app trasforma la posizione di ogni vertice nello spazio di proiezione e passa il colore del vertice al pixel shader.
La matrice dell'app delle strutture D3D11_INPUT_ELEMENT_DESC che descrivono il layout del codice del vertex shader ha due elementi di layout: un elemento definisce la posizione del vertice, l'altro definisce il colore.
Si creano buffer dei vertici, index buffer e buffer costanti per definire un cubo orbitante.
Per definire un cubo orbitante
- Innanzitutto, si definisce il cubo. A ogni vertice si assegnano un colore e una posizione. In questo modo, il pixel shader colora ogni faccia in modo diverso per poterle distinguere.
- Si descriveranno quindi i buffer dei vertici e gli index buffer (D3D11_BUFFER_DESC e D3D11_SUBRESOURCE_DATA) usando la definizione del cubo. Si chiama ID3D11Device::CreateBuffer una volta per ogni buffer.
- Si creerà quindi un buffer costante (D3D11_BUFFER_DESC) per passare le matrici modello, visualizzazione e proiezione al vertex shader. In un secondo momento è possibile usare il buffer costante per ruotare il cubo e applicare a esso una proiezione prospettica. Chiamare ID3D11Device::CreateBuffer per creare il buffer costante.
- Successivamente, si specifica la trasformazione della visualizzazione corrispondente a una posizione della fotocamera X = 0, Y = 1, Z = 2.
- Infine, si dichiara una variabile grado che si userà per animare il cubo ruotandolo a ogni frame.
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. Rotazione e disegno del cubo e presentazione dell'immagine sottoposta a rendering
Si inizia un ciclo infinito per eseguire il rendering e visualizzare la scena in modo continuo. Si chiama la funzione inline rotationY (BasicMath.h) con un valore di rotazione per impostare valori che ruoteranno la matrice del modello del cubo intorno all'asse Y. Si chiama quindi ID3D11DeviceContext::UpdateSubresource per aggiornare il buffer costante e ruotare il modello del cubo. Si chiama ID3D11DeviceContext::OMSetRenderTargets per specificare la destinazione del rendering come destinazione di output. In questa chiamata a OMSetRenderTargets, si passa la visualizzazione depth stencil. Si chiama ID3D11DeviceContext::ClearRenderTargetView per cancellare la destinazione di rendering con un colore blu pieno e si chiama ID3D11DeviceContext::ClearDepthStencilView per cancellare il buffer di intensità.
Nel ciclo infinito si disegna anche il cubo sulla superficie blu.
Per disegnare il cubo
- Innanzitutto, si chiama ID3D11DeviceContext::IASetInputLayout per descrivere il modo in cui i dati del buffer dei vertici vengono trasmessi in streaming nella fase assemblaggio input.
- Successivamente, si chiama ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer per associare i buffer dei vertici e gli index buffer alla fase assemblaggio input.
- Successivamente, si chiama ID3D11DeviceContext::IASetPrimitiveTopology con il valore D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP per specificare che la fase assemblaggio input interpreti i dati dei vertici come striscia di triangoli.
- Successivamente, si chiama ID3D11DeviceContext::VSSetShader per inizializzare la fase del vertex shader con il codice del vertex shader e ID3D11DeviceContext::PSSetShader per inizializzare la fase del pixel shader con il codice del pixel shader.
- Successivamente si chiama ID3D11DeviceContext::VSSetConstantBuffers per impostare il buffer costante usato dalla fase pipeline del vertex shader.
- Infine, si chiama ID3D11DeviceContext::DrawIndexed per disegnare il cubo e inviarlo alla pipeline del rendering.
Si chiama IDXGISwapChain::Present per presentare l'immagine sottoposta a rendering alla finestra.
// 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)
);
Riepilogo e passaggi successivi
Si sono usati profondità, prospettiva, colore e altri effetti sulle primitive.
Successivamente, si applicano texture alle primitive.
Applicazione di texture alle primitive