OpenGL中纹理翻转随X轴距离产生差异的原因咨询
OpenGL翻美元场景纹理翻转异常问题
我正在用OpenGL制作一个“翻美元”场景,每个美元是由4个顶点组成的2D矩形,为其映射了美元正反面两张纹理,绕Y轴旋转时,通过判断矩形右上顶点X坐标是否小于自身中心原点来切换纹理。通过循环绘制10个矩形,每个通过model矩阵平移到屏幕不同位置。
矩形顶点、位置数组代码
float vertices[] = { // positions // texture coords 0.5f, 0.5f, 0.0f, -1.0f, 1.0f, // top right 0.5f, -0.5f, 0.0f, -1.0f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left }; // 用于计算当前顶点位置的独立数组 float verticesPos[] = { // positions 0.5f, 0.5f, 0.0f, 0.5f, -0.5f, 0.0f, -0.5f, -0.5f, 0.0f, -0.5f, 0.5f, 0.0f }; // 用于平移model矩阵,将美元放置在屏幕不同位置 glm::vec3 moneyPositions[] = { glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(2.0f, 5.0f, -15.0f), glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f), glm::vec3(2.4f, -0.4f, -3.5f), glm::vec3(-1.7f, 3.0f, -7.5f), glm::vec3(1.3f, -2.0f, -2.5f), glm::vec3(1.5f, 2.0f, -2.5f), glm::vec3(1.5f, 0.2f, -1.5f), glm::vec3(-1.3f, 1.0f, -1.5f) };
视图与透视矩阵代码
glm::mat4 view = glm::mat4(1.0f); view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); unsigned int viewLoc = glGetUniformLocation(shaders.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view)); glm::mat4 projection = glm::mat4(1.0f); projection = glm::perspective(glm::radians(45.0f), 800.0f/600.0f, 0.1f, 100.0f); unsigned int projectionLoc = glGetUniformLocation(shaders.ID, "projection"); glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
绘制与纹理绑定代码
for (int i = 0; i < 10; ++i) { glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, moneyPositions[i]); model = glm::rotate(model, (float)glfwGetTime(), glm::vec3(0.0f, 1.0f, 0.0f)); shaders.setMat4("model", &model[0][0]); // 计算当前顶点位置 glm::vec3 updatedVertices[4]; for (int j = 0; j < sizeof(verticesPos) / sizeof(float); j += 3) { glm::vec4 pos(verticesPos[j], verticesPos[j + 1], verticesPos[j + 2], 1.0f); glm::vec4 modelPos = model * pos; updatedVertices[j / 3] = glm::vec3(modelPos[0], modelPos[1], modelPos[2]); } // 判断右上顶点X坐标是否小于原点,绑定背面/正面纹理 if (updatedVertices[0].x < moneyPositions[i].x) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture2); } else { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); } glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); }
问题
为什么位于原点(0.0,0.0,0.0)的中心美元矩形纹理翻转正常,其他矩形的纹理翻转却存在延迟,且离中心越远延迟越明显?同时X负半轴的矩形纹理翻转提前,X正半轴的延迟。我推测这与透视矩阵有关,因为尽管每个矩形旋转角度和速度相同,但视觉上旋转速度似乎有差异。
问题根源
你判断纹理切换的逻辑只考虑了模型空间到世界空间的坐标变换,完全忽略了透视投影带来的视觉变形。透视投影会让远离相机的物体看起来更小、移动更慢,而且不同X位置的物体在屏幕上的视觉位置和世界空间坐标的对应关系不是线性的。
具体来说:
- 你用
updatedVertices[0].x < moneyPositions[i].x判断是否翻转,但这个比较是在世界空间做的,而我们看到的是经过透视投影后的屏幕空间效果。 - 透视投影会压缩远处物体的X轴视觉跨度,同时对左右两侧的物体产生不同的视觉偏移,导致世界空间的判断时机和屏幕上实际看到的翻转时机不匹配。
- 离中心(原点)越远,透视变形的影响越大,所以延迟/提前的现象越明显。
解决方法
不要在CPU端计算世界空间坐标来判断纹理切换,而是把判断逻辑放到片元着色器里,基于视角空间或屏幕空间的坐标来做判断,或者更简单的:直接用顶点的法线方向和视角方向的点积来判断正反面。
推荐方案:用顶点法线和视角方向判断
- 给每个矩形添加法线(比如初始法线是
vec3(0.0, 0.0, 1.0),指向正面) - 在顶点着色器中,把法线变换到视角空间(用模型视图矩阵的逆转置):
uniform mat4 modelView; uniform mat4 projection; in vec3 aPos; in vec3 aNormal; in vec2 aTexCoords; out vec3 fragNormal; out vec2 fragTexCoords; void main() { gl_Position = projection * modelView * vec4(aPos, 1.0); mat3 normalMatrix = transpose(inverse(mat3(modelView))); fragNormal = normalMatrix * aNormal; fragTexCoords = aTexCoords; } - 在片元着色器中,计算法线和视角方向(视角空间中,相机看向-Z方向,所以视角方向是
vec3(0.0,0.0,1.0))的点积,根据结果切换纹理:
这样就能根据矩形是否朝向相机来自动切换纹理,完全不受透视和位置的影响。uniform sampler2D frontTexture; uniform sampler2D backTexture; in vec3 fragNormal; in vec2 fragTexCoords; void main() { float facingCamera = dot(fragNormal, vec3(0.0, 0.0, 1.0)); vec4 texColor = texture(facingCamera > 0.0 ? frontTexture : backTexture, fragTexCoords); gl_FragColor = texColor; }
临时修复方案(如果不想改着色器)
如果你暂时不想修改着色器,需要把顶点坐标变换到视角空间后再做判断,因为视角空间更贴近我们看到的视觉效果:
// 替换原来的顶点计算逻辑 glm::mat4 modelView = view * model; // 模型视图矩阵 glm::vec3 updatedVertices[4]; for (int j = 0; j < sizeof(verticesPos) / sizeof(float); j += 3) { glm::vec4 pos(verticesPos[j], verticesPos[j + 1], verticesPos[j + 2], 1.0f); glm::vec4 viewPos = modelView * pos; // 变换到视角空间 updatedVertices[j / 3] = glm::vec3(viewPos); } // 计算矩形中心的视角空间坐标 glm::vec4 centerViewPos = modelView * glm::vec4(moneyPositions[i], 1.0f); // 用视角空间的X坐标比较来判断 if (updatedVertices[0].x < centerViewPos.x) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture2); } else { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); }
但这种方法仍然不如着色器端判断准确,因为透视投影的非线性还是会影响最终屏幕上的视觉效果,优先推荐着色器方案。
内容的提问来源于stack exchange,提问作者Vlad
相关产品推荐
相关产品推荐

