湘潭网站建设方案费用,营销活动,小广告清理,一个内部网站如何做外网映射通过深度值重建世界坐标#xff0c;可以做出很多有意思的后处理效果#xff0c;先实现下度值重建世界坐标这个功能。
一.验证重建效果
首先#xff0c;得先找到一种证明反推回世界空间位置正确的方法。在相机前摆放几个物体#xff0c;尽量使之在世界坐标下的位置小于1可以做出很多有意思的后处理效果先实现下度值重建世界坐标这个功能。
一.验证重建效果
首先得先找到一种证明反推回世界空间位置正确的方法。在相机前摆放几个物体尽量使之在世界坐标下的位置小于1方便判定颜色然后将几个物体的shader换成如下的一个打印世界空间位置的shader //打印在世界空间位置
Shader Universal Render Pipeline/Dejavu/ReconstructPositionWithDepth/WorldPosPrint
{SubShader{Tags { RenderPipeline UniversalPipeline RenderType Opaque }LOD 300ZWrite[_ZWrite]Cull Off ZWrite OnPass{Name ForwardLitTags{LightMode UniversalForward}HLSLINCLUDE
#include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlslENDHLSLHLSLPROGRAM#pragma vertex vert#pragma fragment fragstruct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float3 worldPos : TEXCOORD0;float4 vertex : SV_POSITION;};v2f vert(appdata v){v2f o;UNITY_SETUP_INSTANCE_ID(v);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);o.vertex TransformObjectToHClip(v.vertex);o.worldPos mul(unity_ObjectToWorld, v.vertex);return o;}float4 frag(v2f i) : SV_Target{return float4(i.worldPos, 1.0);}ENDHLSL}Pass{Name DepthOnlyTags{LightMode DepthOnly}ZWrite OnColorMask 0Cull[_Cull]HLSLPROGRAM#pragma exclude_renderers gles gles3 glcore#pragma target 4.5#pragma vertex DepthOnlyVertex#pragma fragment DepthOnlyFragment// -------------------------------------// Material Keywords#pragma shader_feature_local_fragment _ALPHATEST_ON#pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A//--------------------------------------// GPU Instancing#pragma multi_compile_instancing#pragma multi_compile _ DOTS_INSTANCING_ON#include Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl#include Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlslENDHLSL}}
} 得到的效果如下图所示颜色代表了世界坐标位置。要注意的是URP中想要渲染Depth需要Tags{LightMode DepthOnly}这个Pass 在之后的重建世界坐标位置的后处理Shader开启后如果摄像机显示的没有变化则证明重建成功 二.逆矩阵方式重建 世界坐标的重建有两种方法最直观的一种就是通过VP逆矩阵在fragment ShadeClip空间中的位置转换为世界坐标。该方法的核心步骤为
1.通过uv和深度图中采样的深度信息构建NDC中坐标位置从而构建Clip空间中位置
2.NDC坐标乘以逆矩阵即可得到世界坐标。
fragment中的代码为
//fragment shaderfloat4 frag(v2f i) : SV_Target{float sceneRawDepth SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.uv);float4 ndc float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, sceneRawDepth, 1);#if UNITY_UV_STARTS_AT_TOPndc.y * -1;#endiffloat4 worldPos mul(UNITY_MATRIX_I_VP, ndc);worldPos / worldPos.w;return worldPos; }看起来比较简单但是其中有一个/w的操作如果按照正常思维来算应该是先乘以w然后进行逆变换最后再把world中的w抛弃即是最终的世界坐标不过实际上投影变换是一个损失维度的变换我们并不知道应该乘以哪个w所以实际上上面的计算并非按照理想的情况进行的计算而是根据计算推导而来。具体推导How to go from device coordinates back to worldspace in OpenGL (with explanation)
已知条件M为VP矩阵M^-1即为其逆矩阵Clip为裁剪空间ndc为标准设备空间world为世界空间
ndc Clip.xyzw / Clip.w Clip / Clip.w
world M^-1 * Clip
二者结合得
world M ^-1 * ndc * Clip.w
我们已知M和ndc然而还是不知道Clip.w但是有一个特殊情况是world的w坐标经过变换后应该是1即
1 world.w M^-1 * ndc.w * Clip.w
进而得到Clip.w 1 / M^ -1 * ndc.w
带入上面等式得到
world M ^ -1 * ndc / M ^ -1 * ndc.w
优化
上边的shader代码中自己通过uv和深度构建了ndc坐标但是实际上Unity URP提供了ComputeWorldSpacePosition函数可以直接调用函数位置在core rp中的Common.hlsl
float3 ComputeWorldSpacePosition(float2 positionNDC, float deviceDepth, float4x4 invViewProjMatrix)
{float4 positionCS ComputeClipSpacePosition(positionNDC, deviceDepth);float4 hpositionWS mul(invViewProjMatrix, positionCS);return hpositionWS.xyz / hpositionWS.w;
}
其中的omputeClipSpacePosition函数也在core rp中的Common.hlsl中
float4 ComputeClipSpacePosition(float2 positionNDC, float deviceDepth)
{float4 positionCS float4(positionNDC * 2.0 - 1.0, deviceDepth, 1.0);#if UNITY_UV_STARTS_AT_TOP// Our world space, view space, screen space and NDC space are Y-up.// Our clip space is flipped upside-down due to poor legacy Unity design.// The flip is baked into the projection matrix, so we only have to flip// manually when going from CS to NDC and back.positionCS.y -positionCS.y;
#endifreturn positionCS;
}
最终我们的Shader只需要 //fragment shaderfloat4 frag(v2f i) : SV_Target{float sceneRawDepth SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.uv);float3 worldPos ComputeWorldSpacePosition(i.uv, sceneRawDepth, UNITY_MATRIX_I_VP);return float4(worldPos, 1);}
代码立刻显得很清爽ComputeWorldSpacePosition实际内容和自己上边写的shader是一致的
Bonus
以上的方法中用到了UNITY_MATRIX_I_VP这个矩阵在一些博主写的文章中用的方式是从C#端将camera的VP逆矩阵传入shader中
var vpMatrix Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix;
之后传入vpMatrix.inverse
在shader中使用该矩阵进行重建时如果代码还按照上边的shader中写是无法正确还原的。
纠其原因是因为Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix的逆矩阵并不和UNITY_MATRIX_I_VP一致UNITY_MATRIX_I_VP是和平台无关的而Camera相关的矩阵是和Opengl还是directx等相关的在opengl的模式下重建还原代码如下注意的是sceneRawDepth也进行了*2-1的操作这是因为opengl的z范围是-1到1。 //fragment shaderfloat4 frag(v2f i) : SV_Target{float sceneRawDepth SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.uv);#if defined(UNITY_REVERSED_Z)sceneRawDepth 1 - sceneRawDepth;#endiffloat4 ndc float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, sceneRawDepth * 2 - 1, 1);float4 worldPos mul(_InverseVPMatrix, ndc);worldPos / worldPos.w;return worldPos;}
小结
1.这种方式重建世界坐标性能比较差一般来说我们都是逐顶点地进行矩阵运算毕竟定点数一般还是比较少的但是全屏幕逐像素进行矩阵运算这个计算量就不是一般的大了性能堪忧。
2.能用Unity提供的API就用其提供的Unity封装和处理了很多跨平台的情况比如Z的范围左手右手坐标系等自己撸极容易出错。 三.射线方式重建
原理
这种方式的重建可以参考Secrets of CryENGINE 3 Graphics Technology这个CryTech 2011年的PPT。借用一张图 然后偶再画个平面的图 上图中A为相机位置G为空间中我们要重建的一点那么该点的世界坐标为AworldPos 向量AG我们要做的就是求得向量AG即可。根据三角形相似的原理三角形AGH相似于三角形AFC则得到AH / AC AG / AF。由于三角形相似就是比例关系所以我们可以把AH / AC看做01区间的比值那么AC就相当于远裁剪面距离即为1AH就是我们深度图采样后变换到01区间的深度值即Linear01Depth的结果d。那么AG AF * d。所以下一步就是求AF即求出相机到屏幕空间每个像素点对应的射线方向。
如何获得AF呢在后处理Shader中实际上就是绘制了一个Quad对应整个屏幕。这个Quad的四个边界点刚好对应屏幕的四个边界点uv是0,1区间的刚好对应屏幕空间我们通过*2 - 1将其转化到-1,1区间就可以得到四个边界对应NDC坐标系下的xy坐标了在VertexShader中计算出这4个点对应的射线后将其传入FragmentShader中通过插值就会自动得到每个像素点对应的AF。
Frament中还原
//fragment shaderfloat4 frag(v2f i) : SV_Target{float sceneRawDepth SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.uv);float linear01Depth Linear01Depth(sceneRawDepth, _ZBufferParams);float3 worldPos _WorldSpaceCameraPos.xyz ( linear01Depth) * i.viewRayWorld ;return float4(worldPos, 1);}VertexShader中计算射线
方法1 用ComputeWorldSpacePosition方法计算四个顶点对应的世界坐标位置减去摄像机位置后就是所求射线。这种方式还是很好理解的每个顶点矩阵运算也只有一次个人感觉是最优方案。 //vertex shaderv2f vert(appdata v){v2f o;UNITY_SETUP_INSTANCE_ID(v);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);o.positionCS TransformObjectToHClip(v.positionOS.xyz);//方法1float sceneRawDepth 1;#if defined(UNITY_REVERSED_Z)sceneRawDepth 1 - sceneRawDepth;
#endiffloat3 worldPos ComputeWorldSpacePosition(v.uv, sceneRawDepth, UNITY_MATRIX_I_VP);o.viewRayWorld worldPos - _WorldSpaceCameraPos.xyz;o.uv v.uv;return o;}方法2
原理和方法1一样只是先由clip空间转到View空间再由View空间转到摄像机空间。其中_InverseVMatrix是由C#端传入的Camera.main.worldToCameraMatrix.inverse。
那么为什么不能直接使用Unity中的UNITY_MATRIX_I_V呢因为后处理Shader中Unity根本不会为你设置UNITY_MATRIX_I_VPUNITY_MATRIX_I_VP是单位矩阵。 v2f vert(appdata v){v2f o;UNITY_SETUP_INSTANCE_ID(v);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);o.positionCS TransformObjectToHClip(v.positionOS.xyz);float4 clipPos ComputeClipSpacePosition(v.uv, 0);float4 viewPos mul(UNITY_MATRIX_I_P, clipPos);viewPos.xyz viewPos.xyz / viewPos.w;float3 worldPos mul(_InverseVMatrix, viewPos).xyz;o.viewRayWorld worldPos - _WorldSpaceCameraPos.xyz;o.uv v.uv;return o;}
方法3
这次我们不求得四个顶点在世界空间中的位置而是在View空间中以射线的方式做逆变换因为在view空间中camera的位置为0所以viewpos就是viewRay的方向。射线的逆变换要注意的是只需要3X3的3维矩阵要排除掉平移变换的影响因为射线无论如何平移都是一样的射线 v2f vert(appdata v){v2f o;UNITY_SETUP_INSTANCE_ID(v);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);o.positionCS TransformObjectToHClip(v.positionOS.xyz);//方法3 float4 clipPos ComputeClipSpacePosition(v.uv, 0);float4 viewPos mul(UNITY_MATRIX_I_P, clipPos);float3 viewRay viewPos.xyz / viewPos.w;o.viewRayWorld mul((float3x3)_InverseVMatrix, viewRay);o.uv v.uv;return o;}
最终结果 无论用那种方式开关后处理效果都能发现场景是相同效果证明重建成功 完整工程代码
GitHub - Dejavu0709/StudyForShader 中的ReconstructPositionWithDepth文件夹
相关资料感谢大佬们的无私分享
Reconstruct the world space positions of pixels from the depth texture | Universal RP | 10.5.1
Unity Shader-深度相关知识总结与效果实现LinearDepthReverse Z世界坐标重建软粒子高度雾运动模糊扫描线效果_puppet_master的专栏-CSDN博客_shader深度
使用深度图重建世界坐标_小孔明的专栏-CSDN博客