You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
9.4 KiB
257 lines
9.4 KiB
//================================================================================================== |
|
// |
|
// Physically Based Rendering Header for brushes and models |
|
// |
|
//================================================================================================== |
|
|
|
// Universal Constants |
|
static const float PI = 3.141592; |
|
static const float ONE_OVER_PI = 0.318309; |
|
static const float EPSILON = 0.00001; |
|
|
|
// Shlick's approximation of the Fresnel factor |
|
float3 fresnelSchlick(float3 F0, float cosTheta) |
|
{ |
|
return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); |
|
} |
|
|
|
// GGX/Towbridge-Reitz normal distribution function |
|
// Uses Disney's reparametrization of alpha = roughness^2 |
|
float ndfGGX(float cosLh, float roughness) |
|
{ |
|
float alpha = roughness * roughness; |
|
float alphaSq = alpha * alpha; |
|
|
|
float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; |
|
return alphaSq / (PI * denom * denom); |
|
} |
|
|
|
// Single term for separable Schlick-GGX below |
|
float gaSchlickG1(float cosTheta, float k) |
|
{ |
|
return cosTheta / (cosTheta * (1.0 - k) + k); |
|
} |
|
|
|
// Schlick-GGX approximation of geometric attenuation function using Smith's method |
|
float gaSchlickGGX(float cosLi, float cosLo, float roughness) |
|
{ |
|
float r = roughness + 1.0; |
|
float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights |
|
return gaSchlickG1(cosLi, k) * gaSchlickG1(cosLo, k); |
|
} |
|
|
|
// Monte Carlo integration, approximate analytic version based on Dimitar Lazarov's work |
|
// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile |
|
float3 EnvBRDFApprox(float3 SpecularColor, float Roughness, float NoV) |
|
{ |
|
const float4 c0 = { -1, -0.0275, -0.572, 0.022 }; |
|
const float4 c1 = { 1, 0.0425, 1.04, -0.04 }; |
|
float4 r = Roughness * c0 + c1; |
|
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; |
|
float2 AB = float2(-1.04, 1.04) * a004 + r.zw; |
|
return SpecularColor * AB.x + AB.y; |
|
} |
|
|
|
// Compute the matrix used to transform tangent space normals to world space |
|
// This expects DirectX normal maps in Mikk Tangent Space http://www.mikktspace.com |
|
float3x3 compute_tangent_frame(float3 N, float3 P, float2 uv, out float3 T, out float3 B, out float sign_det) |
|
{ |
|
float3 dp1 = ddx(P); |
|
float3 dp2 = ddy(P); |
|
float2 duv1 = ddx(uv); |
|
float2 duv2 = ddy(uv); |
|
|
|
sign_det = dot(dp2, cross(N, dp1)) > 0.0 ? -1 : 1; |
|
|
|
float3x3 M = float3x3(dp1, dp2, cross(dp1, dp2)); |
|
float2x3 inverseM = float2x3(cross(M[1], M[2]), cross(M[2], M[0])); |
|
T = normalize(mul(float2(duv1.x, duv2.x), inverseM)); |
|
B = normalize(mul(float2(duv1.y, duv2.y), inverseM)); |
|
return float3x3(T, B, N); |
|
} |
|
|
|
float GetAttenForLight(float4 lightAtten, int lightNum) |
|
{ |
|
#if (NUM_LIGHTS > 1) |
|
if (lightNum == 1) return lightAtten.y; |
|
#endif |
|
|
|
#if (NUM_LIGHTS > 2) |
|
if (lightNum == 2) return lightAtten.z; |
|
#endif |
|
|
|
#if (NUM_LIGHTS > 3) |
|
if (lightNum == 3) return lightAtten.w; |
|
#endif |
|
|
|
return lightAtten.x; |
|
} |
|
|
|
// Calculate direct light for one source |
|
float3 calculateLight(float3 lightIn, float3 lightIntensity, float3 lightOut, float3 normal, float3 fresnelReflectance, float roughness, float metalness, float lightDirectionAngle, float3 albedo) |
|
{ |
|
// Lh |
|
float3 HalfAngle = normalize(lightIn + lightOut); |
|
float cosLightIn = max(0.0, dot(normal, lightIn)); |
|
float cosHalfAngle = max(0.0, dot(normal, HalfAngle)); |
|
|
|
// F - Calculate Fresnel term for direct lighting |
|
float3 F = fresnelSchlick(fresnelReflectance, max(0.0, dot(HalfAngle, lightOut))); |
|
|
|
// D - Calculate normal distribution for specular BRDF |
|
float D = ndfGGX(cosHalfAngle, roughness); |
|
|
|
// Calculate geometric attenuation for specular BRDF |
|
float G = gaSchlickGGX(cosLightIn, lightDirectionAngle, roughness); |
|
|
|
// Diffuse scattering happens due to light being refracted multiple times by a dielectric medium |
|
// Metals on the other hand either reflect or absorb energso diffuse contribution is always, zero |
|
// To be energy conserving we must scale diffuse BRDF contribution based on Fresnel factor & metalness |
|
#if SPECULAR |
|
// Metalness is not used if F0 map is available |
|
float3 kd = float3(1, 1, 1) - F; |
|
#else |
|
float3 kd = lerp(float3(1, 1, 1) - F, float3(0, 0, 0), metalness); |
|
#endif |
|
|
|
float3 diffuseBRDF = kd * albedo; |
|
|
|
// Cook-Torrance specular microfacet BRDF |
|
float3 specularBRDF = (F * D * G) / max(EPSILON, 4.0 * cosLightIn * lightDirectionAngle); |
|
#if LIGHTMAPPED && !FLASHLIGHT |
|
// Ambient light from static lights is already precomputed in the lightmap. Don't add it again |
|
return specularBRDF * lightIntensity * cosLightIn; |
|
#else |
|
return (diffuseBRDF + specularBRDF) * lightIntensity * cosLightIn; |
|
#endif |
|
} |
|
|
|
// Get diffuse ambient light |
|
float3 ambientLookupLightmap(float3 normal, float3 EnvAmbientCube[6], float3 textureNormal, float4 lightmapTexCoord1And2, float4 lightmapTexCoord3, sampler LightmapSampler, float4 g_DiffuseModulation) |
|
{ |
|
float2 bumpCoord1; |
|
float2 bumpCoord2; |
|
float2 bumpCoord3; |
|
|
|
ComputeBumpedLightmapCoordinates( |
|
lightmapTexCoord1And2, lightmapTexCoord3.xy, |
|
bumpCoord1, bumpCoord2, bumpCoord3); |
|
|
|
float3 lightmapColor1 = LightMapSample(LightmapSampler, bumpCoord1); |
|
float3 lightmapColor2 = LightMapSample(LightmapSampler, bumpCoord2); |
|
float3 lightmapColor3 = LightMapSample(LightmapSampler, bumpCoord3); |
|
|
|
float3 dp; |
|
dp.x = saturate(dot(textureNormal, bumpBasis[0])); |
|
dp.y = saturate(dot(textureNormal, bumpBasis[1])); |
|
dp.z = saturate(dot(textureNormal, bumpBasis[2])); |
|
dp *= dp; |
|
|
|
float3 diffuseLighting = dp.x * lightmapColor1 + |
|
dp.y * lightmapColor2 + |
|
dp.z * lightmapColor3; |
|
|
|
float sum = dot(dp, float3(1, 1, 1)); |
|
diffuseLighting *= g_DiffuseModulation.xyz / sum; |
|
return diffuseLighting; |
|
} |
|
|
|
float3 ambientLookup(float3 normal, float3 EnvAmbientCube[6], float3 textureNormal, float4 lightmapTexCoord1And2, float4 lightmapTexCoord3, sampler LightmapSampler, float4 g_DiffuseModulation) |
|
{ |
|
#if LIGHTMAPPED |
|
return ambientLookupLightmap(normal, EnvAmbientCube, textureNormal, lightmapTexCoord1And2, lightmapTexCoord3, LightmapSampler, g_DiffuseModulation); |
|
#else |
|
return PixelShaderAmbientLight(normal, EnvAmbientCube); |
|
#endif |
|
} |
|
|
|
// Create an ambient cube from the envmap |
|
void setupEnvMapAmbientCube(out float3 EnvAmbientCube[6], sampler EnvmapSampler) |
|
{ |
|
float4 directionPosX = { 1, 0, 0, 12 }; float4 directionNegX = {-1, 0, 0, 12 }; |
|
float4 directionPosY = { 0, 1, 0, 12 }; float4 directionNegY = { 0,-1, 0, 12 }; |
|
float4 directionPosZ = { 0, 0, 1, 12 }; float4 directionNegZ = { 0, 0,-1, 12 }; |
|
EnvAmbientCube[0] = ENV_MAP_SCALE * texCUBElod(EnvmapSampler, directionPosX).rgb; |
|
EnvAmbientCube[1] = ENV_MAP_SCALE * texCUBElod(EnvmapSampler, directionNegX).rgb; |
|
EnvAmbientCube[2] = ENV_MAP_SCALE * texCUBElod(EnvmapSampler, directionPosY).rgb; |
|
EnvAmbientCube[3] = ENV_MAP_SCALE * texCUBElod(EnvmapSampler, directionNegY).rgb; |
|
EnvAmbientCube[4] = ENV_MAP_SCALE * texCUBElod(EnvmapSampler, directionPosZ).rgb; |
|
EnvAmbientCube[5] = ENV_MAP_SCALE * texCUBElod(EnvmapSampler, directionNegZ).rgb; |
|
} |
|
|
|
#if PARALLAXOCCLUSION |
|
float2 parallaxCorrect(float2 texCoord, float3 viewRelativeDir, sampler depthMap, float parallaxDepth, float parallaxCenter) |
|
{ |
|
float fLength = length( viewRelativeDir ); |
|
float fParallaxLength = sqrt( fLength * fLength - viewRelativeDir.z * viewRelativeDir.z ) / viewRelativeDir.z; |
|
float2 vParallaxDirection = normalize( viewRelativeDir.xy ); |
|
float2 vParallaxOffsetTS = vParallaxDirection * fParallaxLength; |
|
vParallaxOffsetTS *= parallaxDepth; |
|
|
|
// Compute all the derivatives: |
|
float2 dx = ddx( texCoord ); |
|
float2 dy = ddy( texCoord ); |
|
|
|
int nNumSteps = 20; |
|
|
|
float fCurrHeight = 0.0; |
|
float fStepSize = 1.0 / (float) nNumSteps; |
|
float fPrevHeight = 1.0; |
|
float fNextHeight = 0.0; |
|
|
|
int nStepIndex = 0; |
|
bool bCondition = true; |
|
|
|
float2 vTexOffsetPerStep = fStepSize * vParallaxOffsetTS; |
|
float2 vTexCurrentOffset = texCoord; |
|
float fCurrentBound = 1.0; |
|
float fParallaxAmount = 0.0; |
|
|
|
float2 pt1 = 0; |
|
float2 pt2 = 0; |
|
|
|
float2 texOffset2 = 0; |
|
|
|
while ( nStepIndex < nNumSteps ) |
|
{ |
|
vTexCurrentOffset -= vTexOffsetPerStep; |
|
|
|
// Sample height map which in this case is stored in the alpha channel of the normal map: |
|
fCurrHeight = parallaxCenter + tex2Dgrad( depthMap, vTexCurrentOffset, dx, dy ).a; |
|
|
|
fCurrentBound -= fStepSize; |
|
|
|
if ( fCurrHeight > fCurrentBound ) |
|
{ |
|
pt1 = float2( fCurrentBound, fCurrHeight ); |
|
pt2 = float2( fCurrentBound + fStepSize, fPrevHeight ); |
|
|
|
texOffset2 = vTexCurrentOffset - vTexOffsetPerStep; |
|
|
|
nStepIndex = nNumSteps + 1; |
|
} |
|
else |
|
{ |
|
nStepIndex++; |
|
fPrevHeight = fCurrHeight; |
|
} |
|
} // End of while ( nStepIndex < nNumSteps ) |
|
|
|
float fDelta2 = pt2.x - pt2.y; |
|
float fDelta1 = pt1.x - pt1.y; |
|
fParallaxAmount = (pt1.x * fDelta2 - pt2.x * fDelta1 ) / ( fDelta2 - fDelta1 ); |
|
float2 vParallaxOffset = vParallaxOffsetTS * (1 - fParallaxAmount); |
|
// The computed texture offset for the displaced point on the pseudo-extruded surface: |
|
float2 texSample = texCoord - vParallaxOffset; |
|
return texSample; |
|
} |
|
#endif |
|
|
|
float3 worldToRelative(float3 worldVector, float3 surfTangent, float3 surfBasis, float3 surfNormal) |
|
{ |
|
return float3( |
|
dot(worldVector, surfTangent), |
|
dot(worldVector, surfBasis), |
|
dot(worldVector, surfNormal) |
|
); |
|
}
|
|
|