OpenGL仿射变换后纹理采样对齐失效及透视校正实现问询
问题背景
我需要在OpenGL画布上对齐两张分辨率相同的图像,为此准备了如下顶点数据:
![两张图像共用的顶点]
float vertices[] = { // 位置 0.0f, 0.0f, // 左下角 width, 0.0f, // 右下角 0.0f, height, // 左上角 width, 0.0f, // 右下角 0.0f, height, // 左上角 width, height, // 右上角 };
通过这种方式,我可以用同一个着色器在画布上叠加绘制任意数量的图像,只需为每张对齐的图像传入纹理和变换矩阵即可。
使用以下顶点着色器和片段着色器时,效果符合预期:
顶点着色器
#version 330 core layout (location = 0) in vec3 vertex_in; out vec2 TexCoord; uniform mat3 cvAffine; // OpenCV 仿射变换矩阵 const vec2 texture_coord[] = { vec2( 0.0, 0.0), vec2( 1.0, 0.0), vec2( 0.0, 1.0), vec2( 1.0, 0.0), vec2( 0.0, 1.0), vec2( 1.0, 1.0) }; void main(){ vec3 cvCoord = vec3(vertex_in.xy,1.0f); // 创建齐次2D向量用于3x3矩阵乘法 vec3 transformedCoord = cvAffine*cvCoord; // 对图像进行变换 gl_Position = vec4((transformedCoord.xy*0.0002), 0.0, transformedCoord.z); // 缩小以便查看 TexCoord = texture_coord[gl_VertexID]; // 向片段着色器传递纹理坐标 };
片段着色器
#version 330 core in vec2 TexCoord; out vec4 FragColor; uniform sampler2D texture; void main(){ FragColor = texture(texture, TexCoord); }
![效果展示]
核心问题
当我把顶点着色器中的这一行:
gl_Position = vec4((transformedCoord.xy*0.0002), 0.0, transformedCoord.z);
替换为理论上等价的代码:
gl_Position = vec4(((transformedCoord.xy/transformedCoord.z)*0.0002), 0.0, 1.0);
图像对齐失效:
![对齐失效效果]
这涉及一类常见问题,有多种称呼:
- Affine Texture mapping(仿射纹理映射)
- Perspective correct Texture mapping(透视校正纹理映射)
- Projective Texture mapping(投影纹理映射)
- Trapezoid texture mapping(梯形纹理映射)
为何向量(x*W,y*W,z*W,W)与向量(x,y,z,1)在片段着色器中表现不一致?我需要将gl_Position的w分量设为1,以便在画布上进一步变换图像时仍能保持对齐。我尝试过修改UV纹理空间的w分量并进行投影,但未成功。请问如何正确变换纹理坐标,才能在扭曲的双三角矩形上实现均匀的纹理采样?(注:修改前后几何形状未发生变化,仅纹理采样方式改变)
问题根源
OpenGL的透视校正插值是核心原因:
- 当
gl_Position.w = transformedCoord.z时,GPU会自动对TexCoord做透视校正插值——它会基于顶点的w分量,在光栅化时按透视空间比例重新计算每个片段的纹理坐标,保证透视变换后纹理采样正确。 - 手动将w设为1时,GPU判定这是正交空间顶点,只会做线性插值。此时纹理坐标的插值逻辑和顶点的透视变换脱节,扭曲区域的纹理采样就会出错,最终导致图像对齐失效。
解决方案:手动实现透视校正纹理采样
要在gl_Position.w=1的前提下保持正确纹理采样,需要自行传递透视校正参数,在片段着色器中完成校正:
修改后的顶点着色器
#version 330 core layout (location = 0) in vec3 vertex_in; out vec3 TexCoord; // 改为传递带w分量的纹理坐标 uniform mat3 cvAffine; // OpenCV 仿射变换矩阵 const vec3 texture_coord[] = { vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 1.0, 1.0) }; void main(){ vec3 cvCoord = vec3(vertex_in.xy, 1.0f); vec3 transformedCoord = cvAffine * cvCoord; float w = transformedCoord.z; gl_Position = vec4((transformedCoord.xy / w) * 0.0002, 0.0, 1.0); // 将纹理坐标乘以w,后续在片段着色器中再除以插值后的w TexCoord = texture_coord[gl_VertexID] * w; };
修改后的片段着色器
#version 330 core in vec3 TexCoord; // 接收带w分量的纹理坐标 out vec4 FragColor; uniform sampler2D texture; void main(){ // 用插值后的w分量校正纹理坐标 vec2 uv = TexCoord.xy / TexCoord.z; FragColor = texture(texture, uv); }
原理说明
- 顶点着色器中,把纹理坐标和顶点的w分量(即
transformedCoord.z)相乘,得到带透视权重的纹理坐标。 - GPU对
TexCoord做线性插值时,会连带权重一起插值。 - 片段着色器中,将插值后的纹理坐标除以其w分量,就能得到经过透视校正的正确UV坐标,效果和
gl_Position.w保留原始值时的自动校正完全一致。
这样既满足了gl_Position.w=1的需求,又保证了纹理采样的正确性,图像对齐不会失效。
内容的提问来源于stack exchange,提问作者Patrik Staron
相关产品推荐
相关产品推荐

