Your First Shaders
5.1 A complete vertex + pixel shader
// shaders.hlsl
struct VSInput
{
float3 position : POSITION;
float4 color : COLOR;
};
struct PSInput
{
float4 position : SV_Position; // clip-space position, required output of the VS
float4 color : COLOR;
};
PSInput VSMain(VSInput input)
{
PSInput output;
output.position = float4(input.position, 1.0);
output.color = input.color;
return output;
}
float4 PSMain(PSInput input) : SV_Target
{
return input.color; // interpolated color
}dxc -T vs_6_0 -E VSMain shaders.hlsl -Fo shaders_vs.cso
dxc -T ps_6_0 -E PSMain shaders.hlsl -Fo shaders_ps.cso
fxc /T vs_5_0 /E VSMain shaders.hlsl /Fo shaders_vs_dx11.cso
fxc /T ps_5_0 /E PSMain shaders.hlsl /Fo shaders_ps_dx11.cso5.2 Minimal Direct3D 11 host (C++, Win32)
// main_d3d11.cpp — link d3d11.lib, dxgi.lib, d3dcompiler.lib
#include <windows.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
LRESULT CALLBACK WndProc(HWND h, UINT m, WPARAM w, LPARAM l)
{
if (m == WM_DESTROY) { PostQuitMessage(0); return 0; }
return DefWindowProc(h, m, w, l);
}
int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR, int show)
{
WNDCLASSW wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = inst;
wc.lpszClassName = L"HLSL"; wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
RegisterClassW(&wc);
HWND hwnd = CreateWindowW(L"HLSL", L"First HLSL Triangle", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr, nullptr, inst, nullptr);
ShowWindow(hwnd, show);
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferCount = 2;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hwnd; sd.SampleDesc.Count = 1; sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
ComPtr<ID3D11Device> dev; ComPtr<ID3D11DeviceContext> ctx; ComPtr<IDXGISwapChain> swap;
UINT flags =
…5.3 Minimal Direct3D 12 host (condensed)
Direct3D 12 is explicit: you create a root signature, a pipeline state object (PSO), command lists, descriptor heaps, and fences yourself. The core shader-related steps (window, device, swap chain, fence setup omitted — see Microsoft's *D3D12HelloTriangle* sample for the full ~600 lines):
// 1. Root signature: empty, but allow the input assembler
D3D12_ROOT_SIGNATURE_DESC rsDesc = {};
rsDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ComPtr<ID3DBlob> rsBlob, rsErr;
D3D12SerializeRootSignature(&rsDesc, D3D_ROOT_SIGNATURE_VERSION_1, &rsBlob, &rsErr);
ComPtr<ID3D12RootSignature> rootSig;
device->CreateRootSignature(0, rsBlob->GetBufferPointer(), rsBlob->GetBufferSize(), IID_PPV_ARGS(&rootSig));
// 2. Load precompiled DXIL (from dxc -T vs_6_0 / ps_6_0)
std::vector<char> vsCode = ReadFile(L"shaders_vs.cso");
std::vector<char> psCode = ReadFile(L"shaders_ps.cso");
// 3. Pipeline state object
D3D12_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
D3D12_GRAPHICS_PIPELINE_STATE_DESC pso = {};
pso.InputLayout = { layout, _countof(layout) };
pso.pRootSignature = rootSig.Get();
pso.VS = { vsCode.data(), vsCode.size() };
pso.PS = { psCode.data(), psCode.size() };
pso.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); // from d3dx12.h
pso.Ble
…5.4 A fullscreen triangle without vertex buffers
A classic trick: generate a triangle that covers the screen from SV_VertexID (draw 3 vertices, no input layout).
struct VSOut { float4 pos : SV_Position; float2 uv : TEXCOORD0; };
VSOut FullscreenVS(uint id : SV_VertexID)
{
VSOut o;
o.uv = float2((id << 1) & 2, id & 2); // (0,0), (2,0), (0,2)
o.pos = float4(o.uv * float2(2, -2) + float2(-1, 1), 0, 1);
return o;
}
cbuffer Frame : register(b0) { float iTime; float2 iResolution; };
float4 PlasmaPS(VSOut i) : SV_Target
{
float2 p = (i.uv - 0.5) * float2(iResolution.x / iResolution.y, 1);
float v = sin(p.x * 10 + iTime) + sin(p.y * 10 + iTime * 0.7) + sin(length(p) * 12 - iTime);
float3 col = 0.5 + 0.5 * cos(3.14159 * v + float3(0, 2, 4));
return float4(col, 1);
}