In this section, we will learn how to render a simple triangle using Direct3D. This is a fundamental step in understanding how to draw shapes and eventually more complex 3D models. We will cover the following steps:
- Setting Up the Vertex Buffer
- Creating the Vertex Shader
- Creating the Pixel Shader
- Drawing the Triangle
- Setting Up the Vertex Buffer
A vertex buffer is a memory buffer that holds vertex data. Each vertex can contain information such as position, color, texture coordinates, etc. For our triangle, we will define three vertices.
Code Example
// Define the vertex structure struct Vertex { DirectX::XMFLOAT3 position; // Position of the vertex DirectX::XMFLOAT4 color; // Color of the vertex }; // Define the vertices of the triangle Vertex vertices[] = { { DirectX::XMFLOAT3(0.0f, 0.5f, 0.0f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }, // Top vertex (red) { DirectX::XMFLOAT3(0.5f, -0.5f, 0.0f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) }, // Bottom-right vertex (green) { DirectX::XMFLOAT3(-0.5f, -0.5f, 0.0f), DirectX::XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) } // Bottom-left vertex (blue) }; // Create the vertex buffer D3D11_BUFFER_DESC bufferDesc = {}; bufferDesc.Usage = D3D11_USAGE_DEFAULT; bufferDesc.ByteWidth = sizeof(vertices); bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; bufferDesc.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA initData = {}; initData.pSysMem = vertices; ID3D11Buffer* vertexBuffer; HRESULT hr = device->CreateBuffer(&bufferDesc, &initData, &vertexBuffer); if (FAILED(hr)) { // Handle error }
Explanation
- Vertex Structure: Defines the format of each vertex, including position and color.
- Vertices Array: Contains the three vertices of the triangle.
- Buffer Description: Specifies the usage, size, and binding flags for the vertex buffer.
- Subresource Data: Points to the vertex data.
- Create Buffer: Creates the vertex buffer on the GPU.
- Creating the Vertex Shader
The vertex shader processes each vertex and transforms it from model space to screen space.
Code Example
// Vertex Shader (VertexShader.hlsl) struct VS_INPUT { float3 position : POSITION; float4 color : COLOR; }; struct VS_OUTPUT { float4 position : SV_POSITION; float4 color : COLOR; }; VS_OUTPUT main(VS_INPUT input) { VS_OUTPUT output; output.position = float4(input.position, 1.0f); output.color = input.color; return output; }
Explanation
- VS_INPUT: Input structure containing the vertex position and color.
- VS_OUTPUT: Output structure containing the transformed position and color.
- main: The main function of the vertex shader that transforms the vertex position and passes the color through.
- Creating the Pixel Shader
The pixel shader determines the color of each pixel.
Code Example
// Pixel Shader (PixelShader.hlsl) struct PS_INPUT { float4 position : SV_POSITION; float4 color : COLOR; }; float4 main(PS_INPUT input) : SV_TARGET { return input.color; }
Explanation
- PS_INPUT: Input structure containing the interpolated position and color.
- main: The main function of the pixel shader that returns the color of the pixel.
- Drawing the Triangle
Finally, we need to set up the pipeline and draw the triangle.
Code Example
// Set the vertex buffer UINT stride = sizeof(Vertex); UINT offset = 0; context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset); // Set the primitive topology context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // Set the input layout, vertex shader, and pixel shader context->IASetInputLayout(inputLayout); context->VSSetShader(vertexShader, nullptr, 0); context->PSSetShader(pixelShader, nullptr, 0); // Draw the triangle context->Draw(3, 0);
Explanation
- IASetVertexBuffers: Binds the vertex buffer to the input assembler stage.
- IASetPrimitiveTopology: Sets the primitive topology to a triangle list.
- IASetInputLayout: Sets the input layout that matches the vertex shader input.
- VSSetShader: Sets the vertex shader.
- PSSetShader: Sets the pixel shader.
- Draw: Draws the triangle using the vertex buffer.
Practical Exercise
Task
- Modify the vertex colors to create a gradient effect.
- Change the vertex positions to form a different shape, such as a square.
Solution
- Gradient Effect: Change the colors in the
vertices
array. - Different Shape: Add more vertices and adjust the
Draw
call to draw more primitives.
Example
// Define the vertices of a square Vertex vertices[] = { { DirectX::XMFLOAT3(-0.5f, 0.5f, 0.0f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }, // Top-left vertex (red) { DirectX::XMFLOAT3(0.5f, 0.5f, 0.0f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) }, // Top-right vertex (green) { DirectX::XMFLOAT3(0.5f, -0.5f, 0.0f), DirectX::XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) }, // Bottom-right vertex (blue) { DirectX::XMFLOAT3(-0.5f, -0.5f, 0.0f), DirectX::XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) } // Bottom-left vertex (yellow) }; // Update the Draw call to draw a square context->Draw(4, 0);
Conclusion
In this section, we learned how to render a simple triangle using Direct3D. We covered setting up the vertex buffer, creating the vertex and pixel shaders, and drawing the triangle. This foundational knowledge will be essential as we move on to more complex rendering techniques in the next modules.
DirectX Programming Course
Module 1: Introduction to DirectX
- What is DirectX?
- Setting Up the Development Environment
- Understanding the DirectX API
- Creating Your First DirectX Application
Module 2: Direct3D Basics
Module 3: Working with Shaders
Module 4: Advanced Rendering Techniques
Module 5: 3D Models and Animation
Module 6: Performance Optimization
- Profiling and Debugging
- Optimizing Rendering Performance
- Memory Management
- Multithreading in DirectX