Skip to content

Commit 5661365

Browse files
authored
improved the dispersion computation (#9544)
- Split the function so that the 3-components loop is more apparent. - Factored out the parts of the computation that don't depend on the index of refraction, so they're only computed once. - Moved the lod-from-roughness computation out of the loop as well. It does depend on the IOR, but so little that it's not worth it. The most important result of this change is that the code is now in good shape if we wanted to change the implementation of dispersion for these cases (for e.g.) : - do the computation in the XYZ space - use more than 3 wavelengths samples - or use a stochastic approach, trading banding for noise RDIFF_BRANCH=ma/better-dispersion
1 parent bcb9d25 commit 5661365

File tree

1 file changed

+70
-65
lines changed

1 file changed

+70
-65
lines changed

shaders/src/surface_light_indirect.fs

Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ float perceptualRoughnessToLod(float perceptualRoughness) {
119119
return frameUniforms.iblRoughnessOneLevel * perceptualRoughness * (2.0 - perceptualRoughness);
120120
}
121121

122-
vec3 prefilteredRadiance(const vec3 r, float perceptualRoughness) {
123-
float lod = perceptualRoughnessToLod(perceptualRoughness);
122+
vec3 prefilteredRadiance(const vec3 r, float lod) {
124123
return decodeDataForIBL(textureLod(sampler0_iblSpecular, r, lod));
125124
}
126125

@@ -391,7 +390,8 @@ void evaluateSheenIBL(const PixelParams pixel, float diffuseAO,
391390
vec3 reflectance = pixel.sheenDFG * pixel.sheenColor;
392391
reflectance *= specularAO(shading_NoV, diffuseAO, pixel.sheenRoughness, cache);
393392

394-
Fr += reflectance * prefilteredRadiance(shading_reflected, pixel.sheenPerceptualRoughness);
393+
Fr += reflectance * prefilteredRadiance(shading_reflected,
394+
perceptualRoughnessToLod(pixel.sheenPerceptualRoughness));
395395
#endif
396396
#endif
397397
}
@@ -422,7 +422,8 @@ void evaluateClearCoatIBL(const PixelParams pixel, float diffuseAO,
422422

423423
// TODO: Should we apply specularAO to the attenuation as well?
424424
float specularAO = specularAO(clearCoatNoV, diffuseAO, pixel.clearCoatRoughness, cache);
425-
Fr += prefilteredRadiance(clearCoatR, pixel.clearCoatPerceptualRoughness) * (specularAO * Fc);
425+
Fr += prefilteredRadiance(clearCoatR,
426+
perceptualRoughnessToLod(pixel.clearCoatPerceptualRoughness)) * (specularAO * Fc);
426427
}
427428
#endif
428429
}
@@ -448,7 +449,7 @@ struct Refraction {
448449
};
449450

450451
void refractionSolidSphere(float etaIR, float etaRI, float thickness,
451-
const vec3 n, vec3 r, out Refraction ray) {
452+
const vec3 n, vec3 r, out Refraction ray) {
452453
r = refract(r, n, etaIR);
453454
float NoR = dot(n, r);
454455
float d = thickness * -NoR;
@@ -459,7 +460,7 @@ void refractionSolidSphere(float etaIR, float etaRI, float thickness,
459460
}
460461

461462
void refractionSolidBox(float etaIR, float thickness,
462-
const vec3 n, vec3 r, out Refraction ray) {
463+
const vec3 n, vec3 r, out Refraction ray) {
463464
vec3 rr = refract(r, n, etaIR);
464465
float NoR = dot(n, rr);
465466
float d = thickness / max(-NoR, 0.001);
@@ -474,7 +475,7 @@ void refractionSolidBox(float etaIR, float thickness,
474475
}
475476

476477
void refractionThinSphere(float etaIR, float uThickness,
477-
const vec3 n, vec3 r, out Refraction ray) {
478+
const vec3 n, vec3 r, out Refraction ray) {
478479
float d = 0.0;
479480
#if defined(MATERIAL_HAS_MICRO_THICKNESS)
480481
// note: we need the refracted ray to calculate the distance traveled
@@ -490,90 +491,94 @@ void refractionThinSphere(float etaIR, float uThickness,
490491
ray.d = d;
491492
}
492493

493-
vec3 evaluateRefraction(
494-
const PixelParams pixel,
495-
const vec3 n0, vec3 E) {
494+
vec3 evaluateRefraction(const PixelParams pixel, const vec3 n0, const float lod,
495+
const float etaIR, const float etaRI) {
496496

497-
#if REFRACTION_TYPE == REFRACTION_TYPE_THIN
498-
// For thin surfaces, the light will bounce off at the second interface in the direction of
499-
// the reflection, effectively adding to the specular, but this process will repeat itself.
500-
// Each time the ray exits the surface on the front side after the first bounce,
501-
// it's multiplied by E^2, and we get: E + E(1-E)^2 + E^3(1-E)^2 + ...
502-
// This infinite series converges and is easy to simplify.
503-
// Note: we calculate these bounces only on a single component,
504-
// since it's a fairly subtle effect.
505-
E *= 1.0 + pixel.transmission * (1.0 - E.g) / (1.0 + E.g);
506-
#endif
507-
508-
vec3 Ft;
497+
Refraction ray;
509498

510-
#if defined(MATERIAL_HAS_DISPERSION) && (REFRACTION_TYPE == REFRACTION_TYPE_SOLID)
511-
for (int i = 0; i < 3; i++) {
512-
float etaIR = pixel.etaIR[i];
513-
float etaRI = pixel.etaRI[i];
499+
#if REFRACTION_TYPE == REFRACTION_TYPE_SOLID
500+
refractionSolidSphere(etaIR, etaRI, pixel.thickness, n0, -shading_view, ray);
501+
#elif REFRACTION_TYPE == REFRACTION_TYPE_THIN
502+
refractionThinSphere(etaIR, pixel.uThickness, n0, -shading_view, ray);
514503
#else
515-
float etaIR = pixel.etaIR;
516-
float etaRI = pixel.etaRI;
504+
# error invalid REFRACTION_TYPE
517505
#endif
518506

519-
Refraction ray;
520-
521-
#if REFRACTION_TYPE == REFRACTION_TYPE_SOLID
522-
refractionSolidSphere(etaIR, etaRI, pixel.thickness, n0, -shading_view, ray);
523-
#elif REFRACTION_TYPE == REFRACTION_TYPE_THIN
524-
refractionThinSphere(etaIR, pixel.uThickness, n0, -shading_view, ray);
507+
/* sample the cubemap or screen-space */
508+
#if REFRACTION_MODE == REFRACTION_MODE_CUBEMAP
509+
// when reading from the cubemap, we are not pre-exposed so we apply iblLuminance
510+
// which is not the case when we'll read from the screen-space buffer
511+
vec3 t = prefilteredRadiance(ray.direction, lod) * frameUniforms.iblLuminance;
525512
#else
526-
#error invalid REFRACTION_TYPE
513+
// compute the point where the ray exits the medium, if needed
514+
highp vec4 p = mulMat4x4Float3(getClipFromWorldMatrix(), ray.position);
515+
vec2 uv = uvToRenderTargetUV(p.xy * (0.5 / p.w) + 0.5);
516+
vec3 t = textureLod(sampler0_ssr, vec3(uv, 0.0), lod).rgb;
527517
#endif
528518

529-
// compute transmission T
519+
// apply absorption
530520
#if defined(MATERIAL_HAS_ABSORPTION)
531-
vec3 T = min(vec3(1.0), exp(-pixel.absorption * ray.d));
521+
// compute transmission T
522+
vec3 T = saturate(exp(-pixel.absorption * ray.d));
523+
t *= T;
532524
#endif
533525

534-
// Roughness remapping so that an IOR of 1.0 means no microfacet refraction and an IOR
535-
// of 1.5 has full microfacet refraction
536-
float perceptualRoughness = mix(pixel.perceptualRoughnessUnclamped, 0.0,
537-
saturate(etaIR * 3.0 - 2.0));
538-
539-
/* sample the cubemap or screen-space */
540-
#if REFRACTION_MODE == REFRACTION_MODE_CUBEMAP
541-
// when reading from the cubemap, we are not pre-exposed so we apply iblLuminance
542-
// which is not the case when we'll read from the screen-space buffer
543-
vec3 t = prefilteredRadiance(ray.direction, perceptualRoughness) * frameUniforms.iblLuminance;
544-
#else
545-
// compute the point where the ray exits the medium, if needed
546-
vec4 p = vec4(getClipFromWorldMatrix() * vec4(ray.position, 1.0));
547-
p.xy = uvToRenderTargetUV(p.xy * (0.5 / p.w) + 0.5);
526+
return t;
527+
}
548528

549-
// distance to camera plane
550-
const float invLog2sqrt5 = 0.8614;
551-
float lod = max(0.0, (2.0 * log2(perceptualRoughness) + frameUniforms.refractionLodOffset) * invLog2sqrt5);
552-
vec3 t = textureLod(sampler0_ssr, vec3(p.xy, 0.0), lod).rgb;
553-
#endif
529+
vec3 evaluateRefraction(const PixelParams pixel, const vec3 n0, vec3 E) {
530+
vec3 Ft;
554531

555-
// base color changes the amount of light passing through the boundary
556-
t *= pixel.diffuseColor;
532+
// Note: We use the average IOR for the roughness lod calculation.
557533

558-
// fresnel from the first interface
559-
t *= 1.0 - E;
534+
// Roughness remapping so that an IOR of 1.0 means no microfacet refraction and an IOR
535+
// of 1.5 has full microfacet refraction
536+
#if defined(MATERIAL_HAS_DISPERSION) && (REFRACTION_TYPE == REFRACTION_TYPE_SOLID)
537+
float perceptualRoughness = mix(pixel.perceptualRoughnessUnclamped, 0.0, saturate(pixel.etaIR[1] * 3.0 - 2.0));
538+
#else
539+
float perceptualRoughness = mix(pixel.perceptualRoughnessUnclamped, 0.0, saturate(pixel.etaIR * 3.0 - 2.0));
540+
#endif
560541

561-
// apply absorption
562-
#if defined(MATERIAL_HAS_ABSORPTION)
563-
t *= T;
542+
#if REFRACTION_MODE == REFRACTION_MODE_CUBEMAP
543+
float lod = perceptualRoughnessToLod(perceptualRoughness);
544+
#else
545+
// distance to camera plane
546+
const float invLog2sqrt5 = 0.8614;
547+
float lod = max(0.0, (2.0 * log2(perceptualRoughness) + frameUniforms.refractionLodOffset) * invLog2sqrt5);
564548
#endif
565549

566550
#if defined(MATERIAL_HAS_DISPERSION) && (REFRACTION_TYPE == REFRACTION_TYPE_SOLID)
551+
for (int i = 0; i < 3; i++) {
552+
vec3 t = evaluateRefraction(pixel, n0, lod, pixel.etaIR[i], pixel.etaRI[i]);
567553
Ft[i] = t[i];
568554
}
569555
#else
570-
Ft = t;
556+
Ft = evaluateRefraction(pixel, n0, lod, pixel.etaIR, pixel.etaRI);
557+
#endif
558+
559+
560+
#if REFRACTION_TYPE == REFRACTION_TYPE_THIN
561+
// For thin surfaces, the light will bounce off at the second interface in the direction of
562+
// the reflection, effectively adding to the specular, but this process will repeat itself.
563+
// Each time the ray exits the surface on the front side after the first bounce,
564+
// it's multiplied by E^2, and we get: E + E(1-E)^2 + E^3(1-E)^2 + ...
565+
// This infinite series converges and is easy to simplify.
566+
// Note: we calculate these bounces only on a single component,
567+
// since it's a fairly subtle effect.
568+
E *= 1.0 + pixel.transmission * (1.0 - E.g) / (1.0 + E.g);
571569
#endif
572570

571+
// fresnel from the first interface
572+
Ft *= 1.0 - E;
573+
574+
// base color changes the amount of light passing through the boundary
575+
Ft *= pixel.diffuseColor;
576+
573577
return Ft;
574578
}
575579
#endif
576580

581+
577582
void evaluateIBL(const MaterialInputs material, const PixelParams pixel, inout vec3 color) {
578583
// specular layer
579584
vec3 Fr = vec3(0.0);
@@ -615,7 +620,7 @@ void evaluateIBL(const MaterialInputs material, const PixelParams pixel, inout v
615620
vec3 E = specularDFG(pixel);
616621
if (ssrFr.a < 1.0) { // prevent reading the IBL if possible
617622
vec3 r = getReflectedVector(pixel, shading_normal);
618-
Fr = E * prefilteredRadiance(r, pixel.perceptualRoughness);
623+
Fr = E * prefilteredRadiance(r, perceptualRoughnessToLod(pixel.perceptualRoughness));
619624
}
620625
#elif IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING
621626
vec3 E = vec3(0.0); // TODO: fix for importance sampling

0 commit comments

Comments
 (0)