Skip to main content

Shading

Tellusim Engine provides a streamlined approach to material customization through the MaterialShading interface, which enables full control over shading behavior via *.shading files. These files support dynamic recompilation with hot-loading, allowing for rapid iteration during development.

info

Visual shader editing can be performed using the Material Flow plugin, which is integrated in the Tellusim Explorer.

info

Tellusim Engine SDK includes numerous *.shading material examples.

Shading files use GLSL-based syntax and include only the shader blocks necessary for the intended material functionality. For example, a material that requires only fragment shading will contain just a single fragment block.

Depending on the configuration, the engine may perform geometry processing using either a mesh shader or a vertex shader. The MaterialShading interface abstracts this difference through a unified varying block.

Global Layouts

Material configuration is defined using layout() annotations.

Two special passthrough layout signatures can be used to forward layout statements directly to the final shader without modification:

layout(statements) in;
layout(statements) out;

The example below demonstrates the declaration of global material layout options:

// Enable or disable (default) texture fetch in the vertex shader
layout(vertex_texture = 0 or 1);

// Declare the rendering passes used by the material
layout(pass = deferred, pass = forward, pass = transparent, pass = shadow_map);

// Set the material rendering order (materials with lower order values render first)
layout(order = 32);

// Set rasterization mode
layout(fill_mode = line|solid);

// Set face culling mode
layout(cull_mode = none|back|front);

// Enable blending operations for deferred and forward passes
layout(blend_op = add|sub|min|max);
layout(blend_src_func = none|zero|one|src_color|src_alpha|src1_color|src1_alpha|dest_color|dest_alpha|inv_src_color|inv_src_alpha|inv_src1_color|inv_src1_alpha|inv_dest_color|inv_dest_alpha);
layout(blend_dest_func = none|zero|one|src_color|src_alpha|src1_color|src1_alpha|dest_color|dest_alpha|inv_src_color|inv_src_alpha|inv_src1_color|inv_src1_alpha|inv_dest_color|inv_dest_alpha);

// Set color mask for deferred and forward passes
layout(color_mask = r|rg|gb|gba|...);

// Set per-target masks for deferred pass
layout(normal_mask = r|rg|gb|gba|...);
layout(diffuse_mask = r|rg|gb|gba|...);
layout(metallic_mask = r|rg|gb|gba|...);
layout(auxiliary_mask = r|rg|gb|gba|...);
layout(motion_mask = r|rg|gb|gba|...);

// Set depth mask for deferred and forward passes
layout(depth_mask = none|read|write);

// Set depth comparison function
layout(depth_func = none|never|always|equal|less|greater|not_equal|less_equal|greater_equal);

// Configure depth bias parameters
layout(depth_bias = 0.1f, depth_slope = 0.1f, depth_clamp = 0.1f);

// Enable depth replacement for deferred, forward, and shadow map passes
layout(depth_replace = 0 or 1);

// Configure stencil operations for deferred and forward passes
layout(stencil_ref = 0, stencil_mask = 0);
layout(stencil_func = none|never|always|equal|less|greater|not_equal|less_equal|greater_equal);
layout(stencil_fail_op = keep|invert|replace|incr_wrap|decr_wrap|incr_sat|decr_sat);
layout(stencil_dfail_op = keep|invert|replace|incr_wrap|decr_wrap|incr_sat|decr_sat);
layout(stencil_dpass_op = keep|invert|replace|incr_wrap|decr_wrap|incr_sat|decr_sat);

Options

Material options control the shader preprocessor step by defining macros based on option values. They do not affect shader performance at runtime. Each option declaration defines a default value and can optionally specify a custom macro name.

layout(macro = BOOL_OPTION) option bool bool_option = false;
layout(macro = UINT_OPTION) option uint uint_option = 0;
layout(macro = FLOAT_OPTION) option float float_option = 0.0f;
layout(macro = STRING_OPTION, name = Editor Name) option string string_option = "string";
info

The macro argument is optional. If omitted, the macro name defaults to the uppercased option name.

info

The name argument specifies a custom display name for the editor interface.

Samplers

In addition to the default render samplers, fully custom samplers can be declared:

// Full sampler declaration with all parameters
layout(sampler = generic_sampler, filter = point|linear|bipoint|bilinear|trilinear, wrap = clamp|repeat|mirror|border, lods = 16) sampler generic_sampler;

// Create a linear sampler with repeat wrap mode
layout(sampler = linear_sampler, filter = linear, wrap = repeat) sampler linear_sampler;

Textures

There are two types of textures in shading materials:

  • Scene-based textures, managed by the parent Material class.
  • Shading-based textures, managed by the MaterialShading class.

Scene textures must use engine-provided sampling functions, while shading textures use standard GLSL sampling functions with custom Sampler objects.

info

Shading textures can be imported from GraphVarying, enabling powerful procedural material workflows.

// Declare a scene texture with the shader name `scene_texture`
layout(texture = scene_texture, group = 2D Textures) texture scene_texture = "texture.png";

// Declare shading textures and load them from files
layout(texture = shading_2d_texture, name = 2D Texture, group = 2D Textures) texture2D shading_2d_texture = "texture_2d.png";
layout(texture = shading_3d_texture, name = 3D Texture) texture3D shading_3d_texture = "texture_3d.image";
layout(texture = shading_cube_texture, name = Cube Texture) textureCube shading_cube_texture = "texture_cube.image";

// Create a texture from a render noise texture
layout(texture = render_noise_texture) texture2DArray render_noise_texture;

// Import a texture from GraphVarying
layout(import = <Graph Name>:<Texture Name>) texture2D imported_texture;
info

The optional texture argument defines the variable name used in shader source for the declared texture.

info

The name, group, and desc arguments specify a custom display name and grouping for the editor interface.

Uniforms

Uniform parameters are inherited from the parent Material class. Each uniform declaration can optionally specify the shader variable name and editor-related metadata through layout hints.

// Integer parameter with checkbox editor
layout(uniform = bool_integer, bool) int bool_integer = 1;

// Integer parameter with slider editor
layout(min = 1, max = 8) int signed_integer = 1 + 2;

// Floating-point parameter with maximum slider value
layout(max = 2.0f) float linear_scalar = 2.0f * 3.0f;

// Logarithmic float parameter with 2-digit precision
layout(base = 2.0f, digits = 2) float log_scalar = 1.0f;

// Linear and sRGB color parameters
layout(color, linear) vec3 color_3 = vec3(0.1f);
layout(color) vec4 color_4 = vec4(0.1f, 0.1f, 0.1f, 1.0f);

// Vector parameters with uniform and non-uniform ranges
layout(digits = 2) vec2 vector_2 = vec2(0.0f);
layout(min = 1.0f, max = 2.0f) vec3 vector_3 = vec3(1.0f, 2.0f, 3.0f);
layout(min = 1.0f, max = 3.0f, max_w = 2.0f) vec4 vector_4 = vec4(1.0f, 2.0f, 3.0f, 4.0f) * 2.0f;

// Matrix parameters with default values
layout() mat2x3 matrix_3x2 = mat2x3::translate(1.0f, 2.0f);
layout() mat3x4 matrix_4x3 = mat3x4::scale(1.0f, 2.0f, 3.0f) * mat3x4::rotateX(90.0f);
layout(spacer) mat4 matrix_4x4 = mat4::perspective(60.0f, 1.0f, 0.1f);
info

The optional uniform argument defines the variable name used in shader source for the declared uniform.

info

min, max, bool, color, linear, name, group, desc, and spacer layout arguments are used as editor hints.

info

Numerical values are resolved using the Expression namespace.

note

The row-major Matrix2x3f type corresponds to mat2x3 in GLSL.
The row-major Matrix4x3f type corresponds to mat3x4 in GLSL.

Per-Pass Layouts

Render passes can optionally override global layout parameters by declaring them within a specific rendering pass. For example, a material can define shared parameters globally for all passes while customizing behavior for a specific pass when needed.


// Set rasterization mode
layout(fill_mode = line|solid);

// Set face culling mode
layout(cull_mode = none|back|front);

// Set blending operations
layout(blend_op = add|sub|min|max);
layout(blend_src_func = none|zero|one|src_color|src_alpha|src1_color|src1_alpha|dest_color|dest_alpha|inv_src_color|inv_src_alpha|inv_src1_color|inv_src1_alpha|inv_dest_color|inv_dest_alpha);
layout(blend_dest_func = none|zero|one|src_color|src_alpha|src1_color|src1_alpha|dest_color|dest_alpha|inv_src_color|inv_src_alpha|inv_src1_color|inv_src1_alpha|inv_dest_color|inv_dest_alpha);

// Set color mask
layout(color_mask = r|rg|gb|gba|...);

// Set depth parameters
layout(depth_mask = none|read|write);
layout(depth_func = none|never|always|equal|less|greater|not_equal|less_equal|greater_equal);
layout(depth_bias = 0.1f, depth_slope = 0.1f, depth_clamp = 0.1f);
layout(depth_replace = 0 or 1);

// Set stencil parameters
layout(stencil_ref = 0, stencil_mask = 0);
layout(stencil_func = none|never|always|equal|less|greater|not_equal|less_equal|greater_equal);
layout(stencil_fail_op = keep|invert|replace|incr_wrap|decr_wrap|incr_sat|decr_sat);
layout(stencil_dfail_op = keep|invert|replace|incr_wrap|decr_wrap|incr_sat|decr_sat);
layout(stencil_dpass_op = keep|invert|replace|incr_wrap|decr_wrap|incr_sat|decr_sat);

Render Pass Blocks

A render-pass block defines shading functions and layout overrides for a specific rendering pass.

The following pass names are supported:

  • deferred - for the deferred rendering pass.
  • forward - for the forward rendering pass.
  • transparent - for the transparent rendering pass.
  • shadow_map - for the shadow map rendering pass.

If a block is not declared for a given pass, the material will not be rendered during that pass.

info

Pass-specific GLSL functions can be declared within the corresponding block.

// Global layouts
layout(...);

// Global function
float global_function(float x) {
return x + 1.0f;
}

deferred {

// Deferred pass layouts
layout(...);

// Deferred pass function
float deferred_function(float x) {
return x + 2.0f;
}
}

transparent {

// Transparent pass layouts
layout(...);

// Transparent pass function
float transparent_function(float x) {
return x + 3.0f;
}
}

Interface Block

The interface block defines user-defined interpolated values (varyings) passed between the vertex and fragment shaders. GLSL interpolation qualifiers such as centroid, flat, and noperspective can be used to control interpolation behavior.

info

Using an interface block can improve performance, as varying computations typically run at a lower frequency than fragment operations.

forward {

interface {
vec4 material_color;
flat vec4 itransform_r0;
flat vec4 itransform_r1;
flat vec4 itransform_r2;
}

varying {

// Calculate per-vertex color
OUT.material_color = get_color();

// Load inverse transformation matrix
mat3x4 itransform = get_itransform(in_batch);
OUT.itransform_r0 = itransform[0];
OUT.itransform_r1 = itransform[1];
OUT.itransform_r2 = itransform[2];
}

fragment {

// Interpolated color value
IN.material_color;

// Flat inverse transformation
IN.itransform_r0;
IN.itransform_r1;
IN.itransform_r2;
}
}

Varying Block

The varying block is used to initialize values declared in the interface block and apply additional effects to vertex shader outputs.

Values from ObjectMesh outputs can be used as inputs in the varying block. The availability of these outputs depends on user-defined macro declarations. For example, if the geometry_normal input is required, the GEOMETRY_NORMAL macro must be defined in the global material scope.

info

A geometry distortion shader must be implemented inside the varying block.

note

ObjectBrep provides a limited set of input and output varying parameters because BREP rendering is fully procedural.

// Batch parameters (material address, object self address, frame geometry address, geometry self address)
#if MATERIAL_BATCH
uvec4 OUT.batch;
#endif

// Camera-relative vertex position
#if MATERIAL_POSITION
vec3 OUT.position;
#endif

// Camera-relative normal vector
#if MATERIAL_BASIS || MATERIAL_NORMAL
vec3 OUT.normal;
#endif

// Camera-relative tangent vector (w stores binormal direction)
#if MATERIAL_BASIS
vec4 OUT.tangent;
#endif

// Input vertex normal attribute
#if ATTRIBUTE_POSITION
vec3 OUT.attribute_position;
#endif

// Input normal attribute
#if ATTRIBUTE_NORMAL
vec3 OUT.attribute_normal;
#endif

// Geometry position (after morphing and skinning)
#if GEOMETRY_POSITION
vec3 OUT.geometry_position;
#endif

// Geometry normal (after morphing and skinning)
#if GEOMETRY_NORMAL
vec3 OUT.geometry_normal;
#endif

// Texture coordinates
#if MATERIAL_TEXCOORD_1 && MATERIAL_TEXCOORD_2
vec4 OUT.texcoord;
#elif MATERIAL_TEXCOORD_1
vec2 OUT.texcoord;
#endif
#elif MATERIAL_TEXCOORD_3 && MATERIAL_TEXCOORD_4
vec4 OUT.texcoord_1;
#elif MATERIAL_TEXCOORD_4
vec2 OUT.texcoord_1;
#endif

// Vertex color
#if MATERIAL_COLOR_1 || MATERIAL_COLOR_2 || MATERIAL_COLOR_3 || MATERIAL_COLOR_4
vec4 OUT.color;
#endif
#if MATERIAL_COLOR_2 || MATERIAL_COLOR_3 || MATERIAL_COLOR_4
vec4 OUT.color_1;
#endif
#if MATERIAL_COLOR_3 || MATERIAL_COLOR_4
vec4 OUT.color_2;
#endif
#if MATERIAL_COLOR_4
vec4 OUT.color_3;
#endif

// Camera-relative motion positions
#if MATERIAL_MOTION
vec4 OUT.position_0;
vec4 OUT.position_1;
#endif

Fragment Block

The fragment block is unified for both ObjectMesh and ObjectBrep shaders. Global material macros are used to enable specific input parameters that are interpolated from previous rendering stages.

// Batch parameters
// ObjectMesh: (material address, object self address, frame geometry address, geometry self address)
// ObjectBrep: (material address, object self address, frame geometry address, face index)
#if MATERIAL_BATCH
uvec4 IN.batch
#endif

// Fragment depth value
#if MATERIAL_DEPTH
float in_depth
#endif

// Interpolated camera-relative position
#if MATERIAL_POSITION
vec3 in_position
#endif

// Interpolated camera-relative basis/normal vectors
#if MATERIAL_BASIS
vec3 in_normal
vec3 in_tangent
vec3 in_binormal
#elif MATERIAL_NORMAL
vec3 in_normal
#endif

// Interpolated input vertex position attribute
#if ATTRIBUTE_POSITION
vec3 in_attribute_position
#endif

// Interpolated input normal attribute
#if ATTRIBUTE_NORMAL
vec3 in_attribute_normal
#endif

// Interpolated geometry position (after morphing and skinning)
#if GEOMETRY_POSITION
vec3 in_geometry_position
#endif

// Interpolated geometry normal (after morphing and skinning)
#if GEOMETRY_NORMAL
vec3 in_geometry_normal
#endif

// Interpolated texture coordinates
#if MATERIAL_TEXCOORD_1 && MATERIAL_TEXCOORD_2
vec4 in_texcoord
#elif MATERIAL_TEXCOORD_1
vec2 in_texcoord
#endif
#elif MATERIAL_TEXCOORD_3 && MATERIAL_TEXCOORD_4
vec4 in_texcoord_1
#elif MATERIAL_TEXCOORD_4
vec2 in_texcoord_1
#endif

// Interpolated vertex color
#if MATERIAL_COLOR_1 || MATERIAL_COLOR_2 || MATERIAL_COLOR_3 || MATERIAL_COLOR_4
vec4 in_color
#endif
#if MATERIAL_COLOR_2 || MATERIAL_COLOR_3 || MATERIAL_COLOR_4
vec4 in_color_1
#endif
#if MATERIAL_COLOR_3 || MATERIAL_COLOR_4
vec4 in_color_2
#endif
#if MATERIAL_COLOR_4
vec4 in_color_3
#endif

// Interpolated camera-relative motion positions
#if MATERIAL_MOTION
vec4 in_position_0 = IN.position_0;
vec4 in_position_1 = IN.position_1;
#endif

Deferred Block

The deferred block outputs shading data to the G-buffer, which is used in deferred rendering. A special DEFERRED_OUT macro is used to return values to the engine in a confuration-independent manner.

The required output arguments for DEFERRED_OUT depend on user-defined macros:

  • MATERIAL_DEFERRED_AUXILIARY - enables auxiliary material parameters and the frame geometry address.
  • MATERIAL_DEFERRED_MOTION - enables per-fragment camera-relative motion vector.
info

The global PASS_DEFERRED macro is defined during deferred pass preprocessing.

deferred {

fragment {

// Emissive color (RGB + alpha)
vec4 emission_color = vec4(r, g, b, a);

// Camera-relative normalized normal and binormal angle
vec4 normal_angle = vec4(frame_normal, binormal_angle);

// Diffuse (base) color in gamma space
vec3 diffuse_color = vec3(r, g, b);

// Material properties (occlusion, roughness, metallic, and scaled reflectivity)
vec4 metallic = vec4(occlusion, roughness, metallic, reflectivity * 4.0f);

// Optional auxiliary material parameters
vec4 auxiliary = vec4(clearcoat_scale, clearcoat_roughness, anisotropy_scale, anisotropy_angle);

// Optional frame geometry address (from IN.batch)
uint frame_geometry_address;

// Optional: camera-relative motion vector
vec2 motion_vector;

// Engine deferred output macro based on enabled features
#if MATERIAL_DEFERRED_AUXILIARY && MATERIAL_DEFERRED_MOTION
DEFERRED_OUT(emission_color, normal_angle, diffuse_color, metallic, auxiliary, frame_geometry_address, motion_vector)
#elif MATERIAL_DEFERRED_AUXILIARY
DEFERRED_OUT(emission_color, normal_angle, diffuse_color, metallic, auxiliary, frame_geometry_address)
#elif MATERIAL_DEFERRED_MOTION
DEFERRED_OUT(emission_color, normal_angle, diffuse_color, metallic, motion_vector)
#else
DEFERRED_OUT(emission_color, normal_angle, diffuse_color, metallic)
#endif
}
}

Forward Block

The forward block defines color output for the forward rendering pass.

info

When dual source blending is enabled via the MATERIAL_DUAL_SOURCE macro, two color outputs must be specified. Ensure the blend layout is configured accordingly.

info

The forward pass can be used for opaque and transparent objects.

info

The global PASS_FORWARD macro is defined during forward pass preprocessing.

forward {

fragment {

#if MATERIAL_DUAL_SOURCE

// Dual source blending output
vec4 color_0 = vec4(r, g, b, a);
vec4 color_1 = vec4(r, g, b, a);

FORWARD_OUT(color_0, color_1)

#else

// Single color output
vec4 forward_color = vec4(r, g, b, a);

FORWARD_OUT(forward_color)

#endif
}
}

Transparent Block

The transparent block defines color outputs for the order-independent transparency (OIT) rendering pass. A special TRANSPARENT_OUT macro is used to return values to the engine in a confuration-independent manner.

The required output arguments for TRANSPARENT_OUT depend on user-defined macros:

  • MATERIAL_TRANSPARENT_VOLUME - enables volumetric transparency parameters.
  • MATERIAL_TRANSPARENT_OFFSET - enables a refraction offset vector.
info

The global PASS_TRANSPARENT macro is defined during transparent pass preprocessing.

transparent {

fragment {

// Current fragment depth value
float fragment_depth = gl_FragDepth.z;

// HDR color to add from this layer
vec3 add_color = vec3(r, g, b);

// LDR color to multiply with underlying layers
vec3 mul_color = vec3(r, g, b);

// Density factor used for volumetric transparency
vec3 mul_density = vec3(r, g, b);

// Inverse-transparency based additive color
vec3 mix_color = vec3(r, g, b);

// Maximum transparency layer thickness
float depth_range = 1.0f;

// Depth-based transparency intensity control
float depth_power = 1.0f;

// Refraction offset for distorting underlying layers
vec2 refraction_offset = vec2(0.0f);

// Engine transparent output macro based on enabled features
#if MATERIAL_TRANSPARENT_VOLUME && MATERIAL_TRANSPARENT_OFFSET
TRANSPARENT_OUT(fragment_depth, add_color, mul_density, mix_color, depth_range, depth_power, refraction_offset)
#elif MATERIAL_TRANSPARENT_VOLUME
TRANSPARENT_OUT(fragment_depth, add_color, mul_density, mix_color, depth_range, depth_power)
#elif MATERIAL_TRANSPARENT_OFFSET
TRANSPARENT_OUT(fragment_depth, add_color, mul_color, refraction_offset)
#else
TRANSPARENT_OUT(fragment_depth, add_color, mul_color)
#endif
}
}

Shadow Map Block

The shadow_map block defines depth output for the shadow map rendering pass.

info

When depth replacement is enabled via the MATERIAL_DEPTH_OUT macro, the depth value must be explicitly output. Ensure the depth_replace layout is configured accordingly.

note

A non-empty fragment block must be declared, even if no depth output is performed. In such cases, a single commented line is sufficient.

info

The global PASS_SHADOW_MAP macro is defined during shadow map pass preprocessing.

shadow_map {

fragment {

#if MATERIAL_DEPTH_OUT

// Depth output for shadow map
float fragment_depth = gl_FragCoord.z;

DEPTH_OUT(fragment_depth)

#else

// This comment is intentional

#endif
}
}

Example

This material implements a transparent volumetric fog effect shaped as a box. The shader uses uniforms to control fog density, depth influence, and color blending.

/* Material layout
*/
layout(pass = transparent);
layout(cull_mode = front, depth_mask = read, depth_func = always);

/* Material uniforms
*/
layout(min = 0.0, max = 2.0) uniform float depth_power = 1.0f;
layout(min = 0.0, max = 32.0) uniform float volumetric_density = 8.0f;
layout(color, linear) uniform vec4 add_color = vec4(0.0f, 0.0f, 0.0f, 1.0f);
layout(color, linear) uniform vec4 mul_color = vec4(0.0f, 0.0f, 0.0f, 1.0f);
layout(color, linear) uniform vec4 mix_color = vec4(0.0f, 0.0f, 0.0f, 1.0f);

/* Global definitions
*/
#define MATERIAL_POSITION 1
#if VERTEX_SHADER
#define get_transform_func 1
#define get_itransform_func 1
#endif
#if FRAGMENT_SHADER
#define mat4x3_mul_vec3_func 1
#define get_box_trace_func 1
#define get_frag_depth_func 1
#endif

/* Transparent pass
*/
transparent {

/* Transparent definitions
*/
#define MATERIAL_TRANSPARENT_VOLUME 1

/* Interface variables
*/
interface {
flat vec4 transform_r0;
flat vec4 transform_r1;
flat vec4 transform_r2;
flat vec4 itransform_r0;
flat vec4 itransform_r1;
flat vec4 itransform_r2;
}

/* Varying shader
*/
varying {

// Frame transformation
mat3x4 transform = get_transform(in_batch);
OUT.transform_r0 = transform[0];
OUT.transform_r1 = transform[1];
OUT.transform_r2 = transform[2];

// Frame inverse transformation
mat3x4 itransform = get_itransform(in_batch);
OUT.itransform_r0 = itransform[0];
OUT.itransform_r1 = itransform[1];
OUT.itransform_r2 = itransform[2];
}

/* Fragment shader
*/
fragment {

// Local-space box position
vec3 local_camera = vec3(IN.itransform_r0.w, IN.itransform_r1.w, IN.itransform_r2.w);
vec3 local_position = mat4x3_mul(mat3x4(IN.itransform_r0, IN.itransform_r1, IN.itransform_r2), in_position);

// Direction to the box
vec3 direction = normalize(local_position - local_camera);

// Unit box intersection
vec2 intersection = get_box_trace(local_camera, 1.0f / direction, vec3(-1.0f), vec3(1.0f));

[[branch]] if(intersection.x < intersection.y && intersection.y > 0.0f) {

// Local-space box intersection
vec3 position_0 = local_camera + direction * max(intersection.x, 1e-6f);
vec3 position_1 = local_camera + direction * intersection.y;

// Frame-space box intersection
position_0 = mat4x3_mul(mat3x4(IN.transform_r0, IN.transform_r1, IN.transform_r2), position_0);
position_1 = mat4x3_mul(mat3x4(IN.transform_r0, IN.transform_r1, IN.transform_r2), position_1);

// Depth range
float range = length(position_1 - position_0);

// Fragment depth
float depth = get_frag_depth(frame.camera_projection, position_0);

// Destination blend
vec3 color_1 = (1.0f - mul_color.xyz) * volumetric_density * 0.01f;

// Transparent output
TRANSPARENT_OUT(depth, add_color.xyz, color_1, mix_color.xyz, range, depth_power)
}
}
}