You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mode7效果:世界坐标转屏幕空间的片段着色器逆向求解

逆向还原Mode7变换的思路

嘿,这个问题我之前做复古风格3D渲染的时候也踩过坑!Mode7这种变换本质是透视投影+仿射变换的组合,没法直接靠矩阵求逆一键搞定,得手动拆解正向流程,一步步倒推回去。我给你理清楚具体怎么操作:

第一步:先把你的正向变换拆得明明白白

逆变换的前提是你得完全清楚自己的片段着色器里,世界坐标是怎么一步步变成屏幕坐标的。举个经典Mode7地面渲染的例子,正向流程一般是这样的:

// 输入:世界坐标 (worldX, worldY)
// 已知参数:旋转角angle,透视偏移offset,屏幕缩放/中心参数
float rotX = worldX * cos(angle) - worldY * sin(angle);
float rotY = worldX * sin(angle) + worldY * cos(angle);

// 透视投影计算屏幕坐标
float perspectiveDivisor = rotY + offset; // 防止除以0的偏移量
float screenX = (rotX / perspectiveDivisor) * screenScaleX + screenCenterX;
float screenY = (rotY / perspectiveDivisor) * screenScaleY + screenCenterY;

不管你的实现细节有多不一样,先把每一步的数学公式列出来,这是逆变换的基础。

第二步:从屏幕坐标反向走每一步

知道了正向的每一步,我们就可以用逆运算一步步推回世界坐标:

1. 先把屏幕坐标还原成透视除法前的归一化值

正向最后一步是把透视除法后的结果缩放平移到屏幕空间,那逆向第一步就是把屏幕坐标转回去:

// 输入:屏幕坐标(x_screen, y_screen)
float normX = (x_screen - screenCenterX) / screenScaleX;
float normY = (y_screen - screenCenterY) / screenScaleY;

这时候normX就是rotX / perspectiveDivisor,normY就是rotY / perspectiveDivisor。

2. 求解透视除法的除数

回忆正向里perspectiveDivisor = rotY + offset,把rotY = normY * perspectiveDivisor代入这个式子,就能解出perspectiveDivisor:

perspectiveDivisor = (normY * perspectiveDivisor) + offset
// 整理一下:
perspectiveDivisor * (1 - normY) = offset
perspectiveDivisor = offset / (1 - normY)

把这个写成GLSL代码,记得加个小偏移防止除以0:

float divisor = offset / (1.0 - normY + 0.0001); // +0.0001避免除数为0

3. 算出旋转后的坐标

有了除数,就能还原出旋转变换后的rotX和rotY:

float rotX = normX * divisor;
float rotY = normY * divisor;

4. 逆向旋转变换得到世界坐标

旋转矩阵的逆矩阵就是它的转置,所以逆旋转的公式很容易推导:
正向旋转是:

rotX = worldX*cosθ - worldY*sinθ
rotY = worldX*sinθ + worldY*cosθ

那逆旋转就是:

worldX = rotX*cosθ + rotY*sinθ
worldY = -rotX*sinθ + rotY*cosθ

写成代码就是:

float cosAngle = cos(angle);
float sinAngle = sin(angle);
float worldX = rotX * cosAngle + rotY * sinAngle;
float worldY = -rotX * sinAngle + rotY * cosAngle;

第三步:适配你的自定义变换

如果你的Mode7还有额外操作(比如先平移世界坐标、非均匀缩放、甚至自定义扭曲),只需要对应着逆向:

  • 比如正向先做了worldX -= originX,那逆向最后要加回去:worldX += originX
  • 如果正向给rotX乘了自定义缩放scaleX,那逆向就要除以scaleX:rotX /= scaleX

完整的逆向代码示例

把上面的步骤整合起来,就是一个可直接用的GLSL片段:

// 输入:屏幕坐标(x_screen, y_screen)
// 已知参数:angle, offset, screenScaleX, screenScaleY, screenCenterX, screenCenterY

// 1. 还原归一化坐标
float normX = (x_screen - screenCenterX) / screenScaleX;
float normY = (y_screen - screenCenterY) / screenScaleY;

// 2. 计算透视除数
float divisor = offset / (1.0 - normY + 0.0001);

// 3. 得到旋转后坐标
float rotX = normX * divisor;
float rotY = normY * divisor;

// 4. 逆旋转得到世界坐标
float cosA = cos(angle);
float sinA = sin(angle);
float worldX = rotX * cosA + rotY * sinA;
float worldY = -rotX * sinA + rotY * cosA;

几个要注意的点

  • 必须严格匹配正向流程:如果你的正向有不可逆操作(比如clamp或者取整),那逆变换可能会有歧义,得针对性处理边界情况。
  • 精度问题:如果渲染时出现抖动,可以给变量加上highp精度限定符,提升计算精度。
  • 除数保护:一定要给可能为0的除数加个极小的偏移,不然会出现NaN导致渲染错误。

内容的提问来源于stack exchange,提问作者mfl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:45:43