diff --git a/src/scene/shader-lib/glsl/chunks/lit/frag/reflectionEnv.js b/src/scene/shader-lib/glsl/chunks/lit/frag/reflectionEnv.js index aa10ba24df8..48d7fc95226 100644 --- a/src/scene/shader-lib/glsl/chunks/lit/frag/reflectionEnv.js +++ b/src/scene/shader-lib/glsl/chunks/lit/frag/reflectionEnv.js @@ -25,32 +25,32 @@ vec3 calcReflection(vec3 reflDir, float gloss) { vec3 dir = cubeMapProject(reflDir) * vec3(-1.0, 1.0, 1.0); vec2 uv = toSphericalUv(dir); - // calculate roughness level + // roughness mip level based on material gloss float level = saturate(1.0 - gloss) * 5.0; - float ilevel = floor(level); - // accessing the shiny (top level) reflection - perform manual mipmap lookup + // screen-space minification level - drives specular anti-aliasing float level2 = shinyMipLevel(uv * atlasSize); - float ilevel2 = floor(level2); + + // the reflection must be at least as blurry as screen-space minification requires, + // otherwise high-curvature / minified surfaces alias badly + level = max(level, level2); + + float ilevel = floor(level); vec2 uv0, uv1; - float weight; if (ilevel == 0.0) { - uv0 = mapShinyUv(uv, ilevel2); - uv1 = mapShinyUv(uv, ilevel2 + 1.0); - weight = level2 - ilevel2; + // sharp reflection: blend the shiny (top mip) level into roughness level 1 + uv0 = mapShinyUv(uv, 0.0); + uv1 = mapRoughnessUv(uv, 1.0); } else { - // accessing rough reflection - just sample the same part twice - uv0 = uv1 = mapRoughnessUv(uv, ilevel); - weight = 0.0; + // blurry reflection: blend two pre-convolved roughness levels + uv0 = mapRoughnessUv(uv, ilevel); + uv1 = mapRoughnessUv(uv, ilevel + 1.0); } vec3 linearA = {reflectionDecode}(texture2D(texture_envAtlas, uv0)); vec3 linearB = {reflectionDecode}(texture2D(texture_envAtlas, uv1)); - vec3 linear0 = mix(linearA, linearB, weight); - vec3 linear1 = {reflectionDecode}(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel + 1.0))); - - return processEnvironment(mix(linear0, linear1, level - ilevel)); + return processEnvironment(mix(linearA, linearB, level - ilevel)); } void addReflection(vec3 reflDir, float gloss) { diff --git a/src/scene/shader-lib/wgsl/chunks/lit/frag/reflectionEnv.js b/src/scene/shader-lib/wgsl/chunks/lit/frag/reflectionEnv.js index a9d3bdc4d46..58ad453011e 100644 --- a/src/scene/shader-lib/wgsl/chunks/lit/frag/reflectionEnv.js +++ b/src/scene/shader-lib/wgsl/chunks/lit/frag/reflectionEnv.js @@ -26,34 +26,33 @@ fn calcReflection(reflDir: vec3f, gloss: f32) -> vec3f { let dir: vec3f = cubeMapProject(reflDir) * vec3f(-1.0, 1.0, 1.0); let uv: vec2f = toSphericalUv(dir); - // calculate roughness level - let level: f32 = saturate(1.0 - gloss) * 5.0; - let ilevel: f32 = floor(level); + // roughness mip level based on material gloss + var level: f32 = saturate(1.0 - gloss) * 5.0; - // accessing the shiny (top level) reflection - perform manual mipmap lookup + // screen-space minification level - drives specular anti-aliasing let level2: f32 = shinyMipLevel(uv * atlasSize); - let ilevel2: f32 = floor(level2); + + // the reflection must be at least as blurry as screen-space minification requires, + // otherwise high-curvature / minified surfaces alias badly + level = max(level, level2); + + let ilevel: f32 = floor(level); var uv0: vec2f; var uv1: vec2f; - var weight: f32; if (ilevel == 0.0) { - uv0 = mapShinyUv(uv, ilevel2); - uv1 = mapShinyUv(uv, ilevel2 + 1.0); - weight = level2 - ilevel2; + // sharp reflection: blend the shiny (top mip) level into roughness level 1 + uv0 = mapShinyUv(uv, 0.0); + uv1 = mapRoughnessUv(uv, 1.0); } else { - // accessing rough reflection - just sample the same part twice + // blurry reflection: blend two pre-convolved roughness levels uv0 = mapRoughnessUv(uv, ilevel); - uv1 = uv0; - weight = 0.0; + uv1 = mapRoughnessUv(uv, ilevel + 1.0); } let linearA: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, uv0)); let linearB: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, uv1)); - let linear0: vec3f = mix(linearA, linearB, weight); - let linear1: vec3f = {reflectionDecode}(textureSample(texture_envAtlas, texture_envAtlasSampler, mapRoughnessUv(uv, ilevel + 1.0))); - - return processEnvironment(mix(linear0, linear1, level - ilevel)); + return processEnvironment(mix(linearA, linearB, level - ilevel)); } fn addReflection(reflDir: vec3f, gloss: f32) {