Implemented experimental direct physically based shading.

The implementation is similar to the one used in Marmoset Toolbag 2

See blog.selfshadow.com/publications/s2013-shading-course/ for more
information.
This commit is contained in:
Robert Beckebans 2014-09-27 12:25:41 +02:00
parent 0d3e4733af
commit e9e0fd3c08
6 changed files with 406 additions and 39 deletions

135
base/renderprogs/BRDF.inc Normal file
View file

@ -0,0 +1,135 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 2014 Robert Beckebans
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
// Normal Distribution Function ( NDF ) or D( h )
// GGX ( Trowbridge-Reitz )
half Distribution_GGX( half hdotN, half alpha )
{
// alpha is assumed to be roughness^2
float a2 = alpha * alpha;
//float tmp = ( hdotN * hdotN ) * ( a2 - 1.0 ) + 1.0;
float tmp = ( hdotN * a2 - hdotN ) * hdotN + 1.0;
return ( a2 / ( PI * tmp * tmp ) );
}
half Distribution_GGX_Disney( half hdotN, half alphaG )
{
float a2 = alphaG * alphaG;
float tmp = ( hdotN * hdotN ) * ( a2 - 1.0 ) + 1.0;
//tmp *= tmp;
return ( a2 / ( PI * tmp ) );
}
half Distribution_GGX_1886( half hdotN, half alpha )
{
// alpha is assumed to be roughness^2
return ( alpha / ( PI * pow( hdotN * hdotN * ( alpha - 1.0 ) + 1.0, 2.0 ) ) );
}
// Fresnel term F( v, h )
// Fnone( v, h ) = F(0°) = specularColor
half3 Fresnel_Schlick( half3 specularColor, half vdotH )
{
return specularColor + ( 1.0 - specularColor ) * pow( 1.0 - vdotH, 5.0 );
}
// 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 k = alpha * 0.5;
float schlickL = ( ldotN * ( 1.0 - k ) + k );
float schlickV = ( vdotN * ( 1.0 - k ) + k );
return ( 0.25 / ( 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 )
{
// alpha is already roughness^2
float V1 = ldotN + sqrt( alpha + ( 1.0 - alpha ) * ldotN * ldotN );
float V2 = vdotN + sqrt( alpha + ( 1.0 - alpha ) * vdotN * vdotN );
// RB: avoid too bright spots
return ( 1.0 / max( V1 * V2, 0.15 ) );
}
// Environment BRDF approximations
// see s2013_pbs_black_ops_2_notes.pdf
half a1vf( half g )
{
return ( 0.25 * g + 0.75 );
}
half a004( half g, half vdotN )
{
float t = min( 0.475 * g, exp2( -9.28 * vdotN ) );
return ( t + 0.0275 ) * g + 0.015;
}
half a0r( half g, half vdotN )
{
return ( ( a004( g, vdotN ) - a1vf( g ) * 0.04 ) / 0.96 );
}
float3 EnvironmentBRDF( half g, half 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;
return saturate( a0 + rf0 * ( a1 - a0 ) );
}
half3 EnvironmentBRDFApprox( half roughness, half vdotN, half3 specularColor )
{
const half4 c0 = half4( -1, -0.0275, -0.572, 0.022 );
const half4 c1 = half4( 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;
return specularColor * AB.x + AB.y;
}

View file

@ -180,6 +180,8 @@ float rand( float2 co ) {
#define DEG2RAD( a ) ( ( a ) * PI / 180.0f )
#define RAD2DEG( a ) ( ( a ) * 180.0f / PI )
static const half4 LUMINANCE_VECTOR = half4( 0.2125, 0.7154, 0.0721, 0.0 );
// RB end
#define _half2( x ) half2( x )

View file

@ -28,6 +28,7 @@ If you have questions concerning this license or the applicable additional terms
*/
#include "renderprogs/global.inc"
#include "renderprogs/BRDF.inc"
uniform sampler2D samp0 : register(s0); // texture 1 is the per-surface bump map
uniform sampler2D samp1 : register(s1); // texture 2 is the light falloff texture
@ -35,7 +36,8 @@ uniform sampler2D samp2 : register(s2); // texture 3 is the light projection tex
uniform sampler2D samp3 : register(s3); // texture 4 is the per-surface diffuse map
uniform sampler2D samp4 : register(s4); // texture 5 is the per-surface specular map
struct PS_IN {
struct PS_IN
{
half4 position : VPOS;
half4 texcoord0 : TEXCOORD0_centroid;
half4 texcoord1 : TEXCOORD1_centroid;
@ -47,11 +49,13 @@ struct PS_IN {
half4 color : COLOR0;
};
struct PS_OUT {
struct PS_OUT
{
half4 color : COLOR;
};
void main( PS_IN fragment, out PS_OUT result ) {
void main( PS_IN fragment, out PS_OUT result )
{
half4 bumpMap = tex2D( samp0, fragment.texcoord1.xy );
half4 lightFalloff = idtex2Dproj( samp1, fragment.texcoord2 );
half4 lightProj = idtex2Dproj( samp2, fragment.texcoord3 );
@ -59,6 +63,7 @@ void main( PS_IN fragment, out PS_OUT result ) {
half4 specMap = tex2D( samp4, fragment.texcoord5.xy );
half3 lightVector = normalize( fragment.texcoord0.xyz );
half3 viewVector = normalize( fragment.texcoord6.xyz );
half3 diffuseMap = ConvertYCoCgToRGB( YCoCG );
half3 localNormal;
@ -73,7 +78,7 @@ void main( PS_IN fragment, out PS_OUT result ) {
localNormal = normalize( localNormal );
// traditional very dark Lambert light model used in Doom 3
half ldotN = dot3( localNormal, lightVector );
half ldotN = saturate( dot3( localNormal, lightVector ) );
#if defined(USE_HALF_LAMBERT)
// RB: http://developer.valvesoftware.com/wiki/Half_Lambert
@ -84,20 +89,134 @@ void main( PS_IN fragment, out PS_OUT result ) {
#else
half lambert = ldotN;
#endif
half3 halfAngleVector = normalize( lightVector + viewVector );
half hdotN = saturate( dot3( halfAngleVector, localNormal ) );
#if 1
/*
Physically based shading
Lambert diffuse BRDF combined with Cook-Torrance microfacet specular BRDF
D( h ) * F( v, h ) * G( l, v, h )
f( l, v ) = diffuse + ---------------------------------
4 * ( n * l ) ( n * v )
*/
// RB: compensate r_lightScale 3 and the division of Pi
lambert *= 1.3;
const half3 goldColor = half3( 1.00, 0.71, 0.29 );
//const half3 baseColor = goldColor;
const half3 baseColor = diffuseMap;
const half metallic = 0.0;
// rpDiffuseModifier contains light color
half3 lightColor = lightProj.xyz * lightFalloff.xyz * rpDiffuseModifier.xyz;
half vdotN = saturate( dot3( viewVector, localNormal ) );
half vdotH = saturate( dot3( viewVector, halfAngleVector ) );
// the vast majority of real-world materials (anything not metal or gems) have F(0°) values in a very narrow range (~0.02 - 0.06)
// HACK calculate roughness from D3 gloss maps
// converting from linear to sRGB space give pretty results
const half glossiness = clamp( pow( dot( LUMINANCE_VECTOR.rgb, specMap.rgb ) * 0.4, 1.0 / 2.2 ) * 1.0, 0.0, 0.98 );
const half roughness = 1.0 - glossiness;
// compensate r_lightScale 3 * 2
half3 reflectColor = specMap.rgb * rpSpecularModifier.rgb * 0.5;
// alpha modifications by Disney - s2012_pbs_disney_brdf_notes_v2.pdf
const half alpha = roughness * roughness;
// reduce roughness range from [0 .. 1] to [0.5 .. 1]
const half alphaG = pow( 0.5 + roughness * 0.5, 2.0 );
//half3 D = _half3( pow( abs( hdotN ), 10.0f ) );
half3 D = _half3( Distribution_GGX( hdotN, alpha ) );
//half3 D = _half3( Distribution_GGX_1886( hdotN, alpha ) );
half3 G = _half3( Visibility_Schlick( ldotN, vdotN, alpha ) );
//half3 G = _half3( Visibility_SmithGGX( ldotN, vdotN, alpha ) );
half3 F = Fresnel_Schlick( reflectColor, vdotH );
// horizon
float horizon = 1.0 - ldotN;
horizon *= horizon;
horizon *= horizon;
half3 specLightColor = lightColor.rgb - lightColor.rgb * horizon;
float3 specularColor = saturate( D * G * ( F * ( specLightColor.rgb * lambert ) ) );
//specularColor = EnvironmentBRDFApprox( roughness, vdotN, specularColor.rgb );// * 0.45;
#if 0
result.color = float4( _half3( F ), 1.0 );
return;
#endif
// see http://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
lambert /= PI;
//half3 diffuseColor = mix( diffuseMap, F0, metal ) * rpDiffuseModifier.xyz;
half3 diffuseColor = baseColor * rpDiffuseModifier.xyz;
diffuseColor *= lightColor * lambert;
/*
maintain energy conservation
Energy conservation is a restriction on the reflection model
that requires that the total amount of reflected light
cannot be more than the incoming light.
http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
Cdiff + Cspec <= 1
*/
//diffuseColor.rgb *= ( half3( 1.0 ) - specularColor.rgb );
#if 0 //defined(USE_METALNESS)
//specularColor *= ( 0.96 * metallic ) * diffuseColor + half( 0.04 );
diffuseColor.rgb *= ( 1.0 - metallic );
//diffuseColor.rgb = mix( diffuseColor, specularColor, metallic );
#endif
// apply r_lightScale overbright for both diffuse and specular
result.color.xyz = ( diffuseColor + specularColor ) * fragment.color.rgb;// + rimColor;
result.color.w = 1.0;
#else
/*
OLD Blinn Phong
*/
const half specularPower = 10.0f;
half hDotN = dot3( normalize( fragment.texcoord6.xyz ), localNormal );
// RB: added abs
half3 specularContribution = _half3( pow( abs( hDotN ), specularPower ) );
half3 specularContribution = _half3( pow( abs( hdotN ), specularPower ) );
half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz;
half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz;
half3 lightColor = lightProj.xyz * lightFalloff.xyz;
half rim = 1.0f - saturate( hDotN );
/*
half rim = 1.0f - saturate( hdotN );
half rimPower = 16.0f;
half3 rimColor = diffuseColor * lightProj.xyz * lightFalloff.xyz * 1.0f * pow( rim, rimPower ) * fragment.color.rgb;// * halfLdotN;
*/
result.color.xyz = ( diffuseColor + specularColor ) * lambert * lightColor * fragment.color.rgb;// + rimColor;
result.color.xyz = ( diffuseColor + specularColor ) * lambert * lightColor * fragment.color.rgb; // + rimColor;
result.color.w = 1.0;
#endif
}

View file

@ -174,13 +174,10 @@ void main( VS_IN vertex, out VS_OUT result ) {
//# calculate normalized vector to viewer in R1
float4 toView = normalize( rpLocalViewOrigin - modelPosition );
//# add together to become the half angle vector in object space (non-normalized)
float4 halfAngleVector = toLight + toView;
//# put into texture space
result.texcoord6.x = dot3( tangent, halfAngleVector );
result.texcoord6.y = dot3( bitangent, halfAngleVector );
result.texcoord6.z = dot3( normal, halfAngleVector );
result.texcoord6.x = dot3( tangent, toView );
result.texcoord6.y = dot3( bitangent, toView );
result.texcoord6.z = dot3( normal, toView );
result.texcoord6.w = 1.0f;
#if defined( USE_GPU_SKINNING )

View file

@ -28,6 +28,7 @@ If you have questions concerning this license or the applicable additional terms
*/
#include "renderprogs/global.inc"
#include "renderprogs/BRDF.inc"
uniform sampler2D samp0 : register(s0); // texture 1 is the per-surface bump map
uniform sampler2D samp1 : register(s1); // texture 2 is the light falloff texture
@ -35,7 +36,7 @@ uniform sampler2D samp2 : register(s2); // texture 3 is the light projection
uniform sampler2D samp3 : register(s3); // texture 4 is the per-surface diffuse map
uniform sampler2D samp4 : register(s4); // texture 5 is the per-surface specular map
uniform sampler2DArrayShadow samp5 : register(s5); // texture 6 is the shadowmap array
uniform sampler2D samp6 : register(s6); // texture 7 is the jitter texture
uniform sampler2D samp6 : register(s6); // texture 7 is the jitter texture
@ -69,6 +70,7 @@ void main( PS_IN fragment, out PS_OUT result )
half4 specMap = tex2D( samp4, fragment.texcoord5.xy );
half3 lightVector = normalize( fragment.texcoord0.xyz );
half3 viewVector = normalize( fragment.texcoord6.xyz );
half3 diffuseMap = ConvertYCoCgToRGB( YCoCG );
half3 localNormal;
@ -83,7 +85,7 @@ void main( PS_IN fragment, out PS_OUT result )
localNormal = normalize( localNormal );
// traditional very dark Lambert light model used in Doom 3
half ldotN = dot3( localNormal, lightVector );
half ldotN = saturate( dot3( localNormal, lightVector ) );
#if defined(USE_HALF_LAMBERT)
// RB: http://developer.valvesoftware.com/wiki/Half_Lambert
@ -94,20 +96,8 @@ void main( PS_IN fragment, out PS_OUT result )
#else
half lambert = ldotN;
#endif
const half specularPower = 10.0f;
half hDotN = dot3( normalize( fragment.texcoord6.xyz ), localNormal );
// RB: added abs
half3 specularContribution = _half3( pow( abs( hDotN ), specularPower ) );
half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz;
half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz;
half3 lightColor = lightProj.xyz * lightFalloff.xyz;
half rim = 1.0f - saturate( hDotN );
half rimPower = 16.0f;
half3 rimColor = diffuseColor * lightProj.xyz * lightFalloff.xyz * 1.0f * pow( rim, rimPower ) * fragment.color.rgb;// * halfLdotN;
//
// shadow mapping
//
@ -278,6 +268,133 @@ void main( PS_IN fragment, out PS_OUT result )
//float shadow = texture( samp5, shadowTexcoord.xywz );
#endif
half3 halfAngleVector = normalize( lightVector + viewVector );
half hdotN = saturate( dot3( halfAngleVector, localNormal ) );
#if 1
/*
Physically based shading
Lambert diffuse BRDF combined with Cook-Torrance microfacet specular BRDF
D( h ) * F( v, h ) * G( l, v, h )
f( l, v ) = diffuse + ---------------------------------
4 * ( n * l ) ( n * v )
*/
// RB: compensate r_lightScale 3 and the division of Pi
lambert *= 1.3;
const half3 goldColor = half3( 1.00, 0.71, 0.29 );
//const half3 baseColor = goldColor;
const half3 baseColor = diffuseMap;
const half metallic = 0.0;
// rpDiffuseModifier contains light color
half3 lightColor = lightProj.xyz * lightFalloff.xyz * rpDiffuseModifier.xyz;
half vdotN = saturate( dot3( viewVector, localNormal ) );
half vdotH = saturate( dot3( viewVector, halfAngleVector ) );
// the vast majority of real-world materials (anything not metal or gems) have F(0°) values in a very narrow range (~0.02 - 0.06)
// HACK calculate roughness from D3 gloss maps
// converting from linear to sRGB space give pretty results
const half glossiness = clamp( pow( dot( LUMINANCE_VECTOR.rgb, specMap.rgb ) * 0.4, 1.0 / 2.2 ) * 1.0, 0.0, 0.98 );
const half roughness = 1.0 - glossiness;
// compensate r_lightScale 3 * 2
half3 reflectColor = specMap.rgb * rpSpecularModifier.rgb * 0.5;
// alpha modifications by Disney - s2012_pbs_disney_brdf_notes_v2.pdf
const half alpha = roughness * roughness;
// reduce roughness range from [0 .. 1] to [0.5 .. 1]
const half alphaG = pow( 0.5 + roughness * 0.5, 2.0 );
//half3 D = _half3( pow( abs( hdotN ), 10.0f ) );
half3 D = _half3( Distribution_GGX( hdotN, alpha ) );
//half3 D = _half3( Distribution_GGX_1886( hdotN, alpha ) );
half3 G = _half3( Visibility_Schlick( ldotN, vdotN, alpha ) );
//half3 G = _half3( Visibility_SmithGGX( ldotN, vdotN, alpha ) );
half3 F = Fresnel_Schlick( reflectColor, vdotH );
// horizon
float horizon = 1.0 - ldotN;
horizon *= horizon;
horizon *= horizon;
half3 specLightColor = lightColor.rgb - lightColor.rgb * horizon;
float3 specularColor = saturate( D * G * ( F * ( specLightColor.rgb * lambert ) ) );
//specularColor = EnvironmentBRDFApprox( roughness, vdotN, specularColor.rgb );// * 0.45;
#if 0
result.color = float4( _half3( F ), 1.0 );
return;
#endif
// see http://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
lambert /= PI;
//half3 diffuseColor = mix( diffuseMap, F0, metal ) * rpDiffuseModifier.xyz;
half3 diffuseColor = baseColor * rpDiffuseModifier.xyz;
diffuseColor *= lightColor * lambert;
/*
maintain energy conservation
Energy conservation is a restriction on the reflection model
that requires that the total amount of reflected light
cannot be more than the incoming light.
http://www.rorydriscoll.com/2009/01/25/energy-conservation-in-games/
Cdiff + Cspec <= 1
*/
//diffuseColor.rgb *= ( half3( 1.0 ) - specularColor.rgb );
#if 0 //defined(USE_METALNESS)
//specularColor *= ( 0.96 * metallic ) * diffuseColor + half( 0.04 );
diffuseColor.rgb *= ( 1.0 - metallic );
//diffuseColor.rgb = mix( diffuseColor, specularColor, metallic );
#endif
// apply r_lightScale overbright for both diffuse and specular
result.color.xyz = ( diffuseColor + specularColor ) * fragment.color.rgb * shadow;// + rimColor;
result.color.w = 1.0;
#else
/*
OLD Blinn Phong
*/
const half specularPower = 10.0f;
// RB: added abs
half3 specularContribution = _half3( pow( abs( hdotN ), specularPower ) );
half3 diffuseColor = diffuseMap * rpDiffuseModifier.xyz;
half3 specularColor = specMap.xyz * specularContribution * rpSpecularModifier.xyz;
half3 lightColor = lightProj.xyz * lightFalloff.xyz;
/*
half rim = 1.0f - saturate( hdotN );
half rimPower = 16.0f;
half3 rimColor = diffuseColor * lightProj.xyz * lightFalloff.xyz * 1.0f * pow( rim, rimPower ) * fragment.color.rgb;// * halfLdotN;
*/
result.color.xyz = ( diffuseColor + specularColor ) * lambert * lightColor * fragment.color.rgb * shadow;// + rimColor;
result.color.w = 1.0;
#endif
}

View file

@ -75,22 +75,22 @@ void main( VS_IN vertex, out VS_OUT result ) {
const float w3 = vertex.color2.w;
float4 matX, matY, matZ; // must be float4 for vec4
float joint = vertex.color.x * 255.1 * 3;
int joint = int(vertex.color.x * 255.1 * 3.0);
matX = matrices[int(joint+0)] * w0;
matY = matrices[int(joint+1)] * w0;
matZ = matrices[int(joint+2)] * w0;
joint = vertex.color.y * 255.1 * 3;
joint = int(vertex.color.y * 255.1 * 3.0);
matX += matrices[int(joint+0)] * w1;
matY += matrices[int(joint+1)] * w1;
matZ += matrices[int(joint+2)] * w1;
joint = vertex.color.z * 255.1 * 3;
joint = int(vertex.color.z * 255.1 * 3.0);
matX += matrices[int(joint+0)] * w2;
matY += matrices[int(joint+1)] * w2;
matZ += matrices[int(joint+2)] * w2;
joint = vertex.color.w * 255.1 * 3;
joint = int(vertex.color.w * 255.1 * 3.0);
matX += matrices[int(joint+0)] * w3;
matY += matrices[int(joint+1)] * w3;
matZ += matrices[int(joint+2)] * w3;
@ -177,13 +177,10 @@ void main( VS_IN vertex, out VS_OUT result ) {
//# calculate normalized vector to viewer in R1
float4 toView = normalize( rpLocalViewOrigin - modelPosition );
//# add together to become the half angle vector in object space (non-normalized)
float4 halfAngleVector = toLightLocal + toView;
//# put into texture space
result.texcoord6.x = dot3( tangent, halfAngleVector );
result.texcoord6.y = dot3( bitangent, halfAngleVector );
result.texcoord6.z = dot3( normal, halfAngleVector );
result.texcoord6.x = dot3( tangent, toView );
result.texcoord6.y = dot3( bitangent, toView );
result.texcoord6.z = dot3( normal, toView );
result.texcoord6.w = 1.0f;
result.texcoord7 = modelPosition;