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

C++ OpenGL+GLM实现无拉伸2D图元Z轴旋转的方法

2D旋转拉伸问题原因与修复方案

核心问题根源

旋转后出现类3D拉伸、边长比例失准问题,由三个常见错误导致:

  • 缺失正交投影矩阵做宽高比校正:当前直接将模型矩阵结果输出到裁剪空间,OpenGL默认裁剪空间x、y轴范围均为[-1,1],如果窗口宽高比不是1:1,x、y轴单位长度对应的像素数不一致,哪怕不旋转正方形本身就会被拉成长方形,旋转时拉伸感会更明显,看起来类似3D透视变形。
  • 矩阵累积与乘法顺序错误:当前每帧在原有batchRotator、batchMover矩阵基础上叠加新的旋转、平移变换,浮点误差会随运行时间累积导致形变;同时矩阵乘法遵循右乘优先规则,顺序错误也会导致变换结果不符合预期。
  • 附带逻辑bug:碰撞盒更新逻辑中batchBox[i].z += speed[i].x、batchBox[i].w += speed[i].y属于错误赋值,碰撞盒宽高不应该随位移累加,虽然不影响渲染,但会导致后续碰撞检测失效。

分步修复方案

1. 添加正交投影矩阵校正宽高比

2D渲染必须使用正射投影,不要直接输出模型坐标到裁剪空间。在窗口初始化、窗口尺寸变更时生成对应投影矩阵:

// 传入当前窗口宽高,生成y轴向下、和屏幕像素坐标对齐的正交投影矩阵
glm::mat4 projection = glm::ortho(
    0.0f,
    (float)windowWidth,
    (float)windowHeight,
    0.0f,
    -1.0f,
    1.0f
);

修改顶点着色器,传入投影矩阵,注意矩阵乘法顺序不要写反:

//shader vertex
#version 430 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoord;
uniform mat4 model;
uniform mat4 projection;

out vec2 TexCoord;
void main()
{
    gl_Position = projection * model * vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}

这一步完成后,坐标单位会和屏幕像素1:1对应,从根源上消除窗口比例导致的拉伸。

2. 重构模型矩阵生成逻辑,避免累积误差

不要逐帧在旧矩阵上叠加变换,改为单独存储每个对象的位置、旋转角度、缩放属性,每帧重新构造模型矩阵,彻底消除浮点累积误差。
首先替换原来存储batchRotator、batchMover的数组,改为存储对象基础属性:

  • std::vector<glm::vec2> batchPos:存储每个对象的2D坐标
  • std::vector<float> batchRot:存储每个对象的累计旋转角度
  • std::vector<glm::vec2> batchScale:存储每个对象的x/y轴缩放值,正方形默认填glm::vec2(1.0f, 1.0f)
  • std::vector<glm::mat4> batchModel:存储每帧重新生成的模型矩阵

修改moveBatch函数逻辑:

void Trans::moveBatch(std::vector <glm::vec2>& speed, std::vector <float>& rotDelta)
{
    for (unsigned int i = 0; i < speed.size(); i++)
    {
        // 更新对象基础属性
        batchPos[i] += speed[i];
        batchRot[i] += rotDelta[i];
        // 修正碰撞盒更新逻辑,仅更新位置坐标,宽高不随位移变化
        batchBox[i].x = batchPos[i].x;
        batchBox[i].y = batchPos[i].y;

        // 每帧从零构造模型矩阵,严格遵循变换顺序
        glm::mat4 model = glm::mat4(1.0f);
        // 变换顺序:平移到对应位置 → 绕Z轴旋转 → 缩放,矩阵书写顺序和执行顺序相反
        model = glm::translate(model, glm::vec3(batchPos[i], 0.0f));
        // 如果需要围绕图元中心旋转,取消下方两行注释,替换spriteWidth、spriteHeight为你的图元实际像素宽高
        // model = glm::translate(model, glm::vec3(spriteWidth * 0.5f, spriteHeight * 0.5f, 0.0f));
        model = glm::rotate(model, glm::radians(batchRot[i]), glm::vec3(0.0f, 0.0f, 1.0f));
        // model = glm::translate(model, glm::vec3(-spriteWidth * 0.5f, -spriteHeight * 0.5f, 0.0f));
        model = glm::scale(model, glm::vec3(batchScale[i], 1.0f));

        batchModel[i] = model;
    }
}

注意:默认状态下旋转会围绕图元的左上角顶点进行,如果需要围绕图元自身中心旋转,按注释添加中心偏移的平移变换即可,否则旋转时会出现图元偏移的视觉误差,容易被误判为拉伸。

3. 校验顶点数据

确认正方形顶点的宽高比例为1:1,比如边长100像素的正方形,四个顶点坐标应按(0,0)、(100,0)、(100,100)、(0,100)定义,避免顶点本身比例错误导致的形变。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 15:51:22