DirectX 9

Личный сайт Go-разработчика из Казани

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}

Quotes

[1]DirectX - Wikipedia