Skip to content

Adds HDRI-based Image-Based Lighting (IBL) ambient term to the OpenGL rasterizer - #100

Merged
juanchuletas merged 1 commit into
mainfrom
99-ibl-environment-lighting-for-raster-preview
Aug 9, 2026
Merged

Adds HDRI-based Image-Based Lighting (IBL) ambient term to the OpenGL rasterizer#100
juanchuletas merged 1 commit into
mainfrom
99-ibl-environment-lighting-for-raster-preview

Conversation

@juanchuletas

@juanchuletas juanchuletas commented Aug 9, 2026

Copy link
Copy Markdown
Member

Pull Request Template

Description

Adds HDRI-based Image-Based Lighting (IBL) ambient term to the OpenGL rasterizer, plus a tunable flat ambient fallback for scenes with no HDRI loaded. Fixes the raster preview looking flat/dark and lit only by manually placed point lights.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactor (code change that neither fixes a bug nor adds a feature)
  • Documentation (changes to docs only)
  • Performance (improves performance)

Changes Made

  • Added HDR (.hdr) equirectangular texture loading (Texture::genTextureHDR, GPUTexture::uploadFloat)
  • Added IBLProbe (IBL/ibl_probe.hpp/.cpp): converts an equirectangular HDR image to a cubemap, then convolves it into a diffuse irradiance cubemap
  • Added equirect-to-cubemap and irradiance convolution shaders (resources/equirect_to_cubemap.vs/.fs, resources/irradiance_convolve.vs/.fs)
  • Added an equirect-sampling skybox shader pair (resources/equirect_skybox.vs/.fs) and CubeMap::buildHDR() to render an HDRI directly as a background without a full cubemap conversion
  • Wired IBL into fungt_default.fs: new irradianceMap, hasIBL, iblIntensity uniforms; ambient term now samples real HDRI-derived irradiance instead of a flat 0.05 * material.ambient hack when an environment is loaded
  • Added a tunable flat ambientColor uniform/default (SceneManager-owned) as the fallback ambient term when no HDRI environment is loaded
  • Added Reinhard tonemap + gamma correction to fungt_default.fs's final output, since HDR irradiance/light values can exceed 1.0 and would otherwise hard-clip to white
  • SceneManager::loadEnvironment(hdrPath) builds and owns an IBLProbe, binding its irradiance map and setting hasIBL/iblIntensity/ambientColor uniforms once per frame in renderScene()
  • Extended GPUTexture/OpenGLTexture with allocateEmptyCubemap() for render-target cubemap allocation
  • Refactored IBLProbe to use GPUBuffer (cube mesh) and GPUTexture/Texture (cubemap allocation) instead of raw OpenGL calls, consistent with the existing rendering abstraction layer; offscreen FBO/RBO calls for the two convolution passes remain raw OpenGL (no existing interface covers per-face cubemap render targets)
  • Fixed an invalid OpenGL enum: OpenGLTexture::upload() set GL_TEXTURE_MAG_FILTER to a mipmap filter mode (only valid for MIN_FILTER) and never called glGenerateMipmap; corrected and added the missing mipmap generation call
  • Fixed a texture-unit collision causing glValidateProgram failures: texture_diffuse1 (sampler2D) and irradianceMap (samplerCube) could both default to texture unit 0; both are now explicitly pinned to distinct units at shader link time
  • Switched SimpleGeometry's Sphere and Plane primitives from the separate primitive_vs.glsl/primitive_fs.glsl shader pair to fungt_default.vs/.fs, so they pick up the same ambient/IBL/tonemap behavior as loaded models (vertex layouts were confirmed compatible)

Technical Details

  • Irradiance convolution follows the standard hemisphere-integral approach (per-texel cosine-weighted sampling over the upper hemisphere), matching the widely used LearnOpenGL IBL reference implementation
  • A real bug was found and fixed in the convolution shader: sampling the environment cubemap with implicit LOD (texture()) inside an offscreen FBO pass produced wildly inconsistent, sometimes blown-out brightness across cubemap faces, because automatic mip selection has no meaningful screen-space derivatives in that context; switched to explicit textureLod(..., 0.0)
  • IBL intensity and flat ambient color are both runtime-tunable (SceneManager::setIBLIntensity, setAmbientColor) rather than hardcoded, since raw HDRI irradiance values are frequently too dim or too bright to use directly against arbitrary scene content
  • Cube primitive path (resources/cube.vs/.fs) was intentionally left unchanged: it has no vertex normal data end-to-end (geometry generation never emits normals), so it cannot support the lighting model used by fungt_default.fs without additional geometry-side work; scoped out of this PR

Testing

  • Tested locally
  • Visual comparison before/after
  • Performance benchmarked
  • Edge cases verified

Verified via a dedicated test sample (Samples/hdr_test) against two HDRIs (a sunny daytime environment and a night environment) and multiple test models (an OBJ/MTL street asset, an FBX street asset). Confirmed via direct GPU texture readback (glGetTexImage) that the generated cubemap and irradiance map contain correct, non-garbage HDR data before and after the textureLod fix. Confirmed the flat-ambient fallback and IBL path both respond correctly to their respective intensity/color controls.

Screenshots / Results

Before After
Flat, near-black ambient; only manually placed point lights contribute any light Real ambient light derived from HDRI irradiance (or a tunable flat ambient), visible directional variation across surfaces

Performance Impact

  • Render time before: not measured
  • Render time after: not measured (irradiance/cubemap build is a one-time cost at loadEnvironment() call time, not per-frame)
  • Memory usage: not measured

Related Issues

Closes #

Checklist

  • Code compiles without warnings
  • Code follows project style guidelines
  • Self-reviewed my own code
  • Commented hard-to-understand areas
  • No unnecessary debug code left behind

Notes for Reviewers

  • The cube primitive path (resources/cube.vs/.fs) still uses the old unlit/no-normal shader; fixing it requires adding normal generation to Geometries/cube.cpp first and is left as follow-up work
  • Full PBR (metallic/roughness, Cook-Torrance) shading, prefiltered specular reflections, and a BRDF integration LUT were explicitly out of scope for this PR; only the diffuse/ambient IBL term was implemented on top of the existing Blinn-Phong shader
  • Screen-space ambient occlusion (SSAO) does not exist in this codebase; flat/uniform ambient (with no HDRI loaded) will not produce shape-defining contact shadows on its own, this is expected and not a regression

@juanchuletas juanchuletas linked an issue Aug 9, 2026 that may be closed by this pull request
12 tasks
@juanchuletas

Copy link
Copy Markdown
Member Author

A common Scene using Models and geometries looks correct, according with the ambient light. To have more details on models with pure gray colors, without a specific light source on the scene, we need to add

SSAO ( Screen Space Ambient Occlusion)

image

@juanchuletas
juanchuletas merged commit c487e20 into main Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IBL / Environment Lighting for Raster Preview

1 participant