Help home · Beginner · Intermediate · Advanced authoring · Command reference · Samples

Advanced Shader Authoring

This guide describes how to author shader assets and HLSL for Blitz3D. It assumes you already know how to load, parameterise, attach, copy, and deploy a shader. See the intermediate guide if those workflows are new.

The recommended progression is surface mode, then post mode, and finally raw mode only when the generated surface pipeline cannot express the effect.

For format-3/layout-4 light-cookies 1 opt-in and per-light surface/raw/fog sampling, see Light cookies. Old custom shaders retain their rendering until explicitly opted in and recompiled.

1. Anatomy of a shader asset

A source-backed asset consists of a .b3shader descriptor and an HLSL source file. This minimal material declares two public parameters:

blitz3d_shader 1
layout 3
mode surface
name "Orange material"
source "orange.hlsl"
vertex_entry "VSMain"
pixel_entry "PSMain"
shadow_entry "ShadowVSMain"
shadows classic
color "Tint" 255 130 30 255
float "Shine" 0.35

The first line is asset format 1. layout 3 selects the immutable legacy binding layout. Format 2 and layout 4 add pass graphs and named resources without changing layout-3 bytecode. mode is surface, post, or raw. Paths are relative to the descriptor.

A precompiled descriptor uses vertex, pixel, and, when needed, shadow CSO paths in place of source and entry-point directives. Normally b3dshaderc produces that compiled form; do not maintain it by hand.

2. Declaring parameters

Value declarations are assigned constant slots in declaration order. Layout 3 has sixteen value slots and eight texture stages. Layout 4 keeps those bindings and adds a sixteen-entry named texture/sampler table for material and post inputs.

DescriptorBlitz setterHLSL access
float "Roughness" .5ShaderFloat shader,"Roughness",.8B3DUserFloat(0)
int "Mode" 0ShaderInt shader,"Mode",1B3DUserInt(1)
vector "Wind" 1 0 0 0ShaderVector shader,"Wind",1,0,.2,0B3DUserVector(2)
color "Tint" 255 255 255 255ShaderColor shader,"Tint",80,160,255B3DUserVector(3), converted to 0..1
texture "NormalMap" 1ShaderTexture shader,"NormalMap",maptexture1 and sampler1

A float or integer consumes a complete slot, just like a vector or colour. Do not infer slots from parameter names: count only non-texture declarations from zero. Names form the BlitzBasic interface; slots form the HLSL interface.

3. Surface shaders

Surface mode lets the engine generate transforms, GPU skinning, instancing, lighting, fog, vertex-colour handling, shadow receiving, and the standard vertex and pixel entry points. Your HLSL supplies one callback:

#include "blitz3d_surface.hlsli"

B3DSurface B3DShadeSurface(B3DSurfaceInput input)
{
    B3DSurface surface = B3DDefaultSurface();
    surface.color = B3DUserVector(0).rgb;
    surface.alpha = B3DUserVector(0).a;
    surface.normal = input.worldNormal;
    surface.shininess = saturate(B3DUserFloat(1));
    return surface;
}

Start with B3DDefaultSurface() so brush colour, alpha, and shininess keep their traditional meaning, then replace only the fields your effect owns.

B3DSurface fieldMeaning
color, alphaLinear base colour and opacity.
normalWorld-space shading normal; zero uses the interpolated mesh normal.
shininessClassic specular amount and exponent control.
emissionUnlit linear RGB added to the result.
metallic, roughness, occlusionPBR material values used when pbr is non-zero.
alphaCutoffThreshold used by masked drawing.
indirectAdditional indirect linear RGB for PBR lighting.
pbrNon-zero selects the PBR lighting branch.

B3DSurfaceInput supplies uv0, uv1, vertexColor, worldPosition, worldNormal, worldTangent, and viewPosition. Use B3DSurfaceTextureUV(input,stage) when sampling so Blitz texture coordinate selection and transforms are respected:

float4 texel = texture0.Sample(
    sampler0, B3DSurfaceTextureUV(input,0));
surface.color *= texel.rgb;
surface.alpha *= texel.a;

Vertex displacement

For displacement, include the base layout first, write a function accepting VertexInput, and define B3D_DISPLACE_POSITION before including the surface wrapper:

#include "blitz3d_shader.hlsli"

float3 WavePosition(VertexInput input)
{
    float wave = sin(input.position.x * 3.0f + B3DUserFloat(0));
    return input.position + float3(0.0f,wave * B3DUserFloat(1),0.0f);
}
#define B3D_DISPLACE_POSITION(input) WavePosition(input)
#include "blitz3d_surface.hlsli"

The generated visible and shadow entry points then use the same object-space position. The Vertex Waves sample contains the complete callback and descriptor.

Shadow policy

PolicyUse it when...
shadows classicThe shader does not move vertices and the standard caster shape is correct.
shadows customDisplacement or custom geometry must be repeated in the shadow pass.
shadows disabledThe effect should not cast shadows, as with transparent effects and all post shaders.

A source-backed surface shader normally uses the wrapper's ShadowVSMain. Keep the visible and shadow displacement logic shared to prevent shadows from separating from geometry.

4. Post-processing shaders

Post mode includes blitz3d_post.hlsli and implements one function:

#include "blitz3d_post.hlsli"

float4 B3DPostProcess(B3DPostInput input)
{
    float grey = dot(input.sceneColor.rgb,
                     float3(.299f,.587f,.114f));
    float amount = saturate(B3DUserFloat(0));
    return float4(lerp(input.sceneColor.rgb,grey.xxx,amount),
                  input.sceneColor.a);
}

B3DPostInput contains sceneColor, sceneDepth, uv, screenSize, elapsed time, deltaTime, frameNumber, cameraPosition, near and far planes, and the camera viewport. Scene colour is texture stage 0 and depth is stage 1 if direct sampling is needed.

The wrapper supplies the full-screen vertex and pixel entries, confines output to the viewport, and preserves the input image outside it. Declare shadows disabled for post assets.

5. Raw shaders

Raw mode uses your VSMain and PSMain directly. Include blitz3d_shader.hlsli for the exact engine structures, resources, flags, and helpers. Raw mode is appropriate for a rendering model which cannot fit B3DSurface; it also means taking responsibility for transforms, lighting, fog, colour, texture sampling, and output yourself.

VertexInput supplies position, normal, colour, two UV sets, tangent, instance ID, and vertex ID. Matrices are row-major and positions use Blitz3D's row-vector convention:

float4 world = mul(float4(input.position,1.0f),worldMatrix);
float4 view = mul(world,viewMatrix);
output.position = mul(view,projectionMatrix);

B3DTransformPosition is the convenient object-to-clip helper. Raw shaders currently take the conservative one-instance draw path because their vertex transforms are entirely user-defined. Surface shaders use the instance-aware generated wrapper automatically.

Versioned binding contracts

RegisterContents
b0Pixel and draw flags plus classic texture-stage state.
b1World, view, projection, material, fog, draw and instance data, texture transforms, eight lights, and six shadow views.
b2Sixteen uint4 user slots, read with B3DUserFloat, B3DUserInt, and B3DUserVector.
t0..t7 / s0..s7Classic and named textures with matching samplers.
t8 / s8Engine shadow-map array and comparison sampler.
t9Instance world-matrix buffer.
t10..t12Skin vertices, bone palette, and per-instance palette ranges.

Layout 4 retains the legacy table and adds frame/pass scene inputs, named textures at t16..t31 / s16..s31, auxiliary MRT output contracts, previous transforms/bone palettes, and inverse/jitter matrices. Include blitz3d_surface_v4.hlsli or blitz3d_post_v4.hlsli through the normal wrapper rather than copying structures. The runtime selects separate root signatures for layouts 3 and 4; deployed layout-3 assets remain supported.

6. Format-2 pass graphs

blitz3d_shader 2
layout 4
mode post
stage scene-linear
requires scene-color scene-depth scene-normals blue-noise
target "ao" scale 0.5 format r8-unorm
pass "evaluate" pixel "ao_ps.cso" input "scene-depth" output "ao" clear
pass "composite" pixel "composite_ps.cso" input "scene-color" input "ao" output "scene-output" clear

Targets and passes are ordered and acyclic. A pass can read engine inputs, custom texture:Name bindings, or earlier targets. Supported formats are bounded colour/depth-friendly RT formats; scale must be positive and no larger than the documented parser limit. The compiler rejects duplicate names, forward references, cycles, read/write aliasing, compute passes, incompatible stages/requirements, excessive parameters/textures/targets/passes, and layout reflection mismatches before atomically replacing the prior package.

Post scene inputs include colour, resolved depth, normal/roughness, velocity, object mask, deterministic blue noise, history, opaque colour where appropriate, and up to sixteen named textures. Reconstruction helpers use the unjittered inverse projection/view matrices and explicit render/output sizes. Surface manifests may request opaque colour or read-only scene depth and may override blend, depth-write, cull, and shadow policy; removing the shader restores stored entity/brush state.

Resource limits and colour rules

7. Compilation and packaging

The IDE compiles literal source-backed shader references during Check and Run. For a direct build, use:

b3dshaderc fx\orange.b3shader
b3dshaderc fx\orange.b3shader --force
b3dshaderc fx\orange.b3shader --package-dir package\fx

The compiler invokes DirectX Shader Compiler for Shader Model 6, writes a compiled descriptor and CSO stages, and can copy the runtime package to a target directory. It searches the source directory and the bundled Blitz3D include directory for includes.

Do not edit generated .compiled descriptors or CSO files. Keep source descriptors, HLSL, and project-specific includes under source control; regenerate the compiled results. Runtime packages need the compiled descriptor, its referenced bytecode, textures, and other runtime dependencies while preserving relative paths.

8. Authoring and performance checklist

9. Working references

The shader sample gallery contains editable surface tint, displacement, raw rim-light, and post-effect sources. The builtin_sources folder beside those samples contains the reviewed source versions of packaged built-ins; loading a builtin: alias still uses packaged bytecode.

Shader asset formats 1/2 · binding layouts 3/4 · Shader Model 6 · Extended mode