Skip to content

refactor: unify multiple EnvBRDF calls#1412

Merged
alandtse merged 2 commits into
community-shaders:devfrom
jiayev:envbrdfhirvonen
Aug 23, 2025
Merged

refactor: unify multiple EnvBRDF calls#1412
alandtse merged 2 commits into
community-shaders:devfrom
jiayev:envbrdfhirvonen

Conversation

@jiayev
Copy link
Copy Markdown
Collaborator

@jiayev jiayev commented Aug 18, 2025

This pull request refactors how the environment BRDF approximation is handled across multiple shader files, centralizing the logic in BRDF.hlsli and introducing a new, more accurate approximation method. The main change is the replacement of hardcoded and duplicated BRDF approximation functions with a unified BRDF::EnvBRDFApprox function, which now supports both the existing Lazarov method and the new Hirvonen method via a compile-time switch. This improves maintainability, consistency, and enables future extensibility.

BRDF approximation refactor and centralization:

  • Added BRDF::EnvBRDFApprox and BRDF::EnvBRDFApproxHirvonen functions to package/Shaders/Common/BRDF.hlsli, allowing selection between Lazarov and Hirvonen methods based on the ENV_BRDF_HIRVONEN macro.
  • Removed local/hardcoded BRDF approximation functions from features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli and features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli, and replaced their usage with calls to the centralized BRDF::EnvBRDFApprox. [1] [2]

Shader code consistency and usage updates:

  • Updated all usages of environment BRDF approximation in DynamicCubemaps, WetnessEffects, Hair, and PBR namespaces to call the new unified BRDF::EnvBRDFApprox function, removing references to the old EnvBRDFApproxLazarov and local variants. [1] [2] [3] [4] [5] [6] [7]

Code maintainability improvements:

  • Added #include "Common/BRDF.hlsli" to affected shader files to ensure access to the centralized BRDF functions. [1] [2]

These changes make the codebase easier to maintain and extend, while also enabling the use of more accurate BRDF approximations for specular reflection.

Summary by CodeRabbit

  • New Features

    • Added a selectable environment BRDF approximation option (Hirvonen/Lazarov) to improve specular evaluation across materials.
  • Refactor

    • Unified environment BRDF lookups across PBR, dynamic cubemaps, hair, and wetness effects.
    • Removed duplicated helper implementations for consistent specular behavior.
  • Bug Fixes

    • Minor adjustments to specular highlights; visual differences may appear with varying roughness and view angle.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 18, 2025

Walkthrough

Adds a unified BRDF environment approximation API BRDF::EnvBRDF (with a Hirvonen/Lazarov compile-time switch) and updates multiple shaders to include Common/BRDF.hlsli, replacing local or legacy EnvBRDFApprox* calls with BRDF::EnvBRDF, removing duplicate helpers.

Changes

Cohort / File(s) Summary
BRDF Core
package/Shaders/Common/BRDF.hlsli
Adds BRDF::EnvBRDFApproxHirvonen(roughness, NdotV) and BRDF::EnvBRDF(roughness, NdotV) wrapper that selects Hirvonen when ENV_BRDF_HIRVONEN is defined, otherwise calls the existing Lazarov implementation.
PBR Call Sites
package/Shaders/Common/PBR.hlsli
Replaces calls to BRDF::EnvBRDFApproxLazarov(...) with BRDF::EnvBRDF(...) at four sites (direct light, indirect lobe weights, two-layer coat, wetness direct spec). No control-flow changes.
Dynamic Cubemaps
features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli
Adds #include "Common/BRDF.hlsli", removes local EnvBRDFApprox, and uses BRDF::EnvBRDF(roughness, NoV) in place of the removed helper.
Hair Specular
features/Hair Specular/Shaders/Hair/Hair.hlsli
In GetHairIndirectSpecularLobeWeights, switches BRDF lookups from BRDF::EnvBRDFApproxLazarov to BRDF::EnvBRDF for primary and secondary lobes.
Wetness Effects
features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli
Adds #include "Common/BRDF.hlsli", removes EnvBRDFApproxWater, and replaces its usage with BRDF::EnvBRDF(roughness, NoV).

Sequence Diagram(s)

sequenceDiagram
  participant Shader as Shader (PBR / Hair / DynamicCubemaps / Wetness)
  participant BRDF as BRDF::EnvBRDF
  participant Hirvonen as EnvBRDFApproxHirvonen
  participant Lazarov as EnvBRDFApproxLazarov

  Shader->>BRDF: EnvBRDF(roughness, NdotV)
  alt ENV_BRDF_HIRVONEN defined
    BRDF->>Hirvonen: compute approximation
    Hirvonen-->>BRDF: float2(k1,k0)
  else
    BRDF->>Lazarov: compute approximation
    Lazarov-->>BRDF: float2(k1,k0)
  end
  BRDF-->>Shader: float2(k1,k0)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Pentalimbed
  • alandtse
  • doodlum

Poem

A rabbit nudged the shader night, 🐇
Folded two BRDFs into one light.
Hirvonen or Lazarov at compile-time's call,
Cubes, hair, and raindrops answer the fall.
Now code hops tidy — shimmer for all. ✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Copy Markdown

Using provided base ref: dcdbaf3
Using base ref: dcdbaf3
Base commit date: 2025-08-17T20:52:19+01:00 (Sunday, August 17, 2025 08:52 PM)
No actionable suggestions for changed features.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
package/Shaders/Common/BRDF.hlsli (2)

230-235: Hirvonen rational terms: guard denominators to avoid NaNs at extreme inputs

At very low/high roughness or grazing angles, the rational fits’ denominators can approach zero. Add a small epsilon guard to prevent NaNs/infs propagating through the frame.

Apply this diff:

-        float k0 = dot(float2(1.0, a), mul(m0, float2(1.0, c)));
-        k0 /= dot(float3(1.0, a, a3), mul(m1, float3(1.0, c, c3)));
-        float k1 = dot(float2(1.0, a), mul(m2, float2(1.0, c)));
-        k1 /= dot(float3(1.0, a, a3), mul(m3, float3(1.0, c2, c3)));
+        float k0_num = dot(float2(1.0, a), mul(m0, float2(1.0, c)));
+        float k0_den = dot(float3(1.0, a, a3), mul(m1, float3(1.0, c, c3)));
+        float k1_num = dot(float2(1.0, a), mul(m2, float2(1.0, c)));
+        float k1_den = dot(float3(1.0, a, a3), mul(m3, float3(1.0, c2, c3)));
+        float k0 = k0_num / max(k0_den, EPSILON_DIVISION);
+        float k1 = k1_num / max(k1_den, EPSILON_DIVISION);

215-217: Clarify return semantics (A/B mapping) for maintainability

Please document that the function returns float2(A, B) to be consumed as F0*A + B. This avoids confusion across callsites and between Lazarov vs Hirvonen fits.

Apply this diff:

-    float2 EnvBRDFApproxHirvonen(float roughness, float NdotV)
+    // Returns float2(A, B) such that preintegrated env BRDF ≈ F0 * A + B
+    float2 EnvBRDFApproxHirvonen(float roughness, float NdotV)
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dcdbaf3 and e8ddf52.

📒 Files selected for processing (5)
  • features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli (2 hunks)
  • features/Hair Specular/Shaders/Hair/Hair.hlsli (1 hunks)
  • features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli (2 hunks)
  • package/Shaders/Common/BRDF.hlsli (1 hunks)
  • package/Shaders/Common/PBR.hlsli (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • features/Hair Specular/Shaders/Hair/Hair.hlsli
  • package/Shaders/Common/BRDF.hlsli
  • features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli
  • package/Shaders/Common/PBR.hlsli
  • features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli
features/*/Shaders/**/*.{hlsl,hlsli,fx,fxh}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

features/*/Shaders/**/*.{hlsl,hlsli,fx,fxh}: Place all feature shaders under features/YourFeature/Shaders/
Avoid GPU register/buffer conflicts in HLSL; verify register usage (e.g., with hlslkit buffer scanning)

Files:

  • features/Hair Specular/Shaders/Hair/Hair.hlsli
  • features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli
  • features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli
🧠 Learnings (1)
📚 Learning: 2025-08-03T18:37:19.690Z
Learnt from: jiayev
PR: doodlum/skyrim-community-shaders#0
File: :0-0
Timestamp: 2025-08-03T18:37:19.690Z
Learning: ISReflectionsRayTracing.hlsl and ISWorldMap.hlsl in the skyrim-community-shaders repository are image-space post-processing shaders that perform color sampling and blending operations that need proper linear color space handling for the linear lighting system. ISReflectionsRayTracing handles screen-space reflections and should use conditional Color::IrradianceToLinear/Gamma conversions similar to ISCompositeLensFlareVolumetricLighting.hlsl. ISWorldMap performs 7x7 color accumulation that should be done in linear space similar to the pattern used in ISSAOComposite.hlsl.

Applied to files:

  • features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build plugin and addons
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
🔇 Additional comments (8)
package/Shaders/Common/BRDF.hlsli (1)

238-245: No legacy BRDF helpers remain

Ran pattern searches across all shader files and found zero occurrences of EnvBRDFApproxLazarov or EnvBRDFApproxWater. Migration off the old helpers is complete—ready to merge.

features/Hair Specular/Shaders/Hair/Hair.hlsli (1)

258-260: Good switch to unified BRDF API

Replacing EnvBRDFApproxLazarov with BRDF::EnvBRDFApprox aligns hair with the new centralized implementation and compile-time selection. No behavioral issues spotted around the call.

package/Shaders/Common/PBR.hlsli (2)

337-337: Direct-light energy compensation now follows unified BRDF approx

The change to BRDF::EnvBRDFApprox keeps the F0*A + B contract intact; downstream scaling remains valid. Looks good.


435-436: Consistent migration of indirect/coat/wetness lookups to unified API

All affected callsites now use BRDF::EnvBRDFApprox with the same F0*A + B usage pattern. This de-duplicates logic and centralizes the approximation choice. Good change.

Also applies to: 445-447, 487-488

features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli (2)

1-1: Include addition is correct

Including Common/BRDF.hlsli enables access to the centralized env BRDF functions without introducing register conflicts. Good.


150-150: Unified specular BRDF in wetness path

Switching to BRDF::EnvBRDFApprox and keeping the local S term for roughness-dependent Fresnel preserves the F0*A + B pattern. Change looks correct.

features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli (2)

1-1: Correct dependency inclusion

Adding BRDF.hlsli provides the unified EnvBRDFApprox without altering existing resource bindings. Looks good.


80-80: Correct API migration for env BRDF

Using BRDF::EnvBRDFApprox(roughness, NoV) here aligns the dynamic cubemap path with the centralized implementation and the compile-time Hirvonen/Lazarov switch.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 18, 2025

✅ A pre-release build is available for this PR:
Download

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
package/Shaders/Common/BRDF.hlsli (2)

215-237: Hirvonen approx: add domain clamps and denom guards to prevent NaNs at edges

Inputs outside [0,1] or near-zero denominators can cause instability. Clamp inputs and guard the rational function denominators.

Apply this diff:

 float2 EnvBRDFApproxHirvonen(float roughness, float NdotV)
 {
   const float2x2 m0 = float2x2(0.99044, -1.28514, 1.29678, -0.755907);
   const float3x3 m1 = float3x3(1, 2.92338, 59.4188, 20.3225, -27.0302, 222.592, 121.563, 626.13, 316.627);
   const float2x2 m2 = float2x2(0.0365463, 3.32707, 9.0632, -9.04756);
   const float3x3 m3 = float3x3(1, 3.59685, -1.36772, 9.04401, -16.3174, 9.22949, 5.56589, 19.7886, -20.2123);
 
+  // Clamp inputs to expected domains for numerical stability
+  roughness = saturate(roughness);
+  NdotV     = saturate(NdotV);
+
   float a = roughness * roughness;
   float a2 = a * a;
   float a3 = a * a2;
   float c = NdotV;
   float c2 = c * c;
   float c3 = c * c2;
 
-  float k0 = dot(float2(1.0, a), mul(m0, float2(1.0, c)));
-  k0 /= dot(float3(1.0, a, a3), mul(m1, float3(1.0, c, c3)));
-  float k1 = dot(float2(1.0, a), mul(m2, float2(1.0, c)));
-  k1 /= dot(float3(1.0, a, a3), mul(m3, float3(1.0, c2, c3)));
+  float k0_num = dot(float2(1.0, a), mul(m0, float2(1.0, c)));
+  float k0_den = dot(float3(1.0, a, a3), mul(m1, float3(1.0, c, c3)));
+  float k0 = k0_num / max(k0_den, EPSILON_DIVISION);
+  float k1_num = dot(float2(1.0, a), mul(m2, float2(1.0, c)));
+  float k1_den = dot(float3(1.0, a, a3), mul(m3, float3(1.0, c2, c3)));
+  float k1 = k1_num / max(k1_den, EPSILON_DIVISION);
 
   return float2(k1, k0);
 }

238-245: Tighten preprocessor style and document return semantics of EnvBRDF

Cosmetic but safer across toolchains: remove spaces after ‘#’. Also add a short comment clarifying that the return is (A, B) with spec ≈ F0*A + B.

Apply this diff:

-float2 EnvBRDF(float roughness, float NdotV)
-{
-#   if defined(ENV_BRDF_HIRVONEN)
-    return EnvBRDFApproxHirvonen(roughness, NdotV);
-#   else
-    return EnvBRDFApproxLazarov(roughness, NdotV);
-#   endif
-}
+// Returns float2(A, B) such that the integrated specular term is approximated as:
+//   spec ≈ F0 * A + B
+// Inputs are expected in [0,1]: roughness ∈ [0,1], NdotV ∈ [0,1].
+float2 EnvBRDF(float roughness, float NdotV)
+{
+#if defined(ENV_BRDF_HIRVONEN)
+    return EnvBRDFApproxHirvonen(roughness, NdotV);
+#else
+    return EnvBRDFApproxLazarov(roughness, NdotV);
+#endif
+}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e8ddf52 and 69e2d1b.

📒 Files selected for processing (5)
  • features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli (2 hunks)
  • features/Hair Specular/Shaders/Hair/Hair.hlsli (1 hunks)
  • features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli (2 hunks)
  • package/Shaders/Common/BRDF.hlsli (1 hunks)
  • package/Shaders/Common/PBR.hlsli (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • features/Dynamic Cubemaps/Shaders/DynamicCubemaps/DynamicCubemaps.hlsli
  • features/Hair Specular/Shaders/Hair/Hair.hlsli
  • package/Shaders/Common/PBR.hlsli
  • features/Wetness Effects/Shaders/WetnessEffects/WetnessEffects.hlsli
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,cxx,cc,c,h,hpp,hxx,hlsl,hlsli,fx,fxh,py}

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Do not include TODO/FIXME placeholders; provide complete, working solutions

Files:

  • package/Shaders/Common/BRDF.hlsli
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Validate shader compilation (VR, .github/configs/shader-validation-vr.yaml)
  • GitHub Check: Build plugin and addons
  • GitHub Check: Validate shader compilation (Flatrim, .github/configs/shader-validation.yaml)
🔇 Additional comments (1)
package/Shaders/Common/BRDF.hlsli (1)

215-237: No EnvBRDF call-sites found – AB ordering is safe

I searched every .hlsl, .hlsli, .fx and .fxh file for:

  • any call to EnvBRDF(…)
  • any EnvBRDFApprox* definitions outside package/Shaders/Common/BRDF.hlsli

Results:

  • Zero matches for EnvBRDF(
  • No stray EnvBRDFApprox* definitions

Since this function isn’t referenced elsewhere, there’s no risk of swapping the scale (AB.x) and bias (AB.y) terms at call sites.

@alandtse
Copy link
Copy Markdown
Collaborator

As described this should have been a feat or even two PRs. This isn't just a refactor. This is a feat right?

@jiayev
Copy link
Copy Markdown
Collaborator Author

jiayev commented Aug 23, 2025

As described this should have been a feat or even two PRs. This isn't just a refactor. This is a feat right?

Not really I guess, the added one is just a heavier substitute of what we already have (Lazarov) that does the same thing slightly better. It's more of a refactor that they got unified under same EnvBRDF() for easier use. Just more modular.
Also I removed duplicate codes from DynamicCubemaps and WetnessEffects so I think it's more of a refactor focused on one thing (EnvBRDF).
But if you think it's better to be two PRs I could surely make it.

@alandtse
Copy link
Copy Markdown
Collaborator

I guess the issue is the commit title has two things in it. What is most important in this commit? Like if we're looking at this one month later, what do we need to know about this commit? Imagine you're trying to understand the full history and really don't want to open the commit to see what changed.

@jiayev jiayev changed the title refactor: more unified envbrdf, add hirvonen's approx for future use refactor: unify multiple EnvBRDF calls Aug 23, 2025
@jiayev
Copy link
Copy Markdown
Collaborator Author

jiayev commented Aug 23, 2025

@alandtse ok i've also retitled this one.

@alandtse alandtse merged commit 24278c8 into community-shaders:dev Aug 23, 2025
17 checks passed
@jiayev jiayev deleted the envbrdfhirvonen branch August 23, 2025 08:05
sicsix pushed a commit to sicsix/skyrim-community-shaders that referenced this pull request Sep 1, 2025
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.

3 participants