目錄html
經過WebGL,能夠渲染生成DEM(數字高程模型)。DEM(數字高程模型)是網格點組成的模型,每一個點都有x,y,z值;x,y根據必定的間距組成網格狀,同時根據z值的高低來選定每一個點的顏色RGB。經過這個例子能夠熟悉WebGL顏色渲染的過程。web
這裏使用的DEM文件的數據組織以下,以下圖所示。
其中每一行表示一個點,前三個數值表示位置XYZ,後三個數值表示顏色RGB。chrome
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> 顯示地形 </title> <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="showDEM.js"></script> </head> <body> <div><input type = 'file' id = 'demFile' ></div> <!-- <div><textarea id="output" rows="300" cols="200"></textarea></div> --> <div> <canvas id ="demCanvas" width="600" height="600"> 請使用支持WebGL的瀏覽器 </canvas> </div> </body> </html>
// Vertex shader program var VSHADER_SOURCE = //'precision highp float;\n' + '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'; // Fragment shader program var FSHADER_SOURCE = '#ifdef GL_ES\n' + 'precision mediump float;\n' + '#endif\n' + 'varying vec4 v_Color;\n' + 'void main() {\n' + ' gl_FragColor = v_Color;\n' + '}\n'; // var col = 89; //DEM寬 var row = 245; //DEM高 // Current rotation angle ([x-axis, y-axis] degrees) var currentAngle = [0.0, 0.0]; //當前lookAt()函數初始視點的高度 var eyeHight = 2000.0; //setPerspective()遠截面 var far = 3000; // window.onload = function () { var demFile = document.getElementById('demFile'); if (!demFile) { console.log("Error!"); return; } //demFile.onchange = openFile(event); 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 stringlines = reader.result.split("\n"); verticesColors = new Float32Array(stringlines.length * 6); // var pn = 0; var ci = 0; for (var i = 0; i < stringlines.length; i++) { if (!stringlines[i]) { continue; } var subline = stringlines[i].split(','); if (subline.length != 6) { console.log("錯誤的文件格式!"); return; } for (var j = 0; j < subline.length; j++) { verticesColors[ci] = parseFloat(subline[j]); ci++; } pn++; } if (ci < 3) { console.log("錯誤的文件格式!"); } // var minX = verticesColors[0]; var maxX = verticesColors[0]; var minY = verticesColors[1]; var maxY = verticesColors[1]; var minZ = verticesColors[2]; var maxZ = verticesColors[2]; for (var i = 0; i < pn; i++) { minX = Math.min(minX, verticesColors[i * 6]); maxX = Math.max(maxX, verticesColors[i * 6]); minY = Math.min(minY, verticesColors[i * 6 + 1]); maxY = Math.max(maxY, verticesColors[i * 6 + 1]); minZ = Math.min(minZ, verticesColors[i * 6 + 2]); maxZ = Math.max(maxZ, verticesColors[i * 6 + 2]); } //包圍盒中心 var cx = (minX + maxX) / 2.0; var cy = (minY + maxY) / 2.0; var cz = (minZ + maxZ) / 2.0; //根據視點高度算出setPerspective()函數的合理角度 var fovy = (maxY - minY) / 2.0 / eyeHight; fovy = 180.0 / Math.PI * Math.atan(fovy) * 2; startDraw(verticesColors, cx, cy, cz, fovy); } }; // var input = event.target; reader.readAsText(input.files[0]); }); } function startDraw(verticesColors, cx, cy, cz, fovy) { // Retrieve <canvas> element var canvas = document.getElementById('demCanvas'); // Get the rendering context for WebGL var gl = getWebGLContext(canvas); if (!gl) { console.log('Failed to get the rendering context for WebGL'); return; } // Initialize shaders if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { console.log('Failed to intialize shaders.'); return; } // Set the vertex coordinates and color (the blue triangle is in the front) n = initVertexBuffers(gl, verticesColors); //, verticesColors, n if (n < 0) { console.log('Failed to set the vertex information'); return; } // 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; } // Register the event handler initEventHandlers(canvas); // Specify the color for clearing <canvas> gl.clearColor(0, 0, 0, 1); gl.enable(gl.DEPTH_TEST); // Start drawing var tick = function () { //setPerspective()寬高比 var aspect = canvas.width / canvas.height; // draw(gl, n, aspect, cx, cy, cz, fovy, u_MvpMatrix); requestAnimationFrame(tick, canvas); }; tick(); } // function initEventHandlers(canvas) { var dragging = false; // Dragging or not var lastX = -1, lastY = -1; // Last position of the mouse // Mouse is pressed 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; }; // Mouse is released canvas.onmouseup = function (ev) { dragging = false; }; // Mouse is moved 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); // Limit x-axis rotation angle to -90 to 90 degrees //currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0); currentAngle[0] = currentAngle[0] + dy; currentAngle[1] = currentAngle[1] + dx; } lastX = x, lastY = y; }; //鼠標縮放 canvas.onmousewheel = function (event) { var lastHeight = eyeHight; if (event.wheelDelta > 0) { eyeHight = Math.max(1, eyeHight - 80); } else { eyeHight = eyeHight + 80; } far = far + eyeHight - lastHeight; }; } function draw(gl, n, aspect, cx, cy, cz, fovy, u_MvpMatrix) { //模型矩陣 var modelMatrix = new Matrix4(); 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(-cx, -cy, -cz); //視圖矩陣 var viewMatrix = new Matrix4(); viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0); //投影矩陣 var projMatrix = new Matrix4(); projMatrix.setPerspective(fovy, aspect, 10, far); //模型視圖投影矩陣 var mvpMatrix = new Matrix4(); mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix); // Pass the model view projection matrix to u_MvpMatrix gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements); // Clear color and depth buffer gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Draw the cube gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0); } function initVertexBuffers(gl, verticesColors) { //DEM的一個網格是由兩個三角形組成的 // 0------1 1 // | | // | | // col col------col+1 var indices = new Uint16Array((row - 1) * (col - 1) * 6); var ci = 0; for (var yi = 0; yi < row - 1; 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 vertexColorBuffer = gl.createBuffer(); var indexBuffer = gl.createBuffer(); if (!vertexColorBuffer || !indexBuffer) { return -1; } // 將緩衝區對象綁定到目標 gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer); // 向緩衝區對象中寫入數據 gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW); // var FSIZE = verticesColors.BYTES_PER_ELEMENT; // 向緩衝區對象分配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; } gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0); //開啓a_Position變量 gl.enableVertexAttribArray(a_Position); // 向緩衝區對象分配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; } 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; }
用chrome打開showDEM.html,選擇DEM文件,界面就會顯示DEM的渲染效果:
編程
程序的第一步是經過JS的FileReader()函數讀取DEM文件,在其回調函數中讀取到數組verticesColors中,它包含了位置和顏色信息。讀取完成後調用繪製函數startDraw()。canvas
// var reader = new FileReader(); reader.onload = function () { if (reader.result) { // var stringlines = reader.result.split("\n"); verticesColors = new Float32Array(stringlines.length * 6); // var pn = 0; var ci = 0; for (var i = 0; i < stringlines.length; i++) { if (!stringlines[i]) { continue; } var subline = stringlines[i].split(','); if (subline.length != 6) { console.log("錯誤的文件格式!"); return; } for (var j = 0; j < subline.length; j++) { verticesColors[ci] = parseFloat(subline[j]); ci++; } pn++; } if (ci < 3) { console.log("錯誤的文件格式!"); } // var minX = verticesColors[0]; var maxX = verticesColors[0]; var minY = verticesColors[1]; var maxY = verticesColors[1]; var minZ = verticesColors[2]; var maxZ = verticesColors[2]; for (var i = 0; i < pn; i++) { minX = Math.min(minX, verticesColors[i * 6]); maxX = Math.max(maxX, verticesColors[i * 6]); minY = Math.min(minY, verticesColors[i * 6 + 1]); maxY = Math.max(maxY, verticesColors[i * 6 + 1]); minZ = Math.min(minZ, verticesColors[i * 6 + 2]); maxZ = Math.max(maxZ, verticesColors[i * 6 + 2]); } //包圍盒中心 var cx = (minX + maxX) / 2.0; var cy = (minY + maxY) / 2.0; var cz = (minZ + maxZ) / 2.0; //根據視點高度算出setPerspective()函數的合理角度 var fovy = (maxY - minY) / 2.0 / eyeHight; fovy = 180.0 / Math.PI * Math.atan(fovy) * 2; startDraw(verticesColors, cx, cy, cz, fovy); } }; // var input = event.target; reader.readAsText(input.files[0]);
繪製DEM跟繪製一個簡單三角形的步驟是差很少的:數組
其中最關鍵的步驟是第三步,初始化頂點數組initVertexBuffers()。瀏覽器
function startDraw(verticesColors, cx, cy, cz, fovy) { // Retrieve <canvas> element var canvas = document.getElementById('demCanvas'); // Get the rendering context for WebGL var gl = getWebGLContext(canvas); if (!gl) { console.log('Failed to get the rendering context for WebGL'); return; } // Initialize shaders if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) { console.log('Failed to intialize shaders.'); return; } // Set the vertex coordinates and color (the blue triangle is in the front) n = initVertexBuffers(gl, verticesColors); //, verticesColors, n if (n < 0) { console.log('Failed to set the vertex information'); return; } // 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; } // Register the event handler initEventHandlers(canvas); // Specify the color for clearing <canvas> gl.clearColor(0, 0, 0, 1); gl.enable(gl.DEPTH_TEST); // Start drawing var tick = function () { //setPerspective()寬高比 var aspect = canvas.width / canvas.height; // draw(gl, n, aspect, cx, cy, cz, fovy, u_MvpMatrix); requestAnimationFrame(tick, canvas); }; tick(); }
在函數initVertexBuffers()中包含了使用緩衝區對象向頂點着色器傳入多個頂點數據的過程:函數
在本例中,在JS中申請的數組verticesColors分紅位置和顏色兩部分分配給緩衝區對象,並傳入頂點着色器;vertexAttribPointer()是其關鍵的函數,須要詳細瞭解其參數的用法。最後,把頂點數據的索引值綁定到緩衝區對象,WebGL能夠訪問索引來間接訪問頂點數據進行繪製。webgl
function initVertexBuffers(gl, verticesColors) { //DEM的一個網格是由兩個三角形組成的 // 0------1 1 // | | // | | // col col------col+1 var indices = new Uint16Array((row - 1) * (col - 1) * 6); var ci = 0; for (var yi = 0; yi < row - 1; 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 vertexColorBuffer = gl.createBuffer(); var indexBuffer = gl.createBuffer(); if (!vertexColorBuffer || !indexBuffer) { return -1; } // 將緩衝區對象綁定到目標 gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer); // 向緩衝區對象中寫入數據 gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW); // var FSIZE = verticesColors.BYTES_PER_ELEMENT; // 向緩衝區對象分配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; } gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0); //開啓a_Position變量 gl.enableVertexAttribArray(a_Position); // 向緩衝區對象分配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; } 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; }
1.這裏用到了幾個《WebGL編程指南》書中提供的JS組件。所有源代碼(包含DEM數據)地址連接:https://share.weiyun.com/5cvt8PJ ,密碼:4aqs8e。
2.若是關心如何設置模型視圖投影變換矩陣,以及綁定鼠標鍵盤事件,可參看這篇文章:WebGL或OpenGL關於模型視圖投影變換的設置技巧。
3.渲染的結果若是加入光照,效果會更好。debug