Unity制作扫描效果

一个笔试中遇到的题目,完成一个以主角为中心的扫描效果。下图是我完成的效果图。

主要功能是按下v键,以场景中的角色或者摄像机为中心发出扫描圈,路过的有效目标被高亮且透视。使用了三个脚本和一个Shader完成效果。

脚本
实现思路是ScanCenter脚本监测按键,并且完成切换摄像机和判断是否激活或者重置扫描器。激活的过程中将此脚本所在Object的世界坐标传入扫描器。

void Update()
{
if (Input.GetKeyDown(KeyCode.V))
{
if (scanCamera.enabled == false)
{
mainCamera.enabled = false;
scanCamera.enabled = true;
Scanner.CallScan(transform.position);
}
else {
mainCamera.enabled = true;
scanCamera.enabled = false;
Scanner.reset();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Target脚本实现目标材质和高亮材质的切换。

public void highlight()
{
rend.material = targetMaterial;
}

//重置材质
public void recover()
{
rend.material = baseMaterial;
}

void Start()
{
rend = GetComponent<Renderer>();
rend.enabled = true;
baseMaterial = rend.materials[0];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Scanner脚本用于获取摄像机的深度+法线纹理,并且计算指向摄像机的四个角的向量,传入Shader。这个部分在冯乐乐的《Unity Shader入门级精要》中全局雾效的部分有详细说明。Shader获取到向量后。再经过计算反推出片元所在的世界坐标。
以下是计算指向摄像机近裁/远裁平面四个角的归一向量的计算方式,计算结果由四维矩阵_FrustumCorners传入Shader:

//传入着色器的属性
material.SetVector("_WorldSpaceScannerPos", scanPosition);
material.SetFloat("_ScanDistance", ScanDistance);

Matrix4x4 frustumCorners = Matrix4x4.identity;

//计算摄像机指向摄像机空间四个角的归一化向量
float camFar = _camera.farClipPlane;
float camFov = _camera.fieldOfView;
float camAspect = _camera.aspect;

float fovWHalf = camFov * 0.5f;

Vector3 toRight = _camera.transform.right * Mathf.Tan(fovWHalf * Mathf.Deg2Rad) * camAspect;
Vector3 toTop = _camera.transform.up * Mathf.Tan(fovWHalf * Mathf.Deg2Rad);

Vector3 topLeft = (_camera.transform.forward - toRight + toTop);
float camScale = topLeft.magnitude * camFar;

topLeft.Normalize();
topLeft *= camScale;

Vector3 topRight = (_camera.transform.forward + toRight + toTop);
topRight.Normalize();
topRight *= camScale;

Vector3 bottomRight = (_camera.transform.forward + toRight - toTop);
bottomRight.Normalize();
bottomRight *= camScale;

Vector3 bottomLeft = (_camera.transform.forward - toRight - toTop);
bottomLeft.Normalize();
bottomLeft *= camScale;

frustumCorners.SetRow(0, topLeft);
frustumCorners.SetRow(1, topRight);
frustumCorners.SetRow(2, bottomRight);
frustumCorners.SetRow(3, bottomLeft);
material.SetMatrix("_FrustumCorners", frustumCorners);
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
自定义一个GraphicsBlit让Shader可以通过索引访问该矩阵以求出interpolatedRay,也就是摄像机指向顶点的方向向量。

// 自定义 GraphicsBlit, 将上文四个角设置为顶点的z轴索引,shader中直接提取顶点的vertex.z就可以得到索引。
material.SetTexture("_MainTex", source);
RenderTexture.active = dest;

GL.PushMatrix();
GL.LoadOrtho();

material.SetPass(0);

GL.Begin(GL.QUADS);

GL.MultiTexCoord2(0, 0.0f, 0.0f);
GL.Vertex3(0.0f, 0.0f, 3.0f);

GL.MultiTexCoord2(0, 1.0f, 0.0f);
GL.Vertex3(1.0f, 0.0f, 2.0f);

GL.MultiTexCoord2(0, 1.0f, 1.0f);
GL.Vertex3(1.0f, 1.0f, 1.0f);

GL.MultiTexCoord2(0, 0.0f, 1.0f);
GL.Vertex3(0.0f, 1.0f, 0.0f);

GL.End();
GL.PopMatrix();
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
Shader
Shader主要用于使用Scanner脚本传入的参数反推片元世界坐标并用图片纹理作出扫描效果(一维纹理)

v2f vert(input v)
{
v2f o;
half index = v.vertex.z;
v.vertex.z = 0.1;

o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv.xy;
o.uv_depth = v.uv.xy;

#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
o.uv.y = 1 - o.uv.y;
#endif

o.interpolatedRay = _FrustumCorners[(int)index];

return o;
}

half4 frag (v2f i) : SV_Target
{
half4 col = tex2D(_MainTex, i.uv);
//获取深度值并且反推出像素点的世界坐标
float linearDepth = Linear01Depth(DecodeFloatRG(tex2D(_CameraDepthTexture, i.uv_depth)));
float3 worldPos = _WorldSpaceCameraPos + (linearDepth * i.interpolatedRay.xyz);

half4 sc = half4(0, 0, 0, 0);

//扫描特效
float dist = distance(worldPos, _WorldSpaceScannerPos);
if (dist < _ScanDistance && dist > _ScanDistance - _ScanWidth && linearDepth < 1)
{
float diff = 1 - (_ScanDistance - dist) / (_ScanWidth);
sc = tex2D(_ScanTex, float2(diff,diff));
sc = _ScanColor*sc.a*_Alpha;
}

return col + sc;
}
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
Shader的顶点着色器中计算出interpolatedRay作为摄像机指向顶点的方向向量传入片元着色器。片元着色器将interpolatedRay和摄像机深度纹理采集到的深度值相乘获得摄像机指向片元的向量,加上摄像机坐标及是片元的位置坐标。
得到位置坐标即可判断是否有扫描特效。
————————————————
版权声明:本文为CSDN博主「ZanDatsu」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZanDatsu/article/details/107405599

posted on 2021-11-04 09:27  guanxi0808  阅读(703)  评论(0)    收藏  举报

导航