From a78bbc9de34c0d4c8a107b70de08d893e7df4353 Mon Sep 17 00:00:00 2001 From: Antoine Pilote Date: Tue, 27 Jan 2026 10:28:56 -0800 Subject: [PATCH] Better foliage and bark shader (#3915) * Improved foliage shader - Added rim lighting - Cheap transmissivenes - Grazing cards fade out - Wrapped lighting - Backface darkening - Backface shading - Distance fade of shading features https://files.facepunch.com/antopilo/1b2611b1/sbox-dev_CNoLJhw3ms.mp4 * Tweaked some stuff https://files.facepunch.com/antopilo/1b2611b1/sbox-dev_C9EcKZCojJ.mp4 * use precompueted dist * Made warp lighting ignore shadow * Improved trunk rendering - Specular Occlusion - Trunk bending animation - Syncs with foliage animation https://files.facepunch.com/antopilo/1b2711b1/sbox-dev_WoM5FlFzSn.mp4 --- game/addons/base/Assets/shaders/bark.shader | 93 ++++++ .../Assets/shaders/common/trunk_bending.hlsl | 22 ++ .../addons/base/Assets/shaders/foliage.shader | 287 +++++++++++++----- 3 files changed, 321 insertions(+), 81 deletions(-) create mode 100644 game/addons/base/Assets/shaders/bark.shader create mode 100644 game/addons/base/Assets/shaders/common/trunk_bending.hlsl diff --git a/game/addons/base/Assets/shaders/bark.shader b/game/addons/base/Assets/shaders/bark.shader new file mode 100644 index 00000000..9be61bec --- /dev/null +++ b/game/addons/base/Assets/shaders/bark.shader @@ -0,0 +1,93 @@ +HEADER +{ + Description = "Bark shader for trees, syncs with foliage.shader"; + DevShader = true; +} + +FEATURES +{ + #include "common/features.hlsl" + Feature( F_BARK_ANIMATION, 0..1, "Bark Animation" ); +} + +MODES +{ + Forward(); + Depth( S_MODE_DEPTH ); + ToolsShadingComplexity( "vr_tools_shading_complexity.shader" ); +} + +COMMON +{ + #include "common/shared.hlsl" +} + +struct VertexInput +{ + #include "common/vertexinput.hlsl" +}; + +struct PixelInput +{ + #include "common/pixelinput.hlsl" +}; + +VS +{ + #include "common/vertex.hlsl" + #include "common/trunk_bending.hlsl" + + StaticCombo( S_BARK_ANIMATION, F_BARK_ANIMATION, Sys( ALL ) ); + +#if S_BARK_ANIMATION + float g_flSwayStrength < Default( 1.0 ); Range( 0.0, 25.0 ); UiGroup( "Bark Animation" ); >; + float g_flSwaySpeed < Default( 1.0 ); Range( 0.0, 10.0 ); UiGroup( "Bark Animation" ); >; +#endif + + PixelInput MainVs( VertexInput i ) + { + PixelInput o = ProcessVertex( i ); + +#if S_BARK_ANIMATION + float3 vPositionOs = i.vPositionOs.xyz; + float3 vWind = g_vWindDirection.xyz * g_vWindStrengthFreqMulHighStrength.x; + + ApplyTrunkBending( vPositionOs, g_flSwayStrength, g_flSwaySpeed, vWind, g_flTime ); + + float3x4 matObjectToWorld = GetTransformMatrix( i.nInstanceTransformID ); + o.vPositionWs = mul( matObjectToWorld, float4( vPositionOs, 1.0 ) ); + o.vPositionPs = Position3WsToPs( o.vPositionWs.xyz ); +#endif + + return FinalizeVertex( o ); + } +} + +PS +{ + #include "common/utils/Material.CommonInputs.hlsl" + #include "common/pixel.hlsl" + + RenderState( CullMode, F_RENDER_BACKFACES ? NONE : DEFAULT ); + + float g_flSpecularOcclusion < Default( 0.5 ); Range( 0.0, 1.0 ); UiGroup( "Bark" ); >; + + // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf (page 77) + float ComputeSpecularOcclusion( float NdotV, float ao, float roughness ) + { + return saturate( pow( NdotV + ao, exp2( -16.0 * roughness - 1.0 ) ) - 1.0 + ao ); + } + + float4 MainPs( PixelInput i ) : SV_Target0 + { + Material m = Material::From( i ); + + // Specular occlusion + float3 viewDir = normalize( g_vCameraPositionWs - m.WorldPosition ); + float NdotV = saturate( dot( m.Normal, viewDir ) ); + float specOcc = ComputeSpecularOcclusion( NdotV, m.AmbientOcclusion, m.Roughness ); + m.AmbientOcclusion *= lerp( 1.0, specOcc, g_flSpecularOcclusion ); + + return ShadingModelStandard::Shade( i, m ); + } +} diff --git a/game/addons/base/Assets/shaders/common/trunk_bending.hlsl b/game/addons/base/Assets/shaders/common/trunk_bending.hlsl new file mode 100644 index 00000000..69fa0b73 --- /dev/null +++ b/game/addons/base/Assets/shaders/common/trunk_bending.hlsl @@ -0,0 +1,22 @@ +#ifndef TRUNK_BENDING_HLSL +#define TRUNK_BENDING_HLSL + +// Trunk/branch bending for vegetation +// Based on GPU Gems 3 Chapter 16: "Vegetation Procedural Animation and Shading in Crysis" +// https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch16.html +void ApplyTrunkBending( inout float3 vPositionOs, float flStrength, float flSpeed, float3 vWind, float flTime ) +{ + // Height bend factor + float flHeight = vPositionOs.z; + float flBendFactor = flHeight * flStrength * 0.0001; + flBendFactor *= flBendFactor; // Quadratic falloff + + float3 vWindDir = length( vWind ) > 0.01 ? normalize( vWind ) : float3( 1, 0, 0 ); + float flWindStrength = length( vWind ); + + float flSway = sin( flTime * flSpeed ) + sin( flTime * flSpeed * 0.7 ) * 0.5; + + vPositionOs.xy += vWindDir.xy * flBendFactor * ( flWindStrength + flSway ); +} + +#endif diff --git a/game/addons/base/Assets/shaders/foliage.shader b/game/addons/base/Assets/shaders/foliage.shader index 8ae59d33..090fc6a4 100644 --- a/game/addons/base/Assets/shaders/foliage.shader +++ b/game/addons/base/Assets/shaders/foliage.shader @@ -17,8 +17,7 @@ FEATURES Feature( F_ALPHA_TEST, 0..1, "Rendering" ); Feature( F_FOLIAGE_ANIMATION, 0..1( 0 = "None", 1 = "Vertex Color Based" ), "Foliage Animation" ); Feature( F_TRANSMISSIVE, 0..1, "Rendering" ); - Feature( F_TRANSMISSIVE_BACKFACE_NDOTL, 0..1, "Rendering" ); - FeatureRule( Requires1( F_TRANSMISSIVE_BACKFACE_NDOTL, F_TRANSMISSIVE ), "Requires transmissive" ); + Feature( F_GRAZING_FADE, 0..1, "Rendering" ); } //========================================================================================================================= @@ -27,7 +26,7 @@ FEATURES MODES { Forward(); // Indicates this shader will be used for main rendering - Depth(); + Depth( S_MODE_DEPTH ); ToolsShadingComplexity( "vr_tools_shading_complexity.shader" ); // Shows how expensive drawing is in debug view } @@ -60,39 +59,41 @@ struct PixelInput VS { #include "common/vertex.hlsl" + #include "common/trunk_bending.hlsl" StaticCombo( S_FOLIAGE_ANIMATION, F_FOLIAGE_ANIMATION, Sys( ALL ) ); // Vertex Color #if S_FOLIAGE_ANIMATION == 1 - float g_flEdgeFrequency < Default( 0.33 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation" ); >; - float g_flEdgeAmplitude < Default( 0.3 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation" ); >; - float g_flBranchFrequency < Default( 0.33 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation" ); >; - float g_flBranchAmplitude < Default( 0.3 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation" ); >; - float g_flTrunkDeflection < Default( 0.0 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation" ); >; - float g_flTrunkDeflectionStart < Default( 0.0 ); Range( 0.0, 1000.0 ); UiGroup( "Foliage Animation" ); >; + float g_flEdgeFrequency < Default( 1.0 ); Range( 0.1, 5.0 ); UiGroup( "Foliage Animation,10/Detail" ); >; + float g_flEdgeAmplitude < Default( 0.1 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation,10/Detail" ); >; + float g_flBranchFrequency < Default( 0.5 ); Range( 0.1, 5.0 ); UiGroup( "Foliage Animation,10/Detail" ); >; + float g_flBranchAmplitude < Default( 0.1 ); Range( 0.0, 1.0 ); UiGroup( "Foliage Animation,10/Detail" ); >; + + // Trunk bending + float g_flSwayStrength < Default( 1.0 ); Range( 0.0, 25.0 ); UiGroup( "Foliage Animation,20/Trunk" ); >; + float g_flSwaySpeed < Default( 1.0 ); Range( 0.0, 10.0 ); UiGroup( "Foliage Animation,20/Trunk" ); >; float4 SmoothCurve( float4 x ) - { - return x * x * ( 3.0 - 2.0 * x ); - } + { + return x * x * ( 3.0 - 2.0 * x ); + } float4 TriangleWave( float4 x ) { - return abs( frac( x + 0.5 ) * 2.0 - 1.0 ); - } + return abs( frac( x + 0.5 ) * 2.0 - 1.0 ); + } float4 SmoothTriangleWave( float4 x ) - { - return SmoothCurve( TriangleWave( x ) ); + { + return SmoothCurve( TriangleWave( x ) ); } // High-frequency displacement used in Unity's TerrainEngine; based on "Vegetation Procedural Animation and Shading in Crysis" // http://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch16.html void FoliageDetailBending( inout float3 vPositionOs, float3 vNormalOs, float3 vVertexColor, float3x4 matObjectToWorld, float3 vWind ) { - // 1.975, 0.793, 0.375, 0.193 are good frequencies const float4 vFoliageFreqs = float4( 1.975, 0.793, 0.375, 0.193 ); // Attenuation and phase offset is encoded in the vertex color @@ -101,7 +102,6 @@ VS const float flDetailPhase = vVertexColor.b; // Material defined frequency and amplitude - const float2 vTime = g_flTime.xx * float2( g_flEdgeFrequency, g_flBranchFrequency ); const float flEdgeAmp = g_flEdgeAmplitude; const float flBranchAmp = g_flBranchAmplitude; @@ -110,50 +110,24 @@ VS float flBranchPhase = flDetailPhase + flObjPhase; float flVtxPhase = dot( vPositionOs.xyz, flDetailPhase + flBranchPhase ); - // fmod max phase avoid imprecision at high phases const float maxPhase = 50000.0f; - // x is used for edges; y is used for branches - float2 vWavesIn = ( vTime.xy + fmod( float2( flVtxPhase, flBranchPhase ), maxPhase ) ) * length( vWind ); - - float4 vWaves = ( frac( vWavesIn.xxyy * vFoliageFreqs ) * 2.0 - 1.0 ); + float2 vTime = g_flTime * float2( g_flEdgeFrequency, g_flBranchFrequency ); + float2 vPhaseOffset = fmod( float2( flVtxPhase, flBranchPhase ), maxPhase ); + float2 vWavesIn = vTime + vPhaseOffset; + + float4 vWaves = frac( vWavesIn.xxyy * vFoliageFreqs ) * 2.0 - 1.0; vWaves = SmoothTriangleWave( vWaves ); float2 vWavesSum = vWaves.xz + vWaves.yw; - // Edge (xy) and branch bending (z) - float flBranchWindBend = 1.0f - abs( dot( normalize( vWind.xyz ), normalize( float3( vPositionOs.xy, 0.0f ) ) ) ); + float flWindIntensity = saturate( length( vWind ) * 0.2 ); + + float flBranchWindBend = 1.0f - abs( dot( normalize( vWind.xyz + 0.001 ), normalize( float3( vPositionOs.xy, 0.0f ) + 0.001 ) ) ); flBranchWindBend *= flBranchWindBend; - vPositionOs.xyz += vWavesSum.x * flEdgeAtten * flEdgeAmp * vNormalOs.xyz; - vPositionOs.xyz += vWavesSum.y * flBranchAtten * flBranchAmp * float3( 0.0f, 0.0f, 1.0f ); - vPositionOs.xyz += vWavesSum.y * flBranchAtten * flBranchAmp * flBranchWindBend * vWind.xyz; - } - - // Main vegetation bending - displace verticies' xy positions along the wind direction - // using normalized height to scale the amount of deformation. - void FoliageMainBending( inout float3 vPositionOs, float3 vWind ) - { - if ( g_flTrunkDeflection <= 0.0 ) return; - - float flLength = length( vPositionOs.xyz ); - // Bend factor - Wind variation is done on the CPU. - float flBF = 0.01f * max( vPositionOs.z - g_flTrunkDeflectionStart, 0 ) * g_flTrunkDeflection; - // Smooth bending factor and increase its nearby height limit. - flBF += 1.0f; - flBF *= flBF; - flBF = flBF * flBF - flBF; - - // Back and forth - float flBend = pow( max( 0.0f, length( vWind ) - 1.0f ) / 4.0f, 2 ) * 4.0f; - flBend = flBend + 0.7f * sqrt( flBend ) * sin( g_flTime ); - flBF *= flBend; - - // Displace position - float3 vNewPos = vPositionOs; - vNewPos.xy += vWind.xy * flBF; - - // Rescale (reduces stretch) - vPositionOs.xyz = normalize( vNewPos.xyz ) * flLength; + vPositionOs.xyz += vWavesSum.x * flEdgeAtten * flEdgeAmp * flWindIntensity * vNormalOs.xyz; + vPositionOs.xyz += vWavesSum.y * flBranchAtten * flBranchAmp * flWindIntensity * float3( 0.0f, 0.0f, 1.0f ); + vPositionOs.xyz += vWavesSum.y * flBranchAtten * flBranchAmp * flBranchWindBend * flWindIntensity * vWind.xyz; } #endif // @@ -177,8 +151,11 @@ VS #if ( S_FOLIAGE_ANIMATION == 1 ) float3 vWind = g_vWindDirection.xyz * g_vWindStrengthFreqMulHighStrength.x; + // trunk bending + ApplyTrunkBending( vPositionOs, g_flSwayStrength, g_flSwaySpeed, vWind, g_flTime ); + + // detail bending on top FoliageDetailBending( vPositionOs, vNormalOs, i.vColor.xyz, matObjectToWorld, vWind ); - FoliageMainBending( vPositionOs, vWind ); #endif o.vPositionWs = mul( matObjectToWorld, float4( vPositionOs.xyz, 1.0 ) ); @@ -193,36 +170,184 @@ VS PS { - - #include "common/utils/Material.CommonInputs.hlsl" - #include "common/pixel.hlsl" - - StaticCombo( S_ALPHA_TEST, F_ALPHA_TEST, Sys( ALL ) ); - StaticCombo( S_TRANSMISSIVE, F_TRANSMISSIVE, Sys( ALL ) ); - StaticCombo( S_TRANSMISSIVE_BACKFACE_NDOTL, F_TRANSMISSIVE_BACKFACE_NDOTL, Sys( ALL ) ); + #include "common/utils/Material.CommonInputs.hlsl" + #include "common/pixel.hlsl" + #include "common/classes/Light.hlsl" - #if S_ALPHA_TEST - TextureAttribute( LightSim_Opacity_A, g_tColor ); - #endif + StaticCombo( S_ALPHA_TEST, F_ALPHA_TEST, Sys( ALL ) ); + StaticCombo( S_TRANSMISSIVE, F_TRANSMISSIVE, Sys( ALL ) ); + StaticCombo( S_GRAZING_FADE, F_GRAZING_FADE, Sys( ALL ) ); - #if S_TRANSMISSIVE - CreateInputTexture2D( TextureTransmissiveColor, Srgb, 8, "", "_color", "Material,10/60", Default3( 1.0, 1.0, 1.0 ) ); - Texture2D g_tTransmissiveColor < Channel( RGB, Box( TextureTransmissiveColor ), Srgb ); OutputFormat( BC7 ); SrgbRead( true ); >; - #endif + RenderState( CullMode, F_RENDER_BACKFACES ? NONE : DEFAULT ); + RenderState( AlphaToCoverageEnable, false ); - RenderState( CullMode, F_RENDER_BACKFACES ? NONE : DEFAULT ); + #if ( S_MODE_DEPTH == 0 ) + RenderState( DepthFunc, EQUAL ); + RenderState( DepthWriteEnable, false ); + #endif + + #if S_ALPHA_TEST + TextureAttribute( LightSim_Opacity_A, g_tColor ); + float g_flAlphaDistanceStart < Default( 500.0 ); Range( 0.0, 5000.0 ); UiGroup( "Alpha Test" ); >; + float g_flAlphaDistanceEnd < Default( 2000.0 ); Range( 0.0, 10000.0 ); UiGroup( "Alpha Test" ); >; + #endif + + float g_flWrapAmount < Default( 0.5 ); Range( 0.0, 1.0 ); UiGroup( "Foliage" ); >; + float g_flWrapStrength < Default( 0.3 ); Range( 0.0, 1.0 ); UiGroup( "Foliage" ); >; + + float g_flRimStrength < Default( 0.0 ); Range( 0.0, 2.0 ); UiGroup( "Foliage" ); >; + float g_flRimPower < Default( 4.0 ); Range( 1.0, 8.0 ); UiGroup( "Foliage" ); >; + + float g_flBackfaceDarkening < Default( 0.7 ); Range( 0.0, 1.0 ); UiGroup( "Foliage" ); >; + float g_flAmbientBoost < Default( 0.0 ); Range( 0.0, 0.5 ); UiGroup( "Foliage" ); >; + float g_flDetailFadeDistance < Default( 500.0 ); Range( 100.0, 2000.0 ); UiGroup( "Foliage" ); >; + float g_flMinRoughness < Default( 0.5 ); Range( 0.0, 1.0 ); UiGroup( "Foliage" ); >; + float g_flNormalVariation < Default( 0.1 ); Range( 0.0, 0.5 ); UiGroup( "Foliage" ); >; + float g_flGrassNormalUp < Default( 0.0 ); Range( 0.0, 1.0 ); UiGroup( "Foliage" ); >; + + + #if S_GRAZING_FADE + float g_flGrazingFadeStart < Default( 0.5 ); Range( 0.0, 1.0 ); UiGroup( "Grazing Fade" ); >; + float g_flGrazingFadeEnd < Default( 0.1 ); Range( 0.0, 1.0 ); UiGroup( "Grazing Fade" ); >; + #endif + + #if S_TRANSMISSIVE + CreateInputTexture2D( TextureTransmissiveColor, Srgb, 8, "", "_color", "Material,10/60", Default3( 1.0, 1.0, 1.0 ) ); + Texture2D g_tTransmissiveColor < Channel( RGB, Box( TextureTransmissiveColor ), Srgb ); OutputFormat( BC7 ); SrgbRead( true ); >; + float g_flTransmissionScale < Default( 1.0 ); Range( 0.0, 10.0 ); UiGroup( "Transmissive" ); >; + + void ApplyTranslucency( inout Material m, Light light, float3 viewDir, float3 transmissiveColor ) + { + float3 lightThrough = normalize( light.Direction + m.Normal * 0.2 ); + float backlit = saturate( dot( viewDir, -lightThrough ) ); + backlit *= backlit; + m.Emission += transmissiveColor * m.Albedo * light.Color * light.Visibility * backlit * g_flTransmissionScale * light.Attenuation; + } + #endif + + #if S_ALPHA_TEST + void ApplyAlphaTest( inout Material m, float dist ) + { + float distFactor = saturate( ( dist - g_flAlphaDistanceStart ) / max( g_flAlphaDistanceEnd - g_flAlphaDistanceStart, 0.001 ) ); + float alphaRef = lerp( g_flAlphaTestReference, 0.1, distFactor ); + float sharpness = saturate( ( m.Opacity - alphaRef ) / max( fwidth( m.Opacity ), 0.0001 ) + 0.5 ); + clip( sharpness - 0.5 ); + m.Opacity = 1.0; + } + #endif + + #if S_GRAZING_FADE + void ApplyGrazingAngleFade( float3 positionWs, float3 viewDir, float2 screenPos ) + { + float3 geometricNormal = normalize( cross( ddx( positionWs ), ddy( positionWs ) ) ); + float NdotV = abs( dot( geometricNormal, viewDir ) ); + float fade = saturate( ( NdotV - g_flGrazingFadeEnd ) / max( g_flGrazingFadeStart - g_flGrazingFadeEnd, 0.001 ) ); + + const float4x4 bayer = float4x4( + 0.0/16.0, 8.0/16.0, 2.0/16.0, 10.0/16.0, + 12.0/16.0, 4.0/16.0, 14.0/16.0, 6.0/16.0, + 3.0/16.0, 11.0/16.0, 1.0/16.0, 9.0/16.0, + 15.0/16.0, 7.0/16.0, 13.0/16.0, 5.0/16.0 + ); + + int2 pixel = int2( screenPos ) % 4; + clip( fade - bayer[pixel.x][pixel.y] ); + } + #endif + + // https://developer.nvidia.com/gpugems/gpugems/part-iii-materials/chapter-16-real-time-approximations-subsurface-scattering + void ApplyWrappedLighting( inout Material m, Light light, float lightScale ) + { + if ( g_flWrapStrength <= 0.0 ) + return; + + float NdotL = dot( m.Normal, light.Direction ); + float wrapped = saturate( ( NdotL + g_flWrapAmount ) / ( 1.0 + g_flWrapAmount ) ); + float wrapContribution = wrapped - saturate( NdotL ); + + if ( wrapContribution > 0.0 ) + m.Emission += m.Albedo * light.Color * wrapContribution * g_flWrapStrength * lightScale; + } + + // https://www.ronja-tutorials.com/post/012-fresnel/ + void ApplyRimLighting( inout Material m, float3 viewDir ) + { + if ( g_flRimStrength <= 0.0 ) + return; + + float rim = 1.0 - saturate( dot( m.Normal, viewDir ) ); + rim = pow( rim, g_flRimPower ); + m.Emission += m.Albedo * rim * g_flRimStrength; + } - // - // Main - // float4 MainPs( PixelInput i ) : SV_Target0 { - Material m = Material::From( i ); + Material m = Material::From( i ); - #if ( S_TRANSMISSIVE ) - m.Transmission = g_tTransmissiveColor.Sample( TextureFiltering, i.vTextureCoords.xy ).rgb; - #endif - - return ShadingModelStandard::Shade( i, m ); + // Specular occlusion + m.Roughness = max( m.Roughness, g_flMinRoughness ); + + // Grass normal + m.Normal = normalize( lerp( m.Normal, float3( 0, 0, 1 ), g_flGrassNormalUp ) ); + + // Normal variation + if ( g_flNormalVariation > 0.0 ) + { + float2 uv = floor( m.TextureCoords * 2.0 ); + float hash = frac( sin( dot( uv, float2( 12.9898, 78.233 ) ) ) * 43758.5453 ); + float angle = hash * 6.283; + float2 offset = float2( cos( angle ), sin( angle ) ); + m.Normal = normalize( m.Normal + m.WorldTangentU * offset.x * g_flNormalVariation + m.WorldTangentV * offset.y * g_flNormalVariation ); + } + + float3 viewDir = normalize( g_vCameraPositionWs - m.WorldPosition ); + float dist = length( i.vPositionWithOffsetWs.xyz ); + bool closeUp = dist < g_flDetailFadeDistance * 10.0f; + + bool isBackface = dot( m.Normal, viewDir ) < 0.0; + if ( closeUp && isBackface ) + { + m.Normal = -m.Normal; + + m.Albedo *= g_flBackfaceDarkening; + } + + #if S_GRAZING_FADE + ApplyGrazingAngleFade( m.WorldPosition, viewDir, m.ScreenPosition.xy ); + #endif + + #if S_TRANSMISSIVE + float3 transmissiveColor = g_tTransmissiveColor.Sample( TextureFiltering, i.vTextureCoords.xy ).rgb; + float transmission = dot( transmissiveColor, float3( 0.299, 0.587, 0.114 ) ); + #else + float transmission = 0.0; + #endif + + #if S_ALPHA_TEST + ApplyAlphaTest( m, dist ); + #endif + + uint lightCount = Light::Count( m.WorldPosition ); + if ( lightCount > 0 ) + { + Light light = Light::From( m.WorldPosition, 0, i.vLightmapUV ); + float wrapScale = light.Attenuation; // Wrap lighting ignores shadows + float lightScale = light.Attenuation * light.Visibility; + + ApplyWrappedLighting( m, light, wrapScale ); + + #if S_TRANSMISSIVE + if ( closeUp ) + ApplyTranslucency( m, light, viewDir, transmissiveColor ); + #endif + } + + if ( closeUp ) + ApplyRimLighting( m, viewDir ); + + if ( g_flAmbientBoost > 0.0 ) + m.Emission += m.Albedo * g_flAmbientBoost; + + return ShadingModelStandard::Shade( i, m ); } -} \ No newline at end of file +}