Skip to content

Commit d58f672

Browse files
Half angle fresnel (playcanvas#3172)
* global fresnel no longer affects reflections + f0 directly used by reflections * call envBrdf for all reflection types * spacing * all reflection types, when using metalness and f0, use envBrdf * Update examples/graphics/material-anisotropic.html Co-authored-by: Will Eastcott <will@playcanvas.com> * switch to GGX cubemap filtering * switch punctual lights to half angle fresnel * remove debug tmp code * lint Co-authored-by: Will Eastcott <will@playcanvas.com>
1 parent a00392b commit d58f672

12 files changed

+79
-101
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
vec3 combineColorCC() {
2-
return combineColor() + (ccSpecularLight * ccSpecularity + ccReflection.rgb * ccReflection.a);
2+
return combineColor() + (ccSpecularLight + ccReflection.rgb * ccReflection.a);
33
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
vec3 combineColor() {
2-
return mix(dAlbedo * dDiffuseLight, dSpecularLight, dSpecularity) + dReflection.rgb * dReflection.a;
2+
// NB energy conservation has changed with half-angle fresnel
3+
return dAlbedo * dDiffuseLight + dSpecularLight + dReflection.rgb * dReflection.a;
34
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
vec3 combineColor() {
2-
return dAlbedo * dDiffuseLight + (dSpecularLight * dSpecularity) + dReflection.rgb * dReflection.a;
2+
return dAlbedo * dDiffuseLight + dSpecularLight + dReflection.rgb * dReflection.a;
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
vec3 combineColor() {
2-
return dAlbedo * dDiffuseLight + dSpecularLight * dSpecularity;
2+
return dAlbedo * dDiffuseLight + dSpecularLight;
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
uniform vec3 material_ambient;
22

33
vec3 combineColor() {
4-
return (dDiffuseLight - light_globalAmbient) * dAlbedo + dSpecularLight * dSpecularity + material_ambient * light_globalAmbient;
4+
return (dDiffuseLight - light_globalAmbient) * dAlbedo + dSpecularLight + material_ambient * light_globalAmbient;
55
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
vec3 combineColor() {
2-
return mix(dAlbedo * dDiffuseLight + dSpecularLight * dSpecularity, dReflection.rgb, dReflection.a);
2+
return mix(dAlbedo * dDiffuseLight + dSpecularLight, dReflection.rgb, dReflection.a);
33
}
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
// Schlick's approximation
2-
uniform float material_fresnelFactor; // unused
32

4-
void getFresnel() {
5-
float fresnel = 1.0 - max(dot(dNormalW, dViewDirW), 0.0);
6-
float fresnel2 = fresnel * fresnel;
7-
fresnel *= fresnel2 * fresnel2;
8-
fresnel *= dGlossiness * dGlossiness;
9-
dSpecularity = dSpecularity + (1.0 - dSpecularity) * fresnel;
3+
vec3 calcFresnel(float cosTheta, vec3 F0)
4+
{
5+
return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
6+
}
107

11-
#ifdef CLEARCOAT
12-
fresnel = 1.0 - max(dot(ccNormalW, dViewDirW), 0.0);
13-
fresnel2 = fresnel * fresnel;
8+
vec3 calcFresnel(float cosTheta, vec3 F0, float glossiness)
9+
{
10+
float fresnel = 1.0 - cosTheta;
11+
float fresnel2 = fresnel * fresnel;
1412
fresnel *= fresnel2 * fresnel2;
15-
fresnel *= ccGlossiness * ccGlossiness;
16-
ccSpecularity = ccSpecularity + (1.0 - ccSpecularity) * fresnel;
17-
#endif
13+
fresnel *= glossiness * glossiness;
14+
return F0 + (1.0 - F0) * fresnel;
1815
}

src/graphics/program-lib/chunks/lightSpecularAnisoGGX.frag

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Anisotropic GGX
2-
float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
2+
3+
vec3 calcLightSpecular(float tGlossiness, vec3 tNormalW, vec3 F0, out vec3 tFresnel) {
34
float PI = 3.141592653589793;
45
float roughness = max((1.0 - tGlossiness) * (1.0 - tGlossiness), 0.001);
56
float anisotropy = material_anisotropy * roughness;
@@ -30,13 +31,18 @@ float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
3031
float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL));
3132
float G = 0.5 / (lambdaV + lambdaL);
3233

33-
return D * G;
34+
vec3 F = calcFresnel(NoH, F0);
35+
36+
tFresnel = F;
37+
38+
return D * G * F;
3439
}
3540

36-
float getLightSpecular() {
37-
return calcLightSpecular(dGlossiness, dNormalW);
41+
vec3 getLightSpecular() {
42+
return calcLightSpecular(dGlossiness, dNormalW, dSpecularity, dLightFresnel);
3843
}
3944

40-
float getLightSpecularCC() {
41-
return calcLightSpecular(ccGlossiness, ccNormalW);
45+
vec3 getLightSpecularCC() {
46+
47+
return calcLightSpecular(ccGlossiness, ccNormalW, vec3(ccSpecularity), ccLightFresnel);
4248
}
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Energy-conserving (hopefully) Blinn-Phong
2-
float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
2+
vec3 calcLightSpecular(float tGlossiness, vec3 tNormalW, vec3 F0, out vec3 tFresnel) {
33
vec3 h = normalize( -dLightDirNormW + dViewDirW );
44
float nh = max( dot( h, tNormalW ), 0.0 );
55

@@ -9,13 +9,17 @@ float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
99
// Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little
1010
specPow = max(specPow, 0.0001);
1111

12-
return pow(nh, specPow) * (specPow + 2.0) / 8.0;
12+
vec3 F = calcFresnel(nh, F0);
13+
14+
tFresnel = F;
15+
16+
return F * pow(nh, specPow) * (specPow + 2.0) / 8.0;
1317
}
1418

15-
float getLightSpecular() {
16-
return calcLightSpecular(dGlossiness, dNormalW);
19+
vec3 getLightSpecular() {
20+
return calcLightSpecular(dGlossiness, dNormalW, dSpecularity, dLightFresnel);
1721
}
1822

19-
float getLightSpecularCC() {
20-
return calcLightSpecular(ccGlossiness, ccNormalW);
23+
vec3 getLightSpecularCC() {
24+
return calcLightSpecular(ccGlossiness, ccNormalW, vec3(ccSpecularity), ccLightFresnel);
2125
}
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
float calcLightSpecular(float tGlossiness, vec3 tReflDirW) {
1+
vec3 calcLightSpecular(float tGlossiness, vec3 tReflDirW, vec3 F0, out vec3 tFresnel) {
22
float specPow = tGlossiness;
33

44
specPow = antiAliasGlossiness(specPow);
55

6+
float rl = max(dot(tReflDirW, -dLightDirNormW), 0.0);
7+
8+
vec3 F = calcFresnel(rl, F0);
9+
610
// Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little
7-
return pow(max(dot(tReflDirW, -dLightDirNormW), 0.0), specPow + 0.0001);
11+
return F * pow(rl, specPow + 0.0001);
812
}
913

10-
float getLightSpecular() {
11-
return calcLightSpecular(dGlossiness, dReflDirW);
14+
vec3 getLightSpecular() {
15+
return calcLightSpecular(dGlossiness, dNormalW, dSpecularity, dLightFresnel);
1216
}
1317

14-
float getLightSpecularCC() {
15-
return calcLightSpecular(ccGlossiness, ccReflDirW);
16-
}
18+
vec3 getLightSpecularCC() {
19+
return calcLightSpecular(ccGlossiness, ccNormalW, vec3(ccSpecularity), ccLightFresnel);
20+
}

src/graphics/program-lib/chunks/ltc.frag

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,6 @@ vec2 getLTCLightUV(float tGlossiness, vec3 tNormalW)
125125
return LTC_Uv( tNormalW, dViewDirW, roughness );
126126
}
127127

128-
//used for energy conservation and to modulate specular
129-
vec3 dLTCSpecFres;
130-
#ifdef CLEARCOAT
131-
vec3 ccLTCSpecFres;
132-
#endif
133128
vec3 getLTCLightSpecFres(vec2 uv, vec3 tSpecularity)
134129
{
135130
vec4 t2 = texture2D( areaLightsLutTex2, uv );
@@ -145,11 +140,11 @@ vec3 getLTCLightSpecFres(vec2 uv, vec3 tSpecularity)
145140
void calcLTCLightValues()
146141
{
147142
dLTCUV = getLTCLightUV(dGlossiness, dNormalW);
148-
dLTCSpecFres = getLTCLightSpecFres(dLTCUV, dSpecularityNoFres);
143+
dLightFresnel = getLTCLightSpecFres(dLTCUV, dSpecularity);
149144

150145
#ifdef CLEARCOAT
151146
ccLTCUV = getLTCLightUV(ccGlossiness, ccNormalW);
152-
ccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3(ccSpecularityNoFres));
147+
ccLightFresnel = getLTCLightSpecFres(ccLTCUV, vec3(ccSpecularity));
153148
#endif
154149
}
155150

@@ -413,12 +408,12 @@ float calcRectLightSpecular(vec3 tNormalW, vec2 uv) {
413408
}
414409

415410
float getRectLightSpecular() {
416-
return calcRectLightSpecular(dNormalW, dLTCUV);
411+
return dLightFresnel * calcRectLightSpecular(dNormalW, dLTCUV);
417412
}
418413

419414
#ifdef CLEARCOAT
420415
float getRectLightSpecularCC() {
421-
return calcRectLightSpecular(ccNormalW, ccLTCUV);
416+
return ccLightFresnel * calcRectLightSpecular(ccNormalW, ccLTCUV);
422417
}
423418
#endif
424419

@@ -428,21 +423,21 @@ float calcDiskLightSpecular(vec3 tNormalW, vec2 uv) {
428423
}
429424

430425
float getDiskLightSpecular() {
431-
return calcDiskLightSpecular(dNormalW, dLTCUV);
426+
return dLightFresnel * calcDiskLightSpecular(dNormalW, dLTCUV);
432427
}
433428

434429
#ifdef CLEARCOAT
435430
float getDiskLightSpecularCC() {
436-
return calcDiskLightSpecular(ccNormalW, ccLTCUV);
431+
return ccLightFresnel * calcDiskLightSpecular(ccNormalW, ccLTCUV);
437432
}
438433
#endif
439434

440435
float getSphereLightSpecular() {
441-
return calcDiskLightSpecular(dNormalW, dLTCUV);
436+
return dLightFresnel * calcDiskLightSpecular(dNormalW, dLTCUV);
442437
}
443438

444439
#ifdef CLEARCOAT
445440
float getSphereLightSpecularCC() {
446-
return calcDiskLightSpecular(ccNormalW, ccLTCUV);
441+
return ccLightFresnel * calcDiskLightSpecular(ccNormalW, ccLTCUV);
447442
}
448443
#endif

src/graphics/program-lib/programs/standard.js

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,14 +1283,19 @@ var standard = {
12831283
}
12841284
var useOldAmbient = false;
12851285
if (options.useSpecular) {
1286-
if (lighting) code += options.shadingModel === SPECULAR_PHONG ? chunks.lightSpecularPhongPS : (options.enableGGXSpecular) ? chunks.lightSpecularAnisoGGXPS : chunks.lightSpecularBlinnPS;
1286+
if (lighting) {
1287+
// used for energy conservation and to modulate specular with half-angle fresnel
1288+
code += "vec3 dLightFresnel;\n";
1289+
code += "vec3 ccLightFresnel;\n\n";
1290+
1291+
code += options.shadingModel === SPECULAR_PHONG ? chunks.lightSpecularPhongPS : (options.enableGGXSpecular) ? chunks.lightSpecularAnisoGGXPS : chunks.lightSpecularBlinnPS;
1292+
}
12871293
if (options.sphereMap || cubemapReflection || options.dpAtlas || (options.fresnelModel > 0)) {
12881294
if (options.fresnelModel > 0) {
1289-
if (options.conserveEnergy && !hasAreaLights) {
1290-
// NB if there are area lights, energy conservation is done differently
1291-
code += chunks.combineDiffuseSpecularPS; // this one is correct, others are old stuff
1295+
if (options.conserveEnergy && !options.useMetalness) {
1296+
code += chunks.combineDiffuseSpecularPS; // post half-angle fresnel, this one is for spec-gloss path
12921297
} else {
1293-
code += chunks.combineDiffuseSpecularNoConservePS; // if you don't use environment cubemaps, you may consider this
1298+
code += chunks.combineDiffuseSpecularNoConservePS; // post half-angle fresnel, this one is for metalness-gloss path
12941299
}
12951300
} else {
12961301
code += chunks.combineDiffuseSpecularOldPS;
@@ -1459,17 +1464,7 @@ var standard = {
14591464
code += " getSpecularity();\n";
14601465
if (!getGlossinessCalled) code += " getGlossiness();\n";
14611466

1462-
// this is needed to allow custom area light fresnel calculations
1463-
if (hasAreaLights) {
1464-
code += " #ifdef AREA_LIGHTS\n";
1465-
code += " dSpecularityNoFres = dSpecularity;\n";
1466-
code += " #ifdef CLEARCOAT\n";
1467-
code += " ccSpecularityNoFres = ccSpecularity;\n";
1468-
code += " #endif\n";
1469-
code += " #endif\n";
1470-
}
1471-
1472-
if (!options.useMetalness && options.fresnelModel > 0) code += " getFresnel();\n";
1467+
// NB switched to more correct half-angle fresnel
14731468
}
14741469

14751470
if (addAmbient) {
@@ -1493,14 +1488,9 @@ var standard = {
14931488
code += " addReflection();\n";
14941489
}
14951490

1496-
if (options.useSpecular && options.useMetalness && options.fresnelModel > 0) code += " getFresnel();\n";
1491+
// NB switched to more correct half-angle fresnel
14971492

14981493
if (hasAreaLights) {
1499-
// specular has to be accumulated differently if we want area lights to look correct
1500-
code += " ccReflection.rgb *= ccSpecularity;\n";
1501-
code += " dReflection.rgb *= dSpecularity;\n";
1502-
code += " dSpecularLight *= dSpecularity;\n";
1503-
15041494
code += " float roughness = max((1.0 - dGlossiness) * (1.0 - dGlossiness), 0.001);\n";
15051495
}
15061496

@@ -1650,34 +1640,25 @@ var standard = {
16501640
}
16511641
}
16521642

1643+
// NB specular lighting moved here for legacy energy conservation
1644+
if (options.clearCoat > 0) code += " ccSpecularLight += get" + shapeString + "LightSpecularCC() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1645+
if (options.useSpecular) code += " dSpecularLight += get" + shapeString + "LightSpecular() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1646+
16531647
// non-punctual lights do not mix diffuse lighting into specular attenuation
16541648
if (lightShape !== LIGHTSHAPE_PUNCTUAL) {
1655-
if (options.conserveEnergy && options.useSpecular) {
1656-
code += " dDiffuseLight += mix((dAttenD * dAtten) * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ", vec3(0), dLTCSpecFres);\n";
1649+
if (options.conserveEnergy && options.useSpecular && !options.useMetalness) {
1650+
code += " dDiffuseLight += mix((dAttenD * dAtten) * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ", vec3(0), dLightFresnel * dSpecularity);\n";
16571651
} else {
16581652
code += " dDiffuseLight += (dAttenD * dAtten) * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
16591653
}
16601654
} else {
1661-
if (hasAreaLights && options.conserveEnergy && options.useSpecular) {
1662-
code += " dDiffuseLight += mix(dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ", vec3(0), dSpecularity);\n";
1655+
if (options.conserveEnergy && options.useSpecular && !options.useMetalness) {
1656+
code += " dDiffuseLight += mix(dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ", vec3(0), dLightFresnel * dSpecularity);\n";
16631657
} else {
16641658
code += " dDiffuseLight += dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
16651659
}
16661660
}
16671661

1668-
if (lightShape !== LIGHTSHAPE_PUNCTUAL) {
1669-
if (options.clearCoat > 0) code += " ccSpecularLight += ccLTCSpecFres * get" + shapeString + "LightSpecularCC() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1670-
if (options.useSpecular) code += " dSpecularLight += dLTCSpecFres * get" + shapeString + "LightSpecular() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1671-
} else {
1672-
if (hasAreaLights) {
1673-
// if LTC lights are present, specular must be accumulated with specularity (specularity is pre multiplied by punctual light fresnel)
1674-
if (options.clearCoat > 0) code += " ccSpecularLight += ccSpecularity * getLightSpecularCC() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1675-
if (options.useSpecular) code += " dSpecularLight += dSpecularity * getLightSpecular() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1676-
} else {
1677-
if (options.clearCoat > 0) code += " ccSpecularLight += getLightSpecularCC() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1678-
if (options.useSpecular) code += " dSpecularLight += getLightSpecular() * dAtten * light" + i + "_color" + (usesCookieNow ? " * dAtten3" : "") + ";\n";
1679-
}
1680-
}
16811662

16821663
if (lightType !== LIGHTTYPE_DIRECTIONAL) {
16831664
code += " }\n"; // BRANCH END
@@ -1686,15 +1667,7 @@ var standard = {
16861667
code += "\n";
16871668
}
16881669

1689-
if (hasAreaLights) {
1690-
// specular has to be accumulated differently if we want area lights to look correct
1691-
if (options.clearCoat > 0) {
1692-
code += " ccSpecularity = 1.0;\n";
1693-
}
1694-
if (options.useSpecular) {
1695-
code += " dSpecularity = vec3(1);\n";
1696-
}
1697-
}
1670+
// NB using half-angle fresnel accumulates specular consistenetly accross all punctual, area and image-based lights
16981671

16991672
if ((cubemapReflection || options.sphereMap || options.dpAtlas) && options.refraction) {
17001673
code += " addRefraction();\n";
@@ -1770,7 +1743,6 @@ var standard = {
17701743
if (code.includes("dShadowCoord")) structCode += "vec3 dShadowCoord;\n";
17711744
if (code.includes("dNormalMap")) structCode += "vec3 dNormalMap;\n";
17721745
if (code.includes("dSpecularity")) structCode += "vec3 dSpecularity;\n";
1773-
if (code.includes("dSpecularityNoFres")) structCode += "vec3 dSpecularityNoFres;\n";
17741746
if (code.includes("dUvOffset")) structCode += "vec2 dUvOffset;\n";
17751747
if (code.includes("dGlossiness")) structCode += "float dGlossiness;\n";
17761748
if (code.includes("dAlpha")) structCode += "float dAlpha;\n";
@@ -1784,7 +1756,6 @@ var standard = {
17841756
if (code.includes("ccReflDirW")) structCode += "vec3 ccReflDirW;\n";
17851757
if (code.includes("ccSpecularLight")) structCode += "vec3 ccSpecularLight;\n";
17861758
if (code.includes("ccSpecularity")) structCode += "float ccSpecularity;\n";
1787-
if (code.includes("ccSpecularityNoFres")) structCode += "float ccSpecularityNoFres;\n";
17881759
if (code.includes("ccGlossiness")) structCode += "float ccGlossiness;\n";
17891760

17901761
code = codeBegin + structCode + code;

0 commit comments

Comments
 (0)