Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/facepunch/sbox-public/llms.txt

Use this file to discover all available pages before exploring further.

s&box uses a custom HLSL-based shader format (.shader files compiled via the Vulkan shader compiler) that supports vertex, pixel, geometry, compute, and ray-tracing programs in a single file. This page explains the file structure, how the compiler works, how to dispatch compute shaders from C#, and how RenderAttributes bridges C# and HLSL.

The .shader file format

A shader file is divided into named blocks. The compiler reads each block and routes it to the appropriate GPU program.

Block reference

BlockProgram typeDescription
HEADERMetadata: description, dev-only flag, etc.
FEATURESDeclares static combo features.
COMMONHLSL shared by all programs.
MODESRender mode declarations.
VSVertex shaderPer-vertex transformation.
PSPixel shaderPer-pixel shading.
GSGeometry shaderOptional per-primitive generation.
CSCompute shaderGPU compute dispatches.
RTXRay-tracing shaderRay generation, hit, miss programs.
PS_RENDER_STATERender stateDeclares blend and depth states for the PS.

Minimal surface shader

HEADER
{
    Description = "My Custom Shader";
}

FEATURES
{
    // Optional: static permutation features
}

COMMON
{
    #include "system.fxc"
    #include "common.fxc"
    #include "vr_common.fxc"
}

VS
{
    #include "vr_common_vs_code.fxc"

    PixelInput MainVs( VertexInput i )
    {
        PixelInput o = ProcessVertex( i );
        return FinalizeVertex( o );
    }
}

PS
{
    #include "sbox_pixel.fxc"
    #include "vr_common_ps_code.fxc"

    // Custom float attribute from C#
    float g_flGlow < Attribute( "GlowStrength" ); Default( 1.0 ); >;

    float4 MainPs( PixelInput i ) : SV_Target0
    {
        float4 color = float4( 1, 0.5, 0, 1 );
        return color * g_flGlow;
    }
}

Shader combos

Combos are compile-time boolean or integer flags that generate shader permutations. There are two kinds:
  • Static combos (StaticCombo) — baked into the material at compile time.
  • Dynamic combos (DynamicCombo) — toggled at runtime via RenderAttributes.SetCombo.
FEATURES
{
    Feature( F_GLOW_ENABLED, 0..1, "My Features" );
}

PS
{
    // Dynamic combo — can be changed per draw call
    DynamicCombo( D_TRANSLUCENT, 0..1, Sys( ALL ) );

    // Static combo driven by a feature
    StaticCombo( S_GLOW, F_GLOW_ENABLED, Sys( ALL ) );

    float4 MainPs( PixelInput i ) : SV_Target0
    {
        float4 color = float4( 1, 1, 1, 1 );

        #if ( S_GLOW )
        color.rgb *= 2.0;
        #endif

        return color;
    }
}

Attribute binding

Any C# value pushed via RenderAttributes.Set is readable in HLSL using the Attribute(...) annotation:
// float
float g_flMyValue < Attribute( "MyFloat" ); Default( 0.5 ); >;

// Vector4
float4 g_vMyColor < Attribute( "MyColor" ); >;

// Texture
Texture2D g_tMyTexture < Attribute( "MyTexture" ); SrgbRead( false ); >;

// Structured constant buffer
cbuffer MyData_t < Attribute( "MyData" ); >
{
    float4 g_vSomeData;
};

Render states in PS

Depth testing, blending, and alpha-to-coverage are declared inside PS or the dedicated PS_RENDER_STATE block:
PS
{
    // Standard depth behaviour from sbox_pixel.fxc:
    // RenderState( DepthEnable, true );
    // RenderState( DepthFunc, GREATER_EQUAL );

    // Override for translucent geometry:
    RenderState( BlendEnable, true );
    RenderState( SrcBlend, SRC_ALPHA );
    RenderState( DstBlend, INV_SRC_ALPHA );
    RenderState( DepthWriteEnable, false );
    ...
}

Standard includes

The engine ships several shared FXC include files under core/shaders/:
FilePurpose
system.fxcAlways the first include in COMMON. Core engine setup.
common.fxcMath helpers, coordinate transforms.
vr_common.fxcVR-aware camera and transform helpers.
common_samplers.fxcPre-built sampler states.
sbox_pixel.fxcStandard depth/blend render states and PS helpers.
sbox_vertex.fxcVertex input/output structs.
vr_lighting.fxcPBR lighting model.
vr_detail_texture.fxcDetail texture blending.

Compute shaders

ComputeShader runs a .shader file that contains a CS block. You create it from C#, set attributes, and dispatch it.

Creating a compute shader

var compute = new ComputeShader( "shaders/my_compute.shader" );
The path is relative to the mounted filesystem, without the _c extension.

Setting attributes and dispatching

// Set input/output resources
compute.Attributes.Set( "InputBuffer",  inputGpuBuffer );
compute.Attributes.Set( "OutputBuffer", outputGpuBuffer );
compute.Attributes.Set( "ItemCount",    itemCount );

// Dispatch: specify total thread counts — the engine divides by thread group size
compute.Dispatch( threadsX: 1024, threadsY: 1, threadsZ: 1 );
You can also pass a different RenderAttributes bag per dispatch:
var attrs = new RenderAttributes();
attrs.Set( "InputBuffer", buffer );
compute.DispatchWithAttributes( attrs, 512, 512, 1 );

Indirect dispatch

When the dispatch dimensions come from a GPU buffer (populated by a previous compute pass):
// Buffer must have UsageFlags.IndirectDrawArguments and element size of 12 bytes
compute.DispatchIndirect( indirectBuffer, indirectElementOffset: 0 );

// With custom attributes
compute.DispatchIndirectWithAttributes( attrs, indirectBuffer, indirectElementOffset: 0 );
DispatchIndirect requires the buffer to have GpuBuffer.UsageFlags.IndirectDrawArguments set and an element size of exactly 12 bytes. The element offset is an element index, not a byte offset.

Compute shader HLSL structure

HEADER
{
    Description = "My Compute Shader";
}

MODES
{
    Default();
}

COMMON
{
    #include "system.fxc"
    #include "common.fxc"
}

CS
{
    ByteAddressBuffer  g_InputBuffer  < Attribute( "InputBuffer" );  >;
    RWByteAddressBuffer g_OutputBuffer < Attribute( "OutputBuffer" ); >;
    uint g_nItemCount < Attribute( "ItemCount" ); >;

    [numthreads( 64, 1, 1 )]
    void MainCs( uint3 vThreadId : SV_DispatchThreadID )
    {
        if ( vThreadId.x >= g_nItemCount )
            return;

        uint value = g_InputBuffer.Load( vThreadId.x * 4 );
        g_OutputBuffer.Store( vThreadId.x * 4, value * 2 );
    }
}
The thread group size declared with [numthreads(...)] divides into the total thread count you pass to Dispatch(). If you dispatch 1024 threads with a group size of 64, the engine dispatches 16 groups.

Ray-tracing shaders

Ray-tracing programs are declared in an RTX block. The compiler produces a ray-tracing shader when it encounters RTX in the source file.
RTX
{
    // Ray generation, any-hit, closest-hit, and miss programs live here.
    // Uses DXR / Vulkan ray-tracing extension semantics.

    RaytracingAccelerationStructure g_TLAS < Attribute( "TopLevelAS" ); >;

    [shader("raygeneration")]
    void RayGenMain()
    {
        uint2 launchIndex = DispatchRaysIndex().xy;
        // ... trace rays
    }
}
Ray-tracing shaders require hardware that supports DXR / Vulkan ray-tracing extensions. Check platform capability before enabling ray-tracing features.

ShaderCompile API

The ShaderCompile class (in Sandbox.Engine.Shaders) is available in the editor only — it is not available at game runtime. It is used by asset pipeline tooling to compile .shader files into engine-ready .shader_c resources.
// Editor-only: compile a shader from an absolute path
var options = new ShaderCompileOptions
{
    ForceRecompile = false,
    SingleThreaded = false,
    ConsoleOutput  = true
};

ShaderCompile.Results results = await ShaderCompile.Compile(
    absoluteFilePath: "/path/to/my.shader",
    relativeFilePath: "shaders/my.shader",
    compileOptions:   options,
    token:            CancellationToken.None
);

if ( results.Success )
{
    Log.Info( "Shader compiled successfully" );
}
else
{
    foreach ( var prog in results.Programs )
    {
        if ( !prog.Success )
            Log.Error( string.Join( "\n", prog.Output ) );
    }
}

Results object

PropertyTypeDescription
Successbooltrue if all programs compiled successfully.
Skippedbooltrue if nothing changed and the compile was skipped.
CompiledShaderbyte[]The serialised resource bytes, ready to write as a .shader_c file.
ProgramsList<Program>Per-program results (VS, PS, CS, etc.) with source and error output.

Using a custom shader in a material

Once compiled, reference the shader in a .vmat material file and assign that material to a model or surface:
{
    "shader": "shaders/my_custom.shader",
    "GlowStrength": 2.0,
    "MyTexture": "textures/noise.vtex"
}
From C#, push per-draw overrides with RenderAttributes:
var attrs = new RenderAttributes();
attrs.Set( "GlowStrength", pulseValue );

// Draw a model with per-draw attribute overrides
Graphics.DrawModel( model, transform, attrs );

Next steps

Rendering pipeline

Understand RenderAttributes, the Graphics class, and the camera render pipeline.

UI panels

Build screen and world-space UI with the Panel system.

Build docs developers (and LLMs) love