将GIMP中的透视变换矩阵应用到GLSL着色器中

3

所以我正在尝试在顶点着色器中为图像添加旋转和透视效果。旋转效果正常,但我无法实现透视效果。我正在2D环境下工作。

旋转矩阵是从代码生成的,但透视矩阵是我使用透视工具从GIMP获得的一堆硬编码值。

private final Matrix3 perspectiveTransform = new Matrix3(new float[] {
    0.58302f, -0.29001f, 103.0f,
    -0.00753f, 0.01827f, 203.0f,
    -0.00002f, -0.00115f, 1.0f
});

这个透视矩阵在GIMP中500x500像素的图像上产生了我想要的结果。现在我尝试在纹理坐标上应用同样的矩阵,所以我先乘以500再除以500。
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;

uniform mat4 u_projTrans;
uniform mat3 u_rotation;
uniform mat3 u_perspective;

varying vec4 v_color;
varying vec2 v_texCoords;

void main() {
    v_color = a_color;

    vec3 vec = vec3(a_texCoord0 * 500.0, 1.0);

    vec = vec * u_perspective;

    vec = vec3((vec.xy / vec.z) / 500.0, 0.0);

    vec -= vec3(0.5, 0.5, 0.0);
    vec = vec * u_rotation;
    v_texCoords = vec.xy + vec2(0.5);

    gl_Position = u_projTrans * a_position;
}

针对旋转,我会偏移原点以使其绕中心而非左上角旋转。
我所知道的关于GIMP透视工具的信息大部分来自http://www.math.ubc.ca/~cass/graphics/manual/pdf/ch10.ps。阅读后,该链接建议我可以重现GIMP的效果,但事实证明我不能。结果没有显示(没有像素),而删除透视部分会正确旋转图像。
如链接中所述,我将我的齐次坐标除以vec.z以将其转换回2D点。我没有使用原点移位进行透视变换,因为链接中提到左上角被用作原点。第11页:

有一件事需要小心 - GIMP坐标的原点在左上角,y向下增加。

编辑
感谢@Rabbid76的答案,现在它正在显示内容!然而,它并没有像矩阵在GIMP上变换我的图像。
我的 GIMP 变换矩阵应该做了一些类似于这样的事情:

Expected

但实际上,它看起来像这样:

Actual

这是我从实际结果中看到的想法: https://imgur.com/X56rp8K(使用Image
(正如指出的那样,它的纹理参数被夹在边缘而不是夹在边框上,但这不是重点)
看起来它正在做我要寻找的相反的事情。我尝试在应用矩阵之前将原点偏移到图像的中心和左下角,但没有成功。这是一个新的结果,但问题仍然存在:如何将GIMP透视矩阵应用到GLSL着色器中? 编辑2
经过更多测试,我可以确认它正在做“相反”的事情。使用这个简单的缩小转换矩阵:
private final Matrix3 perspectiveTransform = new Matrix3(new float[] {
        0.75f, 0f, 50f,
        0f, 0.75f, 50f,
        0f, 0f, 1.0f
});

结果是图像的放大版本:

Opposite result

如果我以编程方式反转矩阵,它对于简单的缩放矩阵有效!但对于透视矩阵,则显示为:

https://imgur.com/v3TLe2d

编辑3::

再次感谢@Rabbid76,结果证明在透视矩阵之后应用旋转会使旋转在透视之前进行,我得到了这样的结果:https://imgur.com/n1vWq0M

接近成功!唯一的问题是图像非常扭曲。就像应用了多次透视矩阵一样。但是如果你仔细看,你可以看到它在透视中旋转,就像我想要的那样。现在的问题是如何取消扭曲,以获得与GIMP中相同的结果。(根本问题仍然是如何将GIMP矩阵应用于着色器)

1个回答

2
这个透视矩阵在 GIMP 中使用一个 500x500 的图像可以得到我想要的结果。现在我正在尝试将同样的矩阵应用于纹理坐标。这就是为什么我在乘以 500 之前和除以 500 之后的原因。
 0.58302 -0.29001 103.0
-0.00753  0.01827 203.0
-0.00002 -0.00115 1.0f

这是一个2D透视变换矩阵。它使用2D齐次坐标进行操作。
请参见2D仿射和透视变换矩阵
由于在GIMP中显示的矩阵是从透视到正交视图的变换,因此必须使用逆矩阵进行变换。 可以通过调用inv()来计算逆矩阵。
该矩阵被设置为将[0, 500]范围内的笛卡尔坐标转换为[0, 500]范围内的齐次坐标的操作。
您的假设是正确的,您需要将输入从范围[0,1]缩放到[0,500],并将输出从[0,500]缩放到[0,1]。但是,您必须缩放二维笛卡尔坐标。
此外,在透视投影和透视除法之后进行旋转。
可能需要(取决于位图和纹理坐标属性)翻转纹理坐标的V坐标。

最重要的是,变换必须在片段着色器中每个片段中完成。请注意,由于这种变换不是线性变换(它是透视变换),仅计算角点的纹理坐标是不足够的。

vec2 Project2D( in vec2 uv_coord )
{
    vec2 v_texCoords;

    const float scale = 500.0;

    // flip Y
    //vec2 uv = vec2(uv_coord.x, 1.0 - uv_coord.y);
    vec2 uv = uv_coord.xy;

    // uv_h: 3D homougenus in range [0, 500] 
    vec3 uv_h = vec3(uv * scale, 1.0) * u_perspective;

    // uv_h: perspective devide and downscale [0, 500] -> [0, 1]
    vec3 uv_p = vec3(uv_h.xy / uv_h.z / scale, 1.0);

    // rotate
    uv_p = vec3(uv_p.xy - vec2(0.5), 0.0) * u_rotation + vec3(0.5, 0.5, 0.0);

    return uv_p.xy; 
}

当然,你也可以在顶点着色器中进行变换。但是你需要把二维齐次坐标从顶点着色器传递到片段着色器中。这类似于将剪裁空间坐标设置为gl_Position。不同之处在于你有一个二维齐次坐标而不是三维的,并且你需要在片段着色器中手动执行透视除法
顶点着色器:
attribute vec2 a_texCoord0;
varying   vec3 v_texCoords_h;

uniform mat3 u_perspective

vec3 Project2D( in vec2 uv_coord )
{
    vec2 v_texCoords;

    const float scale = 500.0;

    // flip Y
    //vec2 uv = vec2(uv_coord.x, 1.0 - uv_coord.y);
    vec2 uv = uv_coord.xy;

    // uv_h: 3D homougenus in range [0, 500] 
    vec3 uv_h = vec3(uv * scale, 1.0) * u_perspective;

    // downscale
    return vec3(uv_h.xy / scale, uv_h.z);
}

void main()
{
    v_texCoords_h = Project2D( a_texCoord0 );

    .....
}

片段着色器:
varying vec3 v_texCoords_h;

uniform mat3 u_rotation;

void main()
{
    // perspective divide
    vec2 uv = vertTex.xy / vertTex.z;

    // rotation
    uv = (vec3(uv.xy - vec2(0.5), 0.0) * u_rotation + vec3(0.5, 0.5, 0.0)).xy;

    .....
}

查看预览,其中我使用了以下2D投影矩阵,该矩阵是从GIMP中显示的矩阵的逆矩阵:
2.452f,     2.6675f,    -388.0f,
0.0f,       7.7721f,    -138.0f,
0.00001f,   0.00968f,    1.0f

bommerang


进一步说明,与u_projTrans相比,u_perspective是按行主序进行初始化的。
因此,您必须从左侧将向量乘以u_perspective
vec_h = vec3(vec.xy * 500.0, 1.0) * u_perspective;

但是你必须从右侧将向量乘以u_projTrans:
gl_Position = u_projTrans * a_position;

请看GLSL编程/向量和矩阵操作数据类型(GLSL)
当您通过glUniformMatrix*设置矩阵时,如果您对其进行转置,则可能会发生变化。

1
只是一点小提示,为了让它与我从GIMP得到的矩阵配合使用,我不得不调用LibGDX中的inv()方法来反转矩阵。这在你提供的答案中并非如此。 - Winter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接