Skip to content

Commit

Permalink
Material specular color (#4386)
Browse files Browse the repository at this point in the history
* Fresnel refactor, f0 tint and specularity factor.

Added support for KHR_materials_specular, which provides a specular tint (same as specularMap) as well as a specularity factor. The specularity factor isn't a substitute for metalness however, but simply a specular reflection intensity, which is applied to both fresnel and general specularity.

Decoupled the fresnel component from the specularity. This is because fresnel is dependent on each light, and in the case of reflections dependent on the surface normal. Therefore, we should calculate the fresnel factor once per each light source. Currently though, the fresnel factor only applies to reflections, but that's the same behaviour from before, and this should be addressed in a separate ticket.

Metalness modulation (linear interpolation between f0 and albedo) is done in the backend as a separate shader chunk, and metalness is passed to the backend with dMetalness.

Since the specular color, f0, is dependent on the fresnel effect, and the fresnel effect is dependent on a per-light basis, the specular color can't just be applied on top of everything when combining the light together. Instead, the f0 color has to be an argument to each specularity calculation for it to be accurate.

We can also remove the dSpecularityNoFres from the area light calculation as the fresnel function is no longer combined with the f0 color.

* Reverted the GLTF parsing of sheen and ior, as that's supposed to be another PR.

* Fixed specularity factor not being applied.

* Fixed compilation issue with clear coat.

* Revert ior wip code.

* Fixed typo in types.

* Removed the use of useSpecularityFactor boolean

* Update src/graphics/program-lib/chunks/standard/frag/specular.js

Co-authored-by: Will Eastcott <will@playcanvas.com>

* Update src/scene/materials/standard-material.js

Co-authored-by: Will Eastcott <will@playcanvas.com>

* Minor fixes

Replaced the manual gamma correction with the one from gamma2_2.js.
Removed the f0Tint from the material, this can be deduced from the parameters.
Updated the material documentation to be more descriptive of F0 and specularity factor.

* Set specularity factor to default to 1.0.

* Specularity factor wasn't correctly disabled.

* Calculate fresnel for directional lights

This is necessary to not get too much light when using directional lights.
Calculating the half vector is done outside of getLightSpecular, and then passed to the function to prevent multiple evaluations.
Fresnel is calculated using the half vector for lights, and can be enabled by setting the calcFresnel flag to true.

* Forgot to update clustered lights with half dir calculation.

* Fix lint.

* Revert to using the old method for fresnel strength.

* Replaced fresnel balancing function with more correct version.

* Fresnel refactor, f0 tint and specularity factor.

Added support for KHR_materials_specular, which provides a specular tint (same as specularMap) as well as a specularity factor. The specularity factor isn't a substitute for metalness however, but simply a specular reflection intensity, which is applied to both fresnel and general specularity.

Decoupled the fresnel component from the specularity. This is because fresnel is dependent on each light, and in the case of reflections dependent on the surface normal. Therefore, we should calculate the fresnel factor once per each light source. Currently though, the fresnel factor only applies to reflections, but that's the same behaviour from before, and this should be addressed in a separate ticket.

Metalness modulation (linear interpolation between f0 and albedo) is done in the backend as a separate shader chunk, and metalness is passed to the backend with dMetalness.

Since the specular color, f0, is dependent on the fresnel effect, and the fresnel effect is dependent on a per-light basis, the specular color can't just be applied on top of everything when combining the light together. Instead, the f0 color has to be an argument to each specularity calculation for it to be accurate.

We can also remove the dSpecularityNoFres from the area light calculation as the fresnel function is no longer combined with the f0 color.

* Reverted the GLTF parsing of sheen and ior, as that's supposed to be another PR.

* Fixed specularity factor not being applied.

* Fixed compilation issue with clear coat.

* Revert ior wip code.

* Fixed typo in types.

* Removed the use of useSpecularityFactor boolean

* Update src/graphics/program-lib/chunks/standard/frag/specular.js

Co-authored-by: Will Eastcott <will@playcanvas.com>

* Update src/scene/materials/standard-material.js

Co-authored-by: Will Eastcott <will@playcanvas.com>

* Minor fixes

Replaced the manual gamma correction with the one from gamma2_2.js.
Removed the f0Tint from the material, this can be deduced from the parameters.
Updated the material documentation to be more descriptive of F0 and specularity factor.

* Set specularity factor to default to 1.0.

* Specularity factor wasn't correctly disabled.

* Calculate fresnel for directional lights

This is necessary to not get too much light when using directional lights.
Calculating the half vector is done outside of getLightSpecular, and then passed to the function to prevent multiple evaluations.
Fresnel is calculated using the half vector for lights, and can be enabled by setting the calcFresnel flag to true.

* Forgot to update clustered lights with half dir calculation.

* Fix lint.

* Revert to using the old method for fresnel strength.

* Replaced fresnel balancing function with more correct version.

* Fix typo

* Removed the 'energy balancing' combine function.

* Removing redundant normalizations for half vector calculation.

* Removed unnecessary parentheses.

* Missed specularity should be summed together now when we separate the terms.

* Should be no need to have a conserve combine if we calculate the specular and diffuse correctly to begin with.

* Calculate fresnel for clustered lights as well.

If no fresnel is used, simply multiply by specular color.

* Fixed clearcoat having specular weight multiplied in twice.

Also fall back to multiplying specular light with the specularity factor as a cheaper fresnel fallback.

* Revert a bunch of commits that was accidentally included with rebase.

* Fix revert.

* Revert "Fix revert."

This reverts commit fd7d602.

* Rearranged specularity factor and half dir.

Half dir calculation works for multiple directional lights.
SpecularityFactor is applied on all specularity at the end, removing the need to multiply it on a per-light basis.

* Removed the metalness modulate since we multiply the specularity factor on top at the end.

* Repurpose specular map and tint to avoid adding a new texture and tint for metalness specular color.

Replaced the f0 chunk with specularColor, which essentially does the same thing.
Specular now just calls getSpecularColor.
Metalness calls getSpecularColor instead of getF0.
Cleared the f0 related arguments from the standard material.

* Fix shader compilation issue if not using fresnel.

* Set defaults in GLB loader.

* Specular should default to 1, surely, since white specular means no color tint is applied.

* Fix unit test for specular.

* No need to use two switches for specular tint when we can remove the metalness switch.

* Revert defaults.

* Revert the default change, as that actually breaks shading now with metalness.

* Fixed bug with clustered lights and clearcoat.

* Again simplified specular color evaluation.

The metalness and the specular used to do the same thing when getting specular color, so that's been extracted to a separate call in the shader and the specularColor file has been made redundant.

* Fix dSpecularity getting multiplied in twice.

* Revert specular default but set specular to 1 if using metalness.

* Add specularity factor vertex support.

* Cleanup f0 stuff and add arguments for specularity map tiling

* Fix test

* Removed superfluous condition.

* Cleaned up the f0 stuff in the types mixup.

* Added documentation for the specularity factor map stuff.

* Moved comment about not using environment reflections to where it's relevant.

Also realised the reflection alpha is used as an intensity, so we shouldn't multiply it with the specularity factor.

Co-authored-by: Will Eastcott <will@playcanvas.com>
  • Loading branch information
GSterbrant and willeastcott committed Jul 8, 2022
1 parent 755ec1e commit 5643813
Show file tree
Hide file tree
Showing 21 changed files with 274 additions and 200 deletions.
6 changes: 4 additions & 2 deletions src/graphics/program-lib/chunks/chunks.js
Expand Up @@ -27,7 +27,6 @@ import clusteredLightPS from './lit/frag/clusteredLight.js';
import combineClearCoatPS from './lit/frag/combineClearCoat.js';
import combineDiffusePS from './lit/frag/combineDiffuse.js';
import combineDiffuseSpecularPS from './lit/frag/combineDiffuseSpecular.js';
import combineDiffuseSpecularNoConservePS from './lit/frag/combineDiffuseSpecularNoConserve.js';
import combineDiffuseSpecularNoReflPS from './lit/frag/combineDiffuseSpecularNoRefl.js';
import combineDiffuseSpecularNoReflSeparateAmbientPS from './lit/frag/combineDiffuseSpecularNoReflSeparateAmbient.js';
import combineDiffuseSpecularOldPS from './lit/frag/combineDiffuseSpecularOld.js';
Expand Down Expand Up @@ -78,6 +77,7 @@ import lightSpecularPhongPS from './lit/frag/lightSpecularPhong.js';
import ltc from './lit/frag/ltc.js';
import metalnessPS from './standard/frag/metalness.js';
import msdfPS from './common/frag/msdf.js';
import metalnessModulatePS from './lit/frag/metalnessModulate.js';
import msdfVS from './common/vert/msdf.js';
import normalVS from './lit/vert/normal.js';
import normalDetailMapPS from './standard/frag/normalDetailMap.js';
Expand Down Expand Up @@ -164,6 +164,7 @@ import skyboxEnvPS from './skybox/frag/skyboxEnv.js';
import skyboxHDRPS from './skybox/frag/skyboxHDR.js';
import skyboxVS from './skybox/vert/skybox.js';
import specularPS from './standard/frag/specular.js';
import specularityFactorPS from './standard/frag/specularityFactor.js';
import spotPS from './lit/frag/spot.js';
import startPS from './lit/frag/start.js';
import startVS from './lit/vert/start.js';
Expand Down Expand Up @@ -225,7 +226,6 @@ const shaderChunks = {
combineClearCoatPS,
combineDiffusePS,
combineDiffuseSpecularPS,
combineDiffuseSpecularNoConservePS,
combineDiffuseSpecularNoReflPS,
combineDiffuseSpecularNoReflSeparateAmbientPS,
combineDiffuseSpecularOldPS,
Expand Down Expand Up @@ -275,6 +275,7 @@ const shaderChunks = {
lightSpecularPhongPS,
ltc,
metalnessPS,
metalnessModulatePS,
msdfPS,
msdfVS,
normalVS,
Expand Down Expand Up @@ -362,6 +363,7 @@ const shaderChunks = {
skyboxHDRPS,
skyboxVS,
specularPS,
specularityFactorPS,
spotPS,
startPS,
startVS,
Expand Down
27 changes: 11 additions & 16 deletions src/graphics/program-lib/chunks/lit/frag/clusteredLight.js
Expand Up @@ -512,26 +512,21 @@ void evaluateLight(ClusterLightData light) {
// specular and clear coat are material settings and get included by a define based on the material
#ifdef CLUSTER_SPECULAR
vec3 halfDir = normalize(normalize(-dLightDirNormW) + normalize(dViewDirW));
// specular
{
vec3 punctualSpecular = getLightSpecular() * dAtten * light.color * dAtten3;
#if defined(CLUSTER_AREALIGHTS)
punctualSpecular *= dSpecularity;
#endif
dSpecularLight += punctualSpecular;
}
#ifdef CLUSTER_SPECULAR_FRESNEL
dSpecularLight += getLightSpecular(halfDir) * dAtten * light.color * dAtten3 * getFresnel(dot(dViewDirW, halfDir), dSpecularity);
#else
dSpecularLight += getLightSpecular(halfDir) * dAtten * light.color * dAtten3 * dSpecularity;
#endif
#ifdef CLUSTER_CLEAR_COAT
vec3 punctualCC = getLightSpecularCC() * dAtten * light.color * dAtten3;
#if defined(CLUSTER_AREALIGHTS)
punctualCC *= ccSpecularity;
#ifdef CLUSTER_SPECULAR_FRESNEL
ccSpecularLight += getLightSpecularCC(halfDir) * dAtten * light.color * dAtten3 * getFresnel(dot(dViewDirW, halfDir), vec3(ccSpecularity));
#else
ccSpecularLight += getLightSpecularCC(halfDir) * dAtten * light.color * dAtten3 * vec3(ccSpecularity);
#endif
ccSpecularLight += punctualCC;
#endif
#endif
Expand Down
@@ -1,5 +1,5 @@
export default /* glsl */`
vec3 combineColorCC() {
return combineColor()+(ccSpecularLight*ccSpecularity+ccReflection.rgb*ccSpecularity*ccReflection.a);
return combineColor() + ccSpecularLight + ccReflection.rgb * ccReflection.a;
}
`;
@@ -1,5 +1,5 @@
export default /* glsl */`
vec3 combineColor() {
return mix(dAlbedo * dDiffuseLight, dSpecularLight + dReflection.rgb * dReflection.a, dSpecularity);
return dAlbedo * dDiffuseLight + dSpecularLight + dReflection.rgb * dReflection.a;
}
`;

This file was deleted.

20 changes: 4 additions & 16 deletions src/graphics/program-lib/chunks/lit/frag/fresnelSchlick.js
@@ -1,20 +1,8 @@
export default /* glsl */`
// Schlick's approximation
uniform float material_fresnelFactor; // unused
void getFresnel() {
float fresnel = 1.0 - max(dot(dNormalW, dViewDirW), 0.0);
float fresnel2 = fresnel * fresnel;
fresnel *= fresnel2 * fresnel2;
fresnel *= dGlossiness * dGlossiness;
dSpecularity = dSpecularity + (1.0 - dSpecularity) * fresnel;
#ifdef CLEARCOAT
fresnel = 1.0 - max(dot(ccNormalW, dViewDirW), 0.0);
fresnel2 = fresnel * fresnel;
fresnel *= fresnel2 * fresnel2;
fresnel *= ccGlossiness * ccGlossiness;
ccSpecularity = ccSpecularity + (1.0 - ccSpecularity) * fresnel;
#endif
vec3 getFresnel(float cosTheta, vec3 f0) {
float fresnel = pow(1.0 - max(cosTheta, 0.0f), 5.0);
float glossSq = dGlossiness * dGlossiness;
return f0 + (max(vec3(glossSq), f0) - f0) * fresnel;
}
`;
@@ -1,15 +1,13 @@
export default /* glsl */`
// Anisotropic GGX
float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
float calcLightSpecular(float tGlossiness, vec3 tNormalW, vec3 h) {
float PI = 3.141592653589793;
float roughness = max((1.0 - tGlossiness) * (1.0 - tGlossiness), 0.001);
float anisotropy = material_anisotropy * roughness;
float at = max((roughness + anisotropy), roughness / 4.0);
float ab = max((roughness - anisotropy), roughness / 4.0);
vec3 h = normalize(normalize(-dLightDirNormW) + normalize(dViewDirW));
float NoH = dot(tNormalW, h);
float ToH = dot(dTBN[0], h);
float BoH = dot(dTBN[1], h);
Expand All @@ -34,13 +32,13 @@ float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
return D * G;
}
float getLightSpecular() {
return calcLightSpecular(dGlossiness, dNormalW);
float getLightSpecular(vec3 h) {
return calcLightSpecular(dGlossiness, dNormalW, h);
}
#ifdef CLEARCOAT
float getLightSpecularCC() {
return calcLightSpecular(ccGlossiness, ccNormalW);
float getLightSpecularCC(vec3 h) {
return calcLightSpecular(ccGlossiness, ccNormalW, h);
}
#endif
`;
11 changes: 5 additions & 6 deletions src/graphics/program-lib/chunks/lit/frag/lightSpecularBlinn.js
@@ -1,7 +1,6 @@
export default /* glsl */`
// Energy-conserving (hopefully) Blinn-Phong
float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
vec3 h = normalize( -dLightDirNormW + dViewDirW );
float calcLightSpecular(float tGlossiness, vec3 tNormalW, vec3 h) {
float nh = max( dot( h, tNormalW ), 0.0 );
float specPow = exp2(tGlossiness * 11.0); // glossiness is linear, power is not; 0 - 2048
Expand All @@ -12,13 +11,13 @@ float calcLightSpecular(float tGlossiness, vec3 tNormalW) {
return pow(nh, specPow) * (specPow + 2.0) / 8.0;
}
float getLightSpecular() {
return calcLightSpecular(dGlossiness, dNormalW);
float getLightSpecular(vec3 h) {
return calcLightSpecular(dGlossiness, dNormalW, h);
}
#ifdef CLEARCOAT
float getLightSpecularCC() {
return calcLightSpecular(ccGlossiness, ccNormalW);
float getLightSpecularCC(vec3 h) {
return calcLightSpecular(ccGlossiness, ccNormalW, h);
}
#endif
`;
10 changes: 5 additions & 5 deletions src/graphics/program-lib/chunks/lit/frag/lightSpecularPhong.js
@@ -1,18 +1,18 @@
export default /* glsl */`
float calcLightSpecular(float tGlossiness, vec3 tReflDirW) {
float calcLightSpecular(float tGlossiness, vec3 tReflDirW, vec3 h) {
float specPow = tGlossiness;
// Hack: On Mac OS X, calling pow with zero for the exponent generates hideous artifacts so bias up a little
return pow(max(dot(tReflDirW, -dLightDirNormW), 0.0), specPow + 0.0001);
}
float getLightSpecular() {
return calcLightSpecular(dGlossiness, dReflDirW);
float getLightSpecular(vec3 h) {
return calcLightSpecular(dGlossiness, dReflDirW, h);
}
#ifdef CLEARCOAT
float getLightSpecularCC() {
return calcLightSpecular(ccGlossiness, ccReflDirW);
float getLightSpecularCC(vec3 h) {
return calcLightSpecular(ccGlossiness, ccReflDirW,h );
}
#endif
`;
4 changes: 2 additions & 2 deletions src/graphics/program-lib/chunks/lit/frag/ltc.js
Expand Up @@ -141,11 +141,11 @@ vec3 getLTCLightSpecFres(vec2 uv, vec3 tSpecularity)
void calcLTCLightValues()
{
dLTCUV = getLTCLightUV(dGlossiness, dNormalW);
dLTCSpecFres = getLTCLightSpecFres(dLTCUV, dSpecularityNoFres);
dLTCSpecFres = getLTCLightSpecFres(dLTCUV, dSpecularity);
#ifdef CLEARCOAT
ccLTCUV = getLTCLightUV(ccGlossiness, ccNormalW);
ccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3(ccSpecularityNoFres));
ccLTCSpecFres = getLTCLightSpecFres(ccLTCUV, vec3(ccSpecularity));
#endif
}
Expand Down
7 changes: 7 additions & 0 deletions src/graphics/program-lib/chunks/lit/frag/metalnessModulate.js
@@ -0,0 +1,7 @@
export default /* glsl */`
void getMetalnessModulate(float ior) {
vec3 dielectricF0 = ior * dSpecularity;
dSpecularity = mix(dielectricF0, dAlbedo, dMetalness);
dAlbedo *= 1.0 - dMetalness;
}
`;
11 changes: 3 additions & 8 deletions src/graphics/program-lib/chunks/standard/frag/metalness.js
@@ -1,10 +1,4 @@
export default /* glsl */`
void processMetalness(float metalness) {
const float dielectricF0 = 0.04;
dSpecularity = mix(vec3(dielectricF0), dAlbedo, metalness);
dAlbedo *= 1.0 - metalness;
}
#ifdef MAPFLOAT
uniform float material_metalness;
#endif
Expand All @@ -13,7 +7,7 @@ uniform float material_metalness;
uniform sampler2D texture_metalnessMap;
#endif
void getSpecularity() {
void getMetalness() {
float metalness = 1.0;
#ifdef MAPFLOAT
Expand All @@ -28,6 +22,7 @@ void getSpecularity() {
metalness *= saturate(vVertexColor.$VC);
#endif
processMetalness(metalness);
dIor = 0.04;
dMetalness = metalness;
}
`;
11 changes: 7 additions & 4 deletions src/graphics/program-lib/chunks/standard/frag/specular.js
@@ -1,4 +1,5 @@
export default /* glsl */`
#ifdef MAPCOLOR
uniform vec3 material_specular;
#endif
Expand All @@ -8,18 +9,20 @@ uniform sampler2D texture_specularMap;
#endif
void getSpecularity() {
dSpecularity = vec3(1.0);
vec3 specularColor = vec3(1,1,1);
#ifdef MAPCOLOR
dSpecularity *= material_specular;
specularColor *= material_specular;
#endif
#ifdef MAPTEXTURE
dSpecularity *= texture2D(texture_specularMap, $UV, textureBias).$CH;
specularColor *= texture2DSRGB(texture_specularMap, $UV, textureBias).$CH;
#endif
#ifdef MAPVERTEX
dSpecularity *= saturate(vVertexColor.$VC);
specularColor *= saturate(vVertexColor.$VC);
#endif
dSpecularity = specularColor;
}
`;
28 changes: 28 additions & 0 deletions src/graphics/program-lib/chunks/standard/frag/specularityFactor.js
@@ -0,0 +1,28 @@
export default /* glsl */`
#ifdef MAPFLOAT
uniform float material_specularityFactor;
#endif
#ifdef MAPTEXTURE
uniform sampler2D texture_specularityFactorMap;
#endif
void getSpecularityFactor() {
float specularityFactor = 1.0;
#ifdef MAPFLOAT
specularityFactor *= material_specularityFactor;
#endif
#ifdef MAPTEXTURE
specularityFactor *= texture2D(texture_specularityFactorMap, $UV, textureBias).$CH;
#endif
#ifdef MAPVERTEX
specularityFactor *= saturate(vVertexColor.$VC);
#endif
dSpecularityFactor = specularityFactor;
}
`;

0 comments on commit 5643813

Please sign in to comment.