天天看點

58 WebGL在平面繪制透視紋理效果問題原因思路案例代碼

案例檢視位址:點選這裡

問題原因

我公司裡有一個項目,需要能夠一個矩形的四個頂點能夠随意移動,而且上面還繪制的紋理。然後,我按照以前的方式書寫,問題來了,由于一個矩形是由兩個三角形組成的,然後就出現下面喜聞樂見的效果:

58 WebGL在平面繪制透視紋理效果問題原因思路案例代碼

我們會發現,兩個三角形的交彙處,有一道折線,是以比較尴尬了。

而我們需要的是什麼呢:

58 WebGL在平面繪制透視紋理效果問題原因思路案例代碼

我們需要的是上面這種,具有透視效果,而且圖形不會有折線的效果。

即使是随意的扭曲,也不會出現折線,這就是所謂的透視紋理繪制。

58 WebGL在平面繪制透視紋理效果問題原因思路案例代碼

思路

  • 首先,我需要先擷取到目前繪制的那一點的坐标x,y兩個軸的,然後計算這一個點和四個矩形頂點的位置關系,計算出這個點需要繪制的紋理圖檔的位置。關系圖如下:
    58 WebGL在平面繪制透視紋理效果問題原因思路案例代碼

    檢視圖形,其實我們唯一不知道的就是過圖形的線和兩個點切割獲得的比例,通過高中的學的圖形知識,将五個點代進去,計算一下,最後出來的就是這個點。

    由于(c1-v1)/(v0-v1) = (c2-v3)/(v2-v3)這個比例保證每一個點在紋理坐标中确定需要繪制的紋理。

    然後通過兩個點确定一條直線,根據兩點方程(x-c1.x)/(c2.x-c1.x) = (y-c1.y)/(c2.y-c1.y)然後将所有的值代入進入,再通過一進制二次方程的解析,最後得出比例。

    以下是js版本的代碼:

function  calc(p1,p2,p3,p4,p) {

        /*
        *   p1-------p3
        *   |         |
        *   |         |
        *   |         |
        *   p2-------p4
        *假設四個點的坐标分别是(x1,y1)、(x2,y2)、(x3,y3)、(x4,y4)
        * 則下式成立平行
        * (x1-x2)/(y1-y2)=(x3-x4)/(y3-y4)
        *
        */


        /*
        * 根據兩點式方程
        * (y-y1)/(y2-y1) = (x-x1)/(x2-x1)
        * 得出
        * (y-y1)(x2-x1) = (y2-y1)(x-x1)
        * yx2-yx1-y1x2+y1x1 = y2x-y2x1-y1x+y1x1
        * yx2-yx1-y1x2+y1x1-(y2x-y2x1-y1x+y1x1) = ;
        * yx2-yx1-y1x2+y1x1-y2x+y2x1+y1x-y1x1 = ;
        * yx2-yx1-y1x2-y2x+y2x1+y1x = ;\
        * x2(y-y1)+x1(y2-y)+x(y1-y2)=
        *
        * //兩個點的位置為
        * p12Xlen = p2.x-p1.x;
        * p12Ylen = p2.y-p1.y;
        * p34Xlen = p4.x-p3.x;
        * p34Ylen = p4.y-p3.y;
        * p12.x = x1 = p1.x+p12Xlen*s;
        * p12.y = y1 = p1.y+p12Ylen*s;
        * p34.x = x2 = p3.x+p34Xlen*s;
        * p34.y = y2 = p3.y+p34Ylen*s;
        * x = p.x;
        * y = p.y;
        *
        * 由此得出
        * x2(y-y1) = (p3.x+p34Xlen*s)(p.y-(p1.y+p12Ylen*s));
        * x2(y-y1) = (p3.x + p34Xlen*s)(p.y - p1.y - p12Ylen*s);
        * x2(y-y1) = p3.x*p0.y-p3.x*p1.y-p3.x*p12Ylen*s+p.y*p34Xlen*s-p1.y*p34Xlen*s-p12Ylen*p34Xlen*s*s;
        * x2(y-y1) = p3.x*p0.y-p3.x*p1.y+(-p3.x*p12Ylen+p.y*p34Xlen-p1.y*p34Xlen)*s-p12Ylen*p34Xlen*s*s;
        *
        * x1(y2-y) = (p1.x+p12Xlen*s)*(p3.y+p34Ylen*s-p.y);
        * x1(y2-y) = (p1.x+p12Xlen*s)*(p3.y-p.y+p34Ylen*s);
        * x1(y2-y) = p1.x*p3.y-p1.x*p0.y+p1.x*p34Ylen*s+p3.y*p12Xlen*s-p.y*p12Xlen*s+p12Xlen*p34Ylen*s*s;
        * x1(y2-y) = p1.x*p3.y-p1.x*p0.y+(p1.x*p34Ylen+p3.y*p12Xlen-p.y*p12Xlen)*s+p12Xlen*p34Ylen*s*s;
        *
        * x(y1-y2) = p.x(p1.y+p12Ylen*s-(p3.y+p34Ylen*s));
        * x(y1-y2) = p.x(p1.y+p12Ylen*s-p3.y-p34Ylen*s);
        * x(y1-y2) = p.x(p1.y-p3.y+p12Ylen*s-p34Ylen*s);
        * x(y1-y2) = p.x(p1.y-p3.y)+p.x(p12Ylen-p34Ylen)*s;
        *
        * 得出
        *
        * A = p12Xlen*p34Ylen-p12Ylen*p34Xlen;
        *
        * B = p1.x*p34Ylen+p3.y*p12Xlen-p.y*p12Xlen-p3.x*p12Ylen+p.y*p34Xlen-p1.y*p34Xlen+p.x(p12Ylen-p34Ylen);
        *
        * C = p3.x*p0.y-p3.x*p1.y+p1.x*p3.y-p1.x*p0.y+p.x(p1.y-p3.y)
        *
        * Ass + Bs +C = ;
        * */

        var p12Xlen = p2.x-p1.x;
        var p12Ylen = p2.y-p1.y;
        var p34Xlen = p4.x-p3.x;
        var p34Ylen = p4.y-p3.y;

        var a = p12Xlen*p34Ylen-p12Ylen*p34Xlen;
        var b = p1.x*p34Ylen+p3.y*p12Xlen-p.y*p12Xlen-p3.x*p12Ylen+p.y*p34Xlen-p1.y*p34Xlen+p.x*(p12Ylen-p34Ylen);
        var c = p3.x*p0.y-p3.x*p1.y+p1.x*p3.y-p1.x*p0.y+p.x*(p1.y-p3.y);

        //在兩條線平行的情況下
        if(a === ){
            return -c/b;
        }

        //在兩條線不平行的情況下 擷取數組裡面再到範圍内的

        return [(-b+Math.sqrt(b*b-*a*c))/(*a),(-b-Math.sqrt(b*b-*a*c))/(*a)];
    }
           

案例代碼

以下是可以運作的案例代碼,把檔案放到服務裡面,設定好圖檔,就可以直接檢視效果:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Title</title>
    <style>
        body {
            margin: ;
            text-align: center;
        }

        #canvas {
            display: block;
        }
        #div{
            width:px;
            height:px;
            position: relative;
            margin:  auto;
        }
    </style>
</head>
<body onload="main()">
<div id="div">
    <canvas id="canvas" height="800" width="800"></canvas>
</div>
</body>
<script src="https://johnson2heng.github.io/webgl-demo/lib/webgl-utils.js"></script>
<script src="https://johnson2heng.github.io/webgl-demo/lib/webgl-debug.js"></script>
<script src="https://johnson2heng.github.io/webgl-demo/lib/cuon-utils.js"></script>
<script src="https://johnson2heng.github.io/webgl-demo/lib/cuon-matrix.js"></script>
<script>
    /*第一部分頂點着色器接收頂點的紋理坐标,傳遞給片元着色器*/
    var VSHADER_SOURCE = "" +
        "attribute vec4 a_Position;\n" +//
        "varying vec2 v_Position;\n" +
        "void main(){\n" +
        "   gl_Position = a_Position;\n" +
        "   v_Position = vec2(a_Position);\n" +
        "}\n";

    var FSHADER_SOURCE = "" +
        "precision mediump float;\n" +//
        "uniform sampler2D u_Sampler;\n" +//
        "varying vec2 v_Position;\n" +
        "uniform vec2 u_point0;\n" +
        "uniform vec2 u_point1;\n" +
        "uniform vec2 u_point2;\n" +
        "uniform vec2 u_point3;\n"  +
        "float calc(vec2 p1,vec2 p2,vec2 p3,vec2 p4,vec2 p0){\n" +
            //如果不是平行的兩條直線
        "   float p12Xlen = p2.x-p1.x;\n" +
        "   float p12Ylen = p2.y-p1.y;\n" +
        "   float p34Xlen = p4.x-p3.x;\n" +
        "   float p34Ylen = p4.y-p3.y;\n" +
        "   float a = p12Xlen*p34Ylen-p12Ylen*p34Xlen;\n" +
        "   float b = p1.x*p34Ylen+p3.y*p12Xlen-p0.y*p12Xlen-p3.x*p12Ylen+p0.y*p34Xlen-p1.y*p34Xlen+p0.x*(p12Ylen-p34Ylen);\n" +
        "   float c = p3.x*p0.y-p3.x*p1.y+p1.x*p3.y-p1.x*p0.y+p0.x*(p1.y-p3.y);\n" +
            //兩條線都垂直于x軸或者y軸的情況下
        "   if(a == 0.0){\n" +
        "       return -c/b;\n" +
        "   }\n" +
            //兩條線不平行的情況下
        "   float endA = (-b+sqrt(b*b-4.0*a*c))/(2.0*a);\n" +
        "   float endB = (-b-sqrt(b*b-4.0*a*c))/(2.0*a);\n" +
        "   if(endA > 0.0 && endA < 1.0){\n" +
        "       return endA;\n" +
        "   }\n" +
        "   if(endB > 0.0 && endB < 1.0){\n" +
        "       return endB;\n" +
        "   }\n" +
        "   else {\n" +
        "       return -c/b;\n" +
        "   }\n" +
        "}\n" +
        ""+
        //兩個點的位置,第一個calc(v0,v2,v1,v3,p0) 第二個calc(v1,v0,v3,v2,p0)
        "void main(){\n" +
        "   gl_FragColor = texture2D(u_Sampler,vec2(calc(u_point0,u_point2,u_point1,u_point3,v_Position),calc(u_point1,u_point0,u_point3,u_point2,v_Position)));\n" +//
        "}\n";

    //四個頂點的位置
    var v0 = {x:-,y:};
    var v1 = {x:-,y:-};
    var v2 = {x:,y:};
    var v3 = {x:,y:-};
    //圖檔的位址設定
    var imgSrc = "../image/door/001.png";
    var gl;

    /*第二部分 main()方法 初始化着色器,設定頂點資訊,調用配置紋理方法*/
    function main() {
        var canvas = document.getElementById("canvas");
        gl = getWebGLContext(canvas);
        if(!gl){
            console.log("你的電腦不支援WebGL!");
            return;
        }
        if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
            console.log("初始化着色器失敗!");
            return;
        }

        //設定頂點的相關資訊
        var n = initVertexBuffers(gl);

        if(n < ){
            console.log("無法擷取到點的資料");
            return;
        }

        //配置紋理
        if(!initTextures(gl,n)){
            console.log("無法配置紋理");
            return;
        }

    }

    /*第三部分 initVertexBuffers() 設定頂點坐标和紋理坐标 調用initTextures()進行下一步處理*/
    function initVertexBuffers(gl) {
        /*
            *   v0-------v2
            *   |         |
            *   |         |
            *   |         |
            *   v1-------v3
            *
            *   [v0.x,v0.y,v1.x,v1.y,v2.x,v2.y,v3.x,v3.y]
            *   config.Positions[0].Config 數組由八個資料組成,代表四個點的位置,距離中心點的偏移量
            * */
        var verticesSizes = new Float32Array([
            //四個頂點的位置
            v0.x,v0.y,
            v1.x,v1.y,
            v2.x,v2.y,
            v3.x,v3.y
        ]);

        initArrayBuffer(gl, verticesSizes, , gl.FLOAT, "a_Position");

        //将四個頂點位置傳入
        var point0 = gl.getUniformLocation(gl.program, "u_point0");
        var point1 = gl.getUniformLocation(gl.program, "u_point1");
        var point2 = gl.getUniformLocation(gl.program, "u_point2");
        var point3 = gl.getUniformLocation(gl.program, "u_point3");
        if(point0 < ){
            alert("無法擷取到存儲的位置");
        }
        gl.uniform2f(point0,v0.x,v0.y);
        gl.uniform2f(point1,v1.x,v1.y);
        gl.uniform2f(point2,v2.x,v2.y);
        gl.uniform2f(point3,v3.x,v3.y);

        var indexSize = new Uint8Array([,,,,,]);

        //将索引寫入緩沖區對象
        var indexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indexSize, gl.STATIC_DRAW);

        return indexSize.length;
    }

    //建立變量緩沖區
    function initArrayBuffer(gl, data, num, type, attribute) {
        //建立緩沖區對象
        var buffer = gl.createBuffer();
        if (!buffer) {
            console.log("無法建立緩沖區對象");
            return -;
        }

        //綁定緩沖區對象并寫入資料
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

        //擷取頂點位置變量位置
        var a_attribue = gl.getAttribLocation(gl.program, attribute);
        if (a_attribue < ) {
            console.log("無法擷取頂點位置的存儲變量");
            return -;
        }

        //對位置的頂點資料進行配置設定,并開啟
        gl.vertexAttribPointer(a_attribue, num, type, false, , );
        gl.enableVertexAttribArray(a_attribue);
    }

    /*第四部分 initTextures() 建立紋理對象 并調用紋理繪制方法*/
    function initTextures(gl,n) {
        var texture = gl.createTexture();//建立紋理對象
        if(!texture){
            console.log("無法建立紋理對象");
            return;
        }

        //擷取u_Sampler的存儲位置
        var u_Sampler = gl.getUniformLocation(gl.program,"u_Sampler");
        if(u_Sampler < ){
            console.log("無法擷取變量的存儲位置");
            return;
        }

        //建立Image對象,并綁定加載完成事件
        var image = new Image();
        image.onload = function () {
            loadTexture(gl,n,texture,u_Sampler,image);
            //初始化互動事件
            initMoveBtn();
        };

        image.src = imgSrc;
        return true;
    }

    function initMoveBtn() {
        var canvas = document.querySelector("#canvas");
        var div = document.querySelector("#div");
        var canvasWidth = canvas.width;
        var canvasHeight = canvas.height;

        addBtn(v0);
        addBtn(v1);
        addBtn(v2);
        addBtn(v3);

        function addBtn(obj) {
            var x = (obj.x+)/;
            var y = -(obj.y+)/;

            var btn = document.createElement("div");
            btn.style.cssText = "height:10px; width:10px; transform:translate(-50%,-50%); position:absolute; background:green; cursor:pointer;";
            btn.style.left = x *canvasWidth+"px";
            btn.style.top = y * canvasHeight +"px";
            div.appendChild(btn);

            var downX,downY,btnX,btnY;

            btn.addEventListener("mousedown",function (event) {
                //滑鼠按下時的位置
                downX = event.clientX;
                downY = event.clientY;

                //按下時btn的位置
                btnX = parseFloat(btn.style.left);
                btnY = parseFloat(btn.style.top);

                document.addEventListener("mousemove",move,true);
                document.addEventListener("mouseup",up,true);
            },true);

            function move(event) {
                event.preventDefault();
                var moveX = event.clientX;
                var moveY = event.clientY;

                var x = moveX-downX + btnX;
                var y = moveY - downY + btnY;

                btn.style.left = x+"px";
                btn.style.top = y + "px";

                obj.x = x/canvasWidth*-;
                obj.y = (-y/canvasHeight)*-;

                //重新繪制
                reload();
            }

            function up() {
                document.removeEventListener("mousemove",move,true);
                document.removeEventListener("mouseup",up,true);
            }
        }
    }

    /*第五部分 設定紋理相關資訊供WebGL使用,并進行繪制*/
    function loadTexture(gl,n,texture,u_Sampler,image) {
        //對紋理圖像進行y軸反轉
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,);
        //開啟0号紋理單元
        gl.activeTexture(gl.TEXTURE0);
        //向target綁定紋理對象
        gl.bindTexture(gl.TEXTURE_2D,texture);
        //配置紋理參數
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        //配置紋理圖像
        gl.texImage2D(gl.TEXTURE_2D, , gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        //将0号紋理傳遞給着色器
        gl.uniform1i(u_Sampler,);

        draw(n);

    }

    //重新繪制的方法
    function reload(){

        var n = initVertexBuffers(gl);

        //繪制
        draw(n);
    }

    function draw(n) {
        //繪制
        gl.clearColor(,,,);

        gl.clear(gl.COLOR_BUFFER_BIT);

        gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, );
    }

</script>
</html>