Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Procedural IBL Enhancement in CesiumJS #12129

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open

Procedural IBL Enhancement in CesiumJS #12129

wants to merge 43 commits into from

Conversation

ggetz
Copy link
Contributor

@ggetz ggetz commented Aug 16, 2024

Description

This pull request introduces a new system for "procedural" lighting that builds on the Image-Based Lighting (IBL) approach in CesiumJS, aimed at improving the realism and visual quality of the default lighting environment. The changes focus on techniques that create a plausible environment map dynamically based on the current lighting conditions an existing atmospheric scattering code which renders the sky.

Image-Based Lighting Sandcastle

After
image

Before
image

Mirror Ball

After
image

Before
image

High Altitude Mirror Ball

After
image

Before
image

Dynamic Lighting

image

TODO: More screenshot comparisons

Key Changes

  • DynamicEnvironmentMapManager: This new component generates lighting value dynamically based on the existing atmospheric calculations. It uses compute command to generate the following when position or sun position has significantly changed.
    • Environment Map: generated to match lighting conditions based on a central location and the current sun position.
    • Specular Environment Maps: Implements several levels of specular maps, corresponding to various degrees of surface roughness. This allows for more nuanced reflections and highlights that better respond to scene lighting.
    • Spherical Harmonic Diffuse Lighting: Computes of spherical harmonic values for diffuse lighting, enhancing the quality and realism of the light diffusion across different surfaces.
  • The computed values from the new IBL setup are directly used in conjunction with the scene's directional light source, primarily the sun, to establish comprehensive lighting effects across models and 3D Tilesets.
  • I've chosen to make the DynamicEnvironmentMapManager options accessible via the Model and Cesium3DTileset only, at least for this PR, mainly due to the performance impact if these values are constantly updated.

Additional Updates

  • Fix Spherical Harmonic reconstruction: as per the filament maintainers
  • Fix for ImageBasedLighting.imageBasedLightingFactor: This property was broken and went unnoticed.
  • Removal of ImageBasedLighting.luminanceAtZenith: This property was removed instead of deprecated because there is no direct equivalent in the new implementation, and is likely not widely used (based on the fact that the above bug went unnoticed).
  • Modifications to CubeMap: New utility functions in support of the operations in DynamicEnvironmentMapManager, such as copying a texture to a specific face.

Issue number and link

N/A

Testing plan

  1. Pull down this branch and run unit tests locally (I had to tweak rendering tests to account for the new lighting. Please double check me here.)
  2. Run the development PBR Lighting sandcastle example and make sure all the sliders/buttons work.
  3. Review all Sandcastle examples and ensure new lighting parameters are adequate for all cases.

Author checklist

  • Fix race condition where the map sometime does not initialize atmosphere values correctly
  • Fix slightly askew environment map orientation
  • Confirm shadows are affecting lighting correctly
  • Expose DynamicEnvironmentMapManager options for Model entities?
  • Any adjustments needed for HDR?
  • Performance pass and testing
  • Cleanup Sandcastle examples-- Streamline existing ones where possible
  • Cleanup pass on shaders
  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

Copy link

Thank you for the pull request, @ggetz!

✅ We can confirm we have a CLA on file for you.

if (defined(uniformMap)) {
const manualUniforms = this._manualUniforms;
len = manualUniforms.length;
for (i = 0; i < len; ++i) {
const mu = manualUniforms[i];
mu.value = uniformMap[mu.name]();
try {
Copy link
Contributor Author

@ggetz ggetz Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This update is not strictly necessary, but I found it helpful for debugging.

@ggetz ggetz mentioned this pull request Aug 30, 2024
7 tasks
@ggetz ggetz marked this pull request as ready for review October 4, 2024 13:28
@ggetz
Copy link
Contributor Author

ggetz commented Oct 4, 2024

@jjspace Could you please take a review pass on this?

@jjspace
Copy link
Contributor

jjspace commented Oct 11, 2024

@ggetz Couple initial comments playing with the new sandcastle.

  1. When I have a small model selected and modify the height the camera zooms really far out. I saw the comment about not handling small models well but is there a way to mitigate that when modifying the height slider?
  2. As mentioned in standup I feel like models are overly blue. Not sure if it's the result of these changes directly or our general visual changes as a whole. For example, this pot at height 400 almost looks like it's glowing blue, like it's emissive itself instead of just reflecting the light around it.
    2024-10-11_13-23
    Same with the wicker ball. I'd expect it to be brighter in the areas that are highlighted but I'm not completely sure I'd expect the highlights to be so bluish in color? Maybe I'm just not imagining what the materials would look like correctly
    2024-10-11_13-29
  3. When modifying the height value it doesn't seem that the lighting is updated as I thought you said it should.
    Pot loaded at 400 then moved to 80,000ish:
    2024-10-11_13-24
    Pot reloaded already at 80,000ish:
    2024-10-11_13-24_1

Copy link
Contributor

@jjspace jjspace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a handful of really small, mostly optional, code comments in addition to the comments above

I don't know if I feel fully qualified to review the shaders but they look ok to me at a cursory glance

Comment on lines +115 to 116
for (const faceName of CubeMap.faces()) {
const face = source[faceName];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a nitpick but I find this naming change confusing.

Given a function named CubeMap.faces() I would expect that to return a list of actual faces that I could iterate over like const face of CubeMap.faces() (which I was going to suggest this be changed to). But the next line here implies that it's not actually the face itself but still only a face name. The type of the iterable also still says they're FaceName values.

*
* @type {Iterable<CubeMap.FaceName>}
*
* @type {iterable<CubeMap.FaceName>}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @type {iterable<CubeMap.FaceName>}
* @type {Iterable<CubeMap.FaceName>}

height,
level,
) {
xOffset = defaultValue(xOffset, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we're trying to remove defaultValue I think it's good to avoid adding new calls to avoid future conflicts?

Suggested change
xOffset = defaultValue(xOffset, 0);
xOffset = xOffset ?? 0;

* @throws {DeveloperError} framebufferYOffset must be greater than or equal to zero.
* @throws {DeveloperError} xOffset + source.width must be less than or equal to width.
* @throws {DeveloperError} yOffset + source.height must be less than or equal to height.
* @throws {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a meta question: All of these errors get thrown only in development because of the pragma.debug. Given that we use the JSDoc to generate our actual documentation but these will never affect downstream users should they actually be documented like this?

Comment on lines +3548 to +3550
if (!this._environmentMapManager.isDestroyed()) {
this._environmentMapManager.destroy();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change needed but curious about this pattern.

It feels like the responsibility for this check should live in the destroy function itself. I feel like you should be able to just call destroy() regardless and let it handle checking if it's already destroyed and essentially turn into a noop function. Then in this function the logic would just be "We know we want to guarantee this is destroyed so call destroy()" and no wrapper check for if it's already destroyed?

Comment on lines +74 to +75
defined(sphericalHarmonicCoefficients) &&
defined(sphericalHarmonicCoefficients[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a slightly weird syntax so completely optional but Optional Chaining does work on array access

Suggested change
defined(sphericalHarmonicCoefficients) &&
defined(sphericalHarmonicCoefficients[0])
defined(sphericalHarmonicCoefficients?.[0])

Comment on lines +36 to +39
const sphericalHarmonicCoefficients = defaultValue(
imageBasedLighting.sphericalHarmonicCoefficients,
environmentMapManager.sphericalHarmonicCoefficients,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const sphericalHarmonicCoefficients = defaultValue(
imageBasedLighting.sphericalHarmonicCoefficients,
environmentMapManager.sphericalHarmonicCoefficients,
);
const sphericalHarmonicCoefficients =
imageBasedLighting.sphericalHarmonicCoefficients ??
environmentMapManager.sphericalHarmonicCoefficients;

@@ -263,6 +263,11 @@ Model3DTileContent.prototype.update = function (tileset, frameState) {
: undefined;
}

const tilesetEnvironmentMapManager = tileset.environmentMapManager;
if (model.environmentMapManager !== tilesetClippingPlanes) {
model._environmentMapManager = tilesetEnvironmentMapManager;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be using the actual setter so all that logic runs correctly?

Suggested change
model._environmentMapManager = tilesetEnvironmentMapManager;
model.environmentMapManager = tilesetEnvironmentMapManager;

* @name czm_computeAtmosphereColor
* @glslFunction
*
* @param {czm_rat} primaryRay Ray from the origin to sky fragment to in world coords (low precision)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧀 🐀

Suggested change
* @param {czm_rat} primaryRay Ray from the origin to sky fragment to in world coords (low precision)
* @param {czm_ray} primaryRay Ray from the origin to sky fragment to in world coords (low precision)

@@ -3,7 +3,7 @@ void geometryStage(out ProcessedAttributes attributes)
attributes.positionMC = v_positionMC;
attributes.positionEC = v_positionEC;

#if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) || defined(COMPUTE_POSITION_WC_ATMOSPHERE)
#if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) || defined(COMPUTE_POSITION_WC_ATMOSPHERE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) || defined(COMPUTE_POSITION_WC_ATMOSPHERE)
#if defined(COMPUTE_POSITION_WC_CUSTOM_SHADER) || defined(COMPUTE_POSITION_WC_STYLE) || defined(COMPUTE_POSITION_WC_ATMOSPHERE)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants