半透明物体与非透明物体接缝柔和处理

前言

        最近项目中遇到特效和地面的接触的时候,他们的接触面间有着一条很明显的接缝。当镜头拉进的时候这个情况尤为明显。幸好同事找到了一篇文章来解决这个问题。文章中说的是在Cocos Creator中的实现,这里我就按照文章的思路来做出Unity Shader的形式。且为了我自己方便查找,我自然是要写一篇文章来记录一下啦。

核心思想

        我们先获取到深度图中在视图空间下的深度,和自身在视图空间下的深度。然后我们通过一定的公式来修改其颜色或是其透明通道。

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Shader "Test/DepthTransit"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Near ("Near",float) = 0.02
_Far ("Far",float) = 10
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull off
ZWrite Off
ZTest LEqual

Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

struct appdata
{
float4 vertex : POSITION;
};

struct v2f
{
float4 screenPos : TEXCOORD0;
float4 vertex : SV_POSITION;
};

CBUFFER_START(UnityPerMaterial)
half4 _Color;
float _Near;
float _Far;
CBUFFER_END

v2f vert (appdata v)
{
v2f o;
o.vertex = TransformObjectToHClip(v.vertex.xyz);
o.screenPos = ComputeScreenPos(o.vertex);
//o.screenPos = o.screenPos / o.screenPos.w;
// 获取视图空间下的深度值
o.screenPos.z = -TransformWorldToView(TransformObjectToWorld(v.vertex)).z;
return o;
}

half4 frag (v2f i) : SV_Target
{
// sample the texture
half4 col = _Color;
// 将深度图中的信息转为视图空间下的深度值
float depth = LinearEyeDepth(SampleSceneDepth(i.screenPos.xy / i.screenPos.w),_ZBufferParams);
col *= clamp((depth - i.screenPos.z - _Near) * _Far, 0,1);
return col;
}
ENDHLSL
}
}
}

文章中的公式其实是:

1
2
float diff = clamp((depth - screenUV.z - 0.02) / 0.1, 0.0, 1.0);
o.rgb *= diff;

我这里是直接改为了:

1
col *=  clamp((depth - i.screenPos.z - _Near) * _Far, 0,1);

这样大家可以自行调整参数来看变化。至于为什么是_Near_Far,这是因为这样的操作很像Unity中软粒子的实现。下面是Unity中软粒子的其中一个实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
float SoftParticles(float near, float far, ParticleParams params)
{
float fade = 1;
if (near > 0.0 || far > 0.0)
{
float rawDepth = SampleSceneDepth(params.projectedPosition.xy / params.projectedPosition.w);
float sceneZ = (unity_OrthoParams.w == 0) ? LinearEyeDepth(rawDepth, _ZBufferParams) : LinearDepthToEyeDepth(rawDepth);
float thisZ = LinearEyeDepth(params.positionWS.xyz, GetWorldToViewMatrix());
fade = saturate(far * ((sceneZ - near) - thisZ));
}
return fade;
}

至于大家要直接乘以最终的颜色值还是只能修改最终颜色的透明度,那就要看各位的需求了。

参考文章

  1. Cocos Shader入门基础七:一文彻底弄懂深度图:https://zhuanlan.zhihu.com/p/479574432