category | framework | filename | contributors | |||
---|---|---|---|---|---|---|
framework |
DirectX 9 |
learndirectx9.cpp |
|
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.
#include <Windows.h>
bool _running{ false };
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
// Handle incoming message.
switch (msg) {
// Set running to false if the user tries to close the window.
case WM_DESTROY:
_running = false;
PostQuitMessage(0);
break;
}
// Return the handled event.
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
// Set window properties we want to use.
WNDCLASSEX wndEx{ };
wndEx.cbSize = sizeof(WNDCLASSEX); // structure size
wndEx.style = CS_VREDRAW | CS_HREDRAW; // class styles
wndEx.lpfnWndProc = WndProc; // window procedure
wndEx.cbClsExtra = 0; // extra memory (struct)
wndEx.cbWndExtra = 0; // extra memory (window)
wndEx.hInstance = hInstance; // module instance
wndEx.hIcon = LoadIcon(nullptr, IDI_APPLICATION); // icon
wndEx.hCursor = LoadCursor(nullptr, IDC_ARROW); // cursor
wndEx.hbrBackground = (HBRUSH) COLOR_WINDOW; // background color
wndEx.lpszMenuName = nullptr; // menu name
wndEx.lpszClassName = "DirectXClass"; // register class name
wndEx.hIconSm = nullptr; // small icon (taskbar)
// Register created class for window creation.
RegisterClassEx(&wndEx);
// Create a new window handle.
HWND hWnd{ nullptr };
// Create a new window handle using the registered class.
hWnd = CreateWindow("DirectXClass", // registered class
"directx window", // window title
WS_OVERLAPPEDWINDOW, // window style
50, 50, // x, y (position)
1024, 768, // width, height (size)
nullptr, // parent window
nullptr, // menu
hInstance, // module instance
nullptr); // struct for infos
// Check if a window handle has been created.
if (!hWnd)
return -1;
// Show and update the new window.
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// Start the game loop and send incoming messages to the window procedure.
_running = true;
MSG msg{ };
while (_running) {
while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
This should create a window, that can the moved, resized and closed.
// Includes DirectX 9 structures and functions.
// Remember to link "d3d9.lib" and "d3dx9.lib".
// For "d3dx9.lib" the DirectX SDK (June 2010) is needed.
// Don't forget to set your subsystem to Windows.
#include <d3d9.h>
#include <d3dx9.h>
// Includes the ComPtr, a smart pointer automatically releasing COM objects.
#include <wrl.h>
using namespace Microsoft::WRL;
// Next we define some Direct3D9 interface structs we need.
ComPtr<IDirect3D9> _d3d{ };
ComPtr<IDirect3DDevice9> _device{ };
With all interfaces declared we can now initialize Direct3D.
bool InitD3D(HWND hWnd) {
// Store the size of the window rectangle.
RECT clientRect{ };
GetClientRect(hWnd, &clientRect);
// Initialize Direct3D
_d3d = Direct3DCreate9(D3D_SDK_VERSION);
// Get the display mode which format will be the window format.
D3DDISPLAYMODE displayMode{ };
_d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, // use default graphics card
&displayMode); // display mode pointer
// Next we have to set some presentation parameters.
D3DPRESENT_PARAMETERS pp{ };
pp.BackBufferWidth = clientRect.right; // width is window width
pp.BackBufferHeight = clientRect.bottom; // height is window height
pp.BackBufferFormat = displayMode.Format; // use adapter format
pp.BackBufferCount = 1; // 1 back buffer (default)
pp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard after presentation
pp.hDeviceWindow = hWnd; // associated window handle
pp.Windowed = true; // display in window mode
pp.Flags = 0; // no special flags
// Variable to store results of methods to check if everything succeeded.
HRESULT result{ };
result = _d3d->CreateDevice(D3DADAPTER_DEFAULT, // use default graphics card
D3DDEVTYPE_HAL, // use hardware acceleration
hWnd, // the window handle
D3DCREATE_HARDWARE_VERTEXPROCESSING,
// vertices are processed by the hardware
&pp, // the present parameters
&_device); // struct to store the device
// Return false if the device creation failed.
// It is helpful to set breakpoints at the return line.
if (FAILED(result))
return false;
// Create a viewport which hold information about which region to draw to.
D3DVIEWPORT9 viewport{ };
viewport.X = 0; // start at top left corner
viewport.Y = 0; // ..
viewport.Width = clientRect.right; // use the entire window
viewport.Height = clientRect.bottom; // ..
viewport.MinZ = 0.0f; // minimum view distance
viewport.MaxZ = 100.0f; // maximum view distance
// Apply the created viewport.
result = _device->SetViewport(&viewport);
// Always check if something failed.
if (FAILED(result))
return false;
// Everything was successful, return true.
return true;
}
// ...
// Back in our WinMain function we call our initialization function.
// ...
// Check if Direct3D initialization succeeded, else exit the application.
if (!InitD3D(hWnd))
return -1;
MSG msg{ };
while (_running) {
while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Clear to render target to a specified color.
_device->Clear(0, // number of rects to clear
nullptr, // indicates to clear the entire window
D3DCLEAR_TARGET, // clear all render targets
D3DXCOLOR{ 1.0f, 0.0f, 0.0f, 1.0f }, // color (red)
0.0f, // depth buffer clear value
0); // stencil buffer clear value
// ...
// Drawing operations go here.
// ...
// Flip the front- and backbuffer.
_device->Present(nullptr, // no source rectangle
nullptr, // no destination rectangle
nullptr, // don't change the current window handle
nullptr); // pretty much always nullptr
}
// ...
Now the window should be displayed in a bright red color.
Let's create a vertex buffer to store the vertices for our triangle
// At the top of the file we need to add a include.
#include <vector>
// First we declare a new ComPtr holding a vertex buffer.
ComPtr<IDirect3DVertexBuffer9> _vertexBuffer{ };
// Lets define a function to calculate the byte size of a std::vector
template <typename T>
unsigned int GetByteSize(const std::vector<T>& vec) {
return sizeof(vec[0]) * vec.size();
}
// Define "flexible vertex format" describing the content of our vertex struct.
// Use the defined color as diffuse color.
const unsigned long VertexStructFVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
// Define a struct representing the vertex data the buffer will hold.
struct VStruct {
float x, y, z; // store the 3D position
D3DCOLOR color; // store a color
};
// Declare a new function to create a vertex buffer.
IDirect3DVertexBuffer9* CreateBuffer(const std::vector<VStruct>& vertices) {
// Declare the buffer to be returned.
IDirect3DVertexBuffer9* buffer{ };
HRESULT result{ };
result = _device->CreateVertexBuffer(
GetByteSize(vertices), // vector size in bytes
0, // data usage
VertexStructFVF, // FVF of the struct
D3DPOOL_DEFAULT, // use default pool for the buffer
&buffer, // receiving buffer
nullptr); // special shared handle
// Check if buffer was created successfully.
if (FAILED(result))
return nullptr;
// Create a data pointer for copying the vertex data
void* data{ };
// Lock the buffer to get a buffer for data storage.
result = buffer->Lock(0, // byte offset
GetByteSize(vertices), // size to lock
&data, // receiving data pointer
0); // special lock flags
// Check if buffer was locked successfully.
if (FAILED(result))
return nullptr;
// Copy the vertex data using C standard libraries memcpy.
memcpy(data, vertices.data(), GetByteSize(vertices));
buffer->Unlock();
// Set the FVF Direct3D uses for rendering.
_device->SetFVF(VertexStructFVF);
// If everything was successful return the filled vertex buffer.
return buffer;
}
In our WinMain we can now call the new function after the Direct3D initialization.
// ...
if (!InitD3D(hWnd))
return -1;
// Define the vertices we need to draw a triangle.
// Values are declared in a clockwise direction else Direct3D would cull them.
// If you want to disable culling just call:
// _device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
std::vector<VStruct> vertices {
// Bottom left
VStruct{ -1.0f, -1.0f, 1.0f, D3DXCOLOR{ 1.0f, 0.0f, 0.0f, 1.0f } },
// Top left
VStruct{ -1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 1.0f, 0.0f, 1.0f } },
// Top right
VStruct{ 1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 0.0f, 1.0f, 1.0f } }
};
// Try to create the vertex buffer else exit the application.
if (!(_vertexBuffer = CreateBuffer(vertices)))
return -1;
// ...
Before we can use the vertex buffer to draw our primitives, we first need to set up the matrices.
// Lets create a new functions for the matrix transformations.
bool SetupTransform() {
// Create a view matrix that transforms world space to
// view space.
D3DXMATRIX view{ };
// Use a left-handed coordinate system.
D3DXMatrixLookAtLH(
&view, // receiving matrix
&D3DXVECTOR3{ 0.0f, 0.0f, -20.0f }, // "camera" position
&D3DXVECTOR3{ 0.0f, 0.0f, 0.0f }, // position where to look at
&D3DXVECTOR3{ 0.0f, 1.0f, 0.0f }); // positive y-axis is up
HRESULT result{ };
result = _device->SetTransform(D3DTS_VIEW, &view); // apply the view matrix
if (FAILED(result))
return false;
// Create a projection matrix that defines the view frustrum.
// It transforms the view space to projection space.
D3DXMATRIX projection{ };
// Create a perspective projection using a left-handed coordinate system.
D3DXMatrixPerspectiveFovLH(
&projection, // receiving matrix
D3DXToRadian(60.0f), // field of view in radians
1024.0f / 768.0f, // aspect ratio (width / height)
0.0f, // minimum view distance
100.0f); // maximum view distance
result = _device->SetTransform(D3DTS_PROJECTION, &projection);
if (FAILED(result))
return false;
// Disable lighting for now so we can see what we want to render.
result = _device->SetRenderState(D3DRS_LIGHTING, false);
// View and projection matrix are successfully applied, return true.
return true;
}
// ...
// Back in the WinMain function we can now call the transformation function.
// ...
if (!(_vertexBuffer = CreateVertexBuffer(vertices)))
return -1;
// Call the transformation setup function.
if (!SetupTransform())
return -1;
// ...
Now that everything is setup we can start drawing our first 2D triangle in 3D space.
// ...
if (!SetupTransform())
return -1;
// First we have to bind our vertex buffer to the data stream.
HRESULT result{ };
result = _device->SetStreamSource(0, // use the default stream
_vertexBuffer.Get(), // pass the vertex buffer
0, // no offset
sizeof(VStruct)); // size of vertex struct
if (FAILED(result))
return -1;
// Create a world transformation matrix and set it to an identity matrix.
D3DXMATRIX world{ };
D3DXMatrixIdentity(&world);
// Create a scalation matrix scaling our primitive by 10 in the x,
// 10 in the y and keeping the z direction.
D3DXMATRIX scaling{ };
D3DXMatrixScaling(&scaling, // matrix to scale
10, // x scaling
10, // y scaling
1); // z scaling
// Create a rotation matrix storing the current rotation of our primitive.
// We set the current rotation matrix to an identity matrix for now.
D3DXMATRIX rotation{ };
D3DXMatrixIdentity(&rotation);
// Now we multiply the scalation and rotation matrix and store the result
// in the world matrix.
D3DXMatrixMultiply(&world, // destination matrix
&scaling, // matrix 1
&rotation); // matrix 2
// Apply the current world matrix.
_device->SetTransform(D3DTS_WORLD, &world);
// Disable culling so we can see the back of our primitive when it rotates.
_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
// The default cullmode is D3DCULL_CW.
// After we used our the rotation matrix for multiplication we can set it
// to rotate a small amount.
// D3DXToRadian() function converts degree to radians.
D3DXMatrixRotationY(&rotation, // matrix to rotate
D3DXToRadian(0.5f)); // rotation angle in radians
MSG msg{ };
while (_running) {
// ...
_device->Clear(0, nullptr, D3DCLEAR_TARGET,
D3DXCOLOR{ 0.0f, 0.0f, 0.0f, 1.0f }, 0.0f, 0);
// With everything setup we can call the draw function.
_device->BeginScene();
_device->DrawPrimitive(D3DPT_TRIANGLELIST, // primitive type
0, // start vertex
1); // primitive count
_device->EndScene();
_device->Present(nullptr, nullptr, nullptr, nullptr);
// We can keep multiplying the world matrix with our rotation matrix
// to add it's rotation to the world matrix.
D3DXMatrixMultiply(&world, &world, &rotation);
// Update the modified world matrix.
_device->SetTransform(D3DTS_WORLD, &world);
// ...
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
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.
// First we declare a new ComPtr for our index buffer.
ComPtr<IDirect3DIndexBuffer9> _indexBuffer{ };
// ...
// Declare a function creating a index buffer from a std::vector
IDirect3DIndexBuffer9* CreateIBuffer(std::vector<unsigned int>& indices) {
IDirect3DIndexBuffer9* buffer{ };
HRESULT result{ };
result = _device->CreateIndexBuffer(
GetByteSize(indices), // vector size in bytes
0, // data usage
D3DFMT_INDEX32, // format is 32 bit int
D3DPOOL_DEFAULT, // default pool
&buffer, // receiving buffer
nullptr); // special shared handle
if (FAILED(result))
return nullptr;
// Create a data pointer pointing to the buffer data.
void* data{ };
result = buffer->Lock(0, // byte offset
GetByteSize(indices), // byte size
&data, // receiving data pointer
0); // special lock flag
if (FAILED(result))
return nullptr;
// Copy the index data and unlock after copying.
memcpy(data, indices.data(), GetByteSize(indices));
buffer->Unlock();
// Return the filled index buffer.
return buffer;
}
// ...
// In our WinMain we can now change the vertex data and create new index data.
// ...
std::vector<VStruct> vertices {
VStruct{ -1.0f, -1.0f, 1.0f, D3DXCOLOR{ 1.0f, 0.0f, 0.0f, 1.0f } },
VStruct{ -1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 1.0f, 0.0f, 1.0f } },
VStruct{ 1.0f, 1.0f, 1.0f, D3DXCOLOR{ 0.0f, 0.0f, 1.0f, 1.0f } },
// Add a vertex for the bottom right.
VStruct{ 1.0f, -1.0f, 1.0f, D3DXCOLOR{ 1.0f, 1.0f, 0.0f, 1.0f } }
};
// Declare the index data, here we build a rectangle from two triangles.
std::vector<unsigned int> indices {
0, 1, 2, // the first triangle (b,left -> t,left -> t,right)
0, 2, 3 // the second triangle (b,left -> t,right -> b,right)
};
// ...
// Now we call the "CreateIBuffer" function to create a index buffer.
// ...
if (!(_indexBuffer = CreateIBuffer(indices)))
return -1;
// ...
// After binding the vertex buffer we have to bind the index buffer to
// use indexed rendering.
result = _device->SetStreamSource(0, _vertexBuffer.Get(), 0, sizeof(VStruct));
if (FAILED(result))
return -1;
// Bind the index data to the default data stream.
result = _device->SetIndices(_indexBuffer.Get())
if (FAILED(result))
return -1;
// ...
// Now we replace the "DrawPrimitive" function with an indexed version.
_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, // primitive type
0, // base vertex index
0, // minimum index
indices.size(), // amount of vertices
0, // start in index buffer
2); // primitive count
// ...
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
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.
// First we have to REMOVE the following lines:
const unsigned long VertexStructFVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
// and
_device->SetFVF(VertexStructFVF);
// ...
// We also have to change the vertex buffer creation FVF-flag.
result = _device->CreateVertexBuffer(
GetByteSize(vertices),
0,
0, // <- 0 indicates we use vertex declarations
D3DPOOL_DEFAULT,
&buffer,
nullptr);
// Next we have to declare a new ComPtr.
ComPtr<IDirect3DVertexDeclaration9> _vertexDecl{ };
// ...
result = _device->SetIndices(_indexBuffer.Get());
if (FAILED(result))
return -1;
// Now we have to declare and apply the vertex declaration.
// Create a vector of vertex elements making up the vertex declaration.
std::vector<D3DVERTEXELEMENT9> vertexDeclDesc {
{ 0, // stream index
0, // byte offset from the struct beginning
D3DDECLTYPE_FLOAT3, // data type (3d float vector)
D3DDECLMETHOD_DEFAULT, // tessellator operation
D3DDECLUSAGE_POSITION, // usage of the data
0 }, // index (multiples usage of the same type)
{ 0,
12, // byte offset (3 * sizeof(float) bytes)
D3DDECLTYPE_D3DCOLOR,
D3DDECLMETHOD_DEFAULT,
D3DDECLUSAGE_COLOR,
0 },
D3DDECL_END() // marks the end of the vertex declaration
};
// After having defined the vector we can create a vertex declaration from it.
result = _device->CreateVertexDeclaration(
vertexDeclDesc.data(), // the vertex element array
&_vertexDecl); // receiving pointer
if (FAILED(result))
return -1;
// Apply the created vertex declaration.
_device->SetVertexDeclaration(_vertexDecl.Get());
// ...
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.
// ...
_device->SetVertexDeclaration(_vertexDecl.Get());
// First we have to request the device capabilities.
D3DCAPS9 deviceCaps{ };
_device->GetDeviceCaps(&deviceCaps);
// Now we check if shader model 3.0 is supported for the vertex shader.
if (deviceCaps.VertexShaderVersion < D3DVS_VERSION(3, 0))
return -1;
// And the same for the pixel shader.
if (deviceCaps.PixelShaderVersion < D3DPS_VERSION(3, 0))
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
// 3 4x4 float matrices representing the matrices we set in the fixed-function
// pipeline by using the SetTransform() method.
float4x4 projectionMatrix;
float4x4 viewMatrix;
float4x4 worldMatrix;
// The input struct to the vertex shader.
// It holds a 3d float vector for the position and a 4d float vector
// for the color.
struct VS_INPUT {
float3 position : POSITION;
float4 color : COLOR;
};
// The output struct of the vertex shader, that is passed to the pixel shader.
struct VS_OUTPUT {
float4 position : POSITION;
float4 color : COLOR;
};
// The main function of the vertex shader returns the output it sends to the
// pixel shader and receives it's input as a parameter.
VS_OUTPUT main(VS_INPUT input) {
// Declare a empty struct, that the vertex shader returns.
VS_OUTPUT output;
// Set the output position to the input position and set
// the w-component to 1, as the input position is a 3d vector and
// the output position a 4d vector.
output.position = float4(input.position, 1.0f);
// Multiply the output position step by step with the world, view and
// projection matrices.
output.position = mul(output.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// Pass the input color unchanged to the pixel shader.
output.color = input.color;
// Return the output struct to the pixel shader.
// The position value is automatically used as the vertex position.
return output;
}
Pixel Shader
// The pixel shader input struct must be the same as the vertex shader output!
struct PS_INPUT {
float4 position : POSITION;
float4 color : COLOR;
};
// The pixel shader simply returns a 4d vector representing the vertex color.
// It receives it's input as a parameter just like the vertex shader.
// We have to declare the output semantic as color to it gets interpreted
// correctly.
float4 main(PS_INPUT input) : COLOR {
return input.color;
}
For more on semantics: DirectX - Semantics
Now we have to do quite some changes to the code.
ComPtr<IDirect3DDevice9> _device{ };
ComPtr<IDirect3DVertexBuffer9> _vertexBuffer{ };
ComPtr<IDirect3DIndexBuffer9> _indexBuffer{ };
ComPtr<IDirect3DVertexDeclaration9> _vertexDecl{ };
// We have to add a ComPtr for the vertex- and pixel shader, aswell as one
// for the constants (matrices) in our vertex shader.
ComPtr<IDirect3DVertexShader9> _vertexShader{ };
ComPtr<IDirect3DPixelShader9> _pixelShader{ };
ComPtr<ID3DXConstantTable> _vertexTable{ };
// Declare the world and rotation matrix as global, because we use them in
// WinMain and SetupTransform now.
D3DXMATRIX _worldMatrix{ };
D3DXMATRIX _rotationMatrix{ };
// ...
bool SetupTransform() {
// Set the world and rotation matrix to an identity matrix.
D3DXMatrixIdentity(&_worldMatrix);
D3DXMatrixIdentity(&_rotationMatrix);
D3DXMATRIX scaling{ };
D3DXMatrixScaling(&scaling, 10, 10, 1);
D3DXMatrixMultiply(&_worldMatrix, &scaling, &_rotationMatrix);
// After multiplying the scalation and rotation matrix the have to pass
// them to the shader, by using a method from the constant table
// of the vertex shader.
HRESULT result{ };
result = _vertexTable->SetMatrix(
_device.Get(), // direct3d device
"worldMatrix", // matrix name in the shader
&_worldMatrix); // pointer to the matrix
if (FAILED(result))
return false;
D3DXMATRIX view{ };
D3DXMatrixLookAtLH(&view, &D3DXVECTOR3{ 0.0f, 0.0f, -20.0f },
&D3DXVECTOR3{ 0.0f, 0.0f, 0.0f }, &D3DXVECTOR3{ 0.0f, 1.0f, 0.0f });
// Do the same for the view matrix.
result = _vertexTable->SetMatrix(
_device.Get(), // direct 3d device
"viewMatrix", // matrix name
&view); // matrix
if (FAILED(result))
return false;
D3DXMATRIX projection{ };
D3DXMatrixPerspectiveFovLH(&projection, D3DXToRadian(60.0f),
1024.0f / 768.0f, 0.0f, 100.0f);
// And also for the projection matrix.
result = _vertexTable->SetMatrix(
_device.Get(),
"projectionMatrix",
&projection);
if (FAILED(result))
return false;
D3DXMatrixRotationY(&_rotationMatrix, D3DXToRadian(0.5f));
return true;
}
// ...
// Vertex and index buffer creation aswell as initialization stay unchanged.
// ...
// After checking that shader model 3.0 is available we have to compile and
// create the shaders.
// Declare two temporary buffers storing the compiled shader code.
ID3DXBuffer* vertexShaderBuffer{ };
ID3DXBuffer* pixelShaderBuffer{ };
result = D3DXCompileShaderFromFile("vertex.hlsl", // shader name
nullptr, // macro definitions
nullptr, // special includes
"main", // entry point name
"vs_3_0", // shader model version
0, // special flags
&vertexShaderBuffer, // code buffer
nullptr, // error message
&_vertexTable); // constant table
if (FAILED(result))
return -1;
// After the vertex shader compile the pixel shader.
result = D3DXCompileShaderFromFile("pixel.hlsl",
nullptr,
nullptr,
"main",
"ps_3_0", // pixel shader model 3.0
0,
&pixelShaderBuffer,
nullptr,
nullptr); // no need for a constant table
if (FAILED(result))
return -1;
// Create the vertex shader from the code buffer.
result = _device->CreateVertexShader(
(DWORD*)vertexShaderBuffer->GetBufferPointer(), // code buffer
&_vertexShader); // vertex shader pointer
if (FAILED(result))
return -1;
result = _device->CreatePixelShader(
(DWORD*)pixelShaderBuffer->GetBufferPointer(),
&_pixelShader);
if (FAILED(result))
return -1;
// Release the temporary code buffers after the shaders are created.
vertexShaderBuffer->Release();
pixelShaderBuffer->Release();
// Apply the vertex- and pixel shader.
_device->SetVertexShader(_vertexShader.Get());
_device->SetPixelShader(_pixelShader.Get());
// Apply the transform after the shaders have been set.
if (!SetupTransform())
return -1;
// You can also REMOVE the call so set the lighting render state.
_device->SetRenderState(D3DRS_LIGHTING, false);
You can find the complete code here: DirectX - 3
// First we need to declare a ComPtr for the texture.
ComPtr<IDirect3DTexture9> _texture{ };
// Then we have to change the vertex struct.
struct VStruct {
float x, y, z;
float u, v; // Add texture u and v coordinates
D3DCOLOR color;
};
// In the vertex declaration we have to add the texture coordinates.
// the top left of the texture is u: 0, v: 0.
std::vector<VStruct> vertices {
VStruct{ -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, ... }, // bottom left
VStruct{ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, ... }, // top left
VStruct{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, ... }, // top right
VStruct{ 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, ... } // bottom right
};
// Next is the vertex declaration.
std::vector<D3DVERTEXELEMENT9> vertexDecl{
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
// Add a 2d float vector used for texture coordinates.
{0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
// The color offset is not (3 + 2) * sizeof(float) = 20 bytes
{0, 20, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
D3DDECL_END()
};
// Now we have to load the texture and pass its to the shader.
// ...
_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
// Create a Direct3D texture from a png file.
result = D3DXCreateTextureFromFile(_device.Get(), // direct3d device
"texture.png", // texture path
&_texture); // receiving texture pointer
if (FAILED(result))
return -1;
// Attach the texture to shader stage 0, which is equal to texture register 0
// in the pixel shader.
_device->SetTexture(0, _texture.Get());
With the main code ready we now have to adjust the shaders to these changes.
Vertex Shader
float4x4 projectionMatrix;
float4x4 viewMatrix;
float4x4 worldMatrix;
// Add the texture coordinates to the vertex shader in- and output.
struct VS_INPUT {
float3 position : POSITION;
float2 texcoord : TEXCOORD;
float4 color : COLOR;
};
struct VS_OUTPUT {
float4 position : POSITION;
float2 texcoord : TEXCOORD;
float4 color : COLOR;
};
VS_OUTPUT main(VS_INPUT input) {
VS_OUTPUT output;
output.position = float4(input.position, 1.0f);
output.position = mul(output.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
output.color = input.color;
// Set the texcoord output to the input.
output.texcoord = input.texcoord;
return output;
}
Pixel Shader
// Create a sampler called "sam0" using sampler register 0, which is equal
// to the texture stage 0, to which we passed the texture.
sampler sam0 : register(s0);
struct PS_INPUT {
float4 position : POSITION;
float2 texcoord : TEXCOORD;
float4 color : COLOR;
};
float4 main(PS_INPUT input) : COLOR{
// Do a linear interpolation between the texture color and the input color
// using 75% of the input color.
// tex2D returns the texture data at the specified texture coordinate.
return lerp(tex2D(sam0, input.texcoord), input.color, 0.75f);
}