当前位置:   article > 正文

Unity Shader 基础(4) 由深度纹理重建坐标

unity shader //第四纹理坐标

在PostImage中经常会用到物体本身的位置信息,但是Image Effect自身是不包含这些信息的,因为屏幕后处其实是使用特定的材质渲染一个刚好填满屏幕的四边形面片(四个角对应近剪裁面的四个角)。这篇文章主要介绍几种在Image Effct shader中还原世界坐标的方式。这个问题在《Shader入门精要》中也做了描述,这里可能偏重于个人的一些疑惑。

这篇文章相关的两外两篇文章:
Unity Shader 基础(3) 获取深度纹理
Unity Shader 基础(2) Image Effect

1. View-Projection 逆矩阵

虽然在Image Effect中没有实际的顶点信息,但是带有纹理uv'坐标信息以及获得深度信息,根据UV信息可以得知NDC下xy坐标,即:

xndc=2uv.x1yndc=2uv.y1

在加上通过深度纹理或者深度法线纹理,可以获得NDC下深度信息,从而可以计算出世界坐标。
Pworld=Mview1Mprojection1Pndc

详情可可以参考:Unity Answer:Reconstructing world pos from depth 以及GPU Gem3 :Chapter 27. Motion Blur as a Post-Processing Effect
Pixel shader代码*,逆矩阵需要从C#中传递过来:

  1. fixed4 frag(uoutput o) : COLOR
  2. {
  3. fixed4 col = tex2D(_MainTex, o.uv);
  4. float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, o.uv_depth));
  5. float4 ndcPos = float4(o.uv.x* 2 - 1 ,o.uv.y * 2 - 1 ,depth , 1);
  6. //_Matrix_vp_inverse外部传递,具体为:
  7. //Matrix4x4 temp = mCam.projectionMatrix * mCam.worldToCameraMatrix;
  8. //temp = temp.inverse;
  9. //mMat.SetMatrix("_Matrix_vp_inverse", temp);
  10. float4 worldHPos = mul(_Matrix_vp_inverse,ndcPos);
  11. float4 worldPos = worldHPos / worldHPos.w;
  12. float dis = length(worldPos.xyz);
  13. float3 worldPos2 = worldPos.xyz/dis;
  14. worldPos2 = worldPos2 * 0.5 + 0.5;
  15. return fixed4(worldPos2,1);
  16. }

mark

2 远剪裁面插值

理解一下两点

  1. Image Effect是Post Processing 的一种方式,大致过程就是把Color Buffer的输出当做纹理,然后采用特性的材质渲染和屏大小一样的四角形面片(面片的四角即近剪裁面的四个角)。
  2. Vertex shader输出的数据到Pixel shader输入,经过光栅化会进行插值。远剪裁面的四条射线在Pixel shader后是经过插值的,如下图:
    mark
    基于上面两点: 对从摄像机原点出发,经剪裁面的摄像机射线进行插值获得每个位置的摄像机视线方向信息,再已知深度信息的情况下即可 获得摄像机位置 。

取其中一个射线:
mark
根据图中比例关系:

YellowLYellowL+GreenL=BlackVBlackV+BlueV

因为YellowL+GreenL=1 , 所以:
BlackV=YellowL(BlackV+BlueV)

其中YellowL为DepthMap中01空间数值,${\vec{BlackV} + \vec{BlueV}} \vec{interpolatedRay}$。
wPos=camWPos+BlackV=camWPos+YellowLinterpolatedRay

实现过程中:Vertex Shader中计算射线向量,Pixel shader中插值计算结果

  1. struct uoutput
  2. {
  3. float4 pos : SV_POSITION;
  4. half2 uv : TEXCOORD0;
  5. float4 uv_depth : TEXCOORD1;
  6. float4 interpolatedRay : TEXCOORD2;
  7. float3 cameraToFarPlane : TEXCOORD3;
  8. };
  9. uoutput far_ray_vert(uinput i)
  10. {
  11. uoutput o;
  12. o.pos = mul(UNITY_MATRIX_MVP, i.pos);
  13. //o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, i.uv);
  14. o.uv = i.uv ;
  15. o.uv_depth.xy = o.uv ;
  16. #if UNITY_UV_STARTS_AT_TOP
  17. if (_MainTex_TexelSize.y < 0)
  18. o.uv_depth.y = 1 - o.uv_depth.y;
  19. #endif
  20. // 计算远剪裁面每个角相对摄像机的向量
  21. // Clip space X and Y coords
  22. float2 clipXY = o.pos.xy / o.pos.w;
  23. // Position of the far plane in clip space
  24. float4 farPlaneClip = float4(clipXY, 1, 1);
  25. // Homogeneous world position on the far plane
  26. farPlaneClip *= float4(1,_ProjectionParams.x,1,1);
  27. float4 farPlaneWorld4 = mul(_ClipToWorld, farPlaneClip);
  28. // World position on the far plane ?????
  29. float3 farPlaneWorld = farPlaneWorld4.xyz / farPlaneWorld4.w;
  30. // Vector from the camera to the far plane
  31. o.cameraToFarPlane = farPlaneWorld - _WorldSpaceCameraPos;
  32. return o;
  33. }
  34. fixed4 far_ray_frag(uoutput o) : COLOR
  35. {
  36. float linearDepth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, o.uv_depth));
  37. float3 worldPos = _WorldSpaceCameraPos + linearDepth * o.cameraToFarPlane;
  38. //颜色输出
  39. float dis = length(worldPos.xyz);
  40. float3 worldPos2 = worldPos.xyz/dis;
  41. worldPos2 = worldPos2 * 0.5 + 0.5;
  42. return fixed4(worldPos2,1);
  43. }

C#代码:

mMat.SetMatrix("_ClipToWorld", (mCam.cameraToWorldMatrix * mCam.projectionMatrix).inverse);

mark

3. 近剪裁面射线插值

原理上上面类似,实际推导过程,可以参考《Shader 入门精要》或者这里, 代码下载:下载

计算近近剪裁面四个角相对摄像机向量

  1. Matrix4x4 GetFrustumCorners()
  2. {
  3. Matrix4x4 frustumCorners = Matrix4x4.identity;
  4. Camera camera = mCam;
  5. Transform cameraTransform = mCam.gameObject.transform;
  6. float fov = camera.fieldOfView;
  7. float near = camera.nearClipPlane;
  8. float aspect = camera.aspect;
  9. float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
  10. Vector3 toRight = cameraTransform.right * halfHeight * aspect;
  11. Vector3 toTop = cameraTransform.up * halfHeight;
  12. Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
  13. float scale = topLeft.magnitude / near;
  14. topLeft.Normalize();
  15. topLeft *= scale;
  16. Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
  17. topRight.Normalize();
  18. topRight *= scale;
  19. Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
  20. bottomLeft.Normalize();
  21. bottomLeft *= scale;
  22. Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;
  23. bottomRight.Normalize();
  24. bottomRight *= scale;
  25. frustumCorners.SetRow(0, bottomLeft);
  26. frustumCorners.SetRow(1, bottomRight);
  27. frustumCorners.SetRow(2, topRight);
  28. frustumCorners.SetRow(3, topLeft);
  29. return frustumCorners;
  30. }
  31. //设置
  32. mMat.SetMatrix("_FrustumCornersWS", GetFrustumCorners());

Shader

  1. struct uinput
  2. {
  3. float4 pos : POSITION;
  4. half2 uv : TEXCOORD0;
  5. };
  6. struct uoutput
  7. {
  8. float4 pos : SV_POSITION;
  9. half2 uv : TEXCOORD0;
  10. float4 uv_depth : TEXCOORD1;
  11. float4 interpolatedRay : TEXCOORD2;
  12. float4 cameraToFarPlane : TEXCOORD3;
  13. };
  14. uoutput near_ray_vert(uinput i)
  15. {
  16. uoutput o;
  17. o.pos = mul(UNITY_MATRIX_MVP, i.pos);
  18. o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, i.uv);
  19. o.uv = i.uv ;
  20. o.uv_depth.xy = o.uv ;
  21. #if UNITY_UV_STARTS_AT_TOP
  22. if (_MainTex_TexelSize.y < 0)
  23. o.uv_depth.y = 1 - o.uv_depth.y;
  24. #endif
  25. int index = 0;
  26. if (i.uv.x < 0.5 && i.uv.y < 0.5)
  27. {
  28. index = 0;
  29. }
  30. else if (i.uv.x > 0.5 && i.uv.y < 0.5)
  31. {
  32. index = 1;
  33. }
  34. else if (i.uv.x > 0.5 && i.uv.y > 0.5)
  35. {
  36. index = 2;
  37. }
  38. else
  39. {
  40. index = 3;
  41. }
  42. o.interpolatedRay = _FrustumCornersWS[(int)index];
  43. return o;
  44. }
  45. fixed4 near_ray_frag(uoutput o) : COLOR
  46. {
  47. float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, o.uv_depth));
  48. float3 worldPos = _WorldSpaceCameraPos + linearDepth * o.interpolatedRay.xyz;
  49. return WorldPosTo01(worldPos); float dis = length(worldPos.xyz);
  50. float3 worldPos2 = worldPos.xyz/dis;
  51. worldPos2 = worldPos2 * 0.5 + 0.5;
  52. return fixed4(worldPos2,1);
  53. }

mark

4. 小结

上面的推到过程,参考到一些以及Jim的博客,在Unity提供的Effect(Global Fog以及Emotion Blur)中也有使用类似的方式。对Global Fog中插值使用方式还挺有点不甚理解,使用pos.z作为射线索引,没弄明白这个Z为啥可以作为索引。

文章源码测试源码下载:http://pan.baidu.com/s/1c2rHVf6

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

闽ICP备14008679号