Sky shaders are used to procedurally generate skybox cubemaps. Unlike screen shaders, they do not process a rendered frame, they render each face of a cubemap from scratch.
A sky shader runs once per texel of a cubemap to compute the sky color seen from that direction. R3D renders all six faces of the cubemap using an internal unit cube, calling your fragment() function for each pixel.
Sky shaders are not used during the normal frame rendering process, they are invoked explicitly to generate or update a R3D_Cubemap.
void fragment() {
// Simple gradient sky: blue at horizon, dark at zenith
float t = max(EYEDIR.y, 0.0);
COLOR = mix(vec3(0.5, 0.7, 1.0), vec3(0.1, 0.2, 0.5), t);
}
// From file
R3D_SkyShader* shader = R3D_LoadSkyShader("sky.glsl");
// From memory
const char* code = "void fragment() { COLOR = vec3(0.2, 0.4, 0.8); }";
R3D_SkyShader* shader = R3D_LoadSkyShaderFromMemory(code);
// Don't forget to unload when done
R3D_UnloadSkyShader(shader);
Sky shaders have only one required entry point: fragment(). There is no vertex stage, and consequently, no varyings.
Runs once per cubemap texel to compute the sky color for that direction.
void fragment() {
// Use EYEDIR to determine sky color based on view direction
vec3 sunDir = normalize(vec3(0.5, 0.8, 0.3));
float sun = pow(max(dot(EYEDIR, sunDir), 0.0), 64.0);
COLOR = mix(vec3(0.1, 0.3, 0.8), vec3(1.0, 0.9, 0.6), sun);
}
Sky shaders provide built-in variables describing the current cubemap texel:
| Variable | Type | Description |
|---|---|---|
POSITION |
vec3 |
Interpolated local position on the unit cube |
TEXCOORD |
vec2 |
2D texture coordinates on the current cube face (0.0 to 1.0) |
EYEDIR |
vec3 |
Normalized direction vector into the cubemap |
FRAME_INDEX |
int |
Index incremented at each frame |
TIME |
float |
Time provided by raylib's GetTime() |
COLOR |
vec3 |
Output color (write to this) |
EYEDIREYEDIR is the most commonly useful variable. It is the normalized version of POSITION and represents the 3D direction from the origin toward the current texel in the cubemap. Use it to:
EYEDIR.y)dot(EYEDIR, lightDir)Sample an equirectangular texture using the provided helper
void fragment() {
// Sky gradient based on elevation
float horizon = smoothstep(-0.05, 0.1, EYEDIR.y);
COLOR = mix(vec3(0.8, 0.6, 0.4), vec3(0.2, 0.4, 0.9), horizon);
}
TEXCOORD vs EYEDIREYEDIR for 3D directional effects (gradients, sun, stars, procedural atmosphere).TEXCOORD when you need 2D face-local coordinates, for example when sampling a per-face texture or applying a per-face pattern.TIME and FRAME_INDEXThese work identically to their equivalents in surface and screen shaders. TIME is useful for animating sky conditions (moving clouds, day/night cycle), and FRAME_INDEX can drive frame-dependent noise effects when updating the cubemap each frame.
Sky shaders include a built-in helper to convert a direction vector to equirectangular (spherical) UV coordinates:
vec2 GetSphericalCoord(vec3 direction);
Returns UV coordinates suitable for sampling an equirectangular (panoramic) texture from a direction vector.
Example:
uniform sampler2D u_panorama;
void fragment() {
vec2 uv = GetSphericalCoord(EYEDIR);
COLOR = texture(u_panorama, uv).rgb;
}
Sky shaders support the same uniform system as surface and screen shaders, with identical limits and behavior.
Values: bool, int, float, vec2, vec3, vec4, mat2, mat3, mat4
Samplers: sampler1D, sampler2D, sampler3D, samplerCube
R3D_MAX_SHADER_UNIFORMS)R3D_MAX_SHADER_SAMPLERS)float time = GetTime();
R3D_SetSkyShaderUniform(shader, "u_time", &time);
Texture2D panorama = LoadTexture("sky.hdr");
R3D_SetSkyShaderSampler(shader, "u_panorama", panorama);
Sky shaders are used exclusively through two functions:
R3D_Cubemap R3D_GenCustomSky(int size, R3D_SkyShader* shader);
void R3D_UpdateCustomSky(R3D_Cubemap* cubemap, R3D_SkyShader* shader);
R3D_GenCustomSky allocates a new cubemap and renders all six faces using the provided shader.R3D_UpdateCustomSky re-renders an existing cubemap in place.R3D_SkyShader* shader = R3D_LoadSkyShader("sky.glsl");
// Generate once at startup and set it to the environment
R3D_Cubemap sky = R3D_GenCustomSky(512, shader);
R3D_GetEnvironment()->background.sky = sky;
float time = 0.0f;
while (!WindowShouldClose()) {
// Update sky every frame for animated effects
time += GetFrameTime();
R3D_SetSkyShaderUniform(shader, "u_time", &time);
R3D_UpdateCustomSky(&sky, shader);
R3D_Begin();
// ... draw scene with sky cubemap ...
R3D_End();
}
R3D_UnloadSkyShader(shader);
Note: Updating a cubemap every frame can be expensive for large sizes. Consider updating at a lower frequency, or using a smaller size (e.g. 64–128) when details are not critical.
R3D_UpdateCustomSky when the sky actually changes (new time of day, weather, etc.).dot(EYEDIR, sunDir) once and reuse them.EYEDIR is already normalized, but any derived direction (reflected rays, sun direction) should be explicitly normalized.EYEDIR.y: When computing atmospheric effects based on elevation, clamp or check EYEDIR.y to avoid artifacts below the horizon.GetSphericalCoord for panoramas: Manually computing spherical coordinates is error-prone; prefer the built-in helper.R3D_SkyShader* R3D_LoadSkyShader(const char* filePath);
R3D_SkyShader* R3D_LoadSkyShaderFromMemory(const char* code);
void R3D_UnloadSkyShader(R3D_SkyShader* shader);
void R3D_SetSkyShaderUniform(R3D_SkyShader* shader, const char* name, const void* value);
void R3D_SetSkyShaderSampler(R3D_SkyShader* shader, const char* name, Texture texture);
R3D_Cubemap R3D_GenCustomSky(int size, R3D_SkyShader* shader);
void R3D_UpdateCustomSky(R3D_Cubemap* cubemap, R3D_SkyShader* shader);
uniform <type> <name>; // Optional: uniforms
void fragment() { // Required: fragment stage
// Read: POSITION, TEXCOORD, EYEDIR, TIME, FRAME_INDEX
// Helper: GetSphericalCoord(vec3 direction) -> vec2
// Write: COLOR
}
// Input (read-only)
vec3 POSITION; // Local position on the unit cube
vec2 TEXCOORD; // UV coordinates on the current cube face [0..1]
vec3 EYEDIR; // Normalized direction into the cubemap
int FRAME_INDEX; // Frame counter
float TIME; // Elapsed time
// Output (write)
vec3 COLOR; // Output sky color for this texel
vec2 GetSphericalCoord(vec3 direction); // Direction → equirectangular UV