当前位置:   article > 正文

Unity制作扫描效果__frustumcorners

_frustumcorners

一个笔试中遇到的题目,完成一个以主角为中心的扫描效果。下图是我完成的效果图。
在这里插入图片描述
主要功能是按下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和摄像机深度纹理采集到的深度值相乘获得摄像机指向片元的向量,加上摄像机坐标及是片元的位置坐标。
得到位置坐标即可判断是否有扫描特效。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/100001
推荐阅读
相关标签
  

闽ICP备14008679号