Shader "Hidden/ProjectM/PixelOutline" { Properties { [ToggleUI] _MasterEnabled ("Master (whole effect)", Float) = 1 _PixelHeight ("Pixel Grid Height", Float) = 200 [ToggleUI] _PosterizeEnabled ("Posterize (banded color)", Float) = 1 _ColorLevels ("Posterize Levels / channel", Range(2,64)) = 12 _Brightness ("Brightness", Range(0.5,2)) = 1.1 _Contrast ("Contrast", Range(0.5,2)) = 1.05 _Saturation ("Saturation", Range(0,2)) = 1.35 _DepthThreshold ("Depth Threshold (relative)", Range(0,1)) = 0.08 _NormalThreshold ("Normal Threshold", Range(0,4)) = 1.0 _OutlineThickness ("Outline Thickness (texels)", Range(0.25,4)) = 1 _EdgeStrength ("Edge Strength", Range(0,1)) = 1 [HDR] _OutlineColorOuter ("Outline Color - Depth/Silhouette", Color) = (0,0,0,1) [HDR] _OutlineColorInner ("Outline Color - Normal/Detail", Color) = (0,0,0,1) [ToggleUI] _PixelateEnabled ("Pixelate", Float) = 1 [ToggleUI] _DepthEdgesEnabled ("Depth Edges", Float) = 1 [ToggleUI] _NormalEdgesEnabled ("Normal Edges", Float) = 1 } SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } ZWrite Off ZTest Always Cull Off Pass { Name "PixelOutline" HLSLPROGRAM #pragma vertex Vert #pragma fragment Frag #pragma target 3.5 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl" #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" CBUFFER_START(UnityPerMaterial) float _MasterEnabled; float _PixelHeight; float _PosterizeEnabled; float _ColorLevels; float _Brightness; float _Contrast; float _Saturation; float _DepthThreshold; float _NormalThreshold; float _OutlineThickness; float _EdgeStrength; float4 _OutlineColorOuter; float4 _OutlineColorInner; float _PixelateEnabled; float _DepthEdgesEnabled; float _NormalEdgesEnabled; CBUFFER_END float2 SnapUV(float2 uv, float2 res) { return (floor(uv * res) + 0.5) / res; } half4 Frag(Varyings input) : SV_Target { float2 uv = input.texcoord; // Master bypass: return the untouched scene (dev on/off for the whole style). if (_MasterEnabled < 0.5) return half4(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, uv).rgb, 1.0); // Fixed, square-pixel low-res grid derived from a target grid height. float aspect = _ScreenParams.x / max(_ScreenParams.y, 1.0); float2 res = float2(max(_PixelHeight * aspect, 1.0), max(_PixelHeight, 1.0)); bool doPixelate = _PixelateEnabled > 0.5; float2 sampleUV = doPixelate ? SnapUV(uv, res) : uv; float2 texel = 1.0 / res; float2 off = texel * _OutlineThickness; // Chunky fill: point-sampled scene color at the snapped grid cell. float3 col = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, sampleUV).rgb; col = max(col, 0.0); // ---- Tone shaping so flat pixel blocks read clearly ---- col *= _Brightness; col = (col - 0.5) * _Contrast + 0.5; float luma = dot(col, float3(0.2126, 0.7152, 0.0722)); col = lerp(luma.xxx, col, _Saturation); col = saturate(col); // ---- Posterize in perceptual (gamma) space so dark detail survives ---- if (_PosterizeEnabled > 0.5) { float levels = max(_ColorLevels, 2.0); float3 g = pow(col, 1.0 / 2.2); g = floor(g * levels + 0.5) / levels; col = pow(saturate(g), 2.2); } // ---- Depth edges (outer silhouettes) ---- float d0 = LinearEyeDepth(SampleSceneDepth(sampleUV), _ZBufferParams); float dL = LinearEyeDepth(SampleSceneDepth(sampleUV - float2(off.x, 0)), _ZBufferParams); float dR = LinearEyeDepth(SampleSceneDepth(sampleUV + float2(off.x, 0)), _ZBufferParams); float dD = LinearEyeDepth(SampleSceneDepth(sampleUV - float2(0, off.y)), _ZBufferParams); float dU = LinearEyeDepth(SampleSceneDepth(sampleUV + float2(0, off.y)), _ZBufferParams); float depthDiff = abs(d0 - dL) + abs(d0 - dR) + abs(d0 - dU) + abs(d0 - dD); float depthEdge = step(_DepthThreshold, depthDiff / max(d0, 1e-3)) * _DepthEdgesEnabled; // ---- Normal edges (inner detail creases) ---- float3 n0 = SampleSceneNormals(sampleUV); float3 nL = SampleSceneNormals(sampleUV - float2(off.x, 0)); float3 nR = SampleSceneNormals(sampleUV + float2(off.x, 0)); float3 nD = SampleSceneNormals(sampleUV - float2(0, off.y)); float3 nU = SampleSceneNormals(sampleUV + float2(0, off.y)); float ne = (1.0 - saturate(dot(n0, nL))) + (1.0 - saturate(dot(n0, nR))) + (1.0 - saturate(dot(n0, nU))) + (1.0 - saturate(dot(n0, nD))); float normalEdge = step(_NormalThreshold, ne) * _NormalEdgesEnabled; // Mask inner lines out of silhouette edges to avoid double outlines. float normalEdgeMasked = normalEdge * (1.0 - depthEdge); float edge = saturate(depthEdge + normalEdgeMasked) * _EdgeStrength; float3 lineCol = lerp(_OutlineColorInner.rgb, _OutlineColorOuter.rgb, depthEdge); float3 outCol = lerp(col, lineCol, edge); return half4(outCol, 1.0); } ENDHLSL } } Fallback Off }