Vertex Shaders & Transformations
Non-uniform scale skews normals: transforming them by the world matrix makes them non-perpendicular to the surface.
16.1 The world-view-projection transform
// common.hlsli
cbuffer PerFrame : register(b0)
{
float4x4 gViewProj;
float3 gCameraPos;
float gTime;
};
cbuffer PerObject : register(b1)
{
float4x4 gWorld;
float4x4 gWorldInvTranspose; // for normals under non-uniform scale
};
struct VSIn
{
float3 pos : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT; // w = handedness (±1)
float2 uv : TEXCOORD0;
};
struct VSOut
{
float4 posH : SV_Position;
float3 posW : POSITION_WS;
float3 normalW : NORMAL;
float4 tangentW : TANGENT;
float2 uv : TEXCOORD0;
};
// Convention in this course: column vectors, mul(M, v), matrices uploaded as GLM/transposed DirectXMath.
VSOut VSMain(VSIn i)
{
VSOut o;
float4 posW = mul(gWorld, float4(i.pos, 1.0));
o.posW = posW.xyz;
o.posH = mul(gViewProj, posW);
o.normalW = normalize(mul((float3x3)gWorldInvTranspose, i.normal));
o.tangentW = float4(normalize(mul((float3x3)gWorld, i.tangent.xyz)), i.tangent.w);
o.uv = i.uv;
return o;
}16.2 Building matrices in HLSL (normally done on the CPU with DirectXMath)
float4x4 Translation(float3 t)
{
return float4x4(1, 0, 0, t.x,
0, 1, 0, t.y,
0, 0, 1, t.z,
0, 0, 0, 1); // column-vector convention: translation in the 4th column
}
float3x3 RotationY(float a)
{
float s, c; sincos(a, s, c);
return float3x3( c, 0, s,
0, 1, 0,
-s, 0, c);
}
// Left-handed perspective, D3D depth [0, 1]
float4x4 PerspectiveLH(float fovY, float aspect, float zn, float zf)
{
float y = 1.0 / tan(fovY * 0.5);
float x = y / aspect;
float q = zf / (zf - zn);
return float4x4(x, 0, 0, 0,
0, y, 0, 0,
0, 0, q, -q * zn,
0, 0, 1, 0);
}16.3 Why the inverse-transpose for normals?
Non-uniform scale skews normals: transforming them by the world matrix makes them non-perpendicular to the surface. The inverse-transpose of the upper 3×3 preserves perpendicularity. For rotation + uniform scale only, (float3x3)gWorld followed by normalize is enough.
16.4 Vertex animation
VSOut WavingFlagVS(VSIn i)
{
float3 p = i.pos;
float wave = sin(p.x * 4.0 + gTime * 3.0) * 0.1 * i.uv.x; // anchored at uv.x = 0
p.z += wave;
// recompute normal from the analytic derivative dz/dx
float dzdx = cos(p.x * 4.0 + gTime * 3.0) * 0.4 * i.uv.x;
float3 n = normalize(float3(-dzdx, 0, 1));
VSOut o = (VSOut)0;
float4 posW = mul(gWorld, float4(p, 1));
o.posW = posW.xyz; o.posH = mul(gViewProj, posW);
o.normalW = normalize(mul((float3x3)gWorldInvTranspose, n));
o.uv = i.uv;
return o;
}16.5 Billboards (camera-facing quads)
cbuffer Camera : register(b2) { float3 gCamRight; float pad0; float3 gCamUp; float pad1; };
float4 BillboardVS(uint vid : SV_VertexID, float3 center : POSITION, float size : PSIZE) : SV_Position
{
float2 corner = float2(vid & 1, vid >> 1) * 2 - 1; // 4-vertex strip: (-1,-1)(1,-1)(-1,1)(1,1)
float3 posW = center + (gCamRight * corner.x + gCamUp * corner.y) * size;
return mul(gViewProj, float4(posW, 1));
}