Apple Metal实现AE Optics Compensation效果的问题排查
在Metal片段着色器中实现Adobe After Effects的Optics Compensation效果
现有代码的核心问题
- 极坐标变量混淆:代码把半径和角度的变量完全搞反了——
theta被错误赋值为半径(sqrt的结果),r被赋值为角度(atan2的结果),直接导致后续坐标转换逻辑完全错误。 - 畸变逻辑与FOV无关:当前用固定的
a值做多项式畸变,完全没有关联目标效果的FOV参数,无法实现AE中通过FOV调节畸变程度的核心功能。 - 未处理FOV方向:没有适配水平/垂直/对角线的FOV方向差异,无法满足不同方向的视场角需求。
- 坐标转换逻辑错误:畸变后的坐标计算完全不符合光学补偿的数学模型,导致最终效果偏离预期。
正确的实现思路与代码
AE的Optics Compensation本质是基于视场角(FOV)的鱼眼/反向鱼眼畸变,核心是通过FOV计算径向坐标的非线性变换,同时支持中心偏移和FOV方向适配。
核心数学模型
- 坐标归一化:将纹理坐标转换为以
Center为原点的归一化坐标(范围-1到1),并根据FOV方向做宽高比适配。 - 径向畸变计算:利用FOV计算畸变后的径向距离:
- 将FOV转换为弧度:
fov_rad = radians(fov) - 归一化径向距离
r(0到1,从中心到边缘) - 畸变后的径向距离:
r_distorted = tan(r * fov_rad / 2.0) / tan(fov_rad / 2.0)
这个公式保证: - FOV=90°时无畸变
- FOV>90°时产生桶形畸变(边缘向外拉伸)
- FOV<90°时产生枕形畸变(边缘向内收缩)
- 将FOV转换为弧度:
- 坐标还原:将畸变后的极坐标转换回笛卡尔坐标,再转换为纹理采样坐标。
完整Metal片段着色器代码
#include <metal_stdlib> using namespace metal; // Optics Compensation参数结构体 struct OpticsCompensationParams { float2 center; // 畸变中心,归一化坐标(0-1) float fov; // 视场角(0-180度) int fovOrientation; // 0:水平, 1:垂直, 2:对角线 float2 imageSize; // 图像宽高,用于计算宽高比 }; fragment float4 opticsCompensationFragment( metalpetal::VertexOut vertexIn [[ stage_in ]], texture2d<float, access::sample> inputTexture [[ texture(0) ]], sampler inputTextureSampler [[ sampler(0) ]], constant OpticsCompensationParams& params [[ buffer(0) ]] ) { float2 uv = vertexIn.textureCoordinate; // 1. 转换为以center为原点的归一化坐标(-1到1) float2 centeredUV = (uv - params.center) * 2.0; // 2. 根据FOV方向处理宽高比适配 float aspectRatio = params.imageSize.x / params.imageSize.y; float2 scaledUV = centeredUV; if (params.fovOrientation == 0) { // 水平FOV:y方向按宽高比缩放,保证水平FOV准确 scaledUV.y /= aspectRatio; } else if (params.fovOrientation == 1) { // 垂直FOV:x方向按宽高比缩放 scaledUV.x *= aspectRatio; } // 对角线FOV无需缩放 // 3. 计算极坐标:半径和角度 float r = length(scaledUV); float theta = atan2(scaledUV.y, scaledUV.x); // 4. 基于FOV计算畸变后的径向距离,避免极端值报错 float fov_rad = radians(params.fov); float tan_half_fov = tan(fov_rad * 0.5); float r_distorted = r; if (tan_half_fov > 0.0001) { float r_normalized = r / sqrt(2.0); // 归一化到0-1(对角线最大半径为√2) r_distorted = tan(r_normalized * fov_rad * 0.5) / tan_half_fov; r_distorted *= sqrt(2.0); // 还原回原范围 } // 5. 转换回笛卡尔坐标 float2 distortedScaledUV = r_distorted * float2(cos(theta), sin(theta)); // 6. 还原宽高比缩放 if (params.fovOrientation == 0) { distortedScaledUV.y *= aspectRatio; } else if (params.fovOrientation == 1) { distortedScaledUV.x /= aspectRatio; } // 7. 转换回纹理采样坐标(0-1) float2 finalUV = (distortedScaledUV * 0.5) + params.center; // 采样纹理,超出范围的部分用边缘像素填充 return inputTexture.sample(inputTextureSampler, finalUV, clamp_to_edge); }
代码说明
- 参数传递:通过结构体传递所有可调参数,包括中心、FOV、方向和图像尺寸,便于外部控制。
- 宽高比适配:针对不同FOV方向缩放坐标,保证FOV计算准确对应所选方向。
- 畸变计算:严格遵循基于FOV的鱼眼畸变公式,完美匹配AE的Optics Compensation效果。
- 边界处理:添加极端FOV值的保护逻辑,避免除以0错误,同时通过
clamp_to_edge处理超出纹理范围的采样。
验证与调整
- FOV=90°时,输出与原图像一致,无畸变。
- FOV>90°时,出现桶形畸变,边缘向外拉伸;FOV越大,畸变越明显。
- FOV<90°时,出现枕形畸变,边缘向内收缩;FOV越小,畸变越明显。
- 调整
center参数,畸变中心会随之偏移,完全符合AE的交互逻辑。
内容的提问来源于stack exchange,提问作者Alisher
相关产品推荐
相关产品推荐

