Skip to content

Instantly share code, notes, and snippets.

@d7samurai
Last active March 22, 2024 02:16
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save d7samurai/abab8a580d0298cb2f34a44eec41d39d to your computer and use it in GitHub Desktop.
Save d7samurai/abab8a580d0298cb2f34a44eec41d39d to your computer and use it in GitHub Desktop.
Minimal D3D11 pt3

Minimal D3D11 pt3

An elaboration on Minimal D3D11 pt2, adding shadowmapping and incorporating various improvements and alternative approaches to the rendering setup, such as manual vertex fetching, samplerless texture lookup, null shader depth map rendering and procedurally generated texture and instance data.

As before, this is intended to be an an "API familiarizer" - an uncluttered Direct3D 11 setup & basic rendering reference implementation, in the form of a complete, runnable Windows application contained in a single function and laid out in a linear, step-by-step fashion. No modern C++ / OOP / obscuring cruft, only ~190 LOC. View on YouTube

minimald3d11pt3

#pragma comment(lib, "user32")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d3dcompiler")
///////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
#define TITLE "Minimal D3D11 pt3 by d7samurai"
///////////////////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSA wndClass = { 0, DefWindowProcA, 0, 0, 0, 0, 0, 0, 0, TITLE };
RegisterClassA(&wndClass);
HWND window = CreateWindowExA(0, TITLE, TITLE, WS_POPUP | WS_MAXIMIZE | WS_VISIBLE, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0 };
ID3D11Device* baseDevice;
ID3D11DeviceContext* baseDeviceContext;
D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &baseDevice, nullptr, &baseDeviceContext);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3D11Device1* device;
baseDevice->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(&device));
ID3D11DeviceContext1* deviceContext;
baseDeviceContext->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(&deviceContext));
///////////////////////////////////////////////////////////////////////////////////////////////
IDXGIDevice1* dxgiDevice;
device->QueryInterface(__uuidof(IDXGIDevice1), reinterpret_cast<void**>(&dxgiDevice));
IDXGIAdapter* dxgiAdapter;
dxgiDevice->GetAdapter(&dxgiAdapter);
IDXGIFactory2* dxgiFactory;
dxgiAdapter->GetParent(__uuidof(IDXGIFactory2), reinterpret_cast<void**>(&dxgiFactory));
///////////////////////////////////////////////////////////////////////////////////////////////
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = 0; // use window width
swapChainDesc.Height = 0; // use window height
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // can't specify _SRGB here when using DXGI_SWAP_EFFECT_FLIP_* ...
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
IDXGISwapChain1* swapChain;
dxgiFactory->CreateSwapChainForHwnd(device, window, &swapChainDesc, nullptr, nullptr, &swapChain);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3D11Texture2D* framebufferTexture;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&framebufferTexture));
D3D11_RENDER_TARGET_VIEW_DESC framebufferDesc = {};
framebufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; // ... so do this to get _SRGB swapchain (rendertarget view)
framebufferDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
ID3D11RenderTargetView* framebufferRTV;
device->CreateRenderTargetView(framebufferTexture, &framebufferDesc, &framebufferRTV);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_TEXTURE2D_DESC framebufferDepthDesc;
framebufferTexture->GetDesc(&framebufferDepthDesc); // copy from framebuffer properties
framebufferDepthDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
framebufferDepthDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
ID3D11Texture2D* framebufferDepthTexture;
device->CreateTexture2D(&framebufferDepthDesc, nullptr, &framebufferDepthTexture);
ID3D11DepthStencilView* framebufferDSV;
device->CreateDepthStencilView(framebufferDepthTexture, nullptr, &framebufferDSV);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_TEXTURE2D_DESC shadowmapDepthDesc = {};
shadowmapDepthDesc.Width = 2048;
shadowmapDepthDesc.Height = 2048;
shadowmapDepthDesc.MipLevels = 1;
shadowmapDepthDesc.ArraySize = 1;
shadowmapDepthDesc.Format = DXGI_FORMAT_R32_TYPELESS;
shadowmapDepthDesc.SampleDesc.Count = 1;
shadowmapDepthDesc.Usage = D3D11_USAGE_DEFAULT;
shadowmapDepthDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
ID3D11Texture2D* shadowmapDepthTexture;
device->CreateTexture2D(&shadowmapDepthDesc, nullptr, &shadowmapDepthTexture);
D3D11_DEPTH_STENCIL_VIEW_DESC shadowmapDSVdesc = {};
shadowmapDSVdesc.Format = DXGI_FORMAT_D32_FLOAT;
shadowmapDSVdesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
ID3D11DepthStencilView* shadowmapDSV;
device->CreateDepthStencilView(shadowmapDepthTexture, &shadowmapDSVdesc, &shadowmapDSV);
D3D11_SHADER_RESOURCE_VIEW_DESC shadowmapSRVdesc = {};
shadowmapSRVdesc.Format = DXGI_FORMAT_R32_FLOAT;
shadowmapSRVdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
shadowmapSRVdesc.Texture2D.MipLevels = 1;
ID3D11ShaderResourceView* shadowmapSRV;
device->CreateShaderResourceView(shadowmapDepthTexture, &shadowmapSRVdesc, &shadowmapSRV);
///////////////////////////////////////////////////////////////////////////////////////////////
struct float4 { float x, y, z, w; };
struct Constants
{
float4 CameraProjection[4];
float4 LightProjection[4];
float4 LightRotation;
float4 ModelRotation;
float4 ModelTranslation;
float4 ShadowmapSize;
};
D3D11_BUFFER_DESC constantBufferDesc = {};
constantBufferDesc.ByteWidth = sizeof(Constants) + 0xf & 0xfffffff0; // ensure constant buffer size is multiple of 16 bytes
constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
ID3D11Buffer* constantBuffer;
device->CreateBuffer(&constantBufferDesc, nullptr, &constantBuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
float vertexData[] = { -1, 1, -1, 0, 0, 1, 1, -1, 9.5f, 0, -0.58f, 0.58f, -1, 2, 2, 0.58f, 0.58f, -1, 7.5f, 2, -0.58f, 0.58f, -1, 0, 0, 0.58f, 0.58f, -1, 0, 0, -0.58f, 0.58f, -0.58f, 0, 0, 0.58f, 0.58f, -0.58f, 0, 0 }; // pos.x, pos.y, pos.z, tex.u, tex.v, ...
D3D11_BUFFER_DESC vertexBufferDesc = {};
vertexBufferDesc.ByteWidth = sizeof(vertexData);
vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexBufferDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; // using regular shader resource as vertex buffer for manual vertex fetch
vertexBufferDesc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
vertexBufferDesc.StructureByteStride = 5 * sizeof(float); // 5 floats per vertex (float3 position, float2 texcoord)
D3D11_SUBRESOURCE_DATA vertexBufferData = { vertexData };
ID3D11Buffer* vertexBuffer;
device->CreateBuffer(&vertexBufferDesc, &vertexBufferData, &vertexBuffer);
D3D11_SHADER_RESOURCE_VIEW_DESC vertexBufferSRVdesc = {};
vertexBufferSRVdesc.Format = DXGI_FORMAT_UNKNOWN;
vertexBufferSRVdesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
vertexBufferSRVdesc.Buffer.NumElements = vertexBufferDesc.ByteWidth / vertexBufferDesc.StructureByteStride;
ID3D11ShaderResourceView* vertexBufferSRV;
device->CreateShaderResourceView(vertexBuffer, &vertexBufferSRVdesc, &vertexBufferSRV);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_DEPTH_STENCIL_DESC depthStencilDesc = {};
depthStencilDesc.DepthEnable = TRUE;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
ID3D11DepthStencilState* depthStencilState;
device->CreateDepthStencilState(&depthStencilDesc, &depthStencilState);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_RASTERIZER_DESC1 rasterizerDesc = {};
rasterizerDesc.FillMode = D3D11_FILL_SOLID;
rasterizerDesc.CullMode = D3D11_CULL_BACK;
ID3D11RasterizerState1* cullBackRS;
device->CreateRasterizerState1(&rasterizerDesc, &cullBackRS);
rasterizerDesc.CullMode = D3D11_CULL_FRONT;
ID3D11RasterizerState1* cullFrontRS;
device->CreateRasterizerState1(&rasterizerDesc, &cullFrontRS);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* framebufferVSBlob;
D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "framebuffer_vs", "vs_5_0", 0, 0, &framebufferVSBlob, nullptr);
ID3D11VertexShader* framebufferVS;
device->CreateVertexShader(framebufferVSBlob->GetBufferPointer(), framebufferVSBlob->GetBufferSize(), nullptr, &framebufferVS);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* framebufferPSBlob;
D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "framebuffer_ps", "ps_5_0", 0, 0, &framebufferPSBlob, nullptr);
ID3D11PixelShader* framebufferPS;
device->CreatePixelShader(framebufferPSBlob->GetBufferPointer(), framebufferPSBlob->GetBufferSize(), nullptr, &framebufferPS);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* shadowmapVSBlob;
D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "shadowmap_vs", "vs_5_0", 0, 0, &shadowmapVSBlob, nullptr);
ID3D11VertexShader* shadowmapVS;
device->CreateVertexShader(shadowmapVSBlob->GetBufferPointer(), shadowmapVSBlob->GetBufferSize(), nullptr, &shadowmapVS);
///////////////////////////////////////////////////////////////////////////////////////////////
FLOAT framebufferClear[4] = { 0.025f, 0.025f, 0.025f, 1 };
D3D11_VIEWPORT framebufferVP = { 0, 0, static_cast<float>(framebufferDepthDesc.Width), static_cast<float>(framebufferDepthDesc.Height), 0, 1 };
D3D11_VIEWPORT shadowmapVP = { 0, 0, static_cast<float>(shadowmapDepthDesc.Width), static_cast<float>(shadowmapDepthDesc.Height), 0, 1 };
ID3D11ShaderResourceView* nullSRV = nullptr; // null srv used for unbinding resources
///////////////////////////////////////////////////////////////////////////////////////////////
Constants constants = { 2.0f / (framebufferVP.Width / framebufferVP.Height), 0, 0, 0, 0, 2, 0, 0, 0, 0, 1.125f, 1, 0, 0, -1.125f, 0, // camera projection matrix (perspective)
0.5f, 0, 0, 0, 0, 0.5f, 0, 0, 0, 0, 0.125f, 0, 0, 0, -0.125f, 1 }; // light projection matrix (orthographic)
constants.LightRotation = { 0.8f, 0.6f, 0.0f };
constants.ModelRotation = { 0.0f, 0.0f, 0.0f };
constants.ModelTranslation = { 0.0f, 0.0f, 4.0f };
constants.ShadowmapSize = { shadowmapVP.Width, shadowmapVP.Height };
///////////////////////////////////////////////////////////////////////////////////////////////
while (true)
{
MSG msg;
while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_KEYDOWN) return 0;
DispatchMessageA(&msg);
}
///////////////////////////////////////////////////////////////////////////////////////////
constants.ModelRotation.x += 0.001f;
constants.ModelRotation.y += 0.005f;
constants.ModelRotation.z += 0.003f;
///////////////////////////////////////////////////////////////////////////////////////////
D3D11_MAPPED_SUBRESOURCE mappedSubresource;
deviceContext->Map(constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubresource);
*reinterpret_cast<Constants*>(mappedSubresource.pData) = constants;
deviceContext->Unmap(constantBuffer, 0);
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->ClearDepthStencilView(shadowmapDSV, D3D11_CLEAR_DEPTH, 1.0f, 0);
deviceContext->OMSetRenderTargets(0, nullptr, shadowmapDSV); // null rendertarget for depth only
deviceContext->OMSetDepthStencilState(depthStencilState, 0);
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); // using triangle strip this time
deviceContext->VSSetConstantBuffers(0, 1, &constantBuffer);
deviceContext->VSSetShaderResources(0, 1, &vertexBufferSRV);
deviceContext->VSSetShader(shadowmapVS, nullptr, 0);
deviceContext->RSSetViewports(1, &shadowmapVP);
deviceContext->RSSetState(cullFrontRS);
deviceContext->PSSetShader(nullptr, nullptr, 0); // null pixelshader for depth only
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->DrawInstanced(8, 24, 0, 0); // render shadowmap (light pov)
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->ClearRenderTargetView(framebufferRTV, framebufferClear);
deviceContext->ClearDepthStencilView(framebufferDSV, D3D11_CLEAR_DEPTH, 1.0f, 0);
deviceContext->OMSetRenderTargets(1, &framebufferRTV, framebufferDSV);
deviceContext->VSSetShader(framebufferVS, nullptr, 0);
deviceContext->RSSetViewports(1, &framebufferVP);
deviceContext->RSSetState(cullBackRS);
deviceContext->PSSetShaderResources(1, 1, &shadowmapSRV);
deviceContext->PSSetShader(framebufferPS, nullptr, 0);
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->DrawInstanced(8, 24, 0, 0); // render framebuffer (camera pov)
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->PSSetShaderResources(1, 1, &nullSRV); // release shadowmap as srv to avoid srv/dsv conflict
///////////////////////////////////////////////////////////////////////////////////////////
swapChain->Present(1, 0);
}
}
cbuffer constants : register(b0)
{
row_major float4x4 cameraprojection;
row_major float4x4 lightprojection;
float3 lightrotation;
float3 modelrotation;
float4 modeltranslation;
float2 shadowmapsize;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
struct input
{
float3 position;
float2 texcoord;
};
struct output
{
float4 position : SV_POSITION;
float4 lightpos : LPS;
float2 texcoord : TEX;
nointerpolation float4 color : COL;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
StructuredBuffer<input> vertexbuffer : register(t0);
Texture2D<float> shadowmap : register(t1);
///////////////////////////////////////////////////////////////////////////////////////////////////
float3 get_rotation(uint i)
{
return float3(max(0, float(i / 4) * 2 - 7), i / 4 % 5, i % 4) * 1.5708f; // generate XYZ rotation from instance id
}
float4x4 get_rotation_matrix(float3 r)
{
float4x4 x = { 1, 0, 0, 0, 0, cos(r.x), -sin(r.x), 0, 0, sin(r.x), cos(r.x), 0, 0, 0, 0, 1 };
float4x4 y = { cos(r.y), 0, sin(r.y), 0, 0, 1, 0, 0, -sin(r.y), 0, cos(r.y), 0, 0, 0, 0, 1 };
float4x4 z = { cos(r.z), -sin(r.z), 0, 0, sin(r.z), cos(r.z), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
return mul(mul(z, y), x);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
output framebuffer_vs(uint vertexid : SV_VERTEXID, uint instanceid : SV_INSTANCEID)
{
input myinput = vertexbuffer[vertexid]; // manual vertex fetch
static float4 normal[2] = { { 0, 0, -1, 0 }, { 0, -1, 0, 0 } };
static float3 color [6] = { { 0.973f, 0.480f, 0.002f }, { 0.897f, 0.163f, 0.011f }, { 0.612f, 0.000f, 0.069f }, { 0.127f, 0.116f, 0.408f }, { 0.000f, 0.254f, 0.637f }, { 0.001f, 0.447f, 0.067f } };
float4x4 modeltransform = mul(get_rotation_matrix(get_rotation(instanceid)), get_rotation_matrix(modelrotation));
float4x4 lighttransform = mul(modeltransform, get_rotation_matrix(lightrotation));
float light = clamp(dot(mul(normal[vertexid / 4], lighttransform), normal[0]), 0.0f, 1.0f);
output myoutput;
myoutput.position = mul(mul(float4(myinput.position, 1.0f), modeltransform) + modeltranslation, cameraprojection);
myoutput.lightpos = mul(mul(float4(myinput.position, 1.0f), lighttransform) + modeltranslation, lightprojection);
myoutput.texcoord = myinput.texcoord;
myoutput.color = float4(color[instanceid / 4], light);
myoutput.lightpos.xy = (myoutput.lightpos.xy * float2(0.5f, -0.5f) + 0.5f) * shadowmapsize;
return myoutput;
}
float4 framebuffer_ps(output myinput) : SV_TARGET
{
float3 color = myinput.color.rgb * ((uint(myinput.texcoord.x * 2) ^ uint(myinput.texcoord.y * 2)) & 1 ? 0.25f : 1.0f); // procedural checkerboard pattern
float light = myinput.color.a;
if (light > 0.0f && shadowmap[myinput.lightpos.xy] < myinput.lightpos.z) light *= 0.2;
return float4(color * (light * 0.8f + 0.2f), 1);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
float4 shadowmap_vs(uint vertexid : SV_VERTEXID, uint instanceid : SV_INSTANCEID) : SV_POSITION
{
float4x4 modeltransform = mul(get_rotation_matrix(get_rotation(instanceid)), get_rotation_matrix(modelrotation));
float4x4 lighttransform = mul(modeltransform, get_rotation_matrix(lightrotation));
return mul(mul(float4(vertexbuffer[vertexid].position, 1.0f), lighttransform) + modeltranslation, lightprojection);
}
@CptBishop89
Copy link

Great example how instead of wasting time on stack you can study simple, well made examples. I literally cannot stress it enough how yours 2 previous examples helped me to simply understand what goes where and how a simple pipeline should look like, and I will be glad to test this one too when the time comes :)

@DevAgu93
Copy link

Hi! just wanted to say thank you for the minimal example. Feels great not to need to re-study the api.

@pogrammerX
Copy link

The ultimate GOAT! Mate you don't understand how much you've helped me setup Shadows for my game engine, I will be forever thankful!

@Alan-Barclay
Copy link

Finally, a tutorial that cut's out all the crap and just gets down to business.
even better, the sample code supplied actually works (yeah you read that right, WORKS!)

I do have a few small suggestions though :-P (maybe wish list!)

  1. The addition of parallel tutorials achieving the same results in both DX9, and DX12 to highlight the different methods used by each library.

  2. Adding some OOP in the name of class structures and precompiled headers.

  3. using assert() to throw/trap Visual Studio errors when something goes horribly wrong, typos etc. (I got a weird 'shaders.hlsl' error as I had externally created this in notepad?)

as far as what is provided, these tutorials are top of my list for readability, functionality and ease of use, I hope you produce many, many more, especially if they are as good as these tutorials.

🥇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment