Skip to content

Commit

Permalink
Implemented experimental direct physically based shading.
Browse files Browse the repository at this point in the history
The implementation is similar to the one used in Marmoset Toolbag 2

See blog.selfshadow.com/publications/s2013-shading-course/ for more
information.
  • Loading branch information
RobertBeckebans committed Sep 27, 2014
1 parent 0d3e473 commit e9e0fd3
Show file tree
Hide file tree
Showing 6 changed files with 406 additions and 39 deletions.
135 changes: 135 additions & 0 deletions base/renderprogs/BRDF.inc
Original file line number Diff line number Diff line change
@@ -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;

}



2 changes: 2 additions & 0 deletions base/renderprogs/global.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
135 changes: 127 additions & 8 deletions base/renderprogs/interaction.pixel
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ 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
uniform sampler2D samp2 : register(s2); // texture 3 is the light projection texture
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;
Expand All @@ -47,18 +49,21 @@ 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 );
half4 YCoCG = tex2D( samp3, fragment.texcoord4.xy );
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;
Expand All @@ -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
Expand All @@ -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
}
9 changes: 3 additions & 6 deletions base/renderprogs/interaction.vertex
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down

0 comments on commit e9e0fd3

Please sign in to comment.