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

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

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

      WebGL簡易教程(十五):加載gltf模型

      1. 概述

      一般來說,圖形渲染總是需要從磁盤數據開始,最終保存到磁盤數據中,保存這種數據的就是3D模型文件。3D模型文件一般會把頂點、索引、紋理、材質等等信息都保存起來,方便下次直接讀取。3D模型文件格式一般是與圖形渲染工作強關聯的,了解3D模型文件格式的組成,有助于進一步了解圖形渲染的流程。

      glTF可以說是專門為WebGL量身定制的數據格式,具有以下特點:

      1. 場景數據結構是使用JSON來描述的,讀取后即可解析,無需再自定義組織對象。
      2. buffer數據被保存為二進制文件,占用空間小,讀取后即可使用,無需轉換過程。
      3. 紋理數據可以使用jpg文件,方便壓縮和傳輸。

      從以上特性可以看出,glTF特別方便與互聯網的使用場景,便于傳輸且預處理程度小。在這篇教程中,就通過一個帶紋理的地形文件,具體解析以下glTF格式,順便加深一下WebGL中初始化數據的理解。

      2. 實例

      2.1. 數據

      使用的地形glTF文件已經處理好并上傳到文章末尾的地址中(具體的轉換過程可以參看《DEM轉換為gltf》)。glTF是這樣一個JSON文件:

      {
          "asset": {
              "generator": "CL",
              "version": "2.0"
          },
          "scene": 0,
          "scenes": [
              {
                  "nodes": [
                      0
                  ]
              }
          ],
          "nodes": [
              {
                  "mesh": 0
              }
          ],
          "meshes": [
              {
                  "primitives": [
                      {
                          "attributes": {
                              "POSITION": 1,
                              "TEXCOORD_0": 2
                          },
                          "indices": 0,
                          "material": 0
                      }
                  ]
              }
          ],
          "materials": [
              {
                  "pbrMetallicRoughness": {
                      "baseColorTexture": {
                          "index": 0
                      }
                  }
              }
          ],
          "textures": [
              {
                  "sampler": 0,
                  "source": 0
              }
          ],
          "images": [
              {
                  "uri": "tex.jpg"
              }
          ],
          "samplers": [
              {
                  "magFilter": 9729,
                  "minFilter": 9987,
                  "wrapS": 33648,
                  "wrapT": 33648
              }
          ],
          "buffers": [
              {
                  "uri": "new.bin",
                  "byteLength": 595236
              }
          ],
          "bufferViews": [
              {
                  "buffer": 0,
                  "byteOffset": 374400,
                  "byteLength": 220836,
                  "target": 34963
              },
              {
                  "buffer": 0,
                  "byteStride": 20,
                  "byteOffset": 0,
                  "byteLength": 374400,
                  "target": 34962
              }
          ],
          "accessors": [
              {
                  "bufferView": 0,
                  "byteOffset": 0,
                  "componentType": 5123,
                  "count": 110418,
                  "type": "SCALAR",
                  "max": [
                      18719
                  ],
                  "min": [
                      0
                  ]
              },
              {
                  "bufferView": 1,
                  "byteOffset": 0,
                  "componentType": 5126,
                  "count": 18720,
                  "type": "VEC3",
                  "max": [
                      770,
                      0.0,
                      1261.151611328125
                  ],
                  "min": [
                      0.0,
                      -2390,
                      733.5555419921875
                  ]
              },
              {
                  "bufferView": 1,
                  "byteOffset": 12,
                  "componentType": 5126,
                  "count": 18720,
                  "type": "VEC2",
                  "max": [
                      1,
                      1
                  ],
                  "min": [
                      0,
                      0
                  ]
              }
          ]
      }
      
      

      可以看到這個文件鏈接了兩個外部文件new.bin和tex.jpg。new.bin也就是保存的頂點數據信息,是個二進制文件,tex.jpg也就是紋理圖片。將這個數據導入到glTF Viewer網站上查看,顯示結果如下:

      glTF地形顯示

      注意,由于安全策略的原因,瀏覽器導入數據時應該將new.gltf、new.bin、tex.jpg這三個文件一同導入,否則無法正確讀取顯示。

      2.2. 程序

      2.2.1. 文件讀取

      由于需要一次性加載多個文件,所以需要將input控件改成支持多文件的:

      <!DOCTYPE html>
      <html>
      
      <head>
        <meta charset="utf-8" />
        <title> 顯示地形 </title> 
      </head>
      
      <body onload="main()">
        <div><input type='file' id='demFile' multiple="multiple"></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>
      

      glTF Viewer網站中查看glTF的原理并不是將數據提交到后臺,而是直接交給前段頁面的JS進行讀取。可以通過FileReader對象來進行讀取。FileReader讀取的好處是不會觸發瀏覽器的安全策略,不用設置跨域(至少chrome不用):

        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 gltfObj = JSON.parse(reader.result);
      
              for (var fi = 0; fi < input.files.length; fi++) {
                //讀取bin文件
                if (gltfObj.buffers[0].uri === input.files[fi].name) {
                  var binReader = new FileReader();
                  binReader.onload = function () {
                    if (binReader.result) {
                      for (var fi = 0; fi < input.files.length; fi++) {
                        if (gltfObj.images[0].uri === input.files[fi].name) {
                          //讀取紋理圖像   
                          var imgReader = new FileReader();
      
                          imgReader.onload = function () {
                            //創建一個image對象
                            var image = new Image();
                            if (!image) {
                              console.log('Failed to create the image object');
                              return false;
                            }
      
                            //圖像加載的響應函數 
                            image.onload = function () {
                              //繪制函數
                              onDraw(gl, canvas, gltfObj, binReader.result, image);
                            };
      
                            //瀏覽器開始加載圖像
                            image.src = imgReader.result;
                          }
      
                          imgReader.readAsDataURL(input.files[fi]); //按照base64格式讀取
                          break;
                        }
                      }
                    }
                  }
                  binReader.readAsArrayBuffer(input.files[fi]);    //按照ArrayBuffer格式讀取
                  break;
                }
              }
            }
          }
      
          var input = event.target;
      
          var flag = false;
          for (var fi = 0; fi < input.files.length; fi++) {
            if (getFileSuffix(input.files[fi].name) === "gltf") {
              flag = true;
              reader.readAsText(input.files[fi]);      //按照字符串格式讀取
              break;
            }
          }
      
          if (!flag) {
            alert("沒有找到gltf");
          }
        });
      

      這段代碼看起來很繁復,其實原理很簡單:遍歷加載的文件,對于gltf文件采用FileReader.readAsText()也就是字符串格式的方法讀取,這個字符串隨后被解析成JSON;對于bin文件采用FileReader.readAsArrayBuffer()讀取,將其讀取成ArrayBuffer對象;對于jpg文件采用FileReader.readAsDataURL讀取,將其讀取成data:url開頭的base64字符串,這個字符串可以直接生成JS的Image對象。

      注意FileReader的讀取方式都是異步讀取,必須等到三個文件都讀取完成,才調用onDraw()函數進行繪制。讀取得到的對象也不用再多做處理,可以直接在后面的初始化步驟中使用。

      2.2.2. glTF格式解析

      初始化頂點緩沖區函數initVertexBuffers()中就用到了之前獲取的對象。gltfObj是獲取的JSON對象,里面記錄了對三維物體的描述信息。具體解析如下:

      2.2.2.1. 場景節點

          "asset": {
              "generator": "CL",
              "version": "2.0"
          },
          "scene": 0,
          "scenes": [
              {
                  "nodes": [
                      0
                  ]
              }
          ],
          "nodes": [
              {
                  "mesh": 0
              }
          ],
      

      asset表示的是元數據信息,version一般為2.0。
      scene是整個場景的入口,0表示scenes數組的第一個;scenes節點又包含了一個nodes數組,其中每個nodes對象包含一個children數組,這一數組引用了nodes對象的所有子結點。通過孩子結點,構成了整個場景結構:

      The scene graph representation stored in the glTF JSON

      這一段描述的其實是場景的結構層次模型。基本上來講,一般的三維渲染引擎都會將三維場景中的物體分解成節點,采用樹的結構來描述場景,這樣做能夠很方便的進行狀態控制以及姿態傳遞。這里沒有那么復雜的結構,就簡化為0。

      mesh則表示場景節點中的幾何對象。

      2.2.2.2. 網格

      "meshes": [
              {
                  "primitives": [
                      {
                          "attributes": {
                              "POSITION": 1,
                              "TEXCOORD_0": 2
                          },
                          "indices": 0,
                          "material": 0
                      }
                  ]
              }
          ],
      

      mesh對象包含了一個primitive數組對象。primitive表達的是一個圖元,描述每個網格是怎樣的幾何圖形。其attributes對象表達了圖元頂點的屬性。這里的POSITION屬性表示頂點的位置信息,屬性值1表示訪問器對象accessors數組的索引;TEXCOORD_0表示頂點的紋理位置信息,屬性值2表示訪問器對象accessors數組的索引。

      indices屬性表示圖元頂點數據是通過索引來描述的,其值3表示訪問器對象accessors數組的索引。

      而material則表示圖元用到了材質,在materials節點中可以找到其具體的描述。

      2.2.2.3. 緩沖,緩沖視圖和訪問器

          "buffers": [
              {
                  "uri": "new.bin",
                  "byteLength": 595236
              }
          ],
          "bufferViews": [
              {
                  "buffer": 0,
                  "byteOffset": 374400,
                  "byteLength": 220836,
                  "target": 34963
              },
              {
                  "buffer": 0,
                  "byteStride": 20,
                  "byteOffset": 0,
                  "byteLength": 374400,
                  "target": 34962
              }
          ],
          "accessors": [
              {
                  "bufferView": 0,
                  "byteOffset": 0,
                  "componentType": 5123,
                  "count": 110418,
                  "type": "SCALAR",
                  "max": [
                      18719
                  ],
                  "min": [
                      0
                  ]
              },
              {
                  "bufferView": 1,
                  "byteOffset": 0,
                  "componentType": 5126,
                  "count": 18720,
                  "type": "VEC3",
                  "max": [
                      770,
                      0.0,
                      1261.151611328125
                  ],
                  "min": [
                      0.0,
                      -2390,
                      733.5555419921875
                  ]
              },
              {
                  "bufferView": 1,
                  "byteOffset": 12,
                  "componentType": 5126,
                  "count": 18720,
                  "type": "VEC2",
                  "max": [
                      1,
                      1
                  ],
                  "min": [
                      0,
                      0
                  ]
              }
          ]
      

      這里詳細描述了上面提到的訪問器對象accessors。之所以定義這個屬性對象,是因為頂點數據信息被直接保存為二進制buffer了,需要去區分描述buffer哪些是位置信息,哪些是紋理坐標信息,哪些是索引信息。

      buffers對象就是頂點數據的二進制buffer,url表示被保存為外部的二進制文件new.bin,byteLength表示其長度為595236,這個文件在導入的時候會被讀取成JS的ArrayBuffer對象。

      bufferViews對象將buffers分成兩個視圖:前374400個字節表達的是頂點數據,步長byteStride為20個表示每20個字節的數據表達一個頂點,target為34962表示的就是ARRAY_BUFFER;而從374400開始的220836個字節表示的是頂點索引的數據,target為34963表示的就是ELEMENT_ARRAY_BUFFER。

      accessors對象則進一步描述了頂點數據的組織。

      1. 屬性bufferView表示的就是前面bufferViews對象的索引值。
      2. byteOffset表示數據從那個字節開始;componentType表示保存的數據類型,5123表示為UNSIGNED_SHORT型,占用2個字節;而5126表示FLOAT信號,占用4個字節。
      3. count表示數據的個數。
      4. type表示數據的類型,可以為標量SCALAR,也可以為矢量"VEC2"、"VEC3"等,甚至可以為矩陣"MAT3"等。
      5. min,max則表示每個值得最大最小值,填寫正確的范圍,有助于瀏覽操作。

      通過以上屬性值,就能夠正確區分描述頂點數據信息了。注意頂點數據的bufferViews對象在accessors對象被進一步劃分視圖,分別描述了位置信息和紋理坐標信息:bufferViews對象的步長byteStride被設置為20,accessors對象的偏移量byteOffset分別設置為0和12,說明二進制bin中的組織的結構為:

      位置X坐標 位置Y坐標 位置Z坐標 紋理S坐標 紋理T坐標
      位置X坐標 位置Y坐標 位置Z坐標 紋理S坐標 紋理T坐標
      位置X坐標 位置Y坐標 位置Z坐標 紋理S坐標 紋理T坐標
      ...

      當然,二進制bin中是沒有空格和回車的,這里只是為了方便好看。

      2.2.2.4. 紋理材質

          "materials": [
              {
                  "pbrMetallicRoughness": {
                      "baseColorTexture": {
                          "index": 0
                      }
                  }
              }
          ],
          "textures": [
              {
                  "sampler": 0,
                  "source": 0
              }
          ],
          "images": [
              {
                  "uri": "tex.jpg"
              }
          ],
          "samplers": [
              {
                  "magFilter": 9729,
                  "minFilter": 9987,
                  "wrapS": 33648,
                  "wrapT": 33648
              }
          ],
      

      在primitives對象的material的屬性中,指向的就是這個materials節點的索引值。materials對象又指向了紋理對象textures,textures對象通過索引引用了一個sampler對象和一個image對象。image對象包含了一個uri,引用了一個外部圖像文件。samplers是一個采樣器,用于設置紋理具體的采樣方式,其設置參數與WebGL中設置紋理的方式向對應。

      2.2.3. 初始化頂點緩沖區

      讀取后的數據可以直接交給initVertexBuffers()初始化頂點緩沖區,具體的實現代碼如下:

      //
      function initVertexBuffers(gl, gltfObj, binBuf) {
        //獲取頂點數據位置信息  
        var positionAccessorId = gltfObj.meshes[0].primitives[0].attributes.POSITION;
        if (gltfObj.accessors[positionAccessorId].componentType != 5126) {
          return 0;
        }
      
        var positionBufferViewId = gltfObj.accessors[positionAccessorId].bufferView;
        var verticesColors = new Float32Array(binBuf, gltfObj.bufferViews[positionBufferViewId].byteOffset, gltfObj.bufferViews[positionBufferViewId].byteLength / Float32Array.BYTES_PER_ELEMENT);
      
        gltfObj.cuboid = new Cuboid(gltfObj.accessors[positionAccessorId].min[0], gltfObj.accessors[positionAccessorId].max[0], gltfObj.accessors[positionAccessorId].min[1], gltfObj.accessors[positionAccessorId].max[1], gltfObj.accessors[positionAccessorId].min[2], gltfObj.accessors[positionAccessorId].max[2]);
      
        // 創建緩沖區對象
        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, gltfObj.bufferViews[positionBufferViewId].byteStride, gltfObj.accessors[positionAccessorId].byteOffset);
      
        // 連接a_Position變量與分配給它的緩沖區對象
        gl.enableVertexAttribArray(a_Position);
      
        //獲取頂點數據紋理信息  
        var txtCoordAccessorId = gltfObj.meshes[0].primitives[0].attributes.TEXCOORD_0;
        if (gltfObj.accessors[txtCoordAccessorId].componentType != 5126) {
          return 0;
        }
        var txtCoordBufferViewId = gltfObj.accessors[txtCoordAccessorId].bufferView;
      
        //獲取著色器中attribute變量a_TxtCoord的地址 
        var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
        if (a_TexCoord < 0) {
          console.log('Failed to get the storage location of a_TexCoord');
          return -1;
        }
        // 將緩沖區對象分配給a_Color變量
        gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, gltfObj.bufferViews[txtCoordBufferViewId].byteStride, gltfObj.accessors[txtCoordAccessorId].byteOffset);
        // 連接a_Color變量與分配給它的緩沖區對象
        gl.enableVertexAttribArray(a_TexCoord);
      
        //獲取頂點數據索引信息
        var indicesAccessorId = gltfObj.meshes[0].primitives[0].indices;
        var indicesBufferViewId = gltfObj.accessors[indicesAccessorId].bufferView;
        var indices = new Uint16Array(binBuf, gltfObj.bufferViews[indicesBufferViewId].byteOffset, gltfObj.bufferViews[indicesBufferViewId].byteLength / Uint16Array.BYTES_PER_ELEMENT);
      
        // 將頂點索引寫入到緩沖區對象
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
      
        return indices.length;
      }
      

      這段代碼的原理非常簡單,讀取的glTF被直接解析為JSON后,通過primitives屬性找到頂點位置坐標和頂點紋理坐標的訪問器對象accessors,繼而找到緩沖區buffer和緩沖區視圖bufferView。由于緩沖區數據文件new.bin已經被讀取成ArrayBuffer,可以將這個ArrayBuffer分成兩個視圖[6],一組視圖為Float32Array類型的頂點數組,一組視圖為Uint16Array類型的頂點數組索引。其中,頂點數組可以通過 gl.vertexAttribPointer()函數做進一步分配,分別給著色器分配位置變量和紋理坐標變量(可以復習一下《WebGL簡易教程(三):繪制一個三角形(緩沖區對象)》創建緩沖區對象的五個步驟)。

      2.2.4. 其他

      程序其他的步驟基本上沒有變化,由于數據讀取后JS的Image對象已經生成,仍然按照以前的方式根據Image對象生成紋理對象。著色器部分也非常簡單:

      // 頂點著色器程序
      var VSHADER_SOURCE =
        'attribute vec4 a_Position;\n' + //位置
        'attribute vec2 a_TexCoord;\n' + //顏色
        'varying vec2 v_TexCoord;\n' + //紋理坐標
        'uniform mat4 u_MvpMatrix;\n' +
        'void main() {\n' +
        '  gl_Position = u_MvpMatrix * a_Position;\n' + // 設置頂點坐標
        '  v_TexCoord = a_TexCoord;\n' +  //紋理坐標
        '}\n';
      
      // 片元著色器程序
      var FSHADER_SOURCE =
        'precision mediump float;\n' +
        'uniform sampler2D u_Sampler;\n' +
        'varying vec2 v_TexCoord;\n' + //紋理坐標
        'void main() {\n' +
        '  gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
        '}\n';
      

      紋理坐標傳入頂點著色器再傳入片元著色器,通過紋理對象插值得到片元最終值。

      3. 結果

      從以上解析過程可以看到,glTF的格式設計確實非常精妙,讀取的數據能夠直接為WebGL所用,既節省了空間又省略了一些預處理的過程,值得進一步深入研究。

      打開HTML頁面,導入new.gltf、new.bin、tex.jpg,顯示的效果如下:

      WebGL顯示地形

      這個例子是通過JS的FileReader來處理數據,所以不需要設置瀏覽器跨域。

      4. 參考

      1.《WebGL編程指南》
      2.glTF格式詳解(目錄)
      3.glTF Tutorial
      4.前端H5中JS用FileReader對象讀取blob對象二進制數據,文件傳輸
      5.gltf2.0規范
      6.JavaScript 之 ArrayBuffer

      5. 相關

      代碼和數據地址

      上一篇
      目錄
      下一篇

      posted @ 2020-01-20 15:09  charlee44  閱讀(10231)  評論(4)    收藏  舉報
      主站蜘蛛池模板: 1精品啪国产在线观看免费牛牛| 国产成人精品视频网站| 国产片AV国语在线观看手机版| 亚洲天天堂天堂激情性色| 亚洲av免费成人精品区| 人妻丰满熟AV无码区HD| 日本一区二区不卡精品| 五月综合网亚洲乱妇久久| 唐人社视频呦一区二区| 国产午夜福利视频合集| 久久热这里这里只有精品| 蜜桃av无码免费看永久| 91老肥熟女九色老女人| 国产精品天天看天天狠| 国产免费午夜福利在线播放| 四虎国产精品成人免费久久| 日韩在线视频线观看一区| 久久精品网站免费观看| 激情国产一区二区三区四区| av深夜免费在线观看| 亚洲精品不卡av在线播放| 婷婷五月综合丁香在线| 汪清县| 精品黄色av一区二区三区| 国产一区二区不卡在线| 丁香五月亚洲综合在线国内自拍| 极品尤物被啪到呻吟喷水| 强开小雪的嫩苞又嫩又紧| 亚洲精品免费一二三区| 一区二区偷拍美女撒尿视频| 久久国产av影片| 久久久久久av无码免费网站| 亚洲色大成网站WWW永久麻豆| 会东县| 日韩中文字幕免费在线观看| 露脸国产精品自产拍在线观看| 亚洲爆乳成av人在线视菜奈实| 久久毛片少妇高潮| 东方四虎av在线观看| 久久国产自偷自偷免费一区| 国产精品亚洲а∨天堂2021|