408 lines
14 KiB
GLSL
408 lines
14 KiB
GLSL
Shader "Hidden/ToonShadersPro/URP/Outlines"
|
|
{
|
|
SubShader
|
|
{
|
|
Tags
|
|
{
|
|
"RenderType" = "Opaque"
|
|
"RenderPipeline" = "UniversalPipeline"
|
|
}
|
|
|
|
HLSLINCLUDE
|
|
// Credit to https://alexanderameye.github.io/outlineshader.html:
|
|
float3 DecodeNormal(float4 enc)
|
|
{
|
|
float kScale = 1.7777;
|
|
float3 nn = enc.xyz*float3(2 * kScale, 2 * kScale, 0) + float3(-kScale, -kScale, 1);
|
|
float g = 2.0 / dot(nn.xyz, nn.xyz);
|
|
float3 n;
|
|
n.xy = g * nn.xy;
|
|
n.z = g - 1;
|
|
return n;
|
|
}
|
|
|
|
// Code 'liberated' from Shader Graph's Simple Noise node.
|
|
inline float randomValue(float2 uv)
|
|
{
|
|
return frac(sin(dot(uv, float2(12.9898, 78.233)))*43758.5453);
|
|
}
|
|
|
|
inline float perlinLerp(float a, float b, float t)
|
|
{
|
|
return (1.0f - t) * a + (t * b);
|
|
}
|
|
|
|
inline float valueNoise(float2 uv)
|
|
{
|
|
float2 i = floor(uv);
|
|
float2 f = frac(uv);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
|
|
uv = abs(frac(uv) - 0.5);
|
|
float2 c0 = i + float2(0.0, 0.0);
|
|
float2 c1 = i + float2(1.0, 0.0);
|
|
float2 c2 = i + float2(0.0, 1.0);
|
|
float2 c3 = i + float2(1.0, 1.0);
|
|
float r0 = randomValue(c0);
|
|
float r1 = randomValue(c1);
|
|
float r2 = randomValue(c2);
|
|
float r3 = randomValue(c3);
|
|
|
|
float bottomOfGrid = perlinLerp(r0, r1, f.x);
|
|
float topOfGrid = perlinLerp(r2, r3, f.x);
|
|
float t = perlinLerp(bottomOfGrid, topOfGrid, f.y);
|
|
return t;
|
|
}
|
|
|
|
float perlinNoise(float2 uv, float offset, float2 scale)
|
|
{
|
|
float t = 0.0;
|
|
float2 scaledUV = uv * scale + offset;
|
|
|
|
float freq = pow(2.0, float(0));
|
|
float amp = pow(0.5, float(3 - 0));
|
|
t += valueNoise(float2(scaledUV.x / freq, scaledUV.y / freq))*amp;
|
|
|
|
freq = pow(2.0, float(1));
|
|
amp = pow(0.5, float(3 - 1));
|
|
t += valueNoise(float2(scaledUV.x / freq, scaledUV.y / freq))*amp;
|
|
|
|
freq = pow(2.0, float(2));
|
|
amp = pow(0.5, float(3 - 2));
|
|
t += valueNoise(float2(scaledUV.x / freq, scaledUV.y / freq))*amp;
|
|
|
|
return t;
|
|
}
|
|
|
|
float2 perlinNoiseRG(float2 uv, float2 screenSize, float offset, float scale)
|
|
{
|
|
float2 aspectScale = float2(screenSize.x / screenSize.y * scale, scale);
|
|
return float2(perlinNoise(uv, offset, aspectScale), perlinNoise(uv + float2(321.24167f, -47.124f), offset, aspectScale)) * 2.0f - 1.0f;
|
|
}
|
|
ENDHLSL
|
|
|
|
Pass
|
|
{
|
|
Name "DepthNormalOutlines"
|
|
|
|
ZTest Always
|
|
Cull Off
|
|
ZWrite Off
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment frag
|
|
|
|
#pragma shader_feature_local_fragment _USE_NOISE_OFFSETS
|
|
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
|
|
#include "OutlineUtils.hlsl"
|
|
|
|
float4 _OutlineColor;
|
|
float _ColorSensitivity;
|
|
float _ColorStrength;
|
|
float _DepthSensitivity;
|
|
float _DepthStrength;
|
|
float _NormalsSensitivity;
|
|
float _NormalsStrength;
|
|
float _DepthThreshold;
|
|
|
|
#ifdef _USE_NOISE_OFFSETS
|
|
float _NoiseScale;
|
|
float _NoiseOffset;
|
|
float _NoiseStrength;
|
|
#endif
|
|
|
|
float4 frag (Varyings i) : SV_TARGET
|
|
{
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
|
|
|
float2 uv = i.texcoord;
|
|
|
|
float4 col = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
|
|
|
|
#ifdef _USE_NOISE_OFFSETS
|
|
uv += perlinNoiseRG(uv, float2(_ScreenParams.x, _ScreenParams.y), _NoiseOffset, _NoiseScale) * _NoiseStrength;
|
|
#endif
|
|
|
|
// Get UV coords of pixel to the left, right, below, and above the center pixel.
|
|
float2 leftUV = uv + float2(1.0f / -_ScreenParams.x, 0.0f);
|
|
float2 rightUV = uv + float2(1.0f / _ScreenParams.x, 0.0f);
|
|
float2 bottomUV = uv + float2(0.0f, 1.0f / -_ScreenParams.y);
|
|
float2 topUV = uv + float2(0.0f, 1.0f / _ScreenParams.y);
|
|
|
|
// Find differences between nearby pixel colors.
|
|
float3 col0 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, leftUV).rgb;
|
|
float3 col1 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, rightUV).rgb;
|
|
float3 col2 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, bottomUV).rgb;
|
|
float3 col3 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, topUV).rgb;
|
|
|
|
float3 c0 = col1 - col0;
|
|
float3 c1 = col3 - col2;
|
|
|
|
// Detect color-based edges using gradients.
|
|
float edgeCol = sqrt(dot(c0, c0) + dot(c1, c1));
|
|
edgeCol = edgeCol > _ColorSensitivity ? _ColorStrength : 0;
|
|
|
|
// Find differences between nearby pixel depth values.
|
|
float depth0 = Linear01Depth(GetSceneDepth(leftUV), _ZBufferParams);
|
|
float depth1 = Linear01Depth(GetSceneDepth(rightUV), _ZBufferParams);
|
|
float depth2 = Linear01Depth(GetSceneDepth(bottomUV), _ZBufferParams);
|
|
float depth3 = Linear01Depth(GetSceneDepth(topUV), _ZBufferParams);
|
|
|
|
float d0 = depth1 - depth0;
|
|
float d1 = depth3 - depth2;
|
|
|
|
// Detect depth-based edges using gradients.
|
|
float edgeDepth = sqrt(d0 * d0 + d1 * d1);
|
|
edgeDepth = edgeDepth > _DepthSensitivity ? _DepthStrength : 0;
|
|
|
|
// Find differences between nearby pixel normals.
|
|
float3 normal0 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, leftUV));
|
|
float3 normal1 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, rightUV));
|
|
float3 normal2 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, bottomUV));
|
|
float3 normal3 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, topUV));
|
|
|
|
float3 n0 = normal1 - normal0;
|
|
float3 n1 = normal3 - normal2;
|
|
|
|
// Detect normal-based edges using gradients.
|
|
float edgeNormal = sqrt(dot(n0, n0) + dot(n1, n1));
|
|
edgeNormal = edgeNormal > _NormalsSensitivity ? _NormalsStrength : 0;
|
|
|
|
// Detect edges with the highest of the three metrics.
|
|
float edge = max(max(edgeCol, edgeDepth), edgeNormal);
|
|
float depth = GetSceneDepth(uv);
|
|
|
|
// Discard edge-detected pixels which are super far away (usually at/near the skybox).
|
|
depth = Linear01Depth(depth, _ZBufferParams);
|
|
edge = depth > _DepthThreshold ? 0.0f : edge;
|
|
|
|
return lerp(col, _OutlineColor, edge);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "MaskedObjectOutlines"
|
|
|
|
ZTest Always
|
|
Cull Off
|
|
ZWrite Off
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment frag
|
|
|
|
#pragma shader_feature_local_fragment _USE_DEPTH_NORMALS
|
|
#pragma shader_feature_local_fragment _USE_NOISE_OFFSETS
|
|
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
|
|
#ifdef _USE_DEPTH_NORMALS
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
|
|
#endif
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
|
|
#include "OutlineUtils.hlsl"
|
|
|
|
TEXTURE2D(_MaskedObjects);
|
|
|
|
float4 _OutlineColor;
|
|
float _OutlineWidth;
|
|
float _Spread;
|
|
float _OutlineFadeStart;
|
|
float _OutlineFadeEnd;
|
|
float2 _DrawSides;
|
|
#ifdef _USE_DEPTH_NORMALS
|
|
float _NormalsSensitivity;
|
|
float _NormalsStrength;
|
|
#endif
|
|
|
|
#ifdef _USE_NOISE_OFFSETS
|
|
float _NoiseScale;
|
|
float _NoiseOffset;
|
|
float _NoiseStrength;
|
|
#endif
|
|
|
|
inline float distanceFade(float x, float y)
|
|
{
|
|
return (abs(x) + abs(y)) * _Spread;
|
|
}
|
|
|
|
float4 frag (Varyings i) : SV_TARGET
|
|
{
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
|
|
|
float2 uv = i.texcoord;
|
|
|
|
float4 col = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
|
|
|
|
#ifdef _USE_NOISE_OFFSETS
|
|
uv += perlinNoiseRG(uv, float2(_ScreenParams.x, _ScreenParams.y), _NoiseOffset, _NoiseScale) * _NoiseStrength;
|
|
#endif
|
|
|
|
// Get mask and depth values for center pixel.
|
|
float maskMiddle = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, uv).r;
|
|
float depthMiddle = GetSceneDepth(uv);
|
|
|
|
// Will need eye depth later for outline fade-out.
|
|
float eyeDepth = LinearEyeDepth(depthMiddle, _ZBufferParams);
|
|
|
|
// Is the center pixel masked?
|
|
float isMaskedPixel = step(0.0001f, maskMiddle.r);
|
|
float totalDiff = 0.0f;
|
|
|
|
// Iterate over a kernel of width (2 * _OutlineWidth + 1).
|
|
for(int x = -_OutlineWidth; x <= _OutlineWidth; ++x)
|
|
{
|
|
for(int y = -_OutlineWidth; y <= _OutlineWidth; ++y)
|
|
{
|
|
// Get the mask and depth values of kernel pixel.
|
|
float2 maskUV = uv + float2(x / _ScreenParams.x, y / _ScreenParams.y);
|
|
float maskAdjacent = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, maskUV).r;
|
|
float depthAdjacent = GetSceneDepth(maskUV);
|
|
|
|
// Set eye depth to the closest pixel.
|
|
eyeDepth = min(eyeDepth, LinearEyeDepth(depthAdjacent, _ZBufferParams));
|
|
|
|
// Find out how the center and kernel pixel are different/same.
|
|
float adjacentIsMask = step(0.0001f, maskAdjacent.r);
|
|
float maskIsDifferent = step(0.000001f, distance(maskAdjacent.r, maskMiddle.r));
|
|
float adjacentIsCloser = (1.0f - step(depthAdjacent, depthMiddle));
|
|
|
|
// For masked center pixels, detect masked kernel pixels, or any unmasked pixel that is further away.
|
|
float pixelDiff = isMaskedPixel * adjacentIsMask * maskIsDifferent * lerp(_DrawSides.x, _DrawSides.y, adjacentIsCloser);
|
|
pixelDiff += isMaskedPixel * _DrawSides.x * maskIsDifferent * (1.0f - adjacentIsMask) * (1.0f - adjacentIsCloser);
|
|
|
|
// For unmasked center pixels, detect masked pixels that are closer.
|
|
pixelDiff += (1.0f - isMaskedPixel) * adjacentIsMask * adjacentIsCloser * _DrawSides.y;
|
|
|
|
// Smooth out edge detect contributions using distance-based weighting.
|
|
totalDiff += distanceFade(x, y) * saturate(pixelDiff);
|
|
}
|
|
}
|
|
|
|
// Fade out outlines over a distance range.
|
|
float outlineStrength = saturate(totalDiff) * _OutlineColor.a;
|
|
outlineStrength *= smoothstep(_OutlineFadeEnd, _OutlineFadeStart, eyeDepth);
|
|
|
|
#ifdef _USE_DEPTH_NORMALS
|
|
// Get UV coords of pixel to the left, right, below, and above the center pixel.
|
|
float2 leftUV = uv + float2(1.0f / -_ScreenParams.x, 0.0f);
|
|
float2 rightUV = uv + float2(1.0f / _ScreenParams.x, 0.0f);
|
|
float2 bottomUV = uv + float2(0.0f, 1.0f / -_ScreenParams.y);
|
|
float2 topUV = uv + float2(0.0f, 1.0f / _ScreenParams.y);
|
|
|
|
// Find differences between nearby pixel normals.
|
|
float3 normal0 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, leftUV));
|
|
float3 normal1 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, rightUV));
|
|
float3 normal2 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, bottomUV));
|
|
float3 normal3 = DecodeNormal(SAMPLE_TEXTURE2D_X(_CameraNormalsTexture, sampler_LinearClamp, topUV));
|
|
|
|
float3 n0 = normal1 - normal0;
|
|
float3 n1 = normal3 - normal2;
|
|
|
|
// Detect normal-based edges using gradients.
|
|
float edgeNormal = sqrt(dot(n0, n0) + dot(n1, n1));
|
|
edgeNormal = edgeNormal > _NormalsSensitivity ? _NormalsStrength : 0;
|
|
|
|
outlineStrength = max(outlineStrength, edgeNormal);
|
|
#endif
|
|
col.rgb = lerp(col.rgb, _OutlineColor.rgb, outlineStrength);
|
|
|
|
return col;
|
|
}
|
|
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "ThinMaskedObjectOutlines"
|
|
|
|
ZTest Always
|
|
Cull Off
|
|
ZWrite Off
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment frag
|
|
|
|
#pragma shader_feature_local_fragment _USE_NOISE_OFFSETS
|
|
|
|
#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.core/ShaderLibrary/Color.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
|
|
#include "OutlineUtils.hlsl"
|
|
|
|
TEXTURE2D(_MaskedObjects);
|
|
float4 _OutlineColor;
|
|
|
|
#ifdef _USE_NOISE_OFFSETS
|
|
float _NoiseScale;
|
|
float _NoiseOffset;
|
|
float _NoiseStrength;
|
|
#endif
|
|
|
|
float4 frag (Varyings i) : SV_TARGET
|
|
{
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
|
|
|
float2 uv = i.texcoord;
|
|
|
|
float4 col = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
|
|
|
|
#ifdef _USE_NOISE_OFFSETS
|
|
uv += perlinNoiseRG(uv, float2(_ScreenParams.x, _ScreenParams.y), _NoiseOffset, _NoiseScale) * _NoiseStrength;
|
|
#endif
|
|
|
|
// Get UVs for nearby four pixels.
|
|
float2 leftUV = uv + float2(1.0f / -_ScreenParams.x, 0.0f);
|
|
float2 rightUV = uv + float2(1.0f / _ScreenParams.x, 0.0f);
|
|
float2 bottomUV = uv + float2(0.0f, 1.0f / -_ScreenParams.y);
|
|
float2 topUV = uv + float2(0.0f, 1.0f / _ScreenParams.y);
|
|
|
|
// Get mask pixels for center and four adjacent pixels.
|
|
float middleMask = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, uv).r;
|
|
float mask0 = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, leftUV).r;
|
|
float mask1 = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, rightUV).r;
|
|
float mask2 = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, bottomUV).r;
|
|
float mask3 = SAMPLE_TEXTURE2D(_MaskedObjects, sampler_LinearClamp, topUV).r;
|
|
|
|
// Get four adjacent depth pixels.
|
|
float middleDepth = GetSceneDepth(uv);
|
|
float depth0 = GetSceneDepth(leftUV);
|
|
float depth1 = GetSceneDepth(rightUV);
|
|
float depth2 = GetSceneDepth(bottomUV);
|
|
float depth3 = GetSceneDepth(topUV);
|
|
|
|
// Only use mask values for further away pixels.
|
|
mask0 = step(middleDepth, depth0) < 0.5f ? mask0 : middleMask;
|
|
mask1 = step(middleDepth, depth1) < 0.5f ? mask1 : middleMask;
|
|
mask2 = step(middleDepth, depth2) < 0.5f ? mask2 : middleMask;
|
|
mask3 = step(middleDepth, depth3) < 0.5f ? mask3 : middleMask;
|
|
|
|
// Find differences across adjacent pixels to find edges.
|
|
float c0 = mask1 - mask0;
|
|
float c1 = mask3 - mask2;
|
|
float edgeDiff = sqrt(c0 * c0 + c1 * c1);
|
|
float edgeDetect = step(0.0001f, edgeDiff);
|
|
|
|
// Do not detect edges on pixels that are not masked.
|
|
edgeDetect *= step(0.001f, middleMask);
|
|
|
|
col.rgb = lerp(col.rgb, _OutlineColor.rgb, edgeDetect * _OutlineColor.a);
|
|
|
|
return col;
|
|
}
|
|
|
|
ENDHLSL
|
|
}
|
|
}
|
|
}
|