赞
踩
一个笔试中遇到的题目,完成一个以主角为中心的扫描效果。下图是我完成的效果图。
主要功能是按下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(); } } }
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]; }
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);
自定义一个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();
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; }
Shader的顶点着色器中计算出interpolatedRay作为摄像机指向顶点的方向向量传入片元着色器。片元着色器将interpolatedRay和摄像机深度纹理采集到的深度值相乘获得摄像机指向片元的向量,加上摄像机坐标及是片元的位置坐标。
得到位置坐标即可判断是否有扫描特效。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。