Kastell/Assets/Pixel3D/Shaders/PixelPerfectOutline.shader
Gerard Gascón f5c1616018 init
2025-02-02 23:45:04 +01:00

161 lines
5.6 KiB
Text

Shader "Custom/PixelPerfectOutline"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
Cull Off ZWrite Off ZTest Always
Pass
{
Name "Outline"
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _CameraDepthNormalsTexture;
sampler2D _CameraDepthTexture;
float2 _ScreenSize;
float _DepthThreshold;
float _NormalThreshold;
float3 _NormalEdgeBias;
float _AngleThreshold;
float _AngleFactorScale;
bool _DebugOutline;
float4 _OutlineColor;
float4 _EdgeColor;
float _ColorShift;
#define SAMPLE_DEPTH_NORMAL(uv, name) \
float name##Depth; \
float3 name##Normal; \
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, uv), name##Depth, name##Normal);
struct PassValue
{
float depth;
float normal;
};
float getDepthDiff(float depth, float neighborDepth)
{
return neighborDepth - depth;
return saturate(neighborDepth - depth);
}
float getNormalDiff(float3 normal, float3 neighborNormal, float depth, float neighborDepth)
{
float3 normalDiff = normal - neighborNormal;
float normalBiasDiff = dot(normalDiff, _NormalEdgeBias);
float normalIndicator = saturate(smoothstep(-0.1, 0.1, normalBiasDiff));
float depthDiff = neighborDepth - depth;
float depthIndicator = saturate(sign(depthDiff * .25 + .0025));
return (1 - dot(normal, neighborNormal)) * normalIndicator * depthIndicator;
}
// Sources:
// https://roystan.net/articles/outline-shader/
// https://www.youtube.com/watch?v=LRDpEnpWohM
// https://www.youtube.com/watch?v=jFevm02NJ5M
// https://github.com/KodyJKing/three.js/blob/outlined-pixel-example/examples/jsm/postprocessing/RenderPixelatedPass.js
PassValue edgePass(float2 uv)
{
// Convolution offset sizes
float2 offset = 1 / _ScreenSize;
// UVs for convolution sampling
float2 top = uv + offset * float2(0, 1);
float2 bottom = uv + offset * float2(0, -1);
float2 left = uv + offset * float2(-1, 0);
float2 right = uv + offset * float2(1, 0);
// Sample depth and normal
SAMPLE_DEPTH_NORMAL(uv, center);
SAMPLE_DEPTH_NORMAL(top, top);
SAMPLE_DEPTH_NORMAL(bottom, bottom);
SAMPLE_DEPTH_NORMAL(left, left);
SAMPLE_DEPTH_NORMAL(right, right);
// Calculate the angle threshold
float angle = 1 - dot(centerNormal, float3(0, 0, 1));
float angleThreshold = saturate((angle - _AngleThreshold) / (1 - _AngleThreshold));
float angleFactor = angleThreshold * _AngleFactorScale + 1;
// Calculate the depth edge
float depthDiff = 0;
depthDiff += getDepthDiff(centerDepth, topDepth);
depthDiff += getDepthDiff(centerDepth, bottomDepth);
depthDiff += getDepthDiff(centerDepth, leftDepth);
depthDiff += getDepthDiff(centerDepth, rightDepth);
float depthThreshold = _DepthThreshold * centerDepth * angleFactor;
float depth = step(depthThreshold, depthDiff);
// Calculate the normal edge
float normalDiff = 0;
normalDiff += getNormalDiff(centerNormal, topNormal, centerDepth, topDepth);
normalDiff += getNormalDiff(centerNormal, bottomNormal, centerDepth, bottomDepth);
normalDiff += getNormalDiff(centerNormal, leftNormal, centerDepth, leftDepth);
normalDiff += getNormalDiff(centerNormal, rightNormal, centerDepth, rightDepth);
float normal = step(_NormalThreshold, normalDiff);
PassValue edge = { depth, normal };
return edge;
}
v2f vert(appdata v)
{
v2f o = { UnityObjectToClipPos(v.vertex), TRANSFORM_TEX(v.uv, _MainTex) };
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float4 color = tex2D(_MainTex, i.uv);
float4 outlineColor = float4(lerp(color.rgb, _OutlineColor.rgb, _OutlineColor.a), 1);
float4 edgeColor = float4(lerp(color.rgb, _EdgeColor.rgb, _EdgeColor.a), 1);
PassValue edge = edgePass(i.uv);
if (_DebugOutline) return float4(edge.depth, edge.normal, 0, 1);
// Any depth overrides normal
edge.normal = step(edge.depth, 0) * edge.normal;
color = lerp(color, outlineColor, edge.depth);
color = lerp(color, edgeColor, edge.normal);
return color;
}
ENDCG
}
}
Fallback Off
}