//========== Copyright (c) Valve Corporation, All rights reserved. ==========// // STATIC: "MODE" "0..3" // STATIC: "PALETTIZE" "0..1" // SKIP: ( $MODE == 1 ) #include "common_ps_fxc.h" #include "shader_constant_register_map.h" #if ( PALETTIZE == 1 ) const int g_nColors : register( c0 ); #else #define g_nColors 3 #endif const float g_flColorGamma : register( c1 ); sampler sBaseTexture : register( s0 ); sampler sEmbroideryNormal : register( s1 ); sampler sBackingNormal : register( s2 ); sampler sSphereNormal : register( s3 ); sampler sSampleOffset : register( s4 ); #define g_edgeNormalScale 512.0f #define g_detailScale 4.0f #define g_lum float3( 0.299, 0.587, 0.114 ) struct PS_INPUT { float2 vTexCoord : TEXCOORD0; }; float3 posterColor( float3 ic, float colorGamma, int nColors ) { ic = pow(ic, colorGamma); ic = ic * nColors; ic = floor(ic); ic = ic / nColors; ic = pow(ic,1.0/colorGamma); // levels so there's no black or white ic = ic * 0.93726 + 0.03137; return ic; } float4 maxColor( float4 c1, float4 c2 ) { if ( dot( c1.rgb, g_lum ) > dot ( c2.rgb, g_lum ) ) { return c1; } return c2; } float4_color_return_type main( PS_INPUT i ) : COLOR { float2 vOffsets[16] = { float2( -0.00160851f, -0.006003045f ), float2( 0.00195312f, -0.005074365f ), float2( 0.006003045f, -0.00160851f ), float2( -0.001072344f, -0.00400203f ), float2( 0.0014648475f, -0.00253719f ), float2( 0.00400203f, -0.001072344f ), float2( -0.005074365f, -0.00195312f ), float2( -0.00253719f, -0.0014648475f ), float2( 0.00253719f, 0.0014648475f ), float2( 0.005074365f, 0.00195312f ), float2( -0.00400203f, 0.001072344f ), float2( -0.0014648475f, 0.00253719f ), float2( 0.001072344f, 0.00400203f ), float2( -0.006003045f, 0.00160851f ), float2( -0.00195312f, 0.005074365f ), float2( 0.00107234f, 0.00400203f ) }; float4 texCol = tex2D( sBaseTexture, i.vTexCoord ); float3 tc = posterColor( texCol.rgb, g_flColorGamma, g_nColors ); // derive an arbitrary rotation for stitch direction from the posterized color intensity using hue float rotation = -atan2( 1.732f * ( tc.g - tc.b ), 2 * ( tc.r - tc.g - tc.b ) ); float cr, sr; sincos( rotation, cr, sr ); float2 tiledTexCoord = ( i.vTexCoord * g_detailScale ); float2 embroideryTexCoordRotated; embroideryTexCoordRotated.x = tiledTexCoord.x * cr - tiledTexCoord.y * sr; embroideryTexCoordRotated.y = tiledTexCoord.x * sr + tiledTexCoord.y * cr; // Let's do that again, this time using the original result for perturbing the UVs so the stitches are extended slightly across color // edge boundaries float2 vTexCoord = i.vTexCoord; float2 texOffset = ( 0.015625 * ( tex2D( sSampleOffset, embroideryTexCoordRotated.xy ) * 2.0f - 1.0f ) ); texOffset.x = texOffset.x * cr - texOffset.y * sr; texOffset.y = texOffset.x * sr + texOffset.y * cr; vTexCoord += texOffset; texCol = tex2D( sBaseTexture, vTexCoord ); tc = posterColor( texCol.rgb, g_flColorGamma, g_nColors ); float alpha = step( 0.1f, texCol.a ); // noise reduction at color edge boundaries float4 minCol = texCol; int sampleCount = 0; int k = 0; for ( k = 0; k < 16; k = k + 2 ) { float4 sampleTexCol = tex2D( sBaseTexture, vTexCoord + vOffsets[k] ); float3 oc = posterColor( sampleTexCol.rgb, g_flColorGamma, g_nColors ); bool isColorEdge = ( length( tc - oc ) > 0.001 ); sampleCount += isColorEdge; minCol = maxColor( minCol, sampleTexCol ); } if ( sampleCount > 3 ) { tc = posterColor( minCol, g_flColorGamma, g_nColors ); texCol = minCol; } // Embroidery stitch normal float4 embroideryNormSample = tex2D( sEmbroideryNormal, embroideryTexCoordRotated ); float embroiderAlpha = embroideryNormSample.a; rotation = -atan2( 1.732f * ( tc.g - tc.b ), 2 * ( tc.r - tc.g - tc.b ) ); sincos( rotation, cr, sr ); embroideryTexCoordRotated.x = tiledTexCoord.x * cr - tiledTexCoord.y * sr; embroideryTexCoordRotated.y = tiledTexCoord.x * sr + tiledTexCoord.y * cr; embroideryNormSample = tex2D( sEmbroideryNormal, embroideryTexCoordRotated ); embroiderAlpha = embroideryNormSample.a; float stitchingBlend = pow( embroiderAlpha, 0.25f ) * ( sampleCount < 2 ); // mask alpha //rotation = -atan2( 1.732f * ( tc.g - tc.b ), 2 * ( tc.r - tc.g - tc.b ) ); #if ( MODE == 3 ) // anisodir // return anisotropy direction and an alpha that defines where the aniso highlights do not appear (where we have backing // not stitching) return float4( cr * 0.5f + 0.5f, sr * 0.5f + 0.5f, 0.0f, stitchingBlend * pow( embroiderAlpha, 8.0f ) ); #endif float ao = 1.0f; float colorEdgeSamples = 1.0f; float2 colorEdgeNormal = float2( 0.0f, 0.0f ); #if ( MODE == 2 ) float2 alphaEdgeNormal = float2( 0.0f, 0.0f ); float alphaEdgeSamples = 1.0f; #endif for ( k = 0; k < 16; k++ ) { float4 sampleTexCol = tex2D( sBaseTexture, vTexCoord + vOffsets[k] * 1.5f ); float3 oc = posterColor( sampleTexCol.rgb, g_flColorGamma, g_nColors ); bool isColorEdge = ( length( tc - oc ) < 0.3f ); // edges between color islands colorEdgeSamples += isColorEdge; colorEdgeNormal -= isColorEdge * vOffsets[k]; #if ( MODE == 2 ) float sampleAlpha = sampleTexCol.a; bool isAlphaEdge = length( step( 0.1f, sampleAlpha ) - alpha ) < 0.01; // outer edges of alpha alphaEdgeSamples += isAlphaEdge; alphaEdgeNormal -= isAlphaEdge * vOffsets[k]; #endif } colorEdgeNormal /= colorEdgeSamples; colorEdgeNormal *= g_edgeNormalScale; #if ( MODE == 2 ) alphaEdgeNormal /= alphaEdgeSamples; alphaEdgeNormal *= g_edgeNormalScale; #endif #if ( MODE == 2 ) // bump embroideryNormSample.xyz = embroideryNormSample.xyz * 2.0f - 1.0f; float3 texNorm = embroideryNormSample.xyz; // because we rotated the texcoord, we must also rotate the normal texNorm.x = embroideryNormSample.x * cr - embroideryNormSample.y * sr; texNorm.y = embroideryNormSample.x * sr + embroideryNormSample.y * cr; #endif #if ( MODE == 0 ) // diffuse // generate ambient occlusion from stitching and posterized color island edges ao *= 1.0f - abs( colorEdgeNormal.x ) - abs( colorEdgeNormal.y ); ao = pow( embroiderAlpha, 1.0f - ao ); ao = ao * ao * ao; ao *= embroiderAlpha; // diffuse color #if ( PALETTIZE == 0 ) { tc = texCol.rgb; } #endif float3 baseColor = texCol.rgb; // color burn the ao baseColor = lerp( baseColor * baseColor, baseColor, ao ); //baseColor.rgb = lerp( float3( 0.0f, 0.0f, 0.0f ), baseColor.rgb, alpha ); float dropShadow = alpha; for ( k = 0; k < 16; k++ ) { float4 sampleTexCol = tex2D( sBaseTexture, vTexCoord + vOffsets[k] * 3.0f ); dropShadow += step( 0.1f, sampleTexCol.a ); } dropShadow = dropShadow / 16.0f; alpha = lerp( dropShadow, pow( embroiderAlpha, 0.75f ) + 0.1f, alpha ); return float4( baseColor, alpha ); // diffuse #endif #if ( MODE == 2 ) // Spherical normal gives the patch a bit of puffiness so that the anisotropic highlights in the final use-case // have some shape and orientation float4 sphericalNormSample = tex2D( sSphereNormal, i.vTexCoord ); float3 sphericalNorm = sphericalNormSample.xyz * 2.0f - 1.0f; texNorm.xy += sphericalNorm.xy * 0.5f; // use full range in texture for precision, tone it down here. // Add influence of posterized color normals and alpha edges texNorm.xy += lerp( alphaEdgeNormal.xy, colorEdgeNormal.xy, stitchingBlend ); texNorm.xy += alphaEdgeNormal.xy; texNorm = normalize( texNorm ); texNorm = texNorm * 0.5f + 0.5f; // output normal return float4( texNorm, ao ); #endif return 0; }