A shader is a small program with a specific job#
A vertex shader transforms a vertex and passes attributes onward. A pixel shader computes values for a covered fragment, such as color. A compute shader runs a more general grid of work items and is useful for lighting lists, image processing, and other parallel tasks. Shaders do not automatically know the scene. The CPU and renderer provide matrices, textures, buffers, and parameters through a declared binding scheme. If the shader expects a texture at t0, the pipeline must bind the matching resource and keep it alive while the GPU reads it.
A normal tells light which way a surface faces#
Diffuse lighting responds to the relationship between surface direction and incoming light. A surface turned away from a light receives less direct diffuse illumination. Specular reflection depends on view direction as well: moving the camera can move a highlight even when the object stays still. Roughness describes how microscopic surface directions spread reflected light. Metalness changes the reflectance model and the balance between diffuse and specular response. A physically based material model aims for consistent behavior, but it still uses approximations and needs appropriate lighting inputs.
Light meets a surface This illustration uses canvas. The explanation below describes the same process.
The laboratory explicitly uses an interpolated Blinn–Phong approximation. Its controls show relationships without claiming to be a native QubicEngine render. The documented reference renderer uses GGX and accounts for energy sharing between diffuse and specular response.
Color should be calculated in linear space#
Adding light in encoded display space produces incorrect blends. Decode color textures, calculate lighting in linear space, store bright results in an HDR buffer, then tone-map and encode for display. An emissive surface can exceed a display’s direct range, so a value greater than one is meaningful inside the HDR pipeline. Shadow mapping renders depth from a light’s perspective and tests whether a visible point is behind another surface from that light. It has its own sampling, bias, and resolution tradeoffs. Global illumination approximates light that bounces from surfaces rather than only direct light from a lamp.
Read the vertex shader first
cbuffer FrameConstants : register(b0) {
row_major float4x4 worldViewProjection;
};
float4 TransformPosition(float3 localPosition) {
return mul(float4(localPosition, 1.0), worldViewProjection);
}The fourth coordinate permits translation and projection in the matrix calculation. SV_Position on a vertex output identifies clip-space position for later processing. The first native triangle adds complete input/output structures, entry points, and a pixel shader.
Inside QubicEngine#
MaterialAsset chooses a shader family and parameter data. ShaderCompiler caches results by source and configuration. Renderer chooses a compatible pipeline state, binds the frame and material resources, and records a draw packet. A deferred opaque path stores surface properties first; a lighting pass reads them to compute illumination. The Vulkan backend compiles an explicit shader variant to SPIR-V and maps bindings to descriptor sets. Reusing HLSL source requires deliberate layouts and feature support; shader text alone does not create a second backend.
Troubleshoot a shader#
A compilation error is different from a pipeline validation error. Read the compiler’s source location, then inspect entry point, shader target, include paths, and types. A black object with a valid pipeline can still have no light, a wrong normal, an unbound resource, or a failed asset. Use simple output colors and debug views to isolate the stage. Continue with rendering passes, materials, and the DX12 first triangle.