Skip to content

Instantly share code, notes, and snippets.

@hecomi
Last active February 1, 2023 08:18
Show Gist options
  • Save hecomi/1df7704b625d1d5e7639 to your computer and use it in GitHub Desktop.
Save hecomi/1df7704b625d1d5e7639 to your computer and use it in GitHub Desktop.
using UnityEngine;
using UnityStandardAssets.ImageEffects;
[ExecuteInEditMode]
public class SengaEffect : ImageEffectBase
{
public float sampleDistance = 1;
public float normalEdge = 0.1f;
public float depthEdge = 1f;
public float colorEdge = 1f;
public float normalThreshold = 0.1f;
public float depthThreshold = 5;
public float colorThreshold = 1f;
public float edgeEnhanceLineLength = 2f;
public float edgeEnhanceThreshold = 0.8f;
public float edgeEnhancePower = 0.5f;
public Color shadowColor;
public Color skinColor;
public Color shadowIgnoreColor1, shadowIgnoreColor2;
public RenderTexture edgeRW;
void OnEnable()
{
var camera = GetComponent<Camera>();
camera.depthTextureMode |= DepthTextureMode.DepthNormals;
edgeRW = null;
}
protected override void OnDisable()
{
base.OnDisable();
if (edgeRW) {
RenderTexture.DestroyImmediate(edgeRW);
}
}
void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (edgeRW == null || edgeRW.width != source.width || edgeRW.height != source.height) {
if (edgeRW) RenderTexture.DestroyImmediate(edgeRW);
edgeRW = new RenderTexture(source.width, source.height, 0, RenderTextureFormat.R8);
edgeRW.enableRandomWrite = true;
edgeRW.Create();
}
var senga = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.ARGB32);
var temp = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.ARGB32);
material.SetFloat("_SampleDistance", sampleDistance);
material.SetFloat("_LineLen", edgeEnhanceLineLength);
material.SetVector("_EdgePower", new Vector4(normalEdge, depthEdge, colorEdge, edgeEnhancePower));
material.SetVector("_Threshold", new Vector4(normalThreshold, depthThreshold, colorThreshold, edgeEnhanceThreshold));
Graphics.Blit(source, senga, material, 0);
material.SetTexture("_SengaTex", senga);
Graphics.SetRenderTarget(edgeRW);
GL.Clear(true, true, new Color(0, 0, 0, 0));
Graphics.SetRandomWriteTarget(1, edgeRW);
Graphics.Blit(senga, temp, material, 1);
Graphics.ClearRandomWriteTargets();
material.SetTexture("_EdgeRW", edgeRW);
material.SetColor("_ShadowColor", shadowColor);
material.SetColor("_SkinColor", skinColor);
material.SetColor("_ShadowIgnoreColor1", shadowIgnoreColor1);
material.SetColor("_ShadowIgnoreColor2", shadowIgnoreColor2);
material.SetMatrix("_InvCamera2WorldMatrix", GetComponent<Camera>().cameraToWorldMatrix.inverse);
Graphics.Blit(source, destination, material, 2);
RenderTexture.ReleaseTemporary(senga);
RenderTexture.ReleaseTemporary(temp);
}
}
Shader "Hidden/SengaEffect"
{
Properties
{
_MainTex("Main Texture", 2D) = "white" {}
}
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
#pragma target 5.0
uniform float4 _MainTex_TexelSize;
sampler2D _CameraDepthNormalsTexture;
sampler2D _MainTex;
half _SampleDistance;
half4 _Threshold;
half4 _EdgePower;
inline float depthToMeter(float dep)
{
half nearClip = _ProjectionParams.y;
half farClip = _ProjectionParams.z;
return dep * (farClip - nearClip);
}
half4 frag(v2f_img i) : SV_Target
{
float2 duvs[2] = {
float2(_MainTex_TexelSize.x, 0) * _SampleDistance,
float2(0, -_MainTex_TexelSize.y) * _SampleDistance
};
half4 col1 = tex2D(_MainTex, i.uv);
half4 dn1 = tex2D(_CameraDepthNormalsTexture, i.uv);
half dep1;
half3 norm1;
DecodeDepthNormal(dn1, dep1, norm1);
dep1 = depthToMeter(dep1);
half edge = 0;
for (int j = 0; j < 2; j++)
{
half2 uv2 = i.uv + duvs[j];
half4 col2 = tex2D(_MainTex, uv2);
half4 dn2 = tex2D(_CameraDepthNormalsTexture, uv2);
half dep2;
half3 norm2;
DecodeDepthNormal(dn2, dep2, norm2);
dep2 = depthToMeter(dep2);
half2 dNorm = abs(norm1 - norm2);
if (length(dNorm) > _Threshold.x)
{
edge += _EdgePower.x;
}
half dDep1 = abs(dep1 - dep2) / dep1 /* meter/texel -> meter */;
if (dDep1 * 100 /* cm */ > _Threshold.y && dep1 < 3)
{
edge += _EdgePower.y;
}
half3 dCol = col1.rgb - col2.rgb;
if (length(dCol) > _Threshold.z)
{
edge += _EdgePower.z;
}
}
return half4(0, 0, 0, edge);
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#pragma target 5.0
#include "UnityCG.cginc"
uniform float4 _MainTex_TexelSize;
sampler2D _MainTex;
RWTexture2D<float> _EdgeRW;
half _LineLen;
half4 _Threshold;
half4 _EdgePower;
fixed4 frag(v2f_img i) : SV_Target
{
half PI = 3.14159265;
half PI8 = PI / 8;
half lineRot = 0;
for (int k = 0; k < 8; ++k)
{
half2 lineDir = half2(cos(lineRot), sin(lineRot)) * _MainTex_TexelSize.xy;
half p1 = tex2D(_MainTex, i.uv + lineDir * _LineLen).a;
half p2 = tex2D(_MainTex, i.uv - lineDir * _LineLen).a;
if (p1 > _Threshold.w && p2 > _Threshold.w)
{
for (int j = -_LineLen; j <= _LineLen; ++j)
{
int2 coord;
half2 uv = i.uv + lineDir * j;
coord.x = (int)(uv.x * _ScreenParams.x);
coord.y = (int)(uv.y * _ScreenParams.y);
_EdgeRW[coord] = _EdgePower.w;
}
}
lineRot += PI8;
}
return 0;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
#pragma target 5.0
uniform float4 _MainTex_TexelSize;
half4x4 _InvCamera2WorldMatrix;
half4 _ShadowColor;
half4 _SkinColor;
half4 _ShadowIgnoreColor1;
half4 _ShadowIgnoreColor2;
sampler2D _CameraDepthNormalsTexture;
sampler2D _MainTex;
sampler2D _SengaTex;
sampler2D _EdgeRW;
inline half isSkin(half4 col)
{
return length(col.rgb - _SkinColor.rgb) < 0.1;
}
inline half isIgnoreColor(half4 col)
{
return
length(col.rgb - _ShadowIgnoreColor1.rgb) < 0.03 ||
length(col.rgb - _ShadowIgnoreColor2.rgb) < 0.03;
}
inline float depthToMeter(float dep)
{
half nearClip = _ProjectionParams.y;
half farClip = _ProjectionParams.z;
return dep * (farClip - nearClip);
}
half4 calcShadowedImg(v2f_img i)
{
half2 uv1 = i.uv;
half4 col = tex2D(_MainTex, uv1);
half3 lightDir = mul(_InvCamera2WorldMatrix, normalize(_WorldSpaceLightPos0));
// layer-based shadow
half dep1;
half3 norm1;
DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture, uv1), dep1, norm1);
dep1 = depthToMeter(dep1);
half invDep = 1 / dep1;
half len = 50;
half dLen = 0.01;
half2 ray = _MainTex_TexelSize * (-lightDir.xy * half2(1, 0.5)) * invDep;
half2 uv2 = i.uv + ray * dLen;
float dep2 = depthToMeter(DecodeFloatRG(tex2D(_CameraDepthNormalsTexture, uv2).zw));
float grad12 = (dep2 - dep1);
half2 uv3 = i.uv + ray * len;
float dep3 = depthToMeter(DecodeFloatRG(tex2D(_CameraDepthNormalsTexture, uv3).zw));
half4 col3 = tex2D(_MainTex, uv3);
half layerShadowThreshold = 0.03; /* meter */
if (isSkin(col) && isSkin(col3))
{
layerShadowThreshold = 0.06; /* meter */
}
float interpDep3 = dep1 + (grad12 * (len / dLen));
if (dep1 - dep3 < 0.2 /* meter */ &&
interpDep3 - dep3 > layerShadowThreshold &&
!isIgnoreColor(col3) &&
!isIgnoreColor(col))
{
col.rgb *= _ShadowColor.rgb;
return col;
}
// directional light shadow
half directionalShadowThreshold = -0.3;
if (isSkin(col))
{
directionalShadowThreshold = 0.5;
}
if (dep1 < 5 && dot(lightDir, norm1) > directionalShadowThreshold)
{
col.rgb *= _ShadowColor.rgb;
return col;
}
return col;
}
half4 frag(v2f_img i) : SV_Target
{
half4 col = calcShadowedImg(i);
half4 edge = tex2D(_SengaTex, i.uv);
edge.a = saturate(edge.a + tex2D(_EdgeRW, i.uv).r);
col.rgb = lerp(col.rgb, pow(col.rgb * 0.8, 1 + 2 * edge.a), edge.a);
return col;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment