Microsoft DirectX is a collection of application programming interfaces (APIs) for handling tasks related to multimedia, especially game programming and video, on Microsoft platforms. Originally, the names of these APIs all began with Direct, such as Direct3D, DirectDraw, DirectMusic, DirectPlay, DirectSound, and so forth. […] Direct3D (the 3D graphics API within DirectX) is widely used in the development of video games for Microsoft Windows and the Xbox line of consoles.[1]
In this tutorial we will be focusing on DirectX 9, which is not as low-level as it’s successors, which are aimed at programmers very familiar with how graphics hardware works. It makes a great starting point for learning Direct3D. In this tutorial I will be using the Win32-API for window handling and the DirectX 2010 SDK.
Window creation ¶
1#include <Windows.h>
2
3bool _running{ false };
4
5LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
6 // Handle incoming message.
7 switch (msg) {
8 // Set running to false if the user tries to close the window.
9 case WM_DESTROY:
10 _running = false;
11 PostQuitMessage(0);
12 break;
13 }
14 // Return the handled event.
15 return DefWindowProc(hWnd, msg, wParam, lParam);
16}
17
18int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
19 LPSTR lpCmdLine, int nCmdShow) {
20 // Set window properties we want to use.
21 WNDCLASSEX wndEx{ };
22 wndEx.cbSize = sizeof(WNDCLASSEX); // structure size
23 wndEx.style = CS_VREDRAW | CS_HREDRAW; // class styles
24 wndEx.lpfnWndProc = WndProc; // window procedure
25 wndEx.cbClsExtra = 0; // extra memory (struct)
26 wndEx.cbWndExtra = 0; // extra memory (window)
27 wndEx.hInstance = hInstance; // module instance
28 wndEx.hIcon = LoadIcon(nullptr, IDI_APPLICATION); // icon
29 wndEx.hCursor = LoadCursor(nullptr, IDC_ARROW); // cursor
30 wndEx.hbrBackground = (HBRUSH) COLOR_WINDOW; // background color
31 wndEx.lpszMenuName = nullptr; // menu name
32 wndEx.lpszClassName = "DirectXClass"; // register class name
33 wndEx.hIconSm = nullptr; // small icon (taskbar)
34 // Register created class for window creation.
35 RegisterClassEx(&wndEx);
36 // Create a new window handle.
37 HWND hWnd{ nullptr };
38 // Create a new window handle using the registered class.
39 hWnd = CreateWindow("DirectXClass", // registered class
40 "directx window", // window title
41 WS_OVERLAPPEDWINDOW, // window style
42 50, 50, // x, y (position)
43 1024, 768, // width, height (size)
44 nullptr, // parent window
45 nullptr, // menu
46 hInstance, // module instance
47 nullptr); // struct for infos
48 // Check if a window handle has been created.
49 if (!hWnd)
50 return -1;
51 // Show and update the new window.
52 ShowWindow(hWnd, nCmdShow);
53 UpdateWindow(hWnd);
54 // Start the game loop and send incoming messages to the window procedure.
55 _running = true;
56 MSG msg{ };
57 while (_running) {
58 while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
59 TranslateMessage(&msg);
60 DispatchMessage(&msg);
61 }
62 }
63 return 0;
64}
This should create a window, that can the moved, resized and closed.
Direct3D initialization ¶
1// Includes DirectX 9 structures and functions.
2// Remember to link "d3d9.lib" and "d3dx9.lib".
3// For "d3dx9.lib" the DirectX SDK (June 2010) is needed.
4// Don't forget to set your subsystem to Windows.
5#include <d3d9.h>
6#include <d3dx9.h>
7// Includes the ComPtr, a smart pointer automatically releasing COM objects.
8#include <wrl.h>
9using namespace Microsoft::WRL;
10// Next we define some Direct3D9 interface structs we need.
11ComPtr<IDirect3D9> _d3d{ };
12ComPtr<IDirect3DDevice9> _device{ };
With all interfaces declared we can now initialize Direct3D.
1bool InitD3D(HWND hWnd) {
2 // Store the size of the window rectangle.
3 RECT clientRect{ };
4 GetClientRect(hWnd, &clientRect);
5 // Initialize Direct3D
6 _d3d = Direct3DCreate9(D3D_SDK_VERSION);
7 // Get the display mode which format will be the window format.
8 D3DDISPLAYMODE displayMode{ };
9 _d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, // use default graphics card
10 &displayMode); // display mode pointer
11 // Next we have to set some presentation parameters.
12 D3DPRESENT_PARAMETERS pp{ };
13 pp.BackBufferWidth = clientRect.right; // width is window width
14 pp.BackBufferHeight = clientRect.bottom; // height is window height
15 pp.BackBufferFormat = displayMode.Format; // use adapter format
16 pp.BackBufferCount = 1; // 1 back buffer (default)
17 pp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard after presentation
18 pp.hDeviceWindow = hWnd; // associated window handle
19 pp.Windowed = true; // display in window mode
20 pp.Flags = 0; // no special flags
21 // Variable to store results of methods to check if everything succeeded.
22 HRESULT result{ };
23 result = _d3d->CreateDevice(D3DADAPTER_DEFAULT, // use default graphics card
24 D3DDEVTYPE_HAL, // use hardware acceleration
25 hWnd, // the window handle
26 D3DCREATE_HARDWARE_VERTEXPROCESSING,
27 // vertices are processed by the hardware
28 &pp, // the present parameters
29 &_device); // struct to store the device
30 // Return false if the device creation failed.
31 // It is helpful to set breakpoints at the return line.
32 if (FAILED(result))
33 return false;
34 // Create a viewport which hold information about which region to draw to.
35 D3DVIEWPORT9 viewport{ };
36 viewport.X = 0; // start at top left corner
37 viewport.Y = 0; // ..
38 viewport.Width = clientRect.right; // use the entire window
39 viewport.Height = clientRect.bottom; // ..
40 viewport.MinZ = 0.0f; // minimum view distance
41 viewport.MaxZ = 100.0f; // maximum view distance
42 // Apply the created viewport.
43 result = _device->SetViewport(&viewport);
44 // Always check if something failed.
45 if (FAILED(result))
46 return false;
47 // Everything was successful, return true.
48 return true;
49}
50// ...
51// Back in our WinMain function we call our initialization function.
52// ...
53// Check if Direct3D initialization succeeded, else exit the application.
54if (!InitD3D(hWnd))
55 return -1;
56
57MSG msg{ };
58while (_running) {
59 while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
60 TranslateMessage(&msg);
61 DispatchMessage(&msg);
62 }
63 // Clear to render target to a specified color.
64 _device->Clear(0, // number of rects to clear
65 nullptr, // indicates to clear the entire window
66 D3DCLEAR_TARGET, // clear all render targets
67 D3DXCOLOR{ 1.0f, 0.0f, 0.0f, 1.0f }, // color (red)
68 0.0f, // depth buffer clear value
69 0); // stencil buffer clear value
70 // ...
71 // Drawing operations go here.
72 // ...
73 // Flip the front- and backbuffer.
74 _device->Present(nullptr, // no source rectangle
75 nullptr, // no destination rectangle
76 nullptr, // don't change the current window handle
77 nullptr); // pretty much always nullptr
78}
79// ...
Now the window should be displayed in a bright red color.
Vertex Buffer ¶
Let’s create a vertex buffer to store the vertices for our triangle
1// At the top of the file we need to add a include.
2#include <vector>
3// First we declare a new ComPtr holding a vertex buffer.
4ComPtr<IDirect3DVertexBuffer9> _vertexBuffer{ };
5// Lets define a function to calculate the byte size of a std::vector
6template <typename T>
7unsigned int GetByteSize(const std::vector<T>& vec) {
8 return sizeof(vec[0]) * vec.size();
9}
10// Define "flexible vertex format" describing the content of our vertex struct.
11// Use the defined color as diffuse color.
12const unsigned long VertexStructFVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
13// Define a struct representing the vertex data the buffer will hold.
14struct VStruct {
15 float x, y, z; // store the 3D position
16 D3DCOLOR color; // store a color
17};
18// Declare a new function to create a vertex buffer.
19IDirect3DVertexBuffer9* CreateBuffer(const std::vector<VStruct>& vertices) {
20 // Declare the buffer to be returned.
21 IDirect3DVertexBuffer9* buffer{ };
22 HRESULT result{ };
23 result = _device->CreateVertexBuffer(
24 GetByteSize(vertices), // vector size in bytes
25 0, // data usage
26 VertexStructFVF, // FVF of the struct
27 D3DPOOL_DEFAULT, // use default pool for the buffer
28 &buffer, // receiving buffer
29 nullptr); // special shared handle
30 // Check if buffer was created successfully.
31 if (FAILED(result))
32 return nullptr;
33 // Create a data pointer for copying the vertex data
34 void* data{ };
35 // Lock the buffer to get a buffer for data storage.
36 result = buffer->Lock(0, // byte offset
37 GetByteSize(vertices), // size to lock
38 &data, // receiving data pointer
39 0); // special lock flags
40 // Check if buffer was locked successfully.
41 if (FAILED(result))
42 return nullptr;
43 // Copy the vertex data using C standard libraries memcpy.
44 memcpy(data, vertices.data(), GetByteSize(vertices));
45 buffer->Unlock();
46 // Set the FVF Direct3D uses for rendering.
47 _device->SetFVF(VertexStructFVF);
48 // If everything was successful return the filled vertex buffer.
49 return buffer;
50}
In our WinMain we can now call the new function after the Direct3D initialization.
1// ...
2if (!InitD3D(hWnd))
3 return -1;
4// Define the vertices we need to draw a triangle.
5// Values are declared in a clockwise direction else Direct3D would cull them.
6// If you want to disable culling just call:
7// _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
8std::vector<VStruct> vertices {
9 // Bottom left
10 VStruct{ -1.0f, -1.0f, 1.0f, D3DXCOLOR{ 1.0f, 0.0f, 0.0f, 1.0f } },
11 // Top left
12 VStruct{ -1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 1.0f, 0.0f, 1.0f } },
13 // Top right
14 VStruct{ 1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 0.0f, 1.0f, 1.0f } }
15};
16// Try to create the vertex buffer else exit the application.
17if (!(_vertexBuffer = CreateBuffer(vertices)))
18 return -1;
19// ...
Transformations ¶
Before we can use the vertex buffer to draw our primitives, we first need to set up the matrices.
1// Lets create a new functions for the matrix transformations.
2bool SetupTransform() {
3 // Create a view matrix that transforms world space to
4 // view space.
5 D3DXMATRIX view{ };
6 // Use a left-handed coordinate system.
7 D3DXMatrixLookAtLH(
8 &view, // receiving matrix
9 &D3DXVECTOR3{ 0.0f, 0.0f, -20.0f }, // "camera" position
10 &D3DXVECTOR3{ 0.0f, 0.0f, 0.0f }, // position where to look at
11 &D3DXVECTOR3{ 0.0f, 1.0f, 0.0f }); // positive y-axis is up
12 HRESULT result{ };
13 result = _device->SetTransform(D3DTS_VIEW, &view); // apply the view matrix
14 if (FAILED(result))
15 return false;
16 // Create a projection matrix that defines the view frustrum.
17 // It transforms the view space to projection space.
18 D3DXMATRIX projection{ };
19 // Create a perspective projection using a left-handed coordinate system.
20 D3DXMatrixPerspectiveFovLH(
21 &projection, // receiving matrix
22 D3DXToRadian(60.0f), // field of view in radians
23 1024.0f / 768.0f, // aspect ratio (width / height)
24 0.0f, // minimum view distance
25 100.0f); // maximum view distance
26 result = _device->SetTransform(D3DTS_PROJECTION, &projection);
27 if (FAILED(result))
28 return false;
29 // Disable lighting for now so we can see what we want to render.
30 result = _device->SetRenderState(D3DRS_LIGHTING, false);
31 // View and projection matrix are successfully applied, return true.
32 return true;
33}
34// ...
35// Back in the WinMain function we can now call the transformation function.
36// ...
37if (!(_vertexBuffer = CreateVertexBuffer(vertices)))
38 return -1;
39// Call the transformation setup function.
40if (!SetupTransform())
41 return -1;
42// ...
Rendering ¶
Now that everything is setup we can start drawing our first 2D triangle in 3D space.
1// ...
2if (!SetupTransform())
3 return -1;
4// First we have to bind our vertex buffer to the data stream.
5HRESULT result{ };
6result = _device->SetStreamSource(0, // use the default stream
7 _vertexBuffer.Get(), // pass the vertex buffer
8 0, // no offset
9 sizeof(VStruct)); // size of vertex struct
10if (FAILED(result))
11 return -1;
12
13// Create a world transformation matrix and set it to an identity matrix.
14D3DXMATRIX world{ };
15D3DXMatrixIdentity(&world);
16// Create a scalation matrix scaling our primitive by 10 in the x,
17// 10 in the y and keeping the z direction.
18D3DXMATRIX scaling{ };
19D3DXMatrixScaling(&scaling, // matrix to scale
20 10, // x scaling
21 10, // y scaling
22 1); // z scaling
23// Create a rotation matrix storing the current rotation of our primitive.
24// We set the current rotation matrix to an identity matrix for now.
25D3DXMATRIX rotation{ };
26D3DXMatrixIdentity(&rotation);
27// Now we multiply the scalation and rotation matrix and store the result
28// in the world matrix.
29D3DXMatrixMultiply(&world, // destination matrix
30 &scaling, // matrix 1
31 &rotation); // matrix 2
32// Apply the current world matrix.
33_device->SetTransform(D3DTS_WORLD, &world);
34// Disable culling so we can see the back of our primitive when it rotates.
35_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
36// The default cullmode is D3DCULL_CW.
37// After we used our the rotation matrix for multiplication we can set it
38// to rotate a small amount.
39// D3DXToRadian() function converts degree to radians.
40D3DXMatrixRotationY(&rotation, // matrix to rotate
41 D3DXToRadian(0.5f)); // rotation angle in radians
42
43MSG msg{ };
44 while (_running) {
45 // ...
46 _device->Clear(0, nullptr, D3DCLEAR_TARGET,
47 D3DXCOLOR{ 0.0f, 0.0f, 0.0f, 1.0f }, 0.0f, 0);
48 // With everything setup we can call the draw function.
49 _device->BeginScene();
50 _device->DrawPrimitive(D3DPT_TRIANGLELIST, // primitive type
51 0, // start vertex
52 1); // primitive count
53 _device->EndScene();
54
55 _device->Present(nullptr, nullptr, nullptr, nullptr);
56 // We can keep multiplying the world matrix with our rotation matrix
57 // to add it's rotation to the world matrix.
58 D3DXMatrixMultiply(&world, &world, &rotation);
59 // Update the modified world matrix.
60 _device->SetTransform(D3DTS_WORLD, &world);
61 // ...
You should now be viewing a 10x10 units colored triangle from 20 units away, rotating around its origin.
You can find the complete working code here: DirectX - 1
Indexing ¶
To make it easier to draw primitives sharing a lot of vertices we can use indexing, so we only have to declare the unique vertices and put the order they are called in another array.
1// First we declare a new ComPtr for our index buffer.
2ComPtr<IDirect3DIndexBuffer9> _indexBuffer{ };
3// ...
4// Declare a function creating a index buffer from a std::vector
5IDirect3DIndexBuffer9* CreateIBuffer(std::vector<unsigned int>& indices) {
6 IDirect3DIndexBuffer9* buffer{ };
7 HRESULT result{ };
8 result = _device->CreateIndexBuffer(
9 GetByteSize(indices), // vector size in bytes
10 0, // data usage
11 D3DFMT_INDEX32, // format is 32 bit int
12 D3DPOOL_DEFAULT, // default pool
13 &buffer, // receiving buffer
14 nullptr); // special shared handle
15 if (FAILED(result))
16 return nullptr;
17 // Create a data pointer pointing to the buffer data.
18 void* data{ };
19 result = buffer->Lock(0, // byte offset
20 GetByteSize(indices), // byte size
21 &data, // receiving data pointer
22 0); // special lock flag
23 if (FAILED(result))
24 return nullptr;
25 // Copy the index data and unlock after copying.
26 memcpy(data, indices.data(), GetByteSize(indices));
27 buffer->Unlock();
28 // Return the filled index buffer.
29 return buffer;
30}
31// ...
32// In our WinMain we can now change the vertex data and create new index data.
33// ...
34std::vector<VStruct> vertices {
35 VStruct{ -1.0f, -1.0f, 1.0f, D3DXCOLOR{ 1.0f, 0.0f, 0.0f, 1.0f } },
36 VStruct{ -1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 1.0f, 0.0f, 1.0f } },
37 VStruct{ 1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 0.0f, 1.0f, 1.0f } },
38 // Add a vertex for the bottom right.
39 VStruct{ 1.0f, -1.0f, 1.0f, D3DXCOLOR{ 1.0f, 1.0f, 0.0f, 1.0f } }
40};
41// Declare the index data, here we build a rectangle from two triangles.
42std::vector<unsigned int> indices {
43 0, 1, 2, // the first triangle (b,left -> t,left -> t,right)
44 0, 2, 3 // the second triangle (b,left -> t,right -> b,right)
45};
46// ...
47// Now we call the "CreateIBuffer" function to create a index buffer.
48// ...
49if (!(_indexBuffer = CreateIBuffer(indices)))
50 return -1;
51// ...
52// After binding the vertex buffer we have to bind the index buffer to
53// use indexed rendering.
54result = _device->SetStreamSource(0, _vertexBuffer.Get(), 0, sizeof(VStruct));
55if (FAILED(result))
56 return -1;
57// Bind the index data to the default data stream.
58result = _device->SetIndices(_indexBuffer.Get())
59if (FAILED(result))
60 return -1;
61// ...
62// Now we replace the "DrawPrimitive" function with an indexed version.
63_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, // primitive type
64 0, // base vertex index
65 0, // minimum index
66 indices.size(), // amount of vertices
67 0, // start in index buffer
68 2); // primitive count
69// ...
Now you should see a colored rectangle made up of 2 triangles. If you set the primitive count in the «DrawIndexedPrimitive» method to 1 only the first triangle should be rendered and if you set the start of the index buffer to 3 and the primitive count to 1 only the second triangle should be rendered.
You can find the complete working code here: DirectX - 2
Vertex declaration ¶
Instead of using the old «flexible vertex format» we should use vertex declarations instead, as the FVF declarations get converted to vertex declarations internally anyway.
1// First we have to REMOVE the following lines:
2const unsigned long VertexStructFVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
3// and
4_device->SetFVF(VertexStructFVF);
5// ...
6// We also have to change the vertex buffer creation FVF-flag.
7result = _device->CreateVertexBuffer(
8 GetByteSize(vertices),
9 0,
10 0, // <- 0 indicates we use vertex declarations
11 D3DPOOL_DEFAULT,
12 &buffer,
13 nullptr);
14// Next we have to declare a new ComPtr.
15ComPtr<IDirect3DVertexDeclaration9> _vertexDecl{ };
16// ...
17result = _device->SetIndices(_indexBuffer.Get());
18if (FAILED(result))
19 return -1;
20// Now we have to declare and apply the vertex declaration.
21// Create a vector of vertex elements making up the vertex declaration.
22std::vector<D3DVERTEXELEMENT9> vertexDeclDesc {
23 { 0, // stream index
24 0, // byte offset from the struct beginning
25 D3DDECLTYPE_FLOAT3, // data type (3d float vector)
26 D3DDECLMETHOD_DEFAULT, // tessellator operation
27 D3DDECLUSAGE_POSITION, // usage of the data
28 0 }, // index (multiples usage of the same type)
29 { 0,
30 12, // byte offset (3 * sizeof(float) bytes)
31 D3DDECLTYPE_D3DCOLOR,
32 D3DDECLMETHOD_DEFAULT,
33 D3DDECLUSAGE_COLOR,
34 0 },
35 D3DDECL_END() // marks the end of the vertex declaration
36};
37// After having defined the vector we can create a vertex declaration from it.
38result = _device->CreateVertexDeclaration(
39 vertexDeclDesc.data(), // the vertex element array
40 &_vertexDecl); // receiving pointer
41if (FAILED(result))
42 return -1;
43// Apply the created vertex declaration.
44_device->SetVertexDeclaration(_vertexDecl.Get());
45// ...
Shader ¶
The maximum shader model for Direct3D 9 is shader model 3.0. Even though every modern graphics card should support it, it is best to check for capabilities.
1// ...
2_device->SetVertexDeclaration(_vertexDecl.Get());
3// First we have to request the device capabilities.
4D3DCAPS9 deviceCaps{ };
5_device->GetDeviceCaps(&deviceCaps);
6// Now we check if shader model 3.0 is supported for the vertex shader.
7if (deviceCaps.VertexShaderVersion < D3DVS_VERSION(3, 0))
8 return -1;
9// And the same for the pixel shader.
10if (deviceCaps.PixelShaderVersion < D3DPS_VERSION(3, 0))
11 return -1;
Now that we are sure shader model 3.0 is supported let’s create the vertex and pixel shader files. DirectX 9 introduced the HLSL (High Level Shading Language), a C-like shader language, which simplified the shader programming a lot, as you could only write shaders in shader assembly in DirectX 8. Let’s create a simple vertex- and pixel shader.
Vertex Shader
1// 3 4x4 float matrices representing the matrices we set in the fixed-function
2// pipeline by using the SetTransform() method.
3float4x4 projectionMatrix;
4float4x4 viewMatrix;
5float4x4 worldMatrix;
6// The input struct to the vertex shader.
7// It holds a 3d float vector for the position and a 4d float vector
8// for the color.
9struct VS_INPUT {
10 float3 position : POSITION;
11 float4 color : COLOR;
12};
13// The output struct of the vertex shader, that is passed to the pixel shader.
14struct VS_OUTPUT {
15 float4 position : POSITION;
16 float4 color : COLOR;
17};
18// The main function of the vertex shader returns the output it sends to the
19// pixel shader and receives it's input as a parameter.
20VS_OUTPUT main(VS_INPUT input) {
21 // Declare a empty struct, that the vertex shader returns.
22 VS_OUTPUT output;
23 // Set the output position to the input position and set
24 // the w-component to 1, as the input position is a 3d vector and
25 // the output position a 4d vector.
26 output.position = float4(input.position, 1.0f);
27 // Multiply the output position step by step with the world, view and
28 // projection matrices.
29 output.position = mul(output.position, worldMatrix);
30 output.position = mul(output.position, viewMatrix);
31 output.position = mul(output.position, projectionMatrix);
32 // Pass the input color unchanged to the pixel shader.
33 output.color = input.color;
34 // Return the output struct to the pixel shader.
35 // The position value is automatically used as the vertex position.
36 return output;
37}
Pixel Shader
1// The pixel shader input struct must be the same as the vertex shader output!
2struct PS_INPUT {
3 float4 position : POSITION;
4 float4 color : COLOR;
5};
6// The pixel shader simply returns a 4d vector representing the vertex color.
7// It receives it's input as a parameter just like the vertex shader.
8// We have to declare the output semantic as color to it gets interpreted
9// correctly.
10float4 main(PS_INPUT input) : COLOR {
11 return input.color;
12}
For more on semantics: DirectX - Semantics
Now we have to do quite some changes to the code.
1ComPtr<IDirect3DDevice9> _device{ };
2ComPtr<IDirect3DVertexBuffer9> _vertexBuffer{ };
3ComPtr<IDirect3DIndexBuffer9> _indexBuffer{ };
4ComPtr<IDirect3DVertexDeclaration9> _vertexDecl{ };
5// We have to add a ComPtr for the vertex- and pixel shader, aswell as one
6// for the constants (matrices) in our vertex shader.
7ComPtr<IDirect3DVertexShader9> _vertexShader{ };
8ComPtr<IDirect3DPixelShader9> _pixelShader{ };
9ComPtr<ID3DXConstantTable> _vertexTable{ };
10// Declare the world and rotation matrix as global, because we use them in
11// WinMain and SetupTransform now.
12D3DXMATRIX _worldMatrix{ };
13D3DXMATRIX _rotationMatrix{ };
14// ...
15bool SetupTransform() {
16 // Set the world and rotation matrix to an identity matrix.
17 D3DXMatrixIdentity(&_worldMatrix);
18 D3DXMatrixIdentity(&_rotationMatrix);
19
20 D3DXMATRIX scaling{ };
21 D3DXMatrixScaling(&scaling, 10, 10, 1);
22 D3DXMatrixMultiply(&_worldMatrix, &scaling, &_rotationMatrix);
23 // After multiplying the scalation and rotation matrix the have to pass
24 // them to the shader, by using a method from the constant table
25 // of the vertex shader.
26 HRESULT result{ };
27 result = _vertexTable->SetMatrix(
28 _device.Get(), // direct3d device
29 "worldMatrix", // matrix name in the shader
30 &_worldMatrix); // pointer to the matrix
31 if (FAILED(result))
32 return false;
33
34 D3DXMATRIX view{ };
35 D3DXMatrixLookAtLH(&view, &D3DXVECTOR3{ 0.0f, 0.0f, -20.0f },
36 &D3DXVECTOR3{ 0.0f, 0.0f, 0.0f }, &D3DXVECTOR3{ 0.0f, 1.0f, 0.0f });
37 // Do the same for the view matrix.
38 result = _vertexTable->SetMatrix(
39 _device.Get(), // direct 3d device
40 "viewMatrix", // matrix name
41 &view); // matrix
42 if (FAILED(result))
43 return false;
44
45 D3DXMATRIX projection{ };
46 D3DXMatrixPerspectiveFovLH(&projection, D3DXToRadian(60.0f),
47 1024.0f / 768.0f, 0.0f, 100.0f);
48 // And also for the projection matrix.
49 result = _vertexTable->SetMatrix(
50 _device.Get(),
51 "projectionMatrix",
52 &projection);
53 if (FAILED(result))
54 return false;
55
56 D3DXMatrixRotationY(&_rotationMatrix, D3DXToRadian(0.5f));
57 return true;
58}
59// ...
60// Vertex and index buffer creation aswell as initialization stay unchanged.
61// ...
62// After checking that shader model 3.0 is available we have to compile and
63// create the shaders.
64// Declare two temporary buffers storing the compiled shader code.
65ID3DXBuffer* vertexShaderBuffer{ };
66ID3DXBuffer* pixelShaderBuffer{ };
67result = D3DXCompileShaderFromFile("vertex.hlsl", // shader name
68 nullptr, // macro definitions
69 nullptr, // special includes
70 "main", // entry point name
71 "vs_3_0", // shader model version
72 0, // special flags
73 &vertexShaderBuffer, // code buffer
74 nullptr, // error message
75 &_vertexTable); // constant table
76if (FAILED(result))
77 return -1;
78// After the vertex shader compile the pixel shader.
79result = D3DXCompileShaderFromFile("pixel.hlsl",
80 nullptr,
81 nullptr,
82 "main",
83 "ps_3_0", // pixel shader model 3.0
84 0,
85 &pixelShaderBuffer,
86 nullptr,
87 nullptr); // no need for a constant table
88if (FAILED(result))
89 return -1;
90// Create the vertex shader from the code buffer.
91result = _device->CreateVertexShader(
92 (DWORD*)vertexShaderBuffer->GetBufferPointer(), // code buffer
93 &_vertexShader); // vertex shader pointer
94if (FAILED(result))
95 return -1;
96
97result = _device->CreatePixelShader(
98 (DWORD*)pixelShaderBuffer->GetBufferPointer(),
99 &_pixelShader);
100if (FAILED(result))
101 return -1;
102// Release the temporary code buffers after the shaders are created.
103vertexShaderBuffer->Release();
104pixelShaderBuffer->Release();
105// Apply the vertex- and pixel shader.
106_device->SetVertexShader(_vertexShader.Get());
107_device->SetPixelShader(_pixelShader.Get());
108// Apply the transform after the shaders have been set.
109if (!SetupTransform())
110 return -1;
111// You can also REMOVE the call so set the lighting render state.
112_device->SetRenderState(D3DRS_LIGHTING, false);
You can find the complete code here: DirectX - 3
Texturing ¶
1// First we need to declare a ComPtr for the texture.
2ComPtr<IDirect3DTexture9> _texture{ };
3// Then we have to change the vertex struct.
4struct VStruct {
5 float x, y, z;
6 float u, v; // Add texture u and v coordinates
7 D3DCOLOR color;
8};
9// In the vertex declaration we have to add the texture coordinates.
10// the top left of the texture is u: 0, v: 0.
11std::vector<VStruct> vertices {
12 VStruct{ -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, ... }, // bottom left
13 VStruct{ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, ... }, // top left
14 VStruct{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, ... }, // top right
15 VStruct{ 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, ... } // bottom right
16};
17// Next is the vertex declaration.
18std::vector<D3DVERTEXELEMENT9> vertexDecl{
19 {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
20 // Add a 2d float vector used for texture coordinates.
21 {0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
22 // The color offset is not (3 + 2) * sizeof(float) = 20 bytes
23 {0, 20, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
24 D3DDECL_END()
25};
26// Now we have to load the texture and pass its to the shader.
27// ...
28_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
29// Create a Direct3D texture from a png file.
30result = D3DXCreateTextureFromFile(_device.Get(), // direct3d device
31 "texture.png", // texture path
32 &_texture); // receiving texture pointer
33if (FAILED(result))
34 return -1;
35// Attach the texture to shader stage 0, which is equal to texture register 0
36// in the pixel shader.
37_device->SetTexture(0, _texture.Get());
With the main code ready we now have to adjust the shaders to these changes.
Vertex Shader
1float4x4 projectionMatrix;
2float4x4 viewMatrix;
3float4x4 worldMatrix;
4// Add the texture coordinates to the vertex shader in- and output.
5struct VS_INPUT {
6 float3 position : POSITION;
7 float2 texcoord : TEXCOORD;
8 float4 color : COLOR;
9};
10
11struct VS_OUTPUT {
12 float4 position : POSITION;
13 float2 texcoord : TEXCOORD;
14 float4 color : COLOR;
15};
16
17VS_OUTPUT main(VS_INPUT input) {
18 VS_OUTPUT output;
19
20 output.position = float4(input.position, 1.0f);
21 output.position = mul(output.position, worldMatrix);
22 output.position = mul(output.position, viewMatrix);
23 output.position = mul(output.position, projectionMatrix);
24
25 output.color = input.color;
26 // Set the texcoord output to the input.
27 output.texcoord = input.texcoord;
28
29 return output;
30}
Pixel Shader
1// Create a sampler called "sam0" using sampler register 0, which is equal
2// to the texture stage 0, to which we passed the texture.
3sampler sam0 : register(s0);
4
5struct PS_INPUT {
6 float4 position : POSITION;
7 float2 texcoord : TEXCOORD;
8 float4 color : COLOR;
9};
10
11float4 main(PS_INPUT input) : COLOR{
12 // Do a linear interpolation between the texture color and the input color
13 // using 75% of the input color.
14 // tex2D returns the texture data at the specified texture coordinate.
15 return lerp(tex2D(sam0, input.texcoord), input.color, 0.75f);
16}