Search code examples
opengl-esglslshadertexture-mappingperspective

How to correctly map texture when doing perspective warping in glsl using opengl es 2.0


I'm trying to create a four corner perspective effect using vertex shader and fragment shader.

I set the vertex position infos to draw a perspective like shape, then map my texture on it. But somehow the texture is not correctly mapped.

Here are two pictures to show you the problem.

enter image description here

enter image description here

I find some infos from this post that texture2DProj maybe the right answer, but I don't quite understand how it works. I tried to do something like this,

    varying lowp vec4 TexCoordOut;
    uniform sampler2D Texture;

    void main(void) {
        gl_FragColor = texture2DProj(Texture, TexCoordOut);
    }

And for the TexCoordOut I pass in

{1, 0, 0, 1}
{1, 1, 0, 1}
{0, 1, 0, 1}
{0, 0, 0, 1}

to vertex shader, and varying it into fragment shader. But the result is the same.

I only know how to use texture2D with vec2, how can I get the other two components of my texture coordinates to use them with texture2DProj?


Solution

  • If you really want to use texture2DProj then you can copy the W coordinate from the position to the texture coordinate. For example:

    uniform mat4 uPrj;
    attribute vec2 aXY;
    attribute vec2 aST;
    varying vec4 st;
    void main(void) {
        vec4 pos=uPrj*vec4(aXY,0.0,1.0);
        gl_Position=pos;
        st=vec4(aST,0.0,pos.w);
    
        st.xy*=pos.w; // Not sure if this is needed with texture2DProj
    }
    

    However, I got round the problem of textures in perspective by splitting my quadrilateral into a grid of smaller quads, and then splitting each small quad into two triangles. Then you can use GLSL code like this:

    uniform mat4 uPrj;
    attribute vec2 aST;  // Triangles in little squares in range [0,1].
    varying vec2 st;
    void main(void) {
        vec2 xy=aST*2.0-1.0; // Convert from range [0,1] to range [-1,-1] for vertices.
        gl_Position=uPrj*vec4(xy,0.0,1.0);
        st=aST;
    }
    

    The above code runs fast enough (maybe even faster than texture2DProj), because the fragment shader runs for the same number of pixels, and the fragment shader can use the simpler texture2D method (no fragment projection necessary).

    You can reuse the net of triangles in aST for each of your quadrilaterals. Here is how I made aST in JavaScript:

            var nCell = 32;
            var nVertex = nCell * nCell * 6;
            var arST = prog._arST = new Float32Array(nVertex * 2);
            var k = 0;
            for (var j = 0; j < nCell; j++) {
                var j0 = j / nCell;
                var j1 = (j + 1) / nCell;
                for (var i = 0; i < nCell; i++) {
                    var i0 = i / nCell;
                    var i1 = (i + 1) / nCell;
                    arST[k++] = i0;
                    arST[k++] = j0;
                    arST[k++] = i1;
                    arST[k++] = j0;
                    arST[k++] = i0;
                    arST[k++] = j1;
                    arST[k++] = i0;
                    arST[k++] = j1;
                    arST[k++] = i1;
                    arST[k++] = j0;
                    arST[k++] = i1;
                    arST[k++] = j1;
                }
            }
            gl.bindBuffer(gl.ARRAY_BUFFER, prog._bufferST);
            gl.bufferData(gl.ARRAY_BUFFER, prog._arST, gl.STATIC_DRAW);