Files
Studio/Assets/Shader/EdgeDetection.shader
2025-06-11 16:50:56 +09:00

116 lines
5.2 KiB
GLSL

Shader "Custom/EdgeDetectionOutline"
{
Properties
{
_OutlineThickness ("Outline Thickness", Float) = 1.0
_OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)
}
SubShader
{
Tags
{
"RenderPipeline" = "UniversalPipeline"
"RenderType"="Opaque"
}
ZWrite Off
Cull Off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
Name "EDGE DETECTION OUTLINE"
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl" // needed to sample scene depth
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl" // needed to sample scene normals
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl" // needed to sample scene color/luminance
float _OutlineThickness;
float4 _OutlineColor;
#pragma vertex Vert // vertex shader is provided by the Blit.hlsl include
#pragma fragment frag
// Edge detection kernel that works by taking the sum of the squares of the differences between diagonally adjacent pixels (Roberts Cross).
float RobertsCross(float3 samples[4])
{
const float3 difference_1 = samples[1] - samples[2];
const float3 difference_2 = samples[0] - samples[3];
return sqrt(dot(difference_1, difference_1) + dot(difference_2, difference_2));
}
// The same kernel logic as above, but for a single-value instead of a vector3.
float RobertsCross(float samples[4])
{
const float difference_1 = samples[1] - samples[2];
const float difference_2 = samples[0] - samples[3];
return sqrt(difference_1 * difference_1 + difference_2 * difference_2);
}
// Helper function to sample scene normals remapped from [-1, 1] range to [0, 1].
float3 SampleSceneNormalsRemapped(float2 uv)
{
return SampleSceneNormals(uv) * 0.5 + 0.5;
}
// Helper function to sample scene luminance.
float SampleSceneLuminance(float2 uv)
{
float3 color = SampleSceneColor(uv);
return color.r * 0.3 + color.g * 0.59 + color.b * 0.11;
}
half4 frag(Varyings IN) : SV_TARGET
{
// Screen-space coordinates which we will use to sample.
float2 uv = IN.texcoord;
float2 texel_size = float2(1.0 / _ScreenParams.x, 1.0 / _ScreenParams.y);
// Generate 4 diagonally placed samples.
const float half_width_f = floor(_OutlineThickness * 0.5);
const float half_width_c = ceil(_OutlineThickness * 0.5);
float2 uvs[4];
uvs[0] = uv + texel_size * float2(half_width_f, half_width_c) * float2(-1, 1); // top left
uvs[1] = uv + texel_size * float2(half_width_c, half_width_c) * float2(1, 1); // top right
uvs[2] = uv + texel_size * float2(half_width_f, half_width_f) * float2(-1, -1); // bottom left
uvs[3] = uv + texel_size * float2(half_width_c, half_width_f) * float2(1, -1); // bottom right
float3 normal_samples[4];
float depth_samples[4], luminance_samples[4];
for (int i = 0; i < 4; i++) {
depth_samples[i] = SampleSceneDepth(uvs[i]);
normal_samples[i] = SampleSceneNormalsRemapped(uvs[i]);
luminance_samples[i] = SampleSceneLuminance(uvs[i]);
}
// Apply edge detection kernel on the samples to compute edges.
float edge_depth = RobertsCross(depth_samples);
float edge_normal = RobertsCross(normal_samples);
float edge_luminance = RobertsCross(luminance_samples);
// Threshold the edges (discontinuity must be above certain threshold to be counted as an edge). The sensitivities are hardcoded here.
float depth_threshold = 1 / 200.0f;
edge_depth = edge_depth > depth_threshold ? 1 : 0;
float normal_threshold = 1 / 4.0f;
edge_normal = edge_normal > normal_threshold ? 1 : 0;
float luminance_threshold = 1 / 0.5f;
edge_luminance = edge_luminance > luminance_threshold ? 1 : 0;
// Combine the edges from depth/normals/luminance using the max operator.
float edge = max(edge_depth, max(edge_normal, edge_luminance));
// Color the edge with a custom color.
return edge * _OutlineColor;
}
ENDHLSL
}
}
}