diff --git a/packages/core/src/material/PBRBaseMaterial.ts b/packages/core/src/material/PBRBaseMaterial.ts index 7888cf012e..a84b3a1c61 100644 --- a/packages/core/src/material/PBRBaseMaterial.ts +++ b/packages/core/src/material/PBRBaseMaterial.ts @@ -1,4 +1,4 @@ -import { Color, Vector4 } from "@oasis-engine/math"; +import { Color, Vector3, Vector4 } from "@oasis-engine/math"; import { Logger } from ".."; import { Engine } from "../Engine"; import { Shader } from "../shader/Shader"; @@ -20,6 +20,9 @@ export abstract class PBRBaseMaterial extends BaseMaterial { private static _clearCoatRoughnessTextureProp = Shader.getPropertyByName("u_clearCoatRoughnessTexture"); private static _clearCoatNormalTextureProp = Shader.getPropertyByName("u_clearCoatNormalTexture"); + private static _anisotropyProp = Shader.getPropertyByName("u_anisotropy"); + private static _anisotropyDirectionProp = Shader.getPropertyByName("u_anisotropyDirection"); + /** * Base color. */ @@ -243,6 +246,38 @@ export abstract class PBRBaseMaterial extends BaseMaterial { } } + /** + * The Amount of anisotropy. Scalar between −1 and 1 + */ + get anisotropy(): number { + return this.shaderData.getFloat(PBRBaseMaterial._anisotropyProp); + } + + set anisotropy(value: number) { + if (!!this.shaderData.getFloat(PBRBaseMaterial._anisotropyProp) !== !!value) { + if (value === 0) { + this.shaderData.disableMacro("HAS_ANISOTROPY"); + } else { + this.shaderData.enableMacro("HAS_ANISOTROPY"); + } + } + this.shaderData.setFloat(PBRBaseMaterial._anisotropyProp, value); + } + + /** + * The direction of the surface to control the shape of the specular highlights + */ + get anisotropyDirection(): Vector3 { + return this.shaderData.getVector3(PBRBaseMaterial._anisotropyDirectionProp); + } + + set anisotropyDirection(value: Vector3) { + const direction = this.shaderData.getVector3(PBRBaseMaterial._anisotropyDirectionProp); + if (value !== direction) { + direction.copyFrom(value); + } + } + /** * Create a pbr base material instance. * @param engine - Engine to which the material belongs @@ -266,5 +301,8 @@ export abstract class PBRBaseMaterial extends BaseMaterial { shaderData.setFloat(PBRBaseMaterial._clearCoatProp, 0); shaderData.setFloat(PBRBaseMaterial._clearCoatRoughnessProp, 0); + + shaderData.setFloat(PBRBaseMaterial._anisotropyProp, 0); + shaderData.setVector3(PBRBaseMaterial._anisotropyDirectionProp, new Vector3(1, 0, 0)); } } diff --git a/packages/core/src/shaderlib/normal_get.glsl b/packages/core/src/shaderlib/normal_get.glsl index e7bf556813..7c1ef38665 100644 --- a/packages/core/src/shaderlib/normal_get.glsl +++ b/packages/core/src/shaderlib/normal_get.glsl @@ -22,7 +22,7 @@ vec3 getNormalByNormalTexture(mat3 tbn, sampler2D normalTexture, float normalInt } mat3 getTBN(){ - #if defined(O3_HAS_NORMAL) && defined(O3_HAS_TANGENT) && ( defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) ) + #if defined(O3_HAS_NORMAL) && defined(O3_HAS_TANGENT) && ( defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) || defined(HAS_ANISOTROPY) ) mat3 tbn = v_TBN; #else vec3 normal = getNormal(); @@ -43,13 +43,13 @@ mat3 getTBN(){ vec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x; vec3 binormal = dp2perp * duv1.y + dp1perp * duv2.y; - // construct a scale-invariant frame + // construct a scale-invariant frame float invmax = inversesqrt(max(dot(tangent, tangent), dot(binormal, binormal))); mat3 tbn = mat3(tangent * invmax, binormal * invmax, normal); #else mat3 tbn = mat3(vec3(0.0), vec3(0.0), normal); #endif #endif - + return tbn; } \ No newline at end of file diff --git a/packages/core/src/shaderlib/normal_share.glsl b/packages/core/src/shaderlib/normal_share.glsl index 4a936679eb..4e270193f9 100644 --- a/packages/core/src/shaderlib/normal_share.glsl +++ b/packages/core/src/shaderlib/normal_share.glsl @@ -1,7 +1,7 @@ #ifndef OMIT_NORMAL #ifdef O3_HAS_NORMAL varying vec3 v_normal; - #if defined(O3_HAS_TANGENT) && ( defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) ) + #if defined(O3_HAS_TANGENT) && ( defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) || defined(HAS_ANISOTROPY) ) varying mat3 v_TBN; #endif #endif diff --git a/packages/core/src/shaderlib/normal_vert.glsl b/packages/core/src/shaderlib/normal_vert.glsl index 5bf97c295c..269b4537f3 100644 --- a/packages/core/src/shaderlib/normal_vert.glsl +++ b/packages/core/src/shaderlib/normal_vert.glsl @@ -2,7 +2,7 @@ #ifdef O3_HAS_NORMAL v_normal = normalize( mat3(u_normalMat) * normal ); - #if defined(O3_HAS_TANGENT) && ( defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) ) + #if defined(O3_HAS_TANGENT) && ( defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) || defined(HAS_ANISOTROPY) ) vec3 normalW = normalize( mat3(u_normalMat) * normal.xyz ); vec3 tangentW = normalize( mat3(u_normalMat) * tangent.xyz ); vec3 bitangentW = cross( normalW, tangentW ) * tangent.w; diff --git a/packages/core/src/shaderlib/pbr/brdf.glsl b/packages/core/src/shaderlib/pbr/brdf.glsl index e88bcfe381..118cb16f71 100644 --- a/packages/core/src/shaderlib/pbr/brdf.glsl +++ b/packages/core/src/shaderlib/pbr/brdf.glsl @@ -29,6 +29,17 @@ float G_GGX_SmithCorrelated(float alpha, float dotNL, float dotNV ) { } +#if defined(HAS_ANISOTROPY) +float G_GGX_SmithCorrelated_Anisotropic(float at, float ab, float ToV, float BoV, + float ToL, float BoL, float NoV, float NoL) { + // Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" + // TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function + float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV)); + float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL)); + return 0.5 / max(lambdaV + lambdaL, EPSILON); +} +#endif + // Microfacet Models for Refraction through Rough Surfaces - equation (33) // http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html // alpha is "roughness squared" in Disney’s reparameterization @@ -42,25 +53,77 @@ float D_GGX(float alpha, float dotNH ) { } +#if defined(HAS_ANISOTROPY) +float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) { + // Burley 2012, "Physically-Based Shading at Disney" + + // The values at and ab are perceptualRoughness^2, a2 is therefore perceptualRoughness^4 + // The dot product below computes perceptualRoughness^8. We cannot fit in fp16 without clamping + // the roughness to too high values so we perform the dot product and the division in fp32 + float a2 = at * ab; + highp vec3 d = vec3(ab * ToH, at * BoH, a2 * NoH); + highp float d2 = dot(d, d); + float b2 = a2 / d2; + return a2 * b2 * b2 * RECIPROCAL_PI; +} +#endif + +vec3 isotropicLobe(vec3 specularColor, float alpha, float dotNV, float dotNL, float dotNH, float dotLH) { + vec3 F = F_Schlick( specularColor, dotLH ); + + float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV ); + + float D = D_GGX( alpha, dotNH ); + + return F * ( G * D ); +} + +#if defined(HAS_ANISOTROPY) +vec3 anisotropicLobe(vec3 h, vec3 l, Geometry geometry, vec3 specularColor, float alpha, float dotNV, float dotNL, float dotNH, float dotLH) { + vec3 t = geometry.anisotropicT; + vec3 b = geometry.anisotropicB; + vec3 v = geometry.viewDir; + + float dotTV = dot(t, v); + float dotBV = dot(b, v); + float dotTL = dot(t, l); + float dotBL = dot(b, l); + float dotTH = dot(t, h); + float dotBH = dot(b, h); + + float MIN_ROUGHNESS = 0.007921; // mobile + // Anisotropic parameters: at and ab are the roughness along the tangent and bitangent + // to simplify materials, we derive them from a single roughness parameter + // Kulla 2017, "Revisiting Physically Based Shading at Imageworks" + float at = max(alpha * (1.0 + geometry.anisotropy), MIN_ROUGHNESS); + float ab = max(alpha * (1.0 - geometry.anisotropy), MIN_ROUGHNESS); + + // specular anisotropic BRDF + float D = D_GGX_Anisotropic(at, ab, dotTH, dotBH, dotNH); + float V = G_GGX_SmithCorrelated_Anisotropic(at, ab, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL); + vec3 F = F_Schlick( specularColor, dotLH ); + + return (D * V) * F; +} +#endif + // GGX Distribution, Schlick Fresnel, GGX-Smith Visibility -vec3 BRDF_Specular_GGX(vec3 incidentDirection, vec3 viewDir, vec3 normal, vec3 specularColor, float roughness ) { +vec3 BRDF_Specular_GGX(vec3 incidentDirection, Geometry geometry, vec3 normal, vec3 specularColor, float roughness ) { float alpha = pow2( roughness ); // UE4's roughness - vec3 halfDir = normalize( incidentDirection + viewDir ); + vec3 halfDir = normalize( incidentDirection + geometry.viewDir ); float dotNL = saturate( dot( normal, incidentDirection ) ); - float dotNV = saturate( dot( normal, viewDir ) ); + float dotNV = saturate( dot( normal, geometry.viewDir ) ); float dotNH = saturate( dot( normal, halfDir ) ); float dotLH = saturate( dot( incidentDirection, halfDir ) ); - vec3 F = F_Schlick( specularColor, dotLH ); - - float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV ); - - float D = D_GGX( alpha, dotNH ); - - return F * ( G * D ); +#if defined(HAS_ANISOTROPY) + return anisotropicLobe(halfDir, incidentDirection, geometry, specularColor, alpha, dotNV, dotNL, dotNH, dotLH); +#else + return isotropicLobe(specularColor, alpha, dotNV, dotNL, dotNH, dotLH); +#endif } diff --git a/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl b/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl index 4c5409f55e..755381041f 100644 --- a/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl @@ -6,15 +6,15 @@ void addDirectRadiance(vec3 incidentDirection, vec3 color, Geometry geometry, Ma #ifdef CLEARCOAT float clearCoatDotNL = saturate( dot( geometry.clearCoatNormal, incidentDirection ) ); vec3 clearCoatIrradiance = clearCoatDotNL * color; - - reflectedLight.directSpecular += material.clearCoat * clearCoatIrradiance * BRDF_Specular_GGX( incidentDirection, geometry.viewDir, geometry.clearCoatNormal, vec3( 0.04 ), material.clearCoatRoughness ); + + reflectedLight.directSpecular += material.clearCoat * clearCoatIrradiance * BRDF_Specular_GGX( incidentDirection, geometry, geometry.clearCoatNormal, vec3( 0.04 ), material.clearCoatRoughness ); attenuation -= material.clearCoat * F_Schlick(geometry.clearCoatDotNV); #endif float dotNL = saturate( dot( geometry.normal, incidentDirection ) ); vec3 irradiance = dotNL * color * PI; - reflectedLight.directSpecular += attenuation * irradiance * BRDF_Specular_GGX( incidentDirection, geometry.viewDir, geometry.normal, material.specularColor, material.roughness); + reflectedLight.directSpecular += attenuation * irradiance * BRDF_Specular_GGX( incidentDirection, geometry, geometry.normal, material.specularColor, material.roughness); reflectedLight.directDiffuse += attenuation * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor ); } @@ -42,7 +42,7 @@ void addDirectRadiance(vec3 incidentDirection, vec3 color, Geometry geometry, Ma vec3 color = pointLight.color; color *= clamp(1.0 - pow(lightDistance/pointLight.distance, 4.0), 0.0, 1.0); - + addDirectRadiance( direction, color, geometry, material, reflectedLight ); } @@ -64,9 +64,9 @@ void addDirectRadiance(vec3 incidentDirection, vec3 color, Geometry geometry, Ma vec3 color = spotLight.color; color *= spotEffect * decayEffect; - + addDirectRadiance( direction, color, geometry, material, reflectedLight ); - + } diff --git a/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl b/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl index 786293b776..a6dca67c09 100644 --- a/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/ibl_frag_define.glsl @@ -1,5 +1,3 @@ -// ------------------------Diffuse------------------------ - // sh need be pre-scaled in CPU. vec3 getLightProbeIrradiance(vec3 sh[9], vec3 normal){ normal.x = -normal.x; @@ -19,11 +17,8 @@ vec3 getLightProbeIrradiance(vec3 sh[9], vec3 normal){ } -// ------------------------Specular------------------------ - // ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile -vec3 envBRDFApprox(vec3 specularColor,float roughness, float dotNV ) { - +vec2 lutApprox( float roughness, float dotNV ) { const vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); const vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); @@ -34,8 +29,12 @@ vec3 envBRDFApprox(vec3 specularColor,float roughness, float dotNV ) { vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw; - return specularColor * AB.x + AB.y; + return AB; +} +vec3 envBRDF(vec3 specularColor,float roughness, float dotNV ) { + vec2 AB = lutApprox(roughness, dotNV); + return specularColor * AB.x + AB.y; } @@ -43,15 +42,38 @@ float getSpecularMIPLevel(float roughness, int maxMIPLevel ) { return roughness * float(maxMIPLevel); } -vec3 getLightProbeRadiance(vec3 viewDir, vec3 normal, float roughness, int maxMIPLevel, float specularIntensity) { +/** + * Returns the reflected vector at the current shading point. The reflected vector + * return by this function might be different from shading_reflected: + * - For anisotropic material, we bend the reflection vector to simulate + * anisotropic indirect lighting + * - The reflected vector may be modified to point towards the dominant specular + * direction to match reference renderings when the roughness increases + */ + +vec3 getReflectedVector(Geometry geometry, const vec3 n, float roughness) { + #if defined(HAS_ANISOTROPY) + vec3 anisotropyDirection = geometry.anisotropy >= 0.0 ? geometry.anisotropicB : geometry.anisotropicT; + vec3 anisotropicTangent = cross(anisotropyDirection, geometry.viewDir); + vec3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection); + float bendFactor = abs(geometry.anisotropy) * saturate(5.0 * roughness); + vec3 bentNormal = normalize(mix(n, anisotropicNormal, bendFactor)); + + vec3 r = reflect(-geometry.viewDir, bentNormal); + #else + vec3 r = reflect(-geometry.viewDir, n); + #endif + return r; +} + +vec3 getLightProbeRadiance(Geometry geometry, vec3 normal, float roughness, int maxMIPLevel, float specularIntensity) { #ifndef O3_USE_SPECULAR_ENV return vec3(0); #else - - vec3 reflectVec = reflect( -viewDir, normal ); + vec3 reflectVec = getReflectedVector(geometry, normal, roughness); reflectVec.x = -reflectVec.x; // TextureCube is left-hand,so x need inverse float specularMIPLevel = getSpecularMIPLevel(roughness, maxMIPLevel ); @@ -77,4 +99,44 @@ vec3 getLightProbeRadiance(vec3 viewDir, vec3 normal, float roughness, int maxMI #endif -} \ No newline at end of file +} + +void evaluateIBL(inout ReflectedLight reflectedLight, const Geometry geometry, const Material material ){ + #ifdef O3_USE_SH + vec3 irradiance = getLightProbeIrradiance(u_env_sh, geometry.normal); + #ifdef OASIS_COLORSPACE_GAMMA + irradiance = linearToGamma(vec4(irradiance, 1.0)).rgb; + #endif + irradiance *= u_envMapLight.diffuseIntensity; + #else + vec3 irradiance = u_envMapLight.diffuse * u_envMapLight.diffuseIntensity; + irradiance *= PI; + #endif + + reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor ); + + + float radianceAttenuation = 1.0; + #ifdef CLEARCOAT + vec3 clearCoatRadiance = getLightProbeRadiance( geometry, geometry.clearCoatNormal, material.clearCoatRoughness, int(u_envMapLight.mipMapLevel), u_envMapLight.specularIntensity ); + + reflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * envBRDF(vec3( 0.04 ), material.clearCoatRoughness, geometry.clearCoatDotNV); + radianceAttenuation -= material.clearCoat * F_Schlick(geometry.clearCoatDotNV); + #endif + + vec3 radiance = radianceAttenuation * getLightProbeRadiance(geometry, geometry.normal, material.roughness, int(u_envMapLight.mipMapLevel), u_envMapLight.specularIntensity); + vec2 fab = lutApprox( material.roughness, geometry.dotNV ); + vec3 FssEss = material.specularColor * fab.x + fab.y; + reflectedLight.indirectSpecular += FssEss * radiance; + + // multi scattering + float Ess = fab.x + fab.y; + float Ems = 1.0 - Ess; + vec3 Favg = material.specularColor + ( 1.0 - material.specularColor ) * 0.047619; // F0 + (1 - F0)/21; + vec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg ); + vec3 energyCompensation = Fms * Ems; + vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI; + + reflectedLight.indirectSpecular += energyCompensation * cosineWeightedIrradiance; + +} diff --git a/packages/core/src/shaderlib/pbr/pbr_frag.glsl b/packages/core/src/shaderlib/pbr/pbr_frag.glsl index 59081f9a95..a86e7a2750 100644 --- a/packages/core/src/shaderlib/pbr/pbr_frag.glsl +++ b/packages/core/src/shaderlib/pbr/pbr_frag.glsl @@ -8,32 +8,8 @@ initMaterial(material, geometry); // Direct Light addTotalDirectRadiance(geometry, material, reflectedLight); -// IBL diffuse -#ifdef O3_USE_SH - vec3 irradiance = getLightProbeIrradiance(u_env_sh, geometry.normal); - #ifdef OASIS_COLORSPACE_GAMMA - irradiance = linearToGamma(vec4(irradiance, 1.0)).rgb; - #endif - irradiance *= u_envMapLight.diffuseIntensity; -#else - vec3 irradiance = u_envMapLight.diffuse * u_envMapLight.diffuseIntensity; - irradiance *= PI; -#endif - -reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor ); - -// IBL specular -vec3 radiance = getLightProbeRadiance(geometry.viewDir, geometry.normal, material.roughness, int(u_envMapLight.mipMapLevel), u_envMapLight.specularIntensity); -float radianceAttenuation = 1.0; - -#ifdef CLEARCOAT - vec3 clearCoatRadiance = getLightProbeRadiance( geometry.viewDir, geometry.clearCoatNormal, material.clearCoatRoughness, int(u_envMapLight.mipMapLevel), u_envMapLight.specularIntensity ); - - reflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * envBRDFApprox(vec3( 0.04 ), material.clearCoatRoughness, geometry.clearCoatDotNV); - radianceAttenuation -= material.clearCoat * F_Schlick(geometry.clearCoatDotNV); -#endif - -reflectedLight.indirectSpecular += radianceAttenuation * radiance * envBRDFApprox(material.specularColor, material.roughness, geometry.dotNV ); +// IBL +evaluateIBL(reflectedLight, geometry, material); // Occlusion @@ -63,10 +39,10 @@ vec3 emissiveRadiance = u_emissiveColor; #endif // Total -vec3 totalRadiance = reflectedLight.directDiffuse + - reflectedLight.indirectDiffuse + - reflectedLight.directSpecular + - reflectedLight.indirectSpecular + +vec3 totalRadiance = reflectedLight.directDiffuse + + reflectedLight.indirectDiffuse + + reflectedLight.directSpecular + + reflectedLight.indirectSpecular + emissiveRadiance; vec4 targetColor =vec4(totalRadiance, material.opacity); diff --git a/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl b/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl index bc7a29f6c4..db8748f7dd 100644 --- a/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl @@ -53,6 +53,10 @@ uniform float u_occlusionTextureCoord; uniform sampler2D u_clearCoatNormalTexture; #endif +#ifdef HAS_ANISOTROPY + uniform float u_anisotropy; + uniform vec3 u_anisotropyDirection; +#endif // Runtime @@ -74,6 +78,11 @@ struct Geometry { float clearCoatDotNV; #endif + #ifdef HAS_ANISOTROPY + vec3 anisotropicT; + vec3 anisotropicB; + float anisotropy; + #endif }; struct Material { diff --git a/packages/core/src/shaderlib/pbr/pbr_helper.glsl b/packages/core/src/shaderlib/pbr/pbr_helper.glsl index 8a16246cf9..deb00b9b74 100644 --- a/packages/core/src/shaderlib/pbr/pbr_helper.glsl +++ b/packages/core/src/shaderlib/pbr/pbr_helper.glsl @@ -21,7 +21,7 @@ void initGeometry(out Geometry geometry){ geometry.position = v_pos; geometry.viewDir = normalize(u_cameraPos - v_pos); - #if defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) + #if defined(NORMALTEXTURE) || defined(HAS_CLEARCOATNORMALTEXTURE) || defined(HAS_ANISOTROPY) mat3 tbn = getTBN(); #endif @@ -43,6 +43,11 @@ void initGeometry(out Geometry geometry){ geometry.clearCoatDotNV = saturate( dot(geometry.clearCoatNormal, geometry.viewDir) ); #endif + #ifdef HAS_ANISOTROPY + geometry.anisotropy = u_anisotropy; + geometry.anisotropicT = normalize(tbn * u_anisotropyDirection); + geometry.anisotropicB = normalize(cross(geometry.normal, geometry.anisotropicT)); + #endif } void initMaterial(out Material material, const in Geometry geometry){