You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

OpenGL渲染管线中的纹理:透视纹理正确渲染原理问询

OpenGL透视纹理渲染的核心:透视校正插值

嘿,这个问题问到点子上了!这背后全靠透视校正插值(Perspective-Correct Interpolation)——OpenGL在光栅化阶段默默帮我们搞定了这件事,不然纹理会歪得没法看。

为什么直接线性插值行不通?

先想想如果没有校正会发生什么:当你的四边形被投影成梯形后,屏幕上的像素是2D的,但每个像素对应3D空间里不同深度的点。如果直接在屏幕空间线性插值纹理坐标,靠近相机的底边(实际3D跨度更大)的纹理会被压缩,而远处的顶边会被拉伸,完全不符合人眼的透视感知。

举个直观的例子:3D里靠近相机的底边宽2米,对应屏幕200像素;顶边宽1米,对应屏幕100像素。直接线性插值的话,屏幕中间的像素纹理坐标是线性过渡,但实际3D中间位置宽度是1.5米,这样纹理在中间就会被拉伸,完全不对。

OpenGL的解决思路:基于1/w的插值

OpenGL的核心解法是不对原始纹理坐标做线性插值,而是先把纹理坐标和顶点的齐次坐标w分量绑定,再基于1/w来插值,步骤大概是这样:

  • 顶点经过MVP变换后,会得到齐次坐标(x, y, z, w)。在透视投影下,w值和顶点到相机的深度成反比(简单说,越靠近相机,w越大)。
  • 光栅化阶段,OpenGL会对u/wv/w以及1/w这三个值分别做屏幕空间的线性插值。
  • 到了每个像素(片元)阶段,再用(u/w) / (1/w)(v/w) / (1/w)还原出正确的纹理坐标。

这样一来,靠近相机的区域(w更大),1/w更小,相当于给纹理坐标做了“放大”修正,让纹理在底边看起来更大;远处的区域则相反,自动实现了符合透视的纹理缩放。

可编程管线里的细节

在固定管线时代,这一切都是自动完成的,你根本不用管。到了GLSL可编程管线,其实也不需要手动处理——只要你在顶点着色器输出纹理坐标,光栅化阶段会自动按透视校正规则插值。

不过如果想手动实现(比如做一些自定义的纹理映射),也可以这么写:

// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;
out float invW;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
    vec4 clipSpacePos = projection * view * model * vec4(aPos, 1.0);
    gl_Position = clipSpacePos;
    
    // 将纹理坐标和w绑定,同时计算1/w
    TexCoord = aTexCoord / clipSpacePos.w;
    invW = 1.0 / clipSpacePos.w;
}

// 片元着色器
#version 330 core
out vec4 FragColor;

in vec2 TexCoord;
in float invW;
uniform sampler2D texture1;

void main() {
    // 还原正确的纹理坐标
    vec2 correctedTexCoord = TexCoord * invW;
    FragColor = texture(texture1, correctedTexCoord);
}

当然,现在的驱动已经自动优化了这个过程,所以很少需要手动写这段代码。

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

火山引擎 最新活动