Metal着色语言光线追踪着色器的垂直拉伸与水平移动问题
问题分析与修复方案
问题根源
- 垂直拉伸:未考虑屏幕宽高比,NDC坐标直接转换时,y轴比例未适配屏幕分辨率,导致射线方向在相机空间的垂直维度比例错误。
- 水平移动速度异常:硬编码射线深度值(如-1、-3),未结合传入的
fov参数计算透视投影的焦距,导致射线水平覆盖范围与相机实际视场角不匹配,相机移动时场景相对速度偏离预期。
修复步骤与代码修改
核心修复逻辑
- 校正宽高比:对NDC坐标的x分量乘以宽高比,确保相机空间中射线的水平/垂直比例与屏幕一致,消除拉伸。
- 基于FOV计算焦距:用视场角推导相机空间的射线深度,替代硬编码值,匹配相机透视参数,修复移动速度问题。
修改后的代码片段
fragment half4 shader_fragment(Vertex in [[stage_in]], constant SCNSceneBuffer& scn_frame [[buffer(0)]], constant NodeBuffer& scn_node [[buffer(1)]], constant Resolution& resolution [[buffer(3)]], constant float& fov [[buffer(2)]]) { float time = scn_frame.time; float2 resolutionVector = float2(resolution.width, resolution.height); // 计算屏幕宽高比 float aspectRatio = resolutionVector.x / resolutionVector.y; float2 uv = float2(in.position.x, resolutionVector.y - in.position.y) / resolutionVector.xy; float2 ndc = uv * 2.0 - 1.0; // 校正NDC的宽高比,避免垂直拉伸 ndc.x *= aspectRatio; // 将FOV转换为弧度,计算透视焦距 float fovRadians = fov * M_PI / 180.0; float focalLength = 1.0 / tan(fovRadians * 0.5); // 相机空间z轴指向后方,所以射线z分量为负的焦距 float3 rayDirCameraSpace = normalize(float3(ndc.x, ndc.y, -focalLength)); // 射线转换到世界空间(原逻辑保留) float4x4 invViewMatrix = scn_frame.inverseViewTransform; float3 rayDirWorldSpace = (invViewMatrix * float4(rayDirCameraSpace, 0.0)).xyz; float3 rayOrigin = (invViewMatrix * float4(0, 0, 0, 1)).xyz; // 后续着色器代码... }
修复说明
- 宽高比校正:通过
ndc.x *= aspectRatio,让射线在相机空间的水平范围适配屏幕宽度,保证不同分辨率下场景比例正常,解决垂直拉伸问题。 - FOV焦距计算:透视投影的焦距由视场角决定,
1/tan(fov/2)是标准透视相机的焦距公式,用这个值替代硬编码的深度,确保射线的水平覆盖范围与相机视场角匹配,修复移动时的速度异常,同时避免硬改深度带来的场景畸变。
内容的提问来源于stack exchange,提问作者Arthur TL
相关产品推荐
相关产品推荐

