Files
Project-M/Assets/_Project/Shaders/PixelOutline.shader
T
2026-07-07 20:51:18 -07:00

137 lines
6.4 KiB
GLSL

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
}