<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      WebGL簡易教程(九):綜合實例:地形的繪制

      1. 概述

      在上一篇教程《WebGL簡易教程(八):三維場景交互》中,給三維場景加入了簡單的交互,通過鼠標實現場景的旋轉和縮放。那么在這一篇教程中,綜合前面的知識,可以做出一個稍微復雜的實例:繪制一張基于現實的地形圖。

      地形也就是DEM(數字高程模型),是由一組網格點組成的模型,每個點都有x,y,z值;更簡單來說,圖像格式就可以作為DEM的載體,只不過每個圖像的像素值代表的是高程的值。這里準備了一張tif格式的DEM數據DEM.tif:

      這張tif是從谷歌地球上下載下來的,是美國大峽谷的某一塊地形。因為JS處理tif稍微有點麻煩,我這里預先將其處理成DEM.dem,這是一個文本格式:

      其中第一行的六個值分別表示:

      起點X坐標 起點Y坐標 X間距 Y間距 寬 高

      剩下的每一行表示一個點,點的順序為從上至下,從左至右:

      與起點X距離 與起點Y距離 高程值 顏色R 顏色G 顏色B 法向量X坐標 法向量Y坐標 法向量Z坐標

      一般來說DEM里面保存的應該只有點的位置信息也就是XYZ坐標,其渲染的顏色信息和法向量信息是預處理的過程中計算出來的。目前來說可以將其當成已知量,以后有機會將會在后續介紹詳細的預處理過程。

      2. 實例

      2.1. TerrainViewer.html

      <!DOCTYPE html>
      <html>
      
      <head>
        <meta charset="utf-8" />
        <title> 顯示地形 </title>
        <title>Hello Triangle</title>
      </head>
      
      <body onload="main()">
        <div><input type='file' id='demFile'></div>  
        <div>
          <canvas id="webgl" width="600" height="600">
            請使用支持WebGL的瀏覽器
          </canvas>
        </div>
      
        <script src="../lib/webgl-utils.js"></script>
        <script src="../lib/webgl-debug.js"></script>
        <script src="../lib/cuon-utils.js"></script>
        <script src="../lib/cuon-matrix.js"></script>
        <script src="TerrainViewer.js"></script>
      </body>
      
      </html>
      

      在HTML的代碼中,添加了一個input按鈕元素,用來導入DEM文件。一旦加載成功,canvas元素就會把讀取的數據顯示出來。

      2.2. TerrainViewer.js

      // 頂點著色器程序
      var VSHADER_SOURCE =
        'attribute vec4 a_Position;\n' +  //位置
        'attribute vec4 a_Color;\n' + //顏色
        'uniform mat4 u_MvpMatrix;\n' + 
        'varying vec4 v_Color;\n' +
        'void main() {\n' +
        '  gl_Position = u_MvpMatrix * a_Position;\n' + // 設置頂點坐標
        '  v_Color = a_Color;\n' +
        '}\n';
      
      // 片元著色器程序
      var FSHADER_SOURCE =
        'precision mediump float;\n' +
        'varying vec4 v_Color;\n' +
        'void main() {\n' +
        '  gl_FragColor = v_Color;\n' +
        '}\n';
      
      //定義一個矩形體:混合構造函數原型模式
      function Cuboid(minX, maxX, minY, maxY, minZ, maxZ) {
        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;
        this.minZ = minZ;
        this.maxZ = maxZ;
      }
      
      Cuboid.prototype = {
        constructor: Cuboid,
        CenterX: function () {
          return (this.minX + this.maxX) / 2.0;
        },
        CenterY: function () {
          return (this.minY + this.maxY) / 2.0;
        },
        CenterZ: function () {
          return (this.minZ + this.maxZ) / 2.0;
        },
        LengthX: function () {
          return (this.maxX - this.minX);
        },
        LengthY: function () {
          return (this.maxY - this.minY);
        }
      }
      
      //定義DEM
      function Terrain() {
      }
      Terrain.prototype = {
        constructor: Terrain,
        setWH: function (col, row) {
          this.col = col;
          this.row = row;
        }
      }
      
      var currentAngle = [0.0, 0.0]; // 繞X軸Y軸的旋轉角度 ([x-axis, y-axis])
      var curScale = 1.0;   //當前的縮放比例
      
      function main() {
        var demFile = document.getElementById('demFile');
        if (!demFile) {
          console.log("Failed to get demFile element!");
          return;
        }
      
        //加載文件后的事件
        demFile.addEventListener("change", function (event) {
          //判斷瀏覽器是否支持FileReader接口
          if (typeof FileReader == 'undefined') {
            console.log("你的瀏覽器不支持FileReader接口!");
            return;
          }
          
          //讀取文件后的事件
          var reader = new FileReader();
          reader.onload = function () {
            if (reader.result) {        
              var terrain = new Terrain();
              if (!readDEMFile(reader.result, terrain)) {
                console.log("文件格式有誤,不能讀取該文件!");
              }
      
              //繪制函數
              onDraw(gl, canvas, terrain);
            }
          }
      
          var input = event.target;
          reader.readAsText(input.files[0]);
        });
      
        // 獲取 <canvas> 元素
        var canvas = document.getElementById('webgl');
      
        // 獲取WebGL渲染上下文
        var gl = getWebGLContext(canvas);
        if (!gl) {
          console.log('Failed to get the rendering context for WebGL');
          return;
        }
      
        // 初始化著色器
        if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
          console.log('Failed to intialize shaders.');
          return;
        }
      
        // 指定清空<canvas>的顏色
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
      
        // 開啟深度測試
        gl.enable(gl.DEPTH_TEST);
      
        //清空顏色和深度緩沖區
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      }
      
      //繪制函數
      function onDraw(gl, canvas, terrain) {
        // 設置頂點位置
        //var cuboid = new Cuboid(399589.072, 400469.072, 3995118.062, 3997558.062, 732, 1268); 
        var n = initVertexBuffers(gl, terrain);
        if (n < 0) {
          console.log('Failed to set the positions of the vertices');
          return;
        }
      
        //注冊鼠標事件
        initEventHandlers(canvas);
      
        //繪制函數
        var tick = function () {
          //設置MVP矩陣
          setMVPMatrix(gl, canvas, terrain.cuboid);
      
          //清空顏色和深度緩沖區
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      
          //繪制矩形體
          gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);
          //gl.drawArrays(gl.Points, 0, n);
      
          //請求瀏覽器調用tick
          requestAnimationFrame(tick);
        };
      
        //開始繪制
        tick();
      }
      
      //讀取DEM函數
      function readDEMFile(result, terrain) {
        var stringlines = result.split("\n");
        if (!stringlines || stringlines.length <= 0) {
          return false;
        }
      
        //讀取頭信息
        var subline = stringlines[0].split("\t");
        if (subline.length != 6) {
          return false;
        }
        var col = parseInt(subline[4]);       //DEM寬
        var row = parseInt(subline[5]);      //DEM高
        var verticeNum = col * row;
        if (verticeNum + 1 > stringlines.length) {
          return false;
        }
        terrain.setWH(col, row);
      
        //讀取點信息
        var ci = 0;
        terrain.verticesColors = new Float32Array(verticeNum * 6);
        for (var i = 1; i < stringlines.length; i++) {
          if (!stringlines[i]) {
            continue;
          }
      
          var subline = stringlines[i].split(',');
          if (subline.length != 9) {
            continue;
          }
      
          for (var j = 0; j < 6; j++) {
            terrain.verticesColors[ci] = parseFloat(subline[j]);
            ci++;
          }
        }
      
        if (ci !== verticeNum * 6) {
          return false;
        }
      
        //包圍盒
        var minX = terrain.verticesColors[0];
        var maxX = terrain.verticesColors[0];
        var minY = terrain.verticesColors[1];
        var maxY = terrain.verticesColors[1];
        var minZ = terrain.verticesColors[2];
        var maxZ = terrain.verticesColors[2];
        for (var i = 0; i < verticeNum; i++) {
          minX = Math.min(minX, terrain.verticesColors[i * 6]);
          maxX = Math.max(maxX, terrain.verticesColors[i * 6]);
          minY = Math.min(minY, terrain.verticesColors[i * 6 + 1]);
          maxY = Math.max(maxY, terrain.verticesColors[i * 6 + 1]);
          minZ = Math.min(minZ, terrain.verticesColors[i * 6 + 2]);
          maxZ = Math.max(maxZ, terrain.verticesColors[i * 6 + 2]);
        }
      
        terrain.cuboid = new Cuboid(minX, maxX, minY, maxY, minZ, maxZ);
      
        return true;
      }
      
      
      //注冊鼠標事件
      function initEventHandlers(canvas) {
        var dragging = false;         // Dragging or not
        var lastX = -1, lastY = -1;   // Last position of the mouse
      
        //鼠標按下
        canvas.onmousedown = function (ev) {
          var x = ev.clientX;
          var y = ev.clientY;
          // Start dragging if a moue is in <canvas>
          var rect = ev.target.getBoundingClientRect();
          if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
            lastX = x;
            lastY = y;
            dragging = true;
          }
        };
      
        //鼠標離開時
        canvas.onmouseleave = function (ev) {
          dragging = false;
        };
      
        //鼠標釋放
        canvas.onmouseup = function (ev) {
          dragging = false;
        };
      
        //鼠標移動
        canvas.onmousemove = function (ev) {
          var x = ev.clientX;
          var y = ev.clientY;
          if (dragging) {
            var factor = 100 / canvas.height; // The rotation ratio
            var dx = factor * (x - lastX);
            var dy = factor * (y - lastY);
            currentAngle[0] = currentAngle[0] + dy;
            currentAngle[1] = currentAngle[1] + dx;
          }
          lastX = x, lastY = y;
        };
      
        //鼠標縮放
        canvas.onmousewheel = function (event) {
          if (event.wheelDelta > 0) {
            curScale = curScale * 1.1;
          } else {
            curScale = curScale * 0.9;
          }
        };
      }
      
      //設置MVP矩陣
      function setMVPMatrix(gl, canvas, cuboid) {
        // Get the storage location of u_MvpMatrix
        var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
        if (!u_MvpMatrix) {
          console.log('Failed to get the storage location of u_MvpMatrix');
          return;
        }
      
        //模型矩陣
        var modelMatrix = new Matrix4();
        modelMatrix.scale(curScale, curScale, curScale);
        modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis 
        modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis 
        modelMatrix.translate(-cuboid.CenterX(), -cuboid.CenterY(), -cuboid.CenterZ());
      
        //投影矩陣
        var fovy = 60;
        var near = 1;
        var projMatrix = new Matrix4();
        projMatrix.setPerspective(fovy, canvas.width / canvas.height, 1, 10000);
      
        //計算lookAt()函數初始視點的高度
        var angle = fovy / 2 * Math.PI / 180.0;
        var eyeHight = (cuboid.LengthY() * 1.2) / 2.0 / angle;
      
        //視圖矩陣  
        var viewMatrix = new Matrix4();  // View matrix   
        viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);
      
        //MVP矩陣
        var mvpMatrix = new Matrix4();
        mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
      
        //將MVP矩陣傳輸到著色器的uniform變量u_MvpMatrix
        gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
      }
      
      //
      function initVertexBuffers(gl, terrain) {
        //DEM的一個網格是由兩個三角形組成的
        //      0------1            1
        //      |                   |
        //      |                   |
        //      col       col------col+1    
        var col = terrain.col;
        var row = terrain.row;
      
        var indices = new Uint16Array((row - 1) * (col - 1) * 6); 
        var ci = 0;
        for (var yi = 0; yi < row - 1; yi++) {
        //for (var yi = 0; yi < 10; yi++) {
          for (var xi = 0; xi < col - 1; xi++) {
            indices[ci * 6] = yi * col + xi;
            indices[ci * 6 + 1] = (yi + 1) * col + xi;
            indices[ci * 6 + 2] = yi * col + xi + 1;
            indices[ci * 6 + 3] = (yi + 1) * col + xi;
            indices[ci * 6 + 4] = (yi + 1) * col + xi + 1;
            indices[ci * 6 + 5] = yi * col + xi + 1;
            ci++;
          }
        }  
      
        //
        var verticesColors = terrain.verticesColors;
        var FSIZE = verticesColors.BYTES_PER_ELEMENT;   //數組中每個元素的字節數
      
        // 創建緩沖區對象
        var vertexColorBuffer = gl.createBuffer();
        var indexBuffer = gl.createBuffer();
        if (!vertexColorBuffer || !indexBuffer) {
          console.log('Failed to create the buffer object');
          return -1;
        }
      
        // 將緩沖區對象綁定到目標
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
        // 向緩沖區對象寫入數據
        gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
      
        //獲取著色器中attribute變量a_Position的地址 
        var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
        if (a_Position < 0) {
          console.log('Failed to get the storage location of a_Position');
          return -1;
        }
        // 將緩沖區對象分配給a_Position變量
        gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
      
        // 連接a_Position變量與分配給它的緩沖區對象
        gl.enableVertexAttribArray(a_Position);
      
        //獲取著色器中attribute變量a_Color的地址 
        var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
        if (a_Color < 0) {
          console.log('Failed to get the storage location of a_Color');
          return -1;
        }
        // 將緩沖區對象分配給a_Color變量
        gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
        // 連接a_Color變量與分配給它的緩沖區對象
        gl.enableVertexAttribArray(a_Color);
      
        // 將頂點索引寫入到緩沖區對象
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
       
        return indices.length;
      }
      

      與上一篇的JS代碼相比,沒有什么新的知識,大部分流程都是一樣的,只不過對數據的組織略有不同。

      在main()函數中,為按鈕定義了加載事件函數。在函數中通過FileReader()讀取文件,讀取函數為readDEMFile();接著進行繪制,繪制函數為onDraw()。

      //...
      
      var demFile = document.getElementById('demFile');
      if (!demFile) {
        console.log("Failed to get demFile element!");
        return;
      }
      
      //加載文件后的事件
      demFile.addEventListener("change", function (event) {
        //判斷瀏覽器是否支持FileReader接口
        if (typeof FileReader == 'undefined') {
          console.log("你的瀏覽器不支持FileReader接口!");
          return;
        }
        
        //讀取文件后的事件
        var reader = new FileReader();
        reader.onload = function () {
          if (reader.result) {        
            var terrain = new Terrain();
            if (!readDEMFile(reader.result, terrain)) {
              console.log("文件格式有誤,不能讀取該文件!");
            }
      
            //繪制函數
            onDraw(gl, canvas, terrain);
          }
        }
      
        var input = event.target;
        reader.readAsText(input.files[0]);
      });
      
      //...
      

      readDEMFile()函數就是解析這個DEM文件的過程,將讀取到的數據保存到Terrain對象中。Terrain是一個自定義的對象,DEM文件的寬、高、位置信息以及顏色信息都存入到這個對象中。值得注意的是,這里求取了所有點的包圍盒,也一并保存進Terrain對象中了。這個包圍盒信息就是用來設置MVP矩陣的,從而讓場景與鼠標進行交互。

      //定義DEM
      function Terrain() {
      }
      Terrain.prototype = {
        constructor: Terrain,
        setWH: function (col, row) {
          this.col = col;
          this.row = row;
        }
      }
      
      //...
      
      //讀取DEM函數
      function readDEMFile(result, terrain) {
        var stringlines = result.split("\n");
        if (!stringlines || stringlines.length <= 0) {
          return false;
        }
      
        //讀取頭信息
        var subline = stringlines[0].split("\t");
        if (subline.length != 6) {
          return false;
        }
        var col = parseInt(subline[4]);       //DEM寬
        var row = parseInt(subline[5]);      //DEM高
        var verticeNum = col * row;
        if (verticeNum + 1 > stringlines.length) {
          return false;
        }
        terrain.setWH(col, row);
      
        //讀取點信息
        var ci = 0;
        terrain.verticesColors = new Float32Array(verticeNum * 6);
        for (var i = 1; i < stringlines.length; i++) {
          if (!stringlines[i]) {
            continue;
          }
      
          var subline = stringlines[i].split(',');
          if (subline.length != 9) {
            continue;
          }
      
          for (var j = 0; j < 6; j++) {
            terrain.verticesColors[ci] = parseFloat(subline[j]);
            ci++;
          }
        }
      
        if (ci !== verticeNum * 6) {
          return false;
        }
      
        //包圍盒
        var minX = terrain.verticesColors[0];
        var maxX = terrain.verticesColors[0];
        var minY = terrain.verticesColors[1];
        var maxY = terrain.verticesColors[1];
        var minZ = terrain.verticesColors[2];
        var maxZ = terrain.verticesColors[2];
        for (var i = 0; i < verticeNum; i++) {
          minX = Math.min(minX, terrain.verticesColors[i * 6]);
          maxX = Math.max(maxX, terrain.verticesColors[i * 6]);
          minY = Math.min(minY, terrain.verticesColors[i * 6 + 1]);
          maxY = Math.max(maxY, terrain.verticesColors[i * 6 + 1]);
          minZ = Math.min(minZ, terrain.verticesColors[i * 6 + 2]);
          maxZ = Math.max(maxZ, terrain.verticesColors[i * 6 + 2]);
        }
      
        terrain.cuboid = new Cuboid(minX, maxX, minY, maxY, minZ, maxZ);
      
        return true;
      }
      

      繪制函數onDraw()與之前的代碼相比基本沒有變化。可以看到在設置MVP矩陣的函數 setMVPMatrix()中,傳遞的參數是Terrain對象的包圍盒,這一點與上一篇教程是一樣的。但主要的改動是在初始化頂點函數initVertexBuffers()中。

      //繪制函數
      function onDraw(gl, canvas, terrain) {
        // 設置頂點位置
        //var cuboid = new Cuboid(399589.072, 400469.072, 3995118.062, 3997558.062, 732, 1268); 
        var n = initVertexBuffers(gl, terrain);
        if (n < 0) {
          console.log('Failed to set the positions of the vertices');
          return;
        }
      
        //注冊鼠標事件
        initEventHandlers(canvas);
      
        //繪制函數
        var tick = function () {
          //設置MVP矩陣
          setMVPMatrix(gl, canvas, terrain.cuboid);
      
          //清空顏色和深度緩沖區
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      
          //繪制矩形體
          gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);
          //gl.drawArrays(gl.Points, 0, n);
      
          //請求瀏覽器調用tick
          requestAnimationFrame(tick);
        };
      
        //開始繪制
        tick();
      }
      

      在函數initVertexBuffers()中,由于讀取的頂點信息(保存在Terrain對象中)同樣包含位置信息和定點信息,所以同樣將其傳遞到緩沖區對象。不同的在于頂點索引的組織。前面提到過,頂點數組中的點是從上至下,從左至右依次排列的,所以每個網格是上、下、左、右四個不同的點組成的兩個三角形。所以一共要繪制\(((寬 - 1) * (高 - 1) * 2)\)個三角形,頂點索引數組的長度為\(((寬 - 1) * (高 - 1) * 6)\)

      //
      function initVertexBuffers(gl, terrain) {
        //DEM的一個網格是由兩個三角形組成的
        //      0------1            1
        //      |                   |
        //      |                   |
        //      col       col------col+1    
        var col = terrain.col;
        var row = terrain.row;
      
        var indices = new Uint16Array((row - 1) * (col - 1) * 6); 
        var ci = 0;
        for (var yi = 0; yi < row - 1; yi++) {
        //for (var yi = 0; yi < 10; yi++) {
          for (var xi = 0; xi < col - 1; xi++) {
            indices[ci * 6] = yi * col + xi;
            indices[ci * 6 + 1] = (yi + 1) * col + xi;
            indices[ci * 6 + 2] = yi * col + xi + 1;
            indices[ci * 6 + 3] = (yi + 1) * col + xi;
            indices[ci * 6 + 4] = (yi + 1) * col + xi + 1;
            indices[ci * 6 + 5] = yi * col + xi + 1;
            ci++;
          }
        }  
      
        //
        var verticesColors = terrain.verticesColors;
        var FSIZE = verticesColors.BYTES_PER_ELEMENT;   //數組中每個元素的字節數
      
        //...
       
        return indices.length;
      }
      

      3. 結果

      通過瀏覽器運行程序,加載DEM.dem文件,結果如下:

      其鼠標交互操作:

      可以看到最終繪制的結果是一小塊起伏的地形。所有復雜的模型都可以采用本例的辦法,用足夠的三角形繪制而成。當然,這個例子還有個缺點,就是顯示的效果立體感不強,對地形起伏的表現不夠。這是因為缺少了場景渲染中的重要一環,也就是下一篇教程要講的內容——光照。

      4. 參考

      本來部分代碼和插圖來自《WebGL編程指南》,源代碼鏈接:地址 。會在此共享目錄中持續更新后續的內容。

      posted @ 2019-10-08 19:13  charlee44  閱讀(3571)  評論(3)    收藏  舉報
      主站蜘蛛池模板: 国产综合欧美| 日韩伦理片| 久久精产国品一二三产品| 日本视频精品一区二区| 婷婷五月综合丁香在线| 国产日韩综合av在线| 免费 黄 色 人成 视频 在 线| 亚洲精品一区二区三区在线观看 | 久久成人国产精品免费软件| 亚洲精品一区二区妖精| 午夜福利日本一区二区无码| 午夜DY888国产精品影院| 亚洲色无码专区一区| 国产精品不卡一区二区久久 | 日本韩无专砖码高清观看| 中文有无人妻vs无码人妻激烈| 无码人妻一区二区三区线| 丝袜美腿视频一区二区三区| 久久大香萑太香蕉av黄软件| 亚洲一区二区三区| 国产目拍亚洲精品二区| 超碰成人人人做人人爽| 精品亚洲没码中文字幕| 东方av四虎在线观看| 国产欧美精品一区aⅴ影院| 中文字幕有码无码AV| 广昌县| 国产又爽又黄又刺激的视频| 亚洲国产精品久久电影欧美| 久久高潮少妇视频免费| 不卡乱辈伦在线看中文字幕| mm1313亚洲国产精品| 欧美嫩交一区二区三区| 精品国偷自产在线视频99| 风韵丰满熟妇啪啪区老熟熟女| 伊人久久综合无码成人网| 高中女无套中出17p| 亚洲精品一区二区三区不| 国产亚洲av手机在线观看| 久久亚洲美女精品国产精品| 日韩欧美在线综合网另类|