reckfellENGINE

Spatial: surfaces, lighting, alpha and depth

Complete shaders and a 95-name registry: normals, transparency, stencil and material selection.

On this page

A spatial shader defines a 3D mesh surface. Assign a Shader to a ShaderMaterial, then to a MeshInstance3D surface or MaterialOverride. vertex() changes geometry/attributes, fragment() defines material properties, and optional light() replaces direct-light calculation.

First material and assignment

This complete opaque material uses a texture and tint. source_color identifies color data; hint_default_white makes tint work without a separate texture:

Shader
shader_type spatial;
uniform sampler2D albedo_texture : source_color, hint_default_white;
uniform vec4 tint : source_color = vec4(1.0);
void fragment() {
    ALBEDO = texture(albedo_texture, UV).rgb * tint.rgb;
    METALLIC = 0.0;
    ROUGHNESS = 0.7;
}

ALBEDO is base color, not guaranteed final RGB: lighting, a dielectric white highlight, reflections, fog and tonemapping change the pixel. Red ALBEDO can therefore produce nonzero G/B from specular lighting. Use unshaded diagnostically to inspect base color alone.

A complete C# helper creates a QuadMesh with the material:

C#
using Reckfell;

public static class SpatialSetup
{
    public static MeshInstance3D Create(Shader shader)
    {
        return new MeshInstance3D
        {
            Mesh = new QuadMesh { Size = new Vector2(1, 1) },
            MaterialOverride = new ShaderMaterial { Shader = shader },
        };
    }
}

Load the Shader using ResourceLoader.Load<Shader>("asset://PbrSurface.reckfellshader"), call SpatialSetup.Create(shader) and add the result to the tree. A camera at (0,0,5) looking along −Z sees this quad's front. A shaded material needs lights or suitable environment lighting.

Material selection priority is object MaterialOverride → per-surface override → the Mesh resource surface material. GetSurfaceOverrideMaterial returns the stored override, which may be inactive under MaterialOverride. GetActiveMaterial helps inspect the selected base material. GetSurfaceOverrideMaterialCount counts surface slots, not non-null overrides.

MaterialOverlay adds a draw pass governed by its queue, blending and depth, not an unconditional top layer. In the check, an opaque equal-depth overlay was not on top, while a transparent overlay blended with the base. Do not rely on the overlay name alone to fix transparency ordering.

Vertex and normal spaces

By default VERTEX, NORMAL, TANGENT and BINORMAL enter vertex() in mesh-local space. MODEL_MATRIX maps position to world, VIEW_MATRIX to view, and PROJECTION_MATRIX to clip. In fragment(), VERTEX and the ordinary NORMAL are already view-space values. A shadow pass matrix can refer to a light camera; MAIN_CAM_INV_VIEW_MATRIX refers to the viewport's main camera.

Nonuniform scale requires the inverse-transpose normal transform. Multiplying a normal by ordinary MODELVIEW_MATRIX as a direction gives the wrong angle. A complete manual-path diagnostic:

Shader
shader_type spatial;
render_mode unshaded, skip_vertex_transform;
void vertex() {
    VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
    NORMAL = normalize(MODELVIEW_NORMAL_MATRIX * NORMAL);
    TANGENT = normalize(mat3(MODELVIEW_MATRIX) * TANGENT);
    BINORMAL = normalize(mat3(MODELVIEW_MATRIX) * BINORMAL);
}
void fragment() {
    ALBEDO = normalize(NORMAL) * 0.5 + 0.5;
}

skip_vertex_transform disables automatic model→view position/basis transformation; projection remains. NORMAL uses MODELVIEW_NORMAL_MATRIX, while tangent vectors use the linear MODELVIEW_MATRIX part. Complex deformation/nonorthogonal TBN can require rebuilding a consistent tangent basis. This is not a universal fix for every normal-mapped/skinned mesh.

world_vertex_coords supplies world-space attributes to user vertex(); do not apply MODEL_MATRIX twice. Writing POSITION requires a complete clip-space vec4 on every branch: automatic position projection is replaced, while fragment VERTEX still derives from VERTEX. Those outputs are not the same space. Normalized texture UV, viewport SCREEN_UV and pixel FRAGCOORD are also distinct.

Transparency and cutout

blend_mix is the spatial blend default, but ALPHA usage also determines the pipeline. PbrSurface does not use tint.a: setting the color's alpha to zero does not make it transparent. Reading/writing ALPHA can select the transparent queue even at 1, changing sorting and participation in other effects.

Write ALPHA for smooth transparency. Use ALPHA_SCISSOR_THRESHOLD for a cutout boundary. This complete shader keeps the quad's right half opaque:

Shader
shader_type spatial;
render_mode unshaded;
void fragment() {
    ALBEDO = vec3(1.0, 0.0, 0.0);
    ALPHA = UV.x;
    ALPHA_SCISSOR_THRESHOLD = 0.5;
}

Alpha hash uses ALPHA_HASH_SCALE for stochastic coverage. Alpha-to-coverage needs appropriate MSAA and antialiasing configuration; it does not replace texture detail. Transparent surfaces have sorting limitations, not arbitrary per-pixel layer ordering. depth_prepass_alpha helps some cases rather than solving all intersecting transparency.

NORMAL_MAP expects an encoded normal map; NORMAL is a view-space normal vector. Do not assign unprocessed texture RGB to NORMAL. Mark color textures/parameters as source_color and classify data maps by purpose; do not apply sRGB conversion to vectors, masks and physical parameters.

Custom light()

Without light(), built-in lighting applies. A custom function operates for a light in view space. LIGHT points toward it, ATTENUATION accounts for falloff/shadows and LIGHT_COLOR includes color/energy. Accumulate contributions with +=.

A complete simple diffuse calculation:

Shader
shader_type spatial;
uniform vec3 base_color : source_color = vec3(1.0);
void fragment() {
    ALBEDO = base_color;
    ROUGHNESS = 1.0;
}
void light() {
    float ndotl = max(dot(NORMAL, LIGHT), 0.0);
    DIFFUSE_LIGHT += ndotl * ATTENUATION * LIGHT_COLOR / PI;
}

Current Forward+ multiplies the final diffuse by ALBEDO afterward. Multiplying by ALBEDO inside this light() example would darken it twice; the test confirms one application. source_color also converts entered color to linear space: an entered color component of 0.5 is not automatically linear intensity 0.5.

This example handles ordinary direct diffuse light, not a complete PBR model. Use specialized area-light data/multipliers for that path, and SPECULAR_LIGHT for a specular contribution. unshaded bypasses lighting; vertex_lighting can bypass the per-pixel custom light(). Verify the required path before extending a BRDF.

Depth depends on the pass

The checked Forward+/Vulkan path uses reverse-Z: DEPTH=0 is far and DEPTH=1 is near. CLIP_SPACE_FAR describes the renderer's far clip coordinate, not a world distance. Reconstruct screen-depth positions with the appropriate INV_PROJECTION_MATRIX and division by w; see screen-reading shaders.

If any branch writes DEPTH, provide a meaningful value on every branch. Check the depth prepass and shadows separately: they need not repeat an arbitrary fragment algorithm.

A specific installed Forward+ defect is reproduced: a simple opaque custom-DEPTH material can select a shared prepass that does not account for that write. Geometry occludes the rear object in the prepass, then its color fails the new-depth test, leaving a black region. Source selection of the shared prepass material does not check DEPTH writes. This is a configuration limitation, not evidence that reverse-Z is ordinary Z. A separate transparent path without depth writing confirms the 0/1 ordering against an opaque rear surface. Do not treat custom DEPTH as a universal overlay trick without checking every required pass.

Stencil mask writing and reading

Current syntax uses stencil_mode, a reference from 0 through 255, and write/read/write_depth_fail. The correct name is write_depth_fail, not write_if_depth_fail. Reading must use the transparent queue. These examples provide a blue mask writer and a red reader inside its mask:

Shader
shader_type spatial;
render_mode unshaded;
stencil_mode write, 1;
void fragment() {
    ALBEDO = vec3(0.0, 0.0, 1.0);
}
Shader
shader_type spatial;
render_mode unshaded, depth_test_disabled;
stencil_mode read, compare_equal, 1;
void fragment() {
    ALBEDO = vec3(1.0, 0.0, 0.0);
    ALPHA = 1.0;
}

Assign them to two meshes; the reader can be larger. The check sees red only inside the mask, which updates when the writer moves. The reader deliberately writes ALPHA=1 and disables depth testing. This is a minimal mask check, not a complete portal or outline effect.

Comparisons are compare_always, compare_equal, compare_not_equal, compare_less, compare_less_or_equal, compare_greater and compare_greater_or_equal. Reference is compared with stencil contents. Stencil is not an ordinary color texture: spatial has no built-in sampler for it. Check each renderer/pass separately.

Complete built-in registry

G is global; V is vertex(); F is fragment(); L is light(). A star marks a writable stage; unstarred stages are read-only. Initial values and output semantics depend on the field; conditional output writes need values on every relevant branch.

NameTypeStagesMeaning
TIMEfloatGRenderer time following Engine.TimeScale; tree pause alone does not stop it.
EXPOSUREfloatGReciprocal emission exposure normalization in current Forward+, not base color.
IN_SHADOW_PASSboolGWhether the current pass renders shadow maps.
PIfloatGFloat mathematical constant.
TAUfloatGFloat mathematical constant.
EfloatGFloat mathematical constant.
OUTPUT_IS_SRGBboolGsRGB-output flag: false in current Forward+; Compatibility differs.
CLIP_SPACE_FARfloatGFar clip coordinate: Forward+/Mobile 0, Compatibility −1; not world depth.
IS_MULTIVIEWboolGWhether output is multiview.
VERTEXvec3V* / FPosition: default vertex() input is local; fragment() input is view-space.
NORMALvec3V* / F* / LNormal vector; space depends on stage as described above.
TANGENTvec3V* / F*Surface tangent basis; the built-in is named BINORMAL.
BINORMALvec3V* / F*Surface tangent basis; the built-in is named BINORMAL.
POSITIONvec4V*Complete clip-space position replacing automatic projection.
UVvec2V* / F / LFirst/second mesh UV set, interpolated to fragments.
UV2vec2V* / F / LFirst/second mesh UV set, interpolated to fragments.
COLORvec4V* / FVertex/instance color; does not automatically replace custom-shader ALBEDO.
POINT_SIZEfloatV*Point-primitive size, not arbitrary mesh scale.
Z_CLIP_SCALEfloatV*Clip-Z scale: current Forward+ mixes z from w toward normal z; skipped in shadow passes.
INSTANCE_IDintVDraw-instance index, not a gameplay Object/Entity instance ID.
INSTANCE_CUSTOMvec4VInstance data; particles supply simulation CUSTOM.
VERTEX_IDintVVertex index in the current buffer.
ROUGHNESSfloatV* / F* / LRoughness for the applicable lighting/reflection path.
BONE_INDICESuvec4VBone indices/weights from mesh attributes when supplied.
BONE_WEIGHTSvec4VBone indices/weights from mesh attributes when supplied.
CUSTOM0vec4VAdditional mesh attributes; Mesh determines their data/packing.
CUSTOM1vec4VAdditional mesh attributes; Mesh determines their data/packing.
CUSTOM2vec4VAdditional mesh attributes; Mesh determines their data/packing.
CUSTOM3vec4VAdditional mesh attributes; Mesh determines their data/packing.
MODEL_MATRIXmat4V / F / LLocal-to-world position transform.
MODEL_NORMAL_MATRIXmat3V / FLocal-to-world normal matrix.
VIEW_MATRIXmat4V / F / LWorld→view for the current pass.
INV_VIEW_MATRIXmat4V / F / LCurrent-pass view→world; a shadow pass need not use the main camera.
PROJECTION_MATRIXmat4V* / F / LView→clip; writable in vertex().
INV_PROJECTION_MATRIXmat4V / F / LClip→view; divide by w after transforming a position.
MODELVIEW_MATRIXmat4V*Local→view, available for manual transformation.
MODELVIEW_NORMAL_MATRIXmat3V*Local→view normal matrix, needed for nonuniform scale.
VIEWPORT_SIZEvec2V / F / LCurrent viewport dimensions in pixels.
MAIN_CAM_INV_VIEW_MATRIXmat4VMain viewport camera view→world matrix, distinct from shadow camera.
NODE_POSITION_WORLDvec3V / FObject position in world/view according to suffix, not each vertex position.
CAMERA_POSITION_WORLDvec3V / FWorld camera position/direction in the current context; account for stereo/passes.
CAMERA_DIRECTION_WORLDvec3V / FWorld camera position/direction in the current context; account for stereo/passes.
CAMERA_VISIBLE_LAYERSuintV / FCurrent-pass camera visible-layer mask.
NODE_POSITION_VIEWvec3V / FObject position in world/view according to suffix, not each vertex position.
VIEW_INDEXintV / FView index and mono/left/right-eye constants.
VIEW_MONO_LEFTintV / FView index and mono/left/right-eye constants.
VIEW_RIGHTintV / FView index and mono/left/right-eye constants.
EYE_OFFSETvec3V / FEye offset for multiview rendering.
LIGHT_VERTEXvec3F*View-space lighting position; writing it does not move rasterized geometry.
FRAGCOORDvec4F / LFragment-center coordinates; xy in target-buffer pixels.
FRONT_FACINGboolFWhether the current primitive is front-facing.
VIEWvec3F / LNormalized direction toward the viewer in view space.
NORMAL_MAPvec3F*Encoded tangent-space normal map, not a ready-made view NORMAL.
NORMAL_MAP_DEPTHfloatF*Normal-map application weight/depth.
BENT_NORMAL_MAPvec3F*Encoded bent-normal direction for environment-occlusion paths.
STREAMING_UVvec2F*UV for streaming feedback/mip selection, not an automatic texture() coordinate replacement.
ALBEDOvec3F* / LLinear base material color, separate from final lighting/specular.
PREMUL_ALPHA_FACTORfloatF*Premultiplied-alpha factor for shaded surfaces, not an ALPHA replacement.
ALPHAfloatF* / L*Transparency/coverage; usage affects queues and passes.
METALLICfloatF* / LMetallic model weight, normally 0–1.
SPECULARfloatF*Dielectric reflectance setting; metallic reflection also depends on ALBEDO/METALLIC.
RIMfloatF*Rim-light weight and base-color rim tinting.
RIM_TINTfloatF*Rim-light weight and base-color rim tinting.
CLEARCOATfloatF*Top reflection-layer weight and roughness.
CLEARCOAT_ROUGHNESSfloatF*Top reflection-layer weight and roughness.
ANISOTROPYfloatF*Anisotropy strength and tangent-space flow direction.
ANISOTROPY_FLOWvec2F*Anisotropy strength and tangent-space flow direction.
SSS_STRENGTHfloatF*Subsurface-scattering strength; support is renderer-dependent.
SSS_TRANSMITTANCE_COLORvec4F*Color, depth and boost for surface light transmittance.
SSS_TRANSMITTANCE_DEPTHfloatF*Color, depth and boost for surface light transmittance.
SSS_TRANSMITTANCE_BOOSTfloatF*Color, depth and boost for surface light transmittance.
BACKLIGHTvec3F* / LBacklighting/translucent scattering contribution.
AOfloatF*Material ambient occlusion, not an SSAO enable switch.
AO_LIGHT_AFFECTfloatF*Amount of AO influence on direct lighting.
EMISSIONvec3F*Surface HDR self-emission; does not itself create a Light3D.
DEPTHfloatF*Writable depth; check range, branches and prepass interaction.
SCREEN_UVvec2F / LNormalized viewport coordinates, not mesh UV.
POINT_COORDvec2FCoordinates within a point primitive.
FOGvec4F*Custom fog RGB blending using A as weight.
RADIANCEvec4F*RGBA environment-contribution override weighted by A; spatial values are not the sky RADIANCE sampler.
IRRADIANCEvec4F*RGBA environment-contribution override weighted by A; spatial values are not the sky RADIANCE sampler.
ALPHA_SCISSOR_THRESHOLDfloatF*Hard ALPHA cutout threshold.
ALPHA_HASH_SCALEfloatF*Stochastic alpha-hash coverage scale.
ALPHA_ANTIALIASING_EDGEfloatF*Alpha antialiasing edge and texture coordinates for the applicable coverage path.
ALPHA_TEXTURE_COORDINATEvec2F*Alpha antialiasing edge and texture coordinates for the applicable coverage path.
SPECULAR_AMOUNTfloatLCurrent light specular contribution multiplier.
LIGHTvec3LDirection toward the light in view space.
LIGHT_COLORvec3LLight color/energy for the light function; account for its normalization.
LIGHT_IS_DIRECTIONALboolLWhether this is a directional or area light.
LIGHT_IS_AREAboolLWhether this is a directional or area light.
LIGHT_AREA_DIFFUSE_MULTIPLIERvec3LArea-light diffuse/specular multipliers for custom calculation.
LIGHT_AREA_SPECULAR_MULTIPLIERvec3LArea-light diffuse/specular multipliers for custom calculation.
ATTENUATIONfloatLCurrent-light attenuation, including shadows.
DIFFUSE_LIGHTvec3L*Accumulated diffuse/specular light; use += for addition.
SPECULAR_LIGHTvec3L*Accumulated diffuse/specular light; use += for addition.

Render modes

Choose a coherent set rather than enabling every mode. Depth-write policy does not cancel depth testing or queue constraints.

ModePurpose
blend_mix / blend_add / blend_sub / blend_mul / blend_premul_alphaMix, additive, subtractive, multiply and premultiplied blending. Account for ALPHA and queues; do not multiply premultiplied input by alpha twice.
depth_draw_opaque / depth_draw_always / depth_draw_neverDepth-write policy for opaque/all/no surfaces, subject to the selected pass.
depth_prepass_alphaDepth prepass for applicable transparent geometry, not a complete sorting solution.
depth_test_default / depth_test_disabled / depth_test_invertedDefault, disabled or inverted depth test; testing and writing are distinct.
cull_back / cull_front / cull_disabledCull back/front faces, or draw both sides.
unshadedBypass the lighting model; useful for ALBEDO diagnosis.
diffuse_burley / diffuse_lambert / diffuse_lambert_wrap / diffuse_toonDiffuse model for built-in lighting.
specular_schlick_ggx / specular_toon / specular_disabledDirect specular model/disable; disabling it does not remove all environment reflections.
skip_vertex_transform / world_vertex_coordsManual model→view or world-space user vertex(), as described above.
ensure_correct_normalsAccepted name, but its macro does not implement a separate correction in current Forward+ GLSL. Use the correct normal matrix.
sss_mode_skinSkin subsurface-scattering specialization where supported.
shadows_disabledDisables receiving shadows; shadow casting is configured separately.
ambient_light_disabledDisables environment ambient/radiance contribution in the supported path.
shadow_to_opacityMaps lighting/shadows to alpha for specialized shadow-catching effects.
vertex_lightingVertex lighting rather than the ordinary per-pixel path; account for bypassed light().
particle_trailsSpecial GPU-particle trail geometry path, not automatic trail creation.
alpha_to_coverage / alpha_to_coverage_and_oneAlpha coverage for the applicable MSAA path.
fog_disabledDisables surface fog reception.
specular_occlusion_disabledDisables the environment specular-occlusion branch in current RD.
wireframe / debug_shadow_splitsDiagnostic wireframe / shadow-split colors. Wireframe data preparation is backend-dependent.

TIME rolls over at rendering/limits/time/time_rollover_secs, default 3600 seconds. Supply a time uniform for pausable gameplay animation.

With ambient disabled, default specular occlusion can suppress indirect specular even with valid RADIANCE or ReflectionProbe data. Current Forward+ and Mobile use ambient luminance as a reflection-visibility approximation in the branch without a bent-normal map. Use specular_occlusion_disabled for an isolated check, or DisableSpecularOcclusion on StandardMaterial3D. This does not disable direct shadows or diffuse AO. Setup and checked conditions.

Troubleshooting

SymptomCheck
Edited material is not visibleGetActiveMaterial, object MaterialOverride, surface override and Mesh material.
Red material has a white highlightALBEDO is not final RGB; inspect SPECULAR, METALLIC and lighting.
Color parameter 0.5 gives another linear intensitysource_color sRGB→linear conversion and output tonemapping.
Normals break when stretchedMODELVIEW_NORMAL_MATRIX for NORMAL and consistent TBN, not the position matrix.
Polygon disappears when rotatedFront-face orientation and cull mode.
Transparency does not workALPHA usage, material, queue and visibility behind the surface.
Black regions after DEPTH writesValues on all branches and the reproduced shared-prepass limitation.
Custom light is too darkRepeated ALBEDO, linear colors, normalization, ATTENUATION and vector spaces.
Stencil does not maskstencil_mode, reference, correct write_depth_fail and transparent reader queue.
Overlay is not on topIts blending, depth and pass ordering; this is not a UI layer.

Installed Windows Forward+/Vulkan checks execute six complete shaders and the C# helper: base surface, normals, culling, ALPHA/scissor, direct light, DEPTH, stencil and material priority. The 95-name registry and remaining modes are source-reviewed. This is not complete acceptance of every BRDF, SSS, area-light, MSAA, deformation/skinning, streaming, XR, shadow or platform combination. Functional readbacks are not published beauty illustrations; the custom-DEPTH defect is retained in the checks.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.