diff --git a/base/renderprogs/BRDF.inc b/base/renderprogs/BRDF.inc new file mode 100644 index 0000000000..f4b15b3d98 --- /dev/null +++ b/base/renderprogs/BRDF.inc @@ -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 . + +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; + +} + + + diff --git a/base/renderprogs/global.inc b/base/renderprogs/global.inc index 51cac2ab2d..342f1b1f3e 100644 --- a/base/renderprogs/global.inc +++ b/base/renderprogs/global.inc @@ -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 ) diff --git a/base/renderprogs/interaction.pixel b/base/renderprogs/interaction.pixel index 741ded3cfc..25357b9c75 100644 --- a/base/renderprogs/interaction.pixel +++ b/base/renderprogs/interaction.pixel @@ -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 } diff --git a/base/renderprogs/interaction.vertex b/base/renderprogs/interaction.vertex index f42c4dd8a4..f7232a44cc 100644 --- a/base/renderprogs/interaction.vertex +++ b/base/renderprogs/interaction.vertex @@ -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 ) diff --git a/base/renderprogs/interactionSM.pixel b/base/renderprogs/interactionSM.pixel index db40f3acce..260d625d76 100644 --- a/base/renderprogs/interactionSM.pixel +++ b/base/renderprogs/interactionSM.pixel @@ -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 } diff --git a/base/renderprogs/interactionSM.vertex b/base/renderprogs/interactionSM.vertex index b443295ce5..5f42507c93 100644 --- a/base/renderprogs/interactionSM.vertex +++ b/base/renderprogs/interactionSM.vertex @@ -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;