使用InverseViewProjection获取世界坐标存在精度不足及结果异常问题
Raymarching射线方向计算问题修复方案
现有方案问题分析
方案1(逆VP矩阵实现)问题
- 核心错误1:裁剪空间坐标构造错误,你构造的
scc = float4(p,0.0,1.0)中z值为0,对应裁剪空间近远平面中间位置,不是像素对应的正确射线终点,方向本身就存在偏差 - 核心错误2:精度适配问题,大世界场景下直接用远平面世界坐标减相机世界坐标,大数相减会丢失32位浮点数精度,距离原点越远误差越大,表现为画面抖动
方案2(深度转世界位置实现)问题
- 核心错误1:深度处理错误,你直接用投影矩阵输出的非线性深度乘屏幕空间射线,透视投影的深度是w分量插值得到的非线性值,直接相乘会导致比例错误,出现拉伸
- 核心错误2:射线方向反转,
normalize(cameraPos - wPos)得到的是从像素位置指向相机的向量,raymarching需要的是从相机指向像素的向量,方向写反 - 核心错误3:矩阵乘法顺序不匹配,如果你的引擎用列向量优先(Unity、Unreal默认),矩阵乘法应该是
mul(矩阵, 向量),你写的mul(pos, viewInverse)是行向量的写法,顺序反了就会出现旋转时方向翻转的问题
推荐修复实现
根据体积特效的不同需求选对应方案即可,代码已经做了简化适配,直接替换原有逻辑就行:
场景1:不需要深度,直接计算像素射线方向(适合体积雾、体积云等全屏特效)
该方案完全避免大坐标运算,不管相机离世界原点多远都不会出现抖动:
// 输入参数:uv、视图逆矩阵viewInverse、投影矩阵mProjection uv.y = 1 - uv.y; float2 p = uv * 2 - 1; // 先计算视图空间下的射线方向,不涉及世界大坐标运算 float3 viewRay = float3(p.x / mProjection[0][0], p.y / mProjection[1][1], 1.0); // 仅用视图逆矩阵的旋转分量转世界空间,避免精度损失 float3 rd = normalize(mul((float3x3)viewInverse, viewRay));
场景2:需要读取深度缓冲区计算射线(适合和场景物体交互的体积特效)
// 第一步先把硬件返回的非线性深度转成线性深度,引擎有内置函数直接用即可 // 无内置函数可用公式自己算:float linearDepth = (2 * near * far) / (far + near - (2 * depth - 1) * (far - near)); // 其中near是相机近裁剪面距离,far是相机远裁剪面距离 float linearDepth = LinearEyeDepth(depth, _ZBufferParams); float4 DepthToViewPos(float linearDepth, float2 texCoord){ float x = texCoord.x * 2 - 1; float y = texCoord.y * 2 - 1; float3 viewPos = float3(x / mProjection[0][0], y / mProjection[1][1], 1.0) * linearDepth; return float4(viewPos, 1.0); } float3 DepthToWorldPos(float linearDepth, float2 texCoord){ float4 viewPos = DepthToViewPos(linearDepth, texCoord); // 如果还是出现翻转,把矩阵和向量的顺序反过来就行:mul(viewPos, viewInverse) float4 worldPos = mul(viewInverse, viewPos); return worldPos.xyz; } float3 wPos = DepthToWorldPos(linearDepth, uv); // 修正方向为相机指向像素位置 float3 rd = normalize(wPos - cameraPos);
内容的提问来源于stack exchange,提问作者mat42k
相关产品推荐
相关产品推荐

