reckfellENGINE

Sky: background, sun and environment radiance

Complete shaders, environment setup, intermediate passes and reflection updates.

On this page

A sky shader defines sky color by viewing direction. Its sky() function serves the visible background, the environment radiance map for lighting/reflections and enabled intermediate passes. Changing the radiance update policy does not disable background rendering.

Connect the sky to an environment

  1. Add a WorldEnvironment and assign an Environment resource.
  2. Set BackgroundMode = Sky and create a Sky in Environment.Sky.
  3. Assign a ShaderMaterial to SkyMaterial, with a sky-type Shader.
  4. Ensure the active Camera3D can see the background. Opaque geometry occludes it.

This complete shader identifies the direction being used: red zenith, green horizon and blue lower hemisphere. These are diagnostic colors, not an atmospheric model.

Shader
shader_type sky;
uniform vec3 zenith_color : source_color = vec3(1.0, 0.0, 0.0);
uniform vec3 horizon_color : source_color = vec3(0.0, 1.0, 0.0);
uniform vec3 ground_color : source_color = vec3(0.0, 0.0, 1.0);
void sky() {
    float height = clamp(abs(EYEDIR.y) * 2.0, 0.0, 1.0);
    vec3 pole = EYEDIR.y >= 0.0 ? zenith_color : ground_color;
    COLOR = mix(horizon_color, pole, height);
}

For the same resource setup in C#, save SkySetup.cs:

C#
using Reckfell;

public static class SkySetup
{
    public static Reckfell.Environment Create(Shader shader)
    {
        var material = new ShaderMaterial { Shader = shader };
        var sky = new Sky
        {
            SkyMaterial = material,
            ProcessMode = Sky.ProcessModeEnum.Quality,
            RadianceSize = Sky.RadianceSizeEnum.Size256,
        };
        return new Reckfell.Environment
        {
            BackgroundMode = Reckfell.Environment.BGMode.Sky,
            Sky = sky,
            TonemapMode = Reckfell.Environment.ToneMapper.Linear,
        };
    }
}

Load the shader using ResourceLoader.Load<Shader>("asset://DirectionSky.reckfellshader"), call SkySetup.Create(shader) and assign the result to WorldEnvironment.Environment. Place the WorldEnvironment in the same World3D as the camera; a SubViewport with OwnWorld3D needs its own environment. Linear tonemapping is chosen here for color checks, not as a universal art setting.

The test points the camera up/down and observes red/blue. Translating it without changing orientation does not change this shader because it uses EYEDIR, not POSITION. Change uniforms on the ShaderMaterial: material.SetShaderParameter("zenith_color", Colors.Blue). Passing default(Variant) restores the uniform initializer.

Separate background and lighting radiance

AT_CUBEMAP_PASS identifies radiance-map generation. It is false for the main background pass. You can simplify lighting relative to the visible background, but reflections and background then deliberately differ. The test writes blue into the radiance branch and red on screen; reading the generated panorama confirms the distinction.

Move an expensive static calculation into the radiance branch and read its result for the background. A complete minimal example:

Shader
shader_type sky;
void sky() {
    if (AT_CUBEMAP_PASS) {
        COLOR = vec3(0.0, 0.0, 1.0);
    } else {
        COLOR = textureLod(RADIANCE, EYEDIR, 0.0).rgb;
    }
}

Read RADIANCE only outside generation of the map itself: an unfinished result cannot be its own source. textureLod at level 0 explicitly selects base detail. For an image panorama, use a separate sampler2D with SKY_COORDS or PanoramaSkyMaterial; RADIANCE is the processed environment map, not the source image file.

Caching still costs a background sample each frame. Resolution and filtering limit detail; small clouds or stars may disappear. This example proves the path works, not that it accelerates every effect.

Choose an update policy

Sky.ProcessModePurpose
AutomaticRenderer selects a policy from shader dependencies and settings; this does not guarantee Realtime.
QualityHigh-quality updates when needed, intended for static or infrequently changing skies.
IncrementalSpreads map filtering over frames; reflection updates can complete after the visible background changes.
RealtimeA path for frequent changes, with resolution/filtering quality constraints.

The current RD renderer uses size 256 for Realtime. Requesting a different RadianceSize causes a warning and internal adjustment. RadianceSize is an enum, not an arbitrary pixel count. The resource stores the requested setting, so its getter does not prove the internally adjusted size. The test uses Realtime with Size256 and also checks Quality with Size64.

Material/parameter changes, used time, camera translation when POSITION is used, used directional lights and some fog settings mark the map for update. Resizing with intermediate passes requires updating their buffers. Remove unnecessary dependencies: even TIME used in a background-only branch is detected by shader analysis. Current RD Automatic selects Realtime for TIME/POSITION at the supported size, Incremental for lights, fog sun scattering or uniform parameters, otherwise Quality; do not assume identical policies across all renderers.

TIME is renderer time affected by Engine.TimeScale, rolling over at rendering/limits/time/time_rollover_secs, default 3600 seconds. Pausing the tree alone does not stop it. For controlled animation, provide your own time and change it only when necessary.

Half- and quarter-resolution passes

use_half_res_pass enables half the width and height; use_quarter_res_pass uses one quarter of each dimension. These are approximately 1/4 and 1/16 of the corresponding full pass pixel count. Additional passes still cost work; enable them only when an appropriate calculation can move to low resolution.

This diagnostic shader writes distinct values into both passes and reads them at full resolution. Half-pass ALPHA becomes the final red channel; green and blue confirm reads from both textures:

Shader
shader_type sky;
render_mode use_half_res_pass, use_quarter_res_pass;
void sky() {
    if (AT_QUARTER_RES_PASS) {
        COLOR = vec3(0.0, 0.0, 1.0);
        ALPHA = 0.25;
    } else if (AT_HALF_RES_PASS) {
        COLOR = vec3(0.0, 1.0, 0.0);
        ALPHA = 0.5;
    } else {
        COLOR = vec3(HALF_RES_COLOR.a, HALF_RES_COLOR.g, QUARTER_RES_COLOR.b);
    }
}

For clouds, calculate color and coverage at low resolution and explicitly combine them with the sky using mix. Reading HALF_RES_COLOR or QUARTER_RES_COLOR does not automatically blend it. Filtering smooths sampling but cannot recover lost detail. AT_CUBEMAP_PASS can be true at the same time as an intermediate-pass flag, so order conditions deliberately.

ALPHA carries intermediate-pass data. Setting ALPHA=0 in the main sky does not reveal a transparent background. Configure a separate viewport composition path for transparent output rather than relying on sky().

Directional lights

Four slots, LIGHT0…LIGHT3, are available. Check LIGHTX_ENABLED before reading other fields. Eligible DirectionalLight3D objects populate them, not PointLight3D; a slot number is not a stable light ID. LightOnly excludes a light from the sky; SkyOnly keeps it available for the sky.

This complete example forms a light lobe toward the source. It is neither a physical atmosphere nor a disk with an exact angular diameter:

Shader
shader_type sky;
void sky() {
    COLOR = vec3(0.0);
    if (LIGHT0_ENABLED) {
        float alignment = max(dot(EYEDIR, LIGHT0_DIRECTION), 0.0);
        COLOR = LIGHT0_COLOR * LIGHT0_ENERGY * pow(alignment, 64.0);
    }
}

In the tested RD path, LIGHTX_DIRECTION points toward the source along the light transform's world +Z. Thus dot(EYEDIR, LIGHTX_DIRECTION) selects the view toward it; do not invert the sign based on the phrase “light direction.” Rotating DirectionalLight3D by π around Y makes the lobe visible to a camera looking along −Z. LIGHTX_COLOR is linear; ENERGY incorporates energy settings and applicable physical units/exposure. LIGHTX_SIZE is angular diameter in radians, not radius: use the cosine of half the diameter for a disk threshold.

Render modes and built-ins

ModeBehavior
use_half_res_passEnables the half-resolution pass.
use_quarter_res_passEnables the quarter-resolution pass.
disable_fogDisables fog, including custom FOG in the tested RD path.
use_debandingAdds noise to reduce gradient bands; it does not add detail.
Type and nameMeaning
in float TIMERenderer time; update dependencies are described above.
in float PI / TAU / EMathematical constants.
in vec3 POSITIONWorld camera position, not a sky pixel position.
samplerCube RADIANCERead the completed environment map by direction outside its own generation pass.
in bool AT_CUBEMAP_PASSEnvironment map generation, including its intermediate passes.
in bool AT_HALF_RES_PASS / AT_QUARTER_RES_PASSCurrent half-/quarter-resolution pass.
in bool LIGHTX_ENABLEDWhether slot X from 0 through 3 is available.
in vec3 LIGHTX_DIRECTION / LIGHTX_COLORDirection toward the source / linear color.
in float LIGHTX_ENERGY / LIGHTX_SIZEEnergy / angular diameter in radians.
in vec3 EYEDIRNormalized direction of the current ray.
in vec2 SCREEN_UVCurrent-pass coordinates, not spherical panorama UV.
in vec4 FRAGCOORDFragment coordinates in the current target buffer.
in vec2 SKY_COORDSSpherical UV for a panorama texture.
in vec4 HALF_RES_COLOR / QUARTER_RES_COLORSamples from the corresponding enabled passes: color and written ALPHA.
out vec3 COLOROutput linear RGB; exposure/tonemapping applies afterward.
out float ALPHAIntermediate-pass alpha data, not main-background transparency.
out vec4 FOGCustom fog RGB and blend weight in A.

in is read-only, out is a writable result and a sampler is a texture source. Pass flags, POSITION, RADIANCE, LIGHTX and TIME are global; EYEDIR, SCREEN_UV, SKY_COORDS, FRAGCOORD and outputs belong to sky(). FOG blends RGB using A as its weight; it is not FogVolume density. Current RD disable_fog also disables this contribution.

Troubleshooting

SymptomCheck
Black skyBackgroundMode, Environment.Sky, SkyMaterial, shader_type sky, active camera and shared World3D.
Background changes before reflectionsProcessMode and completion of radiance updates/filtering.
Sky is too brightShader HDR values, environment energy, camera exposure and tonemapping.
ALPHA=0 does not hide the skyMain-pass ALPHA does not control blending; configure composition separately.
Wrong color or lost detail after cachingBackground/map branch differences, resolution, RADIANCE LOD and filtering.
Sun lobe appears on the opposite sideLIGHTX_DIRECTION sign, light rotation and selected slot.
No sunLIGHTX_ENABLED, DirectionalLight3D.SkyMode, visibility and the four-light limit.
Gradient bandingRange and tonemapping; use_debanding when appropriate.
Realtime size warningUse Size256 in current RD or choose another update mode.

Actual Windows Forward+/Vulkan pixels check directions, uniforms, background/radiance separation, map sampling, intermediate passes, alpha, FOG and directional-light slots. Quality/Incremental/Realtime settings are checked with a static result, not benchmarked filtering latency or performance. Scheduling policy, remaining built-ins, environment rotation/FOV and configuration constraints are source-reviewed. No new editor capture, artistic atmosphere, physical correctness, HDR/XR or all-platform/export acceptance is claimed here.

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.