一、变换矩阵的三种常见使用场景

1. 动态变换(在while循环中) 

每帧重新计算矩阵,实现动画效果

while (!glfwWindowShouldClose(window))
{
    // 每帧都重新计算 - 产生动态效果
    float time = glfwGetTime();
    float xOffset = sin(time);  // 随时间变化
    
    glm::mat4 transform = glm::mat4(1.0f);
    transform = glm::translate(transform, glm::vec3(xOffset, 0.0f, 0.0f));
    
    // 传递给着色器并绘制...
}

2. 静态变换(在while循环外)

只在程序开始时计算一次,之后保持不变

int main()
{
    // 初始化... 
    
    // 在循环外计算一次,整个程序运行期间都不变
    glm::mat4 transform = glm::mat4(1.0f);
    transform = glm::translate(transform, glm::vec3(0.5f, 0.5f, 0.0f));
    transform = glm::scale(transform, glm::vec3(0.5f, 0.5f, 1.0f));
    
    // 获取uniform位置(也可以只做一次)
    unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
    
    while (!glfwWindowShouldClose(window))
    {
        // 清屏、绑定纹理等...
        
        ourShader.use();
        // 使用预先计算好的矩阵(每帧都一样)
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
        
        glDrawElements(...);
    }
}

适用场景:场景中静止的物体(建筑、地形等)。

3. 混合使用(部分静态 + 部分动态)

int main()
{
    // 静态部分:基础变换(只计算一次)
    glm::mat4 baseTransform = glm::mat4(1.0f);
    baseTransform = glm::scale(baseTransform, glm::vec3(0.5f, 0.5f, 1.0f));
    
    unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
    
    while (!glfwWindowShouldClose(window))
    {
        // 动态部分:每帧添加旋转
        glm::mat4 transform = baseTransform;  // 从静态矩阵开始
        float time = glfwGetTime();
        transform = glm::rotate(transform, time, glm::vec3(0.0f, 0.0f, 1.0f));
        
        ourShader.use();
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform));
        
        glDrawElements(...);
    }
}

适用场景:物体本身有固定缩放和位置,但还要自转。

二、性能考虑

每帧都计算矩阵的性能开销

// 轻量级操作(没问题)
transform = glm::translate(transform, glm::vec3(xOffset, 0.0f, 0.0f));

// 重量级操作(尽量避免每帧都做)
transform = glm::inverse(someComplexMatrix);  // 矩阵求逆很慢
transform = glm::perspective(...);             // 投影矩阵通常只计算一次

最佳实践

  • 视图矩阵投影矩阵:通常在循环外计算一次,除非摄像机在移动

  • 模型矩阵:如果物体在移动/旋转,就在循环内计算

三、实际例子对比

错误示例:不必要的重复计算

while (!glfwWindowShouldClose(window))
{
    // 投影矩阵不会变,却每帧都计算(浪费性能)
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f/600.0f, 0.1f, 100.0f);
    
    // 视图矩阵也不会变(摄像机不动)
    glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
    
    // 只有模型矩阵在变化
    glm::mat4 model = glm::rotate(glm::mat4(1.0f), time, glm::vec3(0.0f, 1.0f, 0.0f));
    
    // 传递三个矩阵...
}

正确示例:优化性能

int main()
{
    // 在循环外计算一次
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), 800.0f/600.0f, 0.1f, 100.0f);
    glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -3.0f));
    
    // 获取uniform位置(只做一次)
    unsigned int projectionLoc = glGetUniformLocation(shader.ID, "projection");
    unsigned int viewLoc = glGetUniformLocation(shader.ID, "view");
    unsigned int modelLoc = glGetUniformLocation(shader.ID, "model");
    
    while (!glfwWindowShouldClose(window))
    {
        float time = glfwGetTime();
        
        // 只有模型矩阵需要每帧更新
        glm::mat4 model = glm::rotate(glm::mat4(1.0f), time, glm::vec3(0.0f, 1.0f, 0.0f));
        
        shader.use();
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        
        glDrawElements(...);
    }
}

四、特殊情况

响应窗口大小变化

投影矩阵通常需要在窗口大小改变时重新计算:

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
    
    // 重新计算投影矩阵(适应新的宽高比)
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)width/height, 0.1f, 100.0f);
    // 然后需要重新传递给着色器...
}

用户输入控制

根据键盘输入动态更新矩阵:

glm::mat4 view = glm::mat4(1.0f);
float cameraX = 0.0f, cameraY = 0.0f;

while (!glfwWindowShouldClose(window))
{
    // 根据按键更新位置
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraX -= 0.01f;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraX += 0.01f;
    
    // 每帧重新计算视图矩阵(因为摄像机在移动)
    view = glm::translate(glm::mat4(1.0f), glm::vec3(cameraX, cameraY, -3.0f));
    
    // 传递给着色器...
}

总结

情况 计算位置 原因
静态物体 while循环外 变换不变,避免重复计算
动画物体 while循环内 每帧都需要新位置
投影矩阵 while循环外 通常不变,除非窗口大小改变
跟随鼠标/键盘 while循环内 需要响应用户输入

记住原则

  • 变的东西 → 放循环内

  • 不变的东西 → 放循环外

更多推荐