Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear coat #192

Merged
merged 7 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Components/Hlms/Pbs/include/OgreHlmsPbs.h
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ namespace Ogre
static const IdString BrdfBlinnPhong;
static const IdString FresnelSeparateDiffuse;
static const IdString GgxHeightCorrelated;
static const IdString ClearCoat;
static const IdString LegacyMathBrdf;
static const IdString RoughnessIsShininess;

Expand Down
15 changes: 14 additions & 1 deletion Components/Hlms/Pbs/include/OgreHlmsPbsDatablock.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ namespace Ogre
float mEmissive[3];
float mNormalMapWeight;
float mRefractionStrength;
float _padding1[3];
float mClearCoat;
float mClearCoatRoughness;
float _padding1;
float mUserValue[3][4]; //can be used in custom pieces
//uint16 mTexIndices[NUM_PBSM_TEXTURE_TYPES];

Expand Down Expand Up @@ -611,6 +613,17 @@ namespace Ogre
void setRefractionStrength( float strength );
float getRefractionStrength( void ) const { return mRefractionStrength; }

/** Sets the strength of the of the clear coat layer and its roughness.
@param strength
This should be treated as a binary value, set to either 0 or 1. Intermediate values are
useful to control transitions between parts of the surface that have a clear coat layers and
parts that don't.
*/
void setClearCoat( float clearCoat );
void setClearCoatRoughness( float roughness );
float getClearCoat( void ) const { return mClearCoat; }
float getClearCoatRoughness( void ) const { return mClearCoatRoughness; }

/** When false, objects with this material will not receive shadows (independent of
whether they case shadows or not)
@remarks
Expand Down
24 changes: 24 additions & 0 deletions Components/Hlms/Pbs/src/OgreHlmsJsonPbs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,20 @@ namespace Ogre
useAlphaFromTextures, changeBlendblock );
}

itor = json.FindMember( "clear_coat" );
if( itor != json.MemberEnd() && itor->value.IsObject() )
{
const rapidjson::Value &subobj = itor->value;

itor = subobj.FindMember( "value" );
if( itor != subobj.MemberEnd() && itor->value.IsNumber() )
pbsDatablock->setClearCoat( static_cast<float>( itor->value.GetDouble() ) );

itor = subobj.FindMember( "roughness" );
if( itor != subobj.MemberEnd() && itor->value.IsNumber() )
pbsDatablock->setClearCoatRoughness( static_cast<float>( itor->value.GetDouble() ) );
}

itor = json.FindMember("diffuse");
if( itor != json.MemberEnd() && itor->value.IsObject() )
{
Expand Down Expand Up @@ -764,6 +778,16 @@ namespace Ogre
outString += "\n\t\t\t}";
}

if( pbsDatablock->getClearCoat() != 0.0f )
{
outString += ",\n\t\t\t\"clear_coat\" :\n\t\t\t{";
outString += "\n\t\t\t\t\"value\" : ";
outString += StringConverter::toString( pbsDatablock->getClearCoat() );
outString += ",\n\t\t\t\t\"roughness\" : ";
outString += StringConverter::toString( pbsDatablock->getClearCoatRoughness() );
outString += "\n\t\t\t}";
}

saveTexture( pbsDatablock->getDiffuse(), "diffuse", PBSM_DIFFUSE,
pbsDatablock, outString, true, pbsDatablock->getBackgroundDiffuse() );
saveTexture( pbsDatablock->getSpecular(), "specular", PBSM_SPECULAR,
Expand Down
3 changes: 3 additions & 0 deletions Components/Hlms/Pbs/src/OgreHlmsPbs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ namespace Ogre
const IdString PbsProperty::BrdfBlinnPhong = IdString( "BRDF_BlinnPhong" );
const IdString PbsProperty::FresnelSeparateDiffuse = IdString( "fresnel_separate_diffuse" );
const IdString PbsProperty::GgxHeightCorrelated = IdString( "GGX_height_correlated" );
const IdString PbsProperty::ClearCoat = IdString( "clear_coat" );
const IdString PbsProperty::LegacyMathBrdf = IdString( "legacy_math_brdf" );
const IdString PbsProperty::RoughnessIsShininess = IdString( "roughness_is_shininess" );

Expand Down Expand Up @@ -748,6 +749,8 @@ namespace Ogre

if( !(brdf & PbsBrdf::FLAG_UNCORRELATED) )
setProperty( PbsProperty::GgxHeightCorrelated, 1 );

setProperty( PbsProperty::ClearCoat, datablock->mClearCoat != 0.0f );
}
else if( (brdf & PbsBrdf::BRDF_MASK) == PbsBrdf::CookTorrance )
setProperty( PbsProperty::BrdfCookTorrance, 1 );
Expand Down
33 changes: 32 additions & 1 deletion Components/Hlms/Pbs/src/OgreHlmsPbsDatablock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,14 @@ namespace Ogre
mTransparencyValue( 1.0f ),
mNormalMapWeight( 1.0f ),
mRefractionStrength( 0.075f ),
mClearCoat( 0.0f ),
mClearCoatRoughness( 0.0f ),
_padding1( 0 ),
mCubemapProbe( 0 ),
mBrdf( PbsBrdf::Default )
{
memset( mUvSource, 0, sizeof( mUvSource ) );
memset( mBlendModes, 0, sizeof( mBlendModes ) );
memset( _padding1, 0, sizeof( _padding1 ) );
memset( mUserValue, 0, sizeof( mUserValue ) );

mBgDiffuse[0] = mBgDiffuse[1] = mBgDiffuse[2] = mBgDiffuse[3] = 1.0f;
Expand Down Expand Up @@ -901,6 +903,35 @@ namespace Ogre
scheduleConstBufferUpdate();
}
//-----------------------------------------------------------------------------------
void HlmsPbsDatablock::setClearCoat( float clearCoat )
{
assert( ( mBrdf & PbsBrdf::BRDF_MASK ) == PbsBrdf::Default );

bool wasZero = mClearCoat == 0.0f;
mClearCoat = clearCoat;

if( wasZero && clearCoat != 0.0f || !wasZero && clearCoat == 0.0f )
{
flushRenderables();
}

scheduleConstBufferUpdate();
}
//-----------------------------------------------------------------------------------
void HlmsPbsDatablock::setClearCoatRoughness( float roughness )
{
mClearCoatRoughness = roughness;

if( mClearCoatRoughness <= 1e-6f )
{
LogManager::getSingleton().logMessage( "WARNING: PBS Datablock '" + mName.getFriendlyText() +
"' Very low clear coat roughness values can "
"cause NaNs in the pixel shader!" );
}

scheduleConstBufferUpdate();
}
//-----------------------------------------------------------------------------------
void HlmsPbsDatablock::setReceiveShadows( bool receiveShadows )
{
if( mReceiveShadows != receiveShadows )
Expand Down
63 changes: 63 additions & 0 deletions Samples/Media/Hlms/Pbs/Any/ClearCoat_piece_ps.any
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@property( clear_coat )
@piece( DeclClearCoatFuncs )
float D_GGX(float roughness, float NoH) {
// Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"

// In mediump, there are two problems computing 1.0 - NoH^2
// 1) 1.0 - NoH^2 suffers floating point cancellation when NoH^2 is close to 1 (highlights)
// 2) NoH doesn't have enough precision around 1.0
// Both problem can be fixed by computing 1-NoH^2 in highp and providing NoH in highp as well

// However, we can do better using Lagrange's identity:
// ||a x b||^2 = ||a||^2 ||b||^2 - (a . b)^2
// since N and H are unit vectors: ||N x H||^2 = 1.0 - NoH^2
// This computes 1.0 - NoH^2 directly (which is close to zero in the highlights and has
// enough precision).
// Overall this yields better performance, keeping all computations in mediump
float oneMinusNoHSquared = 1.0 - NoH * NoH;

float a = NoH * roughness;
float k = roughness / (oneMinusNoHSquared + a * a);
float d = k * k * (1.0 / 3.14159265359);
return d;
}

float V_Kelemen(float LoH) {
// Kelemen 2001, "A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling"
return 0.25 / (LoH * LoH);
}

float F_Schlick(float f0, float f90, float VoH) {
return f0 + (f90 - f0) * pow(1.0 - VoH, 5.0);
}

float clearCoatLobe(const PixelData pixel, const float3 h, float NoH, float LoH, OGRE_OUT_REF(float, Fcc)) {
@property( normal_map )
// If the material has a normal map, we want to use the geometric normal
// instead to avoid applying the normal map details to the clear coat layer
float clearCoatNoH = saturate(dot(pixel.geomNormal, h));
@else
float clearCoatNoH = NoH;
@end

// clear coat specular lobe
float D = D_GGX(pixel.clearCoatRoughness, clearCoatNoH);
float V = V_Kelemen(LoH);
float F = F_Schlick(0.04, 1.0, LoH) * pixel.clearCoat; // fix IOR to 1.5

Fcc = F;
return D * V * F;
}
@end

@piece( LoadClearCoat )
pixelData.clearCoat = material.clearCoat;
pixelData.clearCoatPerceptualRoughness = material.clearCoatRoughness;

@property( perceptual_roughness )
pixelData.clearCoatRoughness = max( pixelData.clearCoatPerceptualRoughness * pixelData.clearCoatPerceptualRoughness, 0.001f );
@else
pixelData.clearCoatRoughness = max( pixelData.clearCoatPerceptualRoughness, 0.001f );
@end
@end
@end
48 changes: 48 additions & 0 deletions Samples/Media/Hlms/Pbs/Any/ForwardPlus_DecalsCubemaps_piece_ps.any
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@
float3 pccEnvS = float3( 0, 0, 0 );
float3 pccEnvD = float3( 0, 0, 0 );

@property( clear_coat )
float3 clearCoatPccEnvS = float3( 0, 0, 0 );
@end

@property( vct_num_probes )
if( pixelData.roughness < 1.0f || vctSpecular.w == 0 )
{
Expand Down Expand Up @@ -196,6 +200,11 @@
float3 normalLS = localCorrect( pixelData.normal, posInProbSpace, probe ).xyz;

float4 pccSingleEnvS;

@property( clear_coat )
float3 clearCoatPccSingleEnvS;
@end

@property( !hlms_cubemaps_use_dpm )
pccSingleEnvS = OGRE_SampleArrayCubeLevel(
texEnvProbeMap, samplerState@value(envMapRegSampler), reflDirLS,
Expand All @@ -206,6 +215,14 @@
probeCubemapIdx, 11.0 ).xyz
@insertpiece( ApplyEnvMapScale ) * probeFade;
@end

@property( clear_coat )
clearCoatPccSingleEnvS = OGRE_SampleArrayCubeLevel( texEnvProbeMap, samplerState@value( envMapRegSampler ),
reflDirLS,
probeCubemapIdx,
@insertpiece( envSpecularRoughnessClearCoat ) ).xyz @insertpiece( ApplyEnvMapScale );
clearCoatPccSingleEnvS *= probeFade;
@end
@else
pccSingleEnvS = OGRE_SampleArray2DLevel(
texEnvProbeMap, samplerState@value(envMapRegSampler), mapCubemapToDpm( reflDirLS ),
Expand All @@ -216,6 +233,14 @@
probeCubemapIdx, 11.0 ).xyz
@insertpiece( ApplyEnvMapScale ) * probeFade;
@end

@property( clear_coat )
clearCoatPccSingleEnvS = OGRE_SampleArray2DLevel( texEnvProbeMap, samplerState@value( envMapRegSampler ),
mapCubemapToDpm( reflDirLS ),
probeCubemapIdx,
@insertpiece( envSpecularRoughnessClearCoat ) ).xyz @insertpiece( ApplyEnvMapScale );
clearCoatPccSingleEnvS *= probeFade;
@end
@end

pccSingleEnvS.xyz *= probeFade;
Expand All @@ -233,12 +258,21 @@
pccSingleEnvS.w );

pccSingleEnvS *= 1.0f - vctLerp;

@property( clear_coat )
clearCoatPccSingleEnvS *= 1.0f - vctLerp;
@end

accumVctLerp += 1.0f - vctLerp;
numProbesVctLerp += 1.0f;
@end

pccEnvS += pccSingleEnvS.xyz;

@property( clear_coat )
clearCoatPccEnvS += clearCoatPccSingleEnvS;
@end

cubemapAccumWeight += probeFade;
}
}
Expand All @@ -248,6 +282,10 @@
@end
pccEnvS.xyz *= cubemapAccumWeight == 0.0f ? 1.0f : (1.0f / cubemapAccumWeight);

@property( clear_coat )
clearCoatPccEnvS *= cubemapAccumWeight == 0.0f ? 1.0f : ( 1.0f / cubemapAccumWeight );
@end

@property( vct_num_probes )
numProbesVctLerp = numProbesVctLerp == 0.0f ? 1.0f : numProbesVctLerp;
pixelData.envColourS.xyz = ( pccEnvS +
Expand All @@ -256,11 +294,21 @@
@property( cubemaps_as_diffuse_gi )
pixelData.envColourD += vctSpecular.w > 0 ? float3( 0, 0, 0 ) : pccEnvD;
@end

@property( clear_coat )
pixelData.clearCoatEnvColourS = ( clearCoatPccEnvS +
pixelData.clearCoatEnvColourS * ( numProbesVctLerp - accumVctLerp ) )
/ numProbesVctLerp;
@end
@else
pixelData.envColourS.xyz = pccEnvS;
@property( cubemaps_as_diffuse_gi )
pixelData.envColourD.xyz = pccEnvD;
@end

@property( clear_coat )
pixelData.clearCoatEnvColourS = clearCoatPccEnvS;
@end
@end


Expand Down
51 changes: 46 additions & 5 deletions Samples/Media/Hlms/Pbs/Any/Main/200.BRDFs_piece_ps.any
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ INLINE float3 BRDF( float3 lightDir, float3 lightDiffuse, float3 lightSpecular,
float_fresnel fresnelS = @insertpiece( getSpecularFresnel );

//We should divide Rs by PI, but it was done inside G for performance
float3 Rs = ( fresnelS * (R * G) ) * pixelData.specular.xyz * lightSpecular;
float3 Rs = ( fresnelS * (R * G) ) * pixelData.specular.xyz;

//Diffuse BRDF (*Normalized* Disney, see course_notes_moving_frostbite_to_pbr.pdf
//"Moving Frostbite to Physically Based Rendering" Sebastien Lagarde & Charles de Rousiers)
Expand All @@ -171,9 +171,33 @@ INLINE float3 BRDF( float3 lightDir, float3 lightDiffuse, float3 lightSpecular,
@end

//We should divide Rd by PI, but it is already included in kD
float3 Rd = (lightScatter * viewScatter * energyFactor * fresnelD) * pixelData.diffuse.xyz * lightDiffuse;
float3 Rd = (lightScatter * viewScatter * energyFactor * fresnelD) * pixelData.diffuse.xyz;

return NdotL * (Rs + Rd);
@property( clear_coat )
float3 color = Rd + Rs;

float Fcc;
float clearCoat = clearCoatLobe(pixelData, halfWay, NdotH, VdotH, Fcc);
float attenuation = 1.0 - Fcc;

@property( normal_map )
color *= attenuation * NdotL;

// If the material has a normal map, we want to use the geometric normal
// instead to avoid applying the normal map details to the clear coat layer
float clearCoatNoL = saturate(dot(pixelData.geomNormal, lightDir));
color += clearCoat * clearCoatNoL;

return color * lightSpecular;
@else
color *= attenuation;
color += clearCoat;

return color * lightSpecular * NdotL;
@end
@else
return NdotL * (Rs * lightSpecular + Rd * lightDiffuse);
@end
}
@end
@end
Expand Down Expand Up @@ -229,8 +253,25 @@ float3 BRDF_IR( float3 lightDir, float3 lightDiffuse, PixelData pixelData )
float fresnelD = 1.0f - @insertpiece( getMaxFresnelS );
@end

finalColour += pixelData.envColourD * pixelData.diffuse.xyz * fresnelD +
pixelData.envColourS * pixelData.specular.xyz * ( fresnelS * envBRDF.x + envBRDF.y );
float3 Rd = pixelData.envColourD * pixelData.diffuse.xyz * fresnelD;
float3 Rs = pixelData.envColourS * pixelData.specular.xyz * ( fresnelS * envBRDF.x + envBRDF.y );

@property( clear_coat )
@property( normal_map )
// We want to use the geometric normal for the clear coat layer
float clearCoatNoV = saturate(dot(pixelData.geomNormal, pixelData.viewDir));
@else
float clearCoatNoV = pixelData.NdotV;
@end
// The clear coat layer assumes an IOR of 1.5 (4% reflectance)
float Fc = F_Schlick(0.04, 1.0, clearCoatNoV) * pixelData.clearCoat;
float attenuation = 1.0 - Fc;
Rd *= attenuation;
Rs *= attenuation;
Rs += pixelData.clearCoatEnvColourS * Fc;
@end

finalColour += Rd + Rs;
@end

@property( hlms_fine_light_mask || hlms_forwardplus_fine_light_mask )
Expand Down
Loading