Skybox Rendering Technique (Equirectangular)ยถ
The engine uses direct Equirectangular mapping for the environment background. This is more memory-efficient as it avoids converting to and storing a generated cubemap.
Early-Z Optimizationยถ
To maximize performance on integrated GPUs, the skybox is rendered after the scene objects.
- Vertex Shader: Positions the skybox triangles exactly on the far plane (
z = 1.0). - Depth Test: By using
glDepthFunc(GL_LEQUAL), the GPU automatically rejects skybox fragments that are occluded by 3D objects (like the icosphere) before launching the fragment shader. - Fragment Shader: Performs an inverse spherical projection to sample the 2D HDR texture.
Optimization Diagramยถ
// Inverse Equirectangular Projection
const vec2 invAtan = vec2(0.1591, 0.3183);
vec2 SampleEquirectangular(vec3 v) {
vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
uv *= invAtan;
uv.x += 0.5;
uv.y = 0.5 - uv.y;
return uv;
}
void skybox_main() {
vec3 dir = normalize(v_direction);
vec2 uv = SampleEquirectangular(dir);
out_color_val = textureLod(environmentMap, uv, blur_lod);
}
C Implementation (View Matrix)ยถ
We remove the translation component from the view matrix so the skybox appears infinitely far away (centered on the camera):
/* Copy view and strip translation to keep skybox at infinity */
mat4 view_sky;
glm_mat4_copy(view, view_sky);
view_sky[3][0] = 0.0f;
view_sky[3][1] = 0.0f;
view_sky[3][2] = 0.0f;
/* Compute inverse view-projection for equirect sampling */
mat4 inv_vp_sky;
glm_mat4_mul(proj, view_sky, inv_vp_sky);
glm_mat4_inv(inv_vp_sky, inv_vp_sky);
๐ Technical Detailsยถ
Mipmap Samplingยถ
Using textureLod with an equirectangular texture allows precise control over bluriness:
- LOD 0: Sharp environment.
- LOD > 0: Blurred environment (useful for PBR background or debugging).
Orientation Correctionยถ
The inversion uv.y = 0.5 - uv.y is crucial to map the "top" of the HDR image to the "top" of the 3D world space.
๐จ Full Workflowยถ
// Main render loop integration
void render_scene_example(App* app) {
// 1. View Matrix without translation
mat4 view_sky;
glm_mat4_copy(app->view, view_sky);
view_sky[3][0] = 0.0f;
view_sky[3][1] = 0.0f;
view_sky[3][2] = 0.0f;
mat4 inv_vp_sky;
glm_mat4_mul(app->proj, view_sky, inv_vp_sky);
glm_mat4_inv(inv_vp_sky, inv_vp_sky);
// 2. Render via skybox module
skybox_render(&app->skybox, app->skybox_shader,
app->hdr_texture, inv_vp_sky, app->env_lod);
}
๐ Advantagesยถ
- Performance: No complex matrix math, just zeroing 3 floats.
- Simplicity: Easy to understand and maintain.
- Robustness: Standard industry technique.
- Quality: Seamless infinite background.
๐ Important Notesยถ
- Use
glDepthFunc(GL_LEQUAL)so the skybox is drawn at the back processing. - The skybox does not write significant depth.
- The LOD (blur_lod) allows controlling the environment blur.
๐ Python โ C Equivalenceยถ
Python (moderngl)ยถ
view_m = camera.matrix
view_m[3][0] = 0
view_m[3][1] = 0
view_m[3][2] = 0
inv_view_proj_sky = glm.inverse(projection_matrix * view_m)
C (cglm)ยถ
mat4 view_m;
glm_lookat(camera_pos, target, up, view_m);
view_m[3][0] = 0.0f;
view_m[3][1] = 0.0f;
view_m[3][2] = 0.0f;
mat4 inv_view_proj_sky;
glm_mat4_mul(proj_matrix, view_m, inv_view_proj_sky);
glm_mat4_inv(inv_view_proj_sky, inv_view_proj_sky);
Perfectly equivalent! โ