物体相交
利用深度图来做效果,不提供数学原理,只提供Shader脚本实现。
思路
已知需要渲染物体的深度信息,通过与_CameraDepthTexture
纹理中的深度值进行一些计算或者判定即可完成相交的效果。
计算方式:在视图(View)坐标系下,假设物体深度值为
e
y
e
Z
eyeZ
eyeZ,深度缓存中的深度值为
s
c
e
n
e
Z
sceneZ
sceneZ,相交宽度为
I
n
t
e
r
s
e
c
t
i
o
n
W
i
d
t
h
IntersectionWidth
IntersectionWidth;通过
a
b
s
(
e
y
e
Z
−
s
c
e
n
e
Z
)
/
I
n
s
t
e
r
s
e
c
t
i
o
n
W
i
d
t
h
abs(eyeZ - sceneZ)/InstersectionWidth
abs(eyeZ−sceneZ)/InstersectionWidth可以得到
[
0
,
n
]
[0, n]
[0,n]的一个值,将
[
0
,
n
]
[0, n]
[0,n]截断到
[
0
,
1
]
[0, 1]
[0,1],最后使用lerp
函数实现一个过渡。
_CameraDepthTexture
纹理记录的是NDC空间下的深度信息,因此需要转到视图(View)空间下。
实现
Shader "Hidden/IntersectionHighlight"
{
Properties
{
_IntersectionColor("Intersection Color", Color) = (1,1,0,0)
_IntersectionWidth("Intersection Width", Range(0, 2)) = 0.1
}
SubShader
{
Pass
{
// 关闭深度写入, 否则渲染会出现问题
ZWrite Off
// 关闭背面剔除, 渲染双面
Cull Off
// 开blend, 在片段后会进行blend混合
Blend SrcAlpha One
Tags {
"RenderType" = "Opaque"
}
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;
float4 screenPos : TEXCOORD1;
float eyeZ : TEXCOORD2;
};
// 深度缓存图
sampler2D _CameraDepthTexture;
// 相交颜色
fixed4 _IntersectionColor;
// 相交宽度
float _IntersectionWidth;
v2f vert (appdata v)
{
v2f o;
// o.vertex为裁剪空间中的点
o.vertex = UnityObjectToClipPos(v.vertex);
// 返回齐次坐标系下的点, 范围是[0, w]; 其中,w不是viewport的width (ps: 需要与tex2Dproj函数配套使用)
o.screenPos = ComputeScreenPos(o.vertex);
// 将v.vertex与model和view矩阵相乘, 得到相机到物体的z坐标, 为view坐标系下
COMPUTE_EYEDEPTH(o.eyeZ);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// UNITY_PROJ_COORD(a)应该是预留的一个接口, 官方解释为大部分平台将返回入参值
// SAMPLE_DEPTH_TEXTURE_PROJ(tex, uv)内部调用tex2Dproj(tex, uv), tex2Dproj会将(uv = uv / w)
// LinearEyeDepth(depth)将返回depth在view坐标系下的表示, 源码中的_ZBufferParams是相机中far和near两个参数表达式的结果值
float screenZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
// 相交计算, abs(物体的深度值 - 深度缓存的深度值) / 长度 => [0, n]的范围 => saturate限制到[0, 1]
float diff = saturate(abs(i.eyeZ - screenZ) / (_IntersectionWidth / 2));
// (0.0 -> 1.0) => (最亮 -> 最暗), diff => color
return lerp(_IntersectionColor, float4(0.0, 0.0, 0.0, 0.0), diff);
}
ENDCG
}
}
}