Failed specular AA experiment but better PBR shader

This commit is contained in:
Robert Beckebans 2024-11-28 19:53:18 +01:00
parent 80c52c50c8
commit f3fd486c60
6 changed files with 128 additions and 58 deletions

View file

@ -573,6 +573,7 @@ public:
idImage* blackImage; // full of 0x00
idImage* blackDiffuseImage; // full of 0x00
idImage* cyanImage; // cyan
idImage* redClayImage; // dark red
idImage* noFalloffImage; // all 255, but zero clamped
idImage* fogImage; // increasing alpha is denser fog
idImage* fogEnterImage; // adjust fogImage alpha based on terminator plane

View file

@ -170,6 +170,24 @@ static void R_CyanImage( idImage* image, nvrhi::ICommandList* commandList )
image->GenerateImage( ( byte* )data, DEFAULT_SIZE, DEFAULT_SIZE, TF_DEFAULT, TR_REPEAT, TD_DIFFUSE, commandList );
}
static void R_RedClayImage( idImage* image, nvrhi::ICommandList* commandList )
{
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
for( int x = 0; x < DEFAULT_SIZE; x++ )
{
for( int y = 0; y < DEFAULT_SIZE; y++ )
{
data[y][x][0] = 165;
data[y][x][1] = 42;
data[y][x][2] = 42;
data[y][x][3] = 255;
}
}
image->GenerateImage( ( byte* )data, DEFAULT_SIZE, DEFAULT_SIZE, TF_DEFAULT, TR_REPEAT, TD_DIFFUSE, commandList );
}
static void R_ChromeSpecImage( idImage* image, nvrhi::ICommandList* commandList )
{
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
@ -181,7 +199,7 @@ static void R_ChromeSpecImage( idImage* image, nvrhi::ICommandList* commandList
data[y][x][0] = 0;
data[y][x][1] = 255;
data[y][x][2] = 255;
data[y][x][3] = 255;
data[y][x][3] = 128;
}
}
@ -196,10 +214,10 @@ static void R_PlasticSpecImage( idImage* image, nvrhi::ICommandList* commandList
{
for( int y = 0; y < DEFAULT_SIZE; y++ )
{
data[y][x][0] = 0;
data[y][x][0] = 32;
data[y][x][1] = 0;
data[y][x][2] = 255;
data[y][x][3] = 255;
data[y][x][3] = 128;
}
}
@ -1046,6 +1064,7 @@ void idImageManager::CreateIntrinsicImages()
blackImage = ImageFromFunction( "_black", R_BlackImage );
blackDiffuseImage = ImageFromFunction( "_blackDiffuse", R_BlackDiffuseImage );
cyanImage = ImageFromFunction( "_cyan", R_CyanImage );
redClayImage = ImageFromFunction( "_redClay", R_RedClayImage );
flatNormalMap = ImageFromFunction( "_flat", R_FlatNormalImage );
alphaNotchImage = ImageFromFunction( "_alphaNotch", R_AlphaNotchImage );
fogImage = ImageFromFunction( "_fog", R_FogImage );

View file

@ -994,7 +994,14 @@ void idRenderBackend::DrawSingleInteraction( drawInteraction_t* din, bool useFas
return;
}
if( r_skipDiffuse.GetInteger() == 2 )
if( r_skipDiffuse.GetInteger() == 3 )
{
// RB: for testing specular aliasing
din->diffuseImage = globalImages->redClayImage;
din->specularImage = globalImages->plasticSpecImage;
din->specularColor = colorWhite;
}
else if( r_skipDiffuse.GetInteger() == 2 )
{
din->diffuseImage = globalImages->whiteImage;
}

View file

@ -28,7 +28,7 @@ If you have questions concerning this license or the applicable additional terms
// Normal Distribution Function ( NDF ) or D( h )
// GGX ( Trowbridge-Reitz )
half Distribution_GGX( half hdotN, half alpha )
float Distribution_GGX( float hdotN, float alpha )
{
// alpha is assumed to be roughness^2
float a2 = alpha * alpha;
@ -38,16 +38,17 @@ half Distribution_GGX( half hdotN, half alpha )
return ( a2 / ( PI * tmp * tmp ) );
}
half Distribution_GGX_Disney( half hdotN, half alphaG )
float Distribution_GGX_Disney( float hdotN, float alphaG )
{
float a2 = alphaG * alphaG;
float tmp = ( hdotN * hdotN ) * ( a2 - 1.0 ) + 1.0;
//tmp *= tmp;
tmp *= tmp;
return ( a2 / ( PI * tmp ) );
//return ( a2 / ( PI * tmp ) );
return ( a2 / tmp );
}
half Distribution_GGX_1886( half hdotN, half alpha )
float Distribution_GGX_1886( float hdotN, float alpha )
{
// alpha is assumed to be roughness^2
return ( alpha / ( PI * pow( hdotN * hdotN * ( alpha - 1.0 ) + 1.0, 2.0 ) ) );
@ -55,16 +56,16 @@ half Distribution_GGX_1886( half hdotN, half alpha )
// Fresnel term F( v, h )
// Fnone( v, h ) = F(0) = specularColor
half3 Fresnel_Schlick( half3 specularColor, half vDotN )
float3 Fresnel_Schlick( float3 specularColor, float vDotN )
{
return specularColor + ( 1.0 - specularColor ) * pow( 1.0 - vDotN, 5.0 );
}
// Fresnel term that takes roughness into account so rough non-metal surfaces aren't too shiny [Lagarde11]
half3 Fresnel_SchlickRoughness( half3 specularColor, half vDotN, half roughness )
float3 Fresnel_SchlickRoughness( float3 specularColor, float vDotN, float roughness )
{
float oneMinusRoughness = 1.0 - roughness;
return specularColor + ( max( half3( oneMinusRoughness, oneMinusRoughness, oneMinusRoughness ), specularColor ) - specularColor ) * pow( 1.0 - vDotN, 5.0 );
return specularColor + ( max( float3( oneMinusRoughness, oneMinusRoughness, oneMinusRoughness ), specularColor ) - specularColor ) * pow( 1.0 - vDotN, 5.0 );
}
// Sebastien Lagarde proposes an empirical approach to derive the specular occlusion term from the diffuse occlusion term in [Lagarde14].
@ -77,21 +78,21 @@ float ComputeSpecularAO( float vDotN, float ao, float roughness )
// Visibility term G( l, v, h )
// Very similar to Marmoset Toolbag 2 and gives almost the same results as Smith GGX
float Visibility_Schlick( half vdotN, half ldotN, float alpha )
float Visibility_Schlick( float vdotN, float ldotN, float alpha )
{
float k = alpha * 0.5;
float schlickL = ( ldotN * ( 1.0 - k ) + k );
float schlickV = ( vdotN * ( 1.0 - k ) + k );
return ( 0.25 / ( schlickL * schlickV ) );
return ( 0.25 / max( 0.001, schlickL * schlickV ) );
//return ( ( schlickL * schlickV ) / ( 4.0 * vdotN * ldotN ) );
}
// see s2013_pbs_rad_notes.pdf
// Crafting a Next-Gen Material Pipeline for The Order: 1886
// this visibility function also provides some sort of back lighting
float Visibility_SmithGGX( half vdotN, half ldotN, float alpha )
float Visibility_SmithGGX( float vdotN, float ldotN, float alpha )
{
// alpha is already roughness^2
@ -148,73 +149,100 @@ void PBRFromSpecmap( float3 specMap, out float3 F0, out float roughness )
// RB: do another sqrt because PBR shader squares it
roughness = sqrt( roughness );
}
// takes roughness, unnormalized 0-1 normalmap texture and the distance to the camera
// returns roughness
float ApplySpecularAA( float roughness, float3 bumpMap, float camDist )
{
float normLen = saturate( length( bumpMap.xyz * 2.0 - 1.0 ) );
// roughness to specular power
float specPow = ( 2.0 / ( roughness * roughness ) ) - 2.0;
// toksvig AA to prevent speckles from sharp normal edges
float specAA = 1.0 / ( 1.0 + specPow * ( ( 1.0 / normLen ) - 1.0 ) );
// fade out AA close to the camera to prevent dull highlights close up
float distMax = 100.0;
float disMin = 1.0;
specAA = lerp( specAA, 1.0, smoothstep( disMin, distMax, camDist ) );
//apply AA
specPow *= specAA;
// specular power to roughness
roughness = sqrt( 2.0 / ( specPow + 2.0 ) );
return roughness;
}
// Kennedith98 end
// https://yusuketokuyoshi.com/papers/2021/Tokuyoshi2021SAA.pdf
float2x2 NonAxisAlignedNDFFiltering( float3 halfvectorTS, float2 roughness2 )
{
// Compute the derivatives of the halfvector in the projected space.
float2 halfvector2D = halfvectorTS.xy / abs( halfvectorTS.z );
float2 deltaU = ddx( halfvector2D );
float2 deltaV = ddy( halfvector2D );
// Compute 2 * covariance matrix for the filter kernel (Eq. (3)).
float SIGMA2 = 0.15915494;
float2x2 delta = {deltaU, deltaV};
float2x2 kernelRoughnessMat = 2.0 * SIGMA2 * mul( transpose( delta ), delta );
// Approximate NDF filtering (Eq. (9)).
float2x2 roughnessMat = {roughness2.x, 0.0, 0.0, roughness2.y};
float2x2 filteredRoughnessMat = roughnessMat + kernelRoughnessMat;
return filteredRoughnessMat;
}
float2 AxisAlignedNDFFiltering( float3 halfvectorTS, float2 roughness2 )
{
// Compute the bounding rectangle of halfvector derivatives.
float2 halfvector2D = halfvectorTS.xy / abs( halfvectorTS.z );
float2 bounds = fwidth( halfvector2D );
// Compute an axis-aligned filter kernel from the bounding rectangle.
float SIGMA2 = 0.15915494;
float2 kernelRoughness2 = 2.0 * SIGMA2 * ( bounds * bounds );
// Approximate NDF filtering (Eq. (9)).
// We clamp the kernel size to avoid overfiltering.
float KAPPA = 0.18;
float2 clampedKernelRoughness2 = min( kernelRoughness2, KAPPA );
float2 filteredRoughness2 = saturate( roughness2 + clampedKernelRoughness2 );
return filteredRoughness2;
}
float IsotropicNDFFiltering( float3 normal, float roughness2 )
{
const float SIGMA2 = 0.15915494;
const float KAPPA = 0.18;
float3 dndu = ddx( normal );
float3 dndv = ddy( normal );
float kernelRoughness2 = 2.0 * SIGMA2 * ( dot( dndu, dndu ) + dot( dndv, dndv ) );
float clampedKernelRoughness2 = min( kernelRoughness2, KAPPA );
float filteredRoughness2 = saturate( roughness2 + clampedKernelRoughness2 );
return filteredRoughness2;
}
// Environment BRDF approximations
// see s2013_pbs_black_ops_2_notes.pdf
/*
half a1vf( half g )
float a1vf( float g )
{
return ( 0.25 * g + 0.75 );
}
half a004( half g, half vdotN )
float a004( float g, float vdotN )
{
float t = min( 0.475 * g, exp2( -9.28 * vdotN ) );
return ( t + 0.0275 ) * g + 0.015;
}
half a0r( half g, half vdotN )
float a0r( float g, float vdotN )
{
return ( ( a004( g, vdotN ) - a1vf( g ) * 0.04 ) / 0.96 );
}
float3 EnvironmentBRDF( half g, half vdotN, float3 rf0 )
float3 EnvironmentBRDF( float g, float vdotN, float3 rf0 )
{
float4 t = float4( 1.0 / 0.96, 0.475, ( 0.0275 - 0.25 * 0.04 ) / 0.96, 0.25 );
t *= float4( g, g, g, g );
t += float4( 0.0, 0.0, ( 0.015 - 0.75 * 0.04 ) / 0.96, 0.75 );
half a0 = t.x * min( t.y, exp2( -9.28 * vdotN ) ) + t.z;
half a1 = t.w;
float a0 = t.x * min( t.y, exp2( -9.28 * vdotN ) ) + t.z;
float a1 = t.w;
return saturate( a0 + rf0 * ( a1 - a0 ) );
}
half3 EnvironmentBRDFApprox( half roughness, half vdotN, half3 specularColor )
float3 EnvironmentBRDFApprox( float roughness, float vdotN, float3 specularColor )
{
const half4 c0 = half4( -1, -0.0275, -0.572, 0.022 );
const half4 c1 = half4( 1, 0.0425, 1.04, -0.04 );
const float4 c0 = float4( -1, -0.0275, -0.572, 0.022 );
const float4 c1 = float4( 1, 0.0425, 1.04, -0.04 );
half4 r = roughness * c0 + c1;
half a004 = min( r.x * r.x, exp2( -9.28 * vdotN ) ) * r.x + r.y;
half2 AB = half2( -1.04, 1.04 ) * a004 + r.zw;
float4 r = roughness * c0 + c1;
float a004 = min( r.x * r.x, exp2( -9.28 * vdotN ) ) * r.x + r.y;
float2 AB = float2( -1.04, 1.04 ) * a004 + r.zw;
return specularColor * AB.x + AB.y;
}

View file

@ -116,9 +116,9 @@ void main( PS_IN fragment, out PS_OUT result )
float hdotN = clamp( dot3( halfAngleVector, localNormal ), 0.0, 1.0 );
#if USE_PBR
// RB: roughness 0 somehow is not shiny so we clamp it
float roughness = max( 0.05, specMapSRGB.r );
const float metallic = specMapSRGB.g;
const float roughness = specMapSRGB.r;
const float glossiness = 1.0 - roughness;
// the vast majority of real-world materials (anything not metal or gems) have F(0)
// values in a very narrow range (~0.02 - 0.08)

View file

@ -474,9 +474,9 @@ void main( PS_IN fragment, out PS_OUT result )
float hdotN = clamp( dot3( halfAngleVector, localNormal ), 0.0, 1.0 );
#if USE_PBR
// RB: roughness 0 somehow is not shiny so we clamp it
float roughness = max( 0.05, specMapSRGB.r );
const float metallic = specMapSRGB.g;
const float roughness = specMapSRGB.r;
const float glossiness = 1.0 - roughness;
// the vast majority of real-world materials (anything not metal or gems) have F(0)
// values in a very narrow range (~0.02 - 0.08)
@ -497,12 +497,21 @@ void main( PS_IN fragment, out PS_OUT result )
PBRFromSpecmap( specMapSRGB.rgb, specularColor, roughness );
#else
const float roughness = EstimateLegacyRoughness( specMapSRGB.rgb );
float roughness = EstimateLegacyRoughness( specMapSRGB.rgb );
float3 diffuseColor = diffuseMap;
float3 specularColor = specMapSRGB.rgb; // RB: should be linear but it looks too flat
#endif
#if 0
// specular AA - https://yusuketokuyoshi.com/papers/2021/Tokuyoshi2021SAA.pdf
//roughness = IsotropicNDFFiltering( localNormal, roughness * roughness );
float r2 = roughness * roughness;
roughness = AxisAlignedNDFFiltering( halfAngleVector, float2( r2, r2 ) ).x;
#endif
// RB FIXME or not: compensate r_lightScale 3 and the division of Pi
//lambert *= 1.3;
@ -531,6 +540,12 @@ void main( PS_IN fragment, out PS_OUT result )
float VFapprox = ( ldotH * ldotH ) * ( roughness + 0.5 );
#if KENNY_PBR
// specular cook-torrance brdf (visibility, geo and denom in one)
//float D = Distribution_GGX_Disney( hdotN, rr );
//float Vis = Visibility_Schlick( vdotN, lambert, rr );
//float3 F = Fresnel_Schlick( reflectColor, vdotH );
//float3 specularLight = D * Vis * F;
float3 specularLight = ( rrrr / ( 4.0 * D * D * VFapprox ) ) * ldotN * reflectColor;
#else
float3 specularLight = ( rrrr / ( 4.0 * PI * D * D * VFapprox ) ) * ldotN * reflectColor;