161 lines
5.6 KiB
Text
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
|
|
}
|