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.cso

5.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);
}