前幾天解決了原生WebGL開發中的一個問題,就是在一個場景中繪制多個幾何網格特征不同的模型,比如本文所做的繪制多個圓錐和圓柱在同一個場景中,今天抽空把解決的辦法記錄下來,同時也附上代碼。首先聲明,圓柱和圓錐的網格生成是我自己寫的polyhedron.js模塊,如果要加載其他模型,只需要把geometry換成其他幾何體的網格即可,本文的重點不在於使用什么幾何模型,而在於如何將各種不同的模型繪制到同一個場景中去。
第一件事,我們還是先把依賴的模型生成的js文件貼出來,以便參考者能夠將代碼組裝起來。首先看工程目錄結構,如下圖
該工程實際用到的文件就是這4個紅框框出的文件,其中除了jquery-2.1.4.min.js以外(請於cdn或百度或51cto站點自行下載),剩下的minMatrix.js,polyhedron.js,testCoordinates.html本作都將貼出完整的源碼,以便參考者本地調試。
首先貼出minMatrix.js源碼,minMatrix.js負責向量矩陣的運算,由於不是本文的中心論點,故不做贅述,如下所示
// ------------------------------------------------------------------------------------------------ // minMatrix.js // version 0.0.1 // Copyright (c) doxas // ------------------------------------------------------------------------------------------------ function matIV(){ this.create = function(){ return new Float32Array(16); }; this.identity = function(dest){ dest[0] = 1; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = 1; dest[6] = 0; dest[7] = 0; dest[8] = 0; dest[9] = 0; dest[10] = 1; dest[11] = 0; dest[12] = 0; dest[13] = 0; dest[14] = 0; dest[15] = 1; return dest; }; this.multiply = function(mat1, mat2, dest){ var a = mat1[0], b = mat1[1], c = mat1[2], d = mat1[3], e = mat1[4], f = mat1[5], g = mat1[6], h = mat1[7], i = mat1[8], j = mat1[9], k = mat1[10], l = mat1[11], m = mat1[12], n = mat1[13], o = mat1[14], p = mat1[15], A = mat2[0], B = mat2[1], C = mat2[2], D = mat2[3], E = mat2[4], F = mat2[5], G = mat2[6], H = mat2[7], I = mat2[8], J = mat2[9], K = mat2[10], L = mat2[11], M = mat2[12], N = mat2[13], O = mat2[14], P = mat2[15]; dest[0] = A * a + B * e + C * i + D * m; dest[1] = A * b + B * f + C * j + D * n; dest[2] = A * c + B * g + C * k + D * o; dest[3] = A * d + B * h + C * l + D * p; dest[4] = E * a + F * e + G * i + H * m; dest[5] = E * b + F * f + G * j + H * n; dest[6] = E * c + F * g + G * k + H * o; dest[7] = E * d + F * h + G * l + H * p; dest[8] = I * a + J * e + K * i + L * m; dest[9] = I * b + J * f + K * j + L * n; dest[10] = I * c + J * g + K * k + L * o; dest[11] = I * d + J * h + K * l + L * p; dest[12] = M * a + N * e + O * i + P * m; dest[13] = M * b + N * f + O * j + P * n; dest[14] = M * c + N * g + O * k + P * o; dest[15] = M * d + N * h + O * l + P * p; return dest; }; this.scale = function(mat, vec, dest){ dest[0] = mat[0] * vec[0]; dest[1] = mat[1] * vec[0]; dest[2] = mat[2] * vec[0]; dest[3] = mat[3] * vec[0]; dest[4] = mat[4] * vec[1]; dest[5] = mat[5] * vec[1]; dest[6] = mat[6] * vec[1]; dest[7] = mat[7] * vec[1]; dest[8] = mat[8] * vec[2]; dest[9] = mat[9] * vec[2]; dest[10] = mat[10] * vec[2]; dest[11] = mat[11] * vec[2]; dest[12] = mat[12]; dest[13] = mat[13]; dest[14] = mat[14]; dest[15] = mat[15]; return dest; }; this.translate = function(mat, vec, dest){ dest[0] = mat[0]; dest[1] = mat[1]; dest[2] = mat[2]; dest[3] = mat[3]; dest[4] = mat[4]; dest[5] = mat[5]; dest[6] = mat[6]; dest[7] = mat[7]; dest[8] = mat[8]; dest[9] = mat[9]; dest[10] = mat[10]; dest[11] = mat[11]; dest[12] = mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12]; dest[13] = mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13]; dest[14] = mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14]; dest[15] = mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15]; return dest; }; this.rotate = function(mat, angle, axis, dest){ var sq = Math.sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]); if(!sq){return null;} var a = axis[0], b = axis[1], c = axis[2]; if(sq != 1){sq = 1 / sq; a *= sq; b *= sq; c *= sq;} var d = Math.sin(angle), e = Math.cos(angle), f = 1 - e, g = mat[0], h = mat[1], i = mat[2], j = mat[3], k = mat[4], l = mat[5], m = mat[6], n = mat[7], o = mat[8], p = mat[9], q = mat[10], r = mat[11], s = a * a * f + e, t = b * a * f + c * d, u = c * a * f - b * d, v = a * b * f - c * d, w = b * b * f + e, x = c * b * f + a * d, y = a * c * f + b * d, z = b * c * f - a * d, A = c * c * f + e; if(angle){ if(mat != dest){ dest[12] = mat[12]; dest[13] = mat[13]; dest[14] = mat[14]; dest[15] = mat[15]; } } else { dest = mat; } dest[0] = g * s + k * t + o * u; dest[1] = h * s + l * t + p * u; dest[2] = i * s + m * t + q * u; dest[3] = j * s + n * t + r * u; dest[4] = g * v + k * w + o * x; dest[5] = h * v + l * w + p * x; dest[6] = i * v + m * w + q * x; dest[7] = j * v + n * w + r * x; dest[8] = g * y + k * z + o * A; dest[9] = h * y + l * z + p * A; dest[10] = i * y + m * z + q * A; dest[11] = j * y + n * z + r * A; return dest; }; this.lookAt = function(eye, center, up, dest){ var eyeX = eye[0], eyeY = eye[1], eyeZ = eye[2]; var centerX = center[0], centerY = center[1], centerZ = center[2]; var upX = up[0], upY = up[1], upZ = up[2]; if(eyeX == centerX && eyeY == centerY && eyeZ == centerZ){ return this.identity(dest); } var x0, x1, x2, y0, y1, y2, z0, z1, z2, l; z0 = eyeX - centerX; z1 = eyeY - centerY; z2 = eyeZ - centerZ; l = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); z0 *= l; z1 *= l; z2 *= l; x0 = upY * z2 - upZ * z1; x1 = upZ * z0 - upX * z2; x2 = upX * z1 - upY * z0; l = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); if(!l){ x0 = 0; x1 = 0; x2 = 0; } else { l = 1 / l; x0 *= l; x1 *= l; x2 *= l; } y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0; l = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); if(!l){ y0 = 0; y1 = 0; y2 = 0; } else { l = 1 / l; y0 *= l; y1 *= l; y2 *= l; } dest[0] = x0; dest[1] = y0; dest[2] = z0; dest[3] = 0; dest[4] = x1; dest[5] = y1; dest[6] = z1; dest[7] = 0; dest[8] = x2; dest[9] = y2; dest[10] = z2; dest[11] = 0; dest[12] = -(x0 * eyeX + x1 * eyeY + x2 * eyeZ); dest[13] = -(y0 * eyeX + y1 * eyeY + y2 * eyeZ); dest[14] = -(z0 * eyeX + z1 * eyeY + z2 * eyeZ); dest[15] = 1; return dest; }; this.view = function(eye, at, up, dest){ var forward = []; forward[0] = at[0] - eye[0]; forward[1] = at[1] - eye[1]; forward[2] = at[2] - eye[2]; var l = Math.sqrt(forward[0]*forward[0] + forward[1]*forward[1] + forward[2]*forward[2]); forward[0] = forward[0]/l; forward[1] = forward[1]/l; forward[2] = forward[2]/l; var side = []; side[0] = forward[2]*up[1] - up[2]*forward[1]; side[1] = forward[0]*up[2] - up[0]*forward[2]; side[2] = forward[1]*up[0] - up[1]*forward[0]; l = Math.sqrt(side[0]*side[0] + side[1]*side[1] + side[2]*side[2]); side[0] = side[0]/l; side[1] = side[1]/l; side[2] = side[2]/l; up[0] = side[2]*forward[1] - forward[2]*side[1]; up[1] = side[0]*forward[2] - forward[0]*side[2]; up[2] = side[1]*forward[0] - forward[1]*side[0]; var dest = []; dest[0] = side[0]; dest[1] = up[0]; dest[2] = -forward[0]; dest[3] = 0; dest[4] = side[1]; dest[5] = up[1]; dest[6] = -forward[1]; dest[7] = 0; dest[8] = side[2]; dest[9] = up[2]; dest[10]= -forward[2]; dest[11]= 0; dest[12]= 0; dest[13]= 0; dest[14]= 0; dest[15]= 1; dest[0] = dest[0]; dest[1] = dest[1]; dest[2] = dest[2]; dest[3] = dest[3]; dest[4] = dest[4]; dest[5] = dest[5]; dest[6] = dest[6]; dest[7] = dest[7]; dest[8] = dest[8]; dest[9] = dest[9]; dest[10] = dest[10]; dest[11] = dest[11]; dest[12] = dest[0] * (-eye[0]) + dest[4] * (-eye[1]) + dest[8] * (-eye[2]) + dest[12]; dest[13] = dest[1] * (-eye[0]) + dest[5] * (-eye[1]) + dest[9] * (-eye[2]) + dest[13]; dest[14] = dest[2] * (-eye[0]) + dest[6] * (-eye[1]) + dest[10] * (-eye[2]) + dest[14]; dest[15] = dest[3] * (-eye[0]) + dest[7] * (-eye[1]) + dest[11] * (-eye[2]) + dest[15]; return dest; } /** * 正交投影 */ this.ortho = function(left, right, bottom, top, near, far, dest){ var a = 2/(right-left); var b = -(right+left)/(right-left); var c = 2/(top-bottom); var d = -(top+bottom)/(top-bottom); var e = 2/(far-near); var f = -(far+near)/(far-near); dest[0] = a; dest[1] = 0; dest[2] = 0; dest[3] = b; dest[4] = 0; dest[5] = c; dest[6] = 0; dest[7] = d; dest[8] = 0; dest[9] = 0; dest[10]= e; dest[11]= f; dest[12]= 0; dest[13]= 0; dest[14]= 0; dest[15]= 1; return dest; } /** * 透視投影 */ this.perspective = function(fovy, aspect, near, far, dest){ var t = near * Math.tan(fovy * Math.PI / 360); var r = t * aspect; var a = r * 2, b = t * 2, c = far - near; dest[0] = near * 2 / a; dest[1] = 0; dest[2] = 0; dest[3] = 0; dest[4] = 0; dest[5] = near * 2 / b; dest[6] = 0; dest[7] = 0; dest[8] = 0; dest[9] = 0; dest[10] = -(far + near) / c; dest[11] = -1; dest[12] = 0; dest[13] = 0; dest[14] = -(far * near * 2) / c; dest[15] = 0; return dest; }; this.transpose = function(mat, dest){ dest[0] = mat[0]; dest[1] = mat[4]; dest[2] = mat[8]; dest[3] = mat[12]; dest[4] = mat[1]; dest[5] = mat[5]; dest[6] = mat[9]; dest[7] = mat[13]; dest[8] = mat[2]; dest[9] = mat[6]; dest[10] = mat[10]; dest[11] = mat[14]; dest[12] = mat[3]; dest[13] = mat[7]; dest[14] = mat[11]; dest[15] = mat[15]; return dest; }; this.inverse = function(mat, dest){ var a = mat[0], b = mat[1], c = mat[2], d = mat[3], e = mat[4], f = mat[5], g = mat[6], h = mat[7], i = mat[8], j = mat[9], k = mat[10], l = mat[11], m = mat[12], n = mat[13], o = mat[14], p = mat[15], q = a * f - b * e, r = a * g - c * e, s = a * h - d * e, t = b * g - c * f, u = b * h - d * f, v = c * h - d * g, w = i * n - j * m, x = i * o - k * m, y = i * p - l * m, z = j * o - k * n, A = j * p - l * n, B = k * p - l * o, ivd = 1 / (q * B - r * A + s * z + t * y - u * x + v * w); dest[0] = ( f * B - g * A + h * z) * ivd; dest[1] = (-b * B + c * A - d * z) * ivd; dest[2] = ( n * v - o * u + p * t) * ivd; dest[3] = (-j * v + k * u - l * t) * ivd; dest[4] = (-e * B + g * y - h * x) * ivd; dest[5] = ( a * B - c * y + d * x) * ivd; dest[6] = (-m * v + o * s - p * r) * ivd; dest[7] = ( i * v - k * s + l * r) * ivd; dest[8] = ( e * A - f * y + h * w) * ivd; dest[9] = (-a * A + b * y - d * w) * ivd; dest[10] = ( m * u - n * s + p * q) * ivd; dest[11] = (-i * u + j * s - l * q) * ivd; dest[12] = (-e * z + f * x - g * w) * ivd; dest[13] = ( a * z - b * x + c * w) * ivd; dest[14] = (-m * t + n * r - o * q) * ivd; dest[15] = ( i * t - j * r + k * q) * ivd; return dest; }; this.rotateByAnyAxis = function(RawMat, axis, rad, destMat){ }; }
接下來就要貼出幾何體生成工具 polyhedron.js,我們用到的幾何體是其中的圓錐 coneGeo和圓柱 cylinderGeo,這兩個類都是PolyhedronGeometry的子類,幾何體都包含頂點成員this.vertices,法向成員this.normals,索引成員this.faces。具體構造詳見代碼如下
/** * Created by ccentry on 2018/10/15. */ /** * 圓錐 * radius:底面半徑 * height:圓錐高度 * meshDensity:網格密度 * */ var coneGeo = function(radius, height, segment){ //錐頂 var top = [0, height, 0]; //錐底,錐底半徑radius //根據segment來切割錐底圓 var sliceNum = segment || 3; var rad = Math.PI*2/sliceNum; var bottom = []; for(var i=0; i<sliceNum; i++){ bottom[i*3] = radius*Math.cos(rad*i); bottom[i*3 + 1] = 0; bottom[i*3 + 2] = radius*Math.sin(rad*i); } //圓錐的頂點 this.vertices = []; //頂點法向 this.normals = []; //錐頂 for(var i=0; i<sliceNum; i++){ this.vertices[i*3] = top[0]; this.vertices[i*3+1] = top[1]; this.vertices[i*3+2] = top[2]; } //錐面圓環 for(var i=0; i<bottom.length; i++){ this.vertices[3*sliceNum+i] = bottom[i]; } //錐底圓環 for(var i=0; i<bottom.length; i++){ this.vertices[2*3*sliceNum+i] = bottom[i]; } //錐底圓心 this.vertices.push(0, 0, 0); //圓錐面索引 this.faces = []; for(var i=sliceNum; i<2*sliceNum-1; i++){ //圓錐側面 this.faces.push(i, i-sliceNum, i+1); //圓錐底面 this.faces.push(3*sliceNum, sliceNum+i, sliceNum+i+1); } //補側面 this.faces.push(2*sliceNum-1, sliceNum-1, sliceNum); //補底面 this.faces.push(3*sliceNum, 3*sliceNum-1, 2*sliceNum); //計算所有頂點法向 this.computeNormal4EachVertex(); }; /** * 圓柱 * radius:底面半徑 * height:圓柱高度 * meshDensity:網格密度 * */ var cylinderGeo = function(radius, height, segment){ radius = radius || 1; height = height || 1; //根據網格密度來切割圓柱體底圓 var sliceNum = segment || 3; //底面圓弧度 var rad = Math.PI*2/sliceNum; //底面圓環頂點 var bottomVertices = []; //頂面頂點 var topVertices = []; //切割底面圓和底面圓 for(var i=0; i<sliceNum; i++){ bottomVertices[3*i] = radius * Math.cos(rad * i); bottomVertices[3*i+1] = 0; bottomVertices[3*i+2] = radius * Math.sin(rad * i); topVertices[3*i] = radius * Math.cos(rad * i); topVertices[3*i+1] = height; topVertices[3*i+2] = radius * Math.sin(rad * i); } //圓柱的頂點 this.vertices = []; for(var i=0; i<bottomVertices.length; i++){ //底面圓 this.vertices[i] = bottomVertices[i]; //頂面圓 this.vertices[bottomVertices.length + i] = topVertices[i]; //柱底圈 this.vertices[2*bottomVertices.length + i] = bottomVertices[i]; //柱頂圈 this.vertices[3*bottomVertices.length + i] = topVertices[i]; } //底面圓心; this.vertices.push(0, 0, 0); //頂面圓心 this.vertices.push(0, height, 0); //圓柱的面 this.faces = []; for(var i=0; i<sliceNum-1; i++){ //底面圓 this.faces.push(4*sliceNum, i, i+1); //頂面圓 this.faces.push(4*sliceNum+1, sliceNum+i+1, sliceNum+i); //柱身 this.faces.push(2*sliceNum+i, 3*sliceNum+i, 2*sliceNum+i+1); this.faces.push(3*sliceNum+i, 3*sliceNum+i+1, 2*sliceNum+i+1); } //補底面圓 this.faces.push(4*sliceNum, sliceNum-1, 0); //補頂面圓 this.faces.push(4*sliceNum+1, sliceNum, 2*sliceNum-1); //補柱身 this.faces.push(3*sliceNum-1, 4*sliceNum-1, 2*sliceNum); this.faces.push(4*sliceNum-1, 3*sliceNum, 2*sliceNum); //頂點法向 this.normals = []; //計算所有頂點法向 this.computeNormal4EachVertex(); }; /** * 球體 * radius:球體半徑 * meshDensity:網格密度 * */ var sphereGeo = function(radius, widthSegments, heightSegments){ var phiStart = 0; var phiLength = Math.PI * 2; var thetaStart = 0; var thetaLength = Math.PI; var thetaEnd = thetaStart + thetaLength; var ix, iy; var index = 0; var grid = []; var vertex = {}; var normal = {}; //存儲 var indices = []; var vertices = []; var normals = []; var uvs = []; //生成頂點,法向,uv for ( iy = 0; iy <= heightSegments; iy ++ ) { var verticesRow = []; var v = iy / heightSegments; for ( ix = 0; ix <= widthSegments; ix ++ ) { var u = ix / widthSegments; //頂點 vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); vertices.push( vertex.x, vertex.y, vertex.z ); //法向 normal.x = vertex.x; normal.y = vertex.y; normal.z = vertex.z; //單位化 this.normalize([normal.x, normal.y, normal.z]); normals.push( normal.x, normal.y, normal.z ); //uv uvs.push( u, 1 - v ); verticesRow.push( index ++ ); } grid.push( verticesRow ); } //索引數組 for ( iy = 0; iy < heightSegments; iy ++ ) { for ( ix = 0; ix < widthSegments; ix ++ ) { var a = grid[ iy ][ ix + 1 ]; var b = grid[ iy ][ ix ]; var c = grid[ iy + 1 ][ ix ]; var d = grid[ iy + 1 ][ ix + 1 ]; if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); } } //構造geometry this.faces = indices; this.vertices = vertices; this.normals = normals; //this.uv = uvs; }; var cubeGeo = function(width, height, depth){ this.vertices = []; this.faces = []; var bottom = []; bottom[0] = [width/2, 0, depth/2]; bottom[1] = [-width/2, 0, depth/2]; bottom[2] = [-width/2, 0, -depth/2]; bottom[3] = [width/2, 0, -depth/2]; var top = []; top[0] = [width/2, height, depth/2]; top[1] = [-width/2, height, depth/2]; top[2] = [-width/2, height, -depth/2]; top[3] = [width/2, height, -depth/2]; for(var i=0; i<4; i++){ for(var j=0; j<3; j++){ this.vertices.push(bottom[i][j]); } } for(var i=0; i<4; i++){ for(var j=0; j<3; j++){ this.vertices.push(top[i][j]); } } for(var i=0; i<3; i++){ this.faces.push(i, i+4, i+1); this.faces.push(i+4, i+5, i+1); } //補面 this.faces.push(3, 7, 0); this.faces.push(7, 4, 0); //補底面 this.faces.push(0, 1, 2, 2, 3, 0); //補底面 this.faces.push(4, 7, 6, 6, 5, 4); }; var PolyhedronGeometry = function(){ this.type = "PolyhedronGeometry"; }; PolyhedronGeometry.prototype = { //坐標單位話 normalize : function(arr){ if(arr instanceof Array){ var sum = 0; for(var i=0; i<arr.length; i++){ sum += arr[i]*arr[i]; } for(var i=0; i<arr.length; i++){ arr[i] = arr[i]/sum; } } }, //計算頂點法向 computeVertexNormal: function(face3){ //頂點逆時針繞向 //向量v1-v0 var v1_0 = {}; v1_0.x = face3[1][0] - face3[0][0]; v1_0.y = face3[1][1] - face3[0][1]; v1_0.z = face3[1][2] - face3[0][2]; //向量v2-v1 var v2_1 = {}; v2_1.x = face3[2][0] - face3[1][0]; v2_1.y = face3[2][1] - face3[1][1]; v2_1.z = face3[2][2] - face3[1][2]; //v1_0 叉乘 v2_1 var normal = {}; normal.x = v1_0.y * v2_1.z - v2_1.y * v1_0.z; normal.y = v1_0.z * v2_1.x - v2_1.z * v1_0.x; normal.z = v1_0.x * v2_1.y - v2_1.x * v1_0.y; var normalArray = [normal.x, normal.y, normal.z]; this.normalize(normalArray); return normalArray; }, computeNormal4EachVertex:function(){ //遍歷索引,通過hash插值構造normals數組 var normalList = []; for(var i=0; i<this.faces.length; i=i+3){ //頂點索引 var vertex0 = this.faces[i]; var vertex1 = this.faces[i+1]; var vertex2 = this.faces[i+2]; //頂點 var v0 = [this.vertices[3*vertex0], this.vertices[3*vertex0+1], this.vertices[3*vertex0+2]]; var v1 = [this.vertices[3*vertex1], this.vertices[3*vertex1+1], this.vertices[3*vertex1+2]]; var v2 = [this.vertices[3*vertex2], this.vertices[3*vertex2+1], this.vertices[3*vertex2+2]]; //取出索引指向的頂點坐標 var face3 = [v0, v1, v2]; var normalArray = this.computeVertexNormal(face3); var normal0 = {index:vertex0, normal:normalArray}; var normal1 = {index:vertex1, normal:normalArray}; var normal2 = {index:vertex2, normal:normalArray}; normalList.push(normal0, normal1, normal2); } //根據index屬性排序 var sortedNormalList = []; var total = normalList.length; for(var i=0; i<total; i++){ for(var j=0; j<normalList.length; j++){ if(normalList[j].index === i){ sortedNormalList[i] = {index:normalList[j].index, normal:normalList[j].normal}; //刪掉該normal節點 normalList.splice(j, 1); break; } } } for(var i=0; i<sortedNormalList.length; i++){ var normal = sortedNormalList[i].normal; this.normals.push(normal[0], normal[1], normal[2]); } } }; coneGeo.prototype = new PolyhedronGeometry(); cylinderGeo.prototype = new PolyhedronGeometry(); sphereGeo.prototype = new PolyhedronGeometry(); cubeGeo.prototype = new PolyhedronGeometry();
以上就是幾何體的構造函數,我們的圓錐和圓柱即將由此生成。接下來進入我們今天的重點,如何將多個圓錐圓柱繪制進一個場景中去,直接上html代碼,場景代碼如下
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/JavaScript" src="minMatrix.js"></script>
<script type="text/JavaScript" src="jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="polyhedron.js"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec3 position;
attribute vec3 normal;
attribute vec4 color;
uniform mat4 mvpMatrix;
uniform mat4 invMatrix;
uniform vec3 lightDirection;
uniform vec4 ambientColor;
varying vec4 vColor;
uniform float lightS;
void main(void){
vec3 invLight = normalize(invMatrix * vec4(lightDirection, 0)).xyz;
float diffuse = clamp(dot(normal, invLight), 0.0, 1.0) * lightS;
vColor = color * vec4(vec3(diffuse), 1.0) + ambientColor;
gl_Position = mvpMatrix * vec4(position, 1.0);
}
</script>
<script id="fs" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vColor;
void main(void){
gl_FragColor = vColor;
}
</script>
<script>
onload = function(){
// canvas對象獲取
var c = document.getElementById('canvas');
c.width = 1000;
c.height = 800;
// webgl的context獲取
var gl = c.getContext('webgl') || c.getContext('experimental-webgl');
//初始化gl
initGL(gl);
// 頂點着色器和片段着色器的生成
var v_shader = create_shader('vs');
var f_shader = create_shader('fs');
// 程序對象的生成和連接
var prg = create_program(v_shader, f_shader);
// attributeLocation的獲取
var attLocation = new Array(2);
attLocation[0] = gl.getAttribLocation(prg, 'position');
attLocation[1] = gl.getAttribLocation(prg, 'normal');
attLocation[2] = gl.getAttribLocation(prg, 'color');
// 將元素數attribute保存到數組中
var attStride = new Array(2);
attStride[0] = 3;
attStride[1] = 3;
attStride[2] = 4;
/**
* 光
* */
// 環境光,漫反射光
var ambientColor = [0.2, 0.2, 0.2, 1.0];
// 光照強度
var lightS = 1.6;
// 平行光源的方向
var lightDirection = [1, 1, 1];
/**
* 視圖矩陣
*/
// matIV對象生成
var m = new matIV();
// 畫布的寬高比
var aspect = c.width / c.height;
var mMatrix = m.identity(m.create());
var invMatrix = m.identity(m.create());
var tmpMatrix = m.identity(m.create());
var mvpMatrix = m.identity(m.create());
// 將視圖矩陣根據寬高比進行反比,避免X/Y平面內出現變形
tmpMatrix[0] = 1/aspect;
// 得到mvpMatrix定位坐標矩陣
//m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
//m.inverse(mMatrix, invMatrix);
/**
* 綁定shader的uniform
* */
// 取得uniformLocation
var uniLocation = new Array();
uniLocation[0] = gl.getUniformLocation(prg, 'mvpMatrix');
uniLocation[1] = gl.getUniformLocation(prg, 'invMatrix');
uniLocation[2] = gl.getUniformLocation(prg, 'lightDirection');
uniLocation[3] = gl.getUniformLocation(prg, 'ambientColor');
uniLocation[4] = gl.getUniformLocation(prg, 'lightS');
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
gl.uniform3fv(uniLocation[2], lightDirection);
gl.uniform4fv(uniLocation[3], ambientColor);
gl.uniform1f(uniLocation[4], lightS);
//繪制坐標系
drawCoordinates(0, 1, 0, 0);
// 清理gl
gl.flush();
/**
* 交互
* */
//判斷是否鼠標左鍵按下
var mouseLeftKeyDown = false;
//判斷是否鼠標右鍵按下
var mouseRightKeyDown = false;
//標記鼠標x坐標
var mouseX;
//標記鼠標y坐標
var mouseY;
//標記鼠標z坐標
var mouseZ;
//旋轉球的半徑
var R = 250;
$('#canvas').mousedown(function(e){
var inCircle = false;
if(e.which == 1){
mouseLeftKeyDown = true;
mouseRightKeyDown = false;
mouseX = e.clientX - 0.5*c.width;
mouseY = -(e.clientY - 0.5*c.height);
if(R*R - mouseX*mouseX - mouseY*mouseY > 0){
mouseZ = Math.sqrt(R*R - mouseX*mouseX - mouseY*mouseY);
} else {
mouseLeftKeyDown = false;
}
//轉動前向量坐標
$('#cordX').val(mouseX);
$('#cordY').val(mouseY);
$('#cordZ').val(mouseZ);
} else if(e.which == 3){
mouseRightKeyDown = true;
mouseLeftKeyDown = false;
}
});
$('#canvas').mouseup(function(e){
if(e.which == 1){
mouseLeftKeyDown = false;
} else if(e.which == 3){
mouseRightKeyDown = false;
}
});
$('#canvas').mouseout(function(e){
mouseLeftKeyDown = false;
mouseRightKeyDown = false;
});
$('#canvas').mousemove(function(e){
if(mouseLeftKeyDown){//鼠標左鍵按下
var X_1 = e.clientX - 0.5*c.width;
var Y_1 = -(e.clientY - 0.5*c.height);
if(R*R - X_1*X_1 - Y_1*Y_1 > 0){
var Z_1 = Math.sqrt(R*R - X_1*X_1 - Y_1*Y_1);
} else {
mouseLeftKeyDown = false;
}
//轉動前向量坐標
$('#cordX').val(mouseX);
$('#cordY').val(mouseY);
$('#cordZ').val(mouseZ);
//轉動后向量坐標
$('#cordX1').val(X_1);
$('#cordY1').val(Y_1);
$('#cordZ1').val(Z_1);
//先算出轉動軸向量
var axisX = -(Z_1*mouseY-mouseZ*Y_1);
var axisY = -(X_1*mouseZ-mouseX*Z_1);
var axisZ = +(Y_1*mouseX-mouseY*X_1);
//軸向量單位化
var mod_axis = Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
axisX = axisX/mod_axis;
axisY = axisY/mod_axis;
axisZ = axisZ/mod_axis;
var a1 = mMatrix[0]*axisX + mMatrix[1]*axisY + mMatrix[2]*axisZ + mMatrix[3]*0;
var a2 = mMatrix[4]*axisX + mMatrix[5]*axisY + mMatrix[6]*axisZ + mMatrix[7]*0;
var a3 = mMatrix[8]*axisX + mMatrix[9]*axisY + mMatrix[10]*axisZ + mMatrix[11]*0;
var a4 = mMatrix[12]*axisX + mMatrix[13]*axisY + mMatrix[14]*axisZ + mMatrix[15]*0;
axisX = a1;
axisY = a2;
axisZ = a3;
//軸向量單位化
mod_axis = Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
axisX = axisX/mod_axis;
axisY = axisY/mod_axis;
axisZ = axisZ/mod_axis;
//法向坐標
$('#axisX').val(axisX);
$('#axisY').val(axisY);
$('#axisZ').val(axisZ);
//計算轉軸向量和轉前向量的點積
$('#00').val(axisX*mouseX + axisY*mouseY + axisZ*mouseZ);
//計算轉軸向量和轉后向量的點積
$('#01').val(axisX*X_1 + axisY*Y_1 + axisZ*Z_1);
//再計算轉動角弧度
//a=(x1,y1,z1),b=(x2,y2,z2) a*b=x1x2+y1y2+z1z2 |a|=√(x1^2+y1^2+z1^2).|b|=√(x2^2+y2^2+z2^2)
//cosθ=a*b/(|a|*|b|) 角θ=arccosθ
//Math.acos(x) 反余弦函數
var ab = X_1*mouseX + Y_1*mouseY + Z_1*mouseZ;
var mod_a = Math.sqrt(X_1*X_1 + Y_1*Y_1 + Z_1*Z_1);
var mod_b = Math.sqrt(mouseX*mouseX + mouseY*mouseY + mouseZ*mouseZ);
var cosθ = ab/(mod_a*mod_b);
var rad = Math.acos(cosθ);
//轉角弧度
$('#rad').val(rad);
rotateModel(axisX, axisY, axisZ, rad);
mouseX = X_1;
mouseY = Y_1;
mouseZ = Z_1;
}
});
$('#rotate').click(function(){
var axisX = $('.axisX').val();
var axisY = $('.axisY').val();
var axisZ = $('.axisZ').val();
var rad = $('.rad').val();
rad = rad * Math.PI / 180;
var a1 = mMatrix[0]*axisX + mMatrix[1]*axisY + mMatrix[2]*axisZ + mMatrix[3]*0;
var a2 = mMatrix[4]*axisX + mMatrix[5]*axisY + mMatrix[6]*axisZ + mMatrix[7]*0;
var a3 = mMatrix[8]*axisX + mMatrix[9]*axisY + mMatrix[10]*axisZ + mMatrix[11]*0;
var a4 = mMatrix[12]*axisX + mMatrix[13]*axisY + mMatrix[14]*axisZ + mMatrix[15]*0;
axisX = a1;
axisY = a2;
axisZ = a3;
//軸向量單位化
mod_axis = Math.sqrt(axisX*axisX + axisY*axisY + axisZ*axisZ);
axisX = axisX/mod_axis;
axisY = axisY/mod_axis;
axisZ = axisZ/mod_axis;
rotateModel(axisX, axisY, axisZ, rad);
});
/**
* 模型旋轉函數
* x:旋轉軸向量的x軸分量
* y:旋轉軸向量的y軸分量
* z:旋轉軸向量的z軸分量
* rad:繞旋轉軸旋轉的弧度
*/
function rotateModel(x, y, z, rad){
initGL(gl);
// 模型旋轉6度
//var rad = 1 * Math.PI / 180;
// 模型坐標變換矩陣的生成(沿着Y軸旋轉)
//m.rotate(mMatrix, rad, [x, y, z], mMatrix);
//m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
//m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
//gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
//gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
// 使用索引進行繪圖,畫三角面
//gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);
drawCoordinates(x, y, z, rad);
// context刷新
gl.flush();
}
/**
* 生成着色器的函數
*/
function create_shader(id){
// 用來保存着色器的變量
var shader;
// 根據id從HTML中獲取指定的script標簽
var scriptElement = document.getElementById(id);
// 如果指定的script標簽不存在,則返回
if(!scriptElement){return;}
// 判斷script標簽的type屬性
switch(scriptElement.type){
// 頂點着色器的時候
case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER);
break;
// 片段着色器的時候
case 'x-shader/x-fragment':
shader = gl.createShader(gl.FRAGMENT_SHADER);
break;
default :
return;
}
// 將標簽中的代碼分配給生成的着色器
gl.shaderSource(shader, scriptElement.text);
// 編譯着色器
gl.compileShader(shader);
// 判斷一下着色器是否編譯成功
if(gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
// 編譯成功,則返回着色器
return shader;
}else{
// 編譯失敗,彈出錯誤消息
alert(gl.getShaderInfoLog(shader));
}
}
/**
* 程序對象的生成和着色器連接的函數
*/
function create_program(vs, fs){
// 程序對象的生成
var program = gl.createProgram();
// 向程序對象里分配着色器
gl.attachShader(program, vs);
gl.attachShader(program, fs);
// 將着色器連接
gl.linkProgram(program);
// 判斷着色器的連接是否成功
if(gl.getProgramParameter(program, gl.LINK_STATUS)){
// 成功的話,將程序對象設置為有效
gl.useProgram(program);
// 返回程序對象
return program;
}else{
// 如果失敗,彈出錯誤信息
alert(gl.getProgramInfoLog(program));
}
}
/**
* 生成VBO的函數
*/
function create_vbo(data){
// 生成緩存對象
var vbo = gl.createBuffer();
// 綁定緩存
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
// 向緩存中寫入數據
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
// 將綁定的緩存設為無效
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// 返回生成的VBO
return vbo;
}
/**
* 綁定VBO相關的函數
*/
function set_attribute(vbo, attL, attS){
// 處理從參數中得到的數組
for(var i in vbo){
// 綁定緩存
gl.bindBuffer(gl.ARRAY_BUFFER, vbo[i]);
// 將attributeLocation設置為有效
gl.enableVertexAttribArray(attL[i]);
//通知並添加attributeLocation
gl.vertexAttribPointer(attL[i], attS[i], gl.FLOAT, false, 0, 0);
}
}
/**
* IBO的生成函數
*/
function create_ibo(data){
// 生成緩存對象
var ibo = gl.createBuffer();
// 綁定緩存
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
// 向緩存中寫入數據
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);
// 將緩存的綁定無效化
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
// 返回生成的IBO
return ibo;
}
/**
* 初始化gl
* */
function initGL(gl){
// 設定canvas初始化的顏色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 設定canvas初始化時候的深度
gl.clearDepth(1.0);
// canvas的初始化
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 將深度測試設置為有效
gl.enable(gl.DEPTH_TEST);
// 指定一般深度測試的評價方法
gl.depthFunc(gl.LEQUAL);
// 將遮擋剔除設置為有效
gl.enable(gl.CULL_FACE);
}
/**
* bindBuffer
* */
function bindBuffer(vertexArray, normalArray, colorArray, indices){
/**
* 三角面
*/
// 生成VBO
var position_vbo = create_vbo(vertexArray);
var normal_vbo = create_vbo(normalArray);
var color_vbo = create_vbo(colorArray);
// 將VBO進行綁定並添加
set_attribute([position_vbo, normal_vbo, color_vbo], attLocation, attStride);
// 生成IBO
var ibo = create_ibo(indices);
// IBO進行綁定並添加
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
}
/**
* 畫坐標系
**/
function drawCoordinates(x, y, z, rad){
/**
* y軸
* */
//空間變換
mMatrix = m.identity(m.create());
m.rotate(mMatrix, rad, [x, y, z], mMatrix);
// 得到mvpMatrix定位坐標矩陣
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
//圓柱模型
var cylinder = new cylinderGeo(0.03, 0.5, 15);
var cylinderPositionY = cylinder.vertices;
var cylinderNormalY = cylinder.normals;
var cylinderColor = [];
for(var i=0; i<cylinderPositionY.length/3; i++){
cylinderColor.push(0.6, 1.0, 0.0, 1.0);
}
var cylinderIndexY = cylinder.faces;
//寫緩存綁定vertex-shader
bindBuffer(cylinderPositionY, cylinderNormalY, cylinderColor, cylinderIndexY);
// 使用索引進行繪圖,畫三角面
gl.drawElements(gl.TRIANGLES, cylinderIndexY.length, gl.UNSIGNED_SHORT, 0);
//空間變換
mMatrix = m.identity(m.create());
m.translate(mMatrix, [0, 0.5, 0], mMatrix);
m.rotate(mMatrix, rad, [x, y, z], mMatrix);
// 得到mvpMatrix定位坐標矩陣
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
//圓錐模型
var cone = new coneGeo(0.05, 0.25, 15);
var conePositionY = cone.vertices;
var coneNormalY = cone.normals;
var coneColorY = [];
for(var i=0; i<conePositionY.length/3; i++){
coneColorY.push(0.6, 1.0, 0.0, 1.0);
}
var coneIndexY = cone.faces;
bindBuffer(conePositionY, coneNormalY, coneColorY, coneIndexY);
// 使用索引進行繪圖,畫三角面
gl.drawElements(gl.TRIANGLES, coneIndexY.length, gl.UNSIGNED_SHORT, 0);
/**
* x軸
* */
//空間變換
mMatrix = m.identity(m.create());
m.rotate(mMatrix, -90*Math.PI/180, [0, 0, 1], mMatrix);
m.rotate(mMatrix, rad, [x, y, z], mMatrix);
// 得到mvpMatrix定位坐標矩陣
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
//圓柱模型
var cylinderPositionX = cylinder.vertices;
var cylinderNormalX = cylinder.normals;
var cylinderColorX = [];
for(var i=0; i<cylinderPositionX.length/3; i++){
cylinderColorX.push(0.6, 0.1, 0.0, 1.0);
}
var cylinderIndexX = cylinder.faces;
//寫緩存綁定vertex-shader
bindBuffer(cylinderPositionX, cylinderNormalX, cylinderColorX, cylinderIndexX);
// 使用索引進行繪圖,畫三角面
gl.drawElements(gl.TRIANGLES, cylinderIndexX.length, gl.UNSIGNED_SHORT, 0);
//空間變換
mMatrix = m.identity(m.create());
m.translate(mMatrix, [0.5, 0, 0], mMatrix);
m.rotate(mMatrix, -90*Math.PI/180, [0, 0, 1], mMatrix);
m.rotate(mMatrix, rad, [x, y, z], mMatrix);
// 得到mvpMatrix定位坐標矩陣
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
//圓錐模型
var conePositionX = cone.vertices;
var coneNormalX = cone.normals;
var coneColorX = [];
for(var i=0; i<conePositionX.length/3; i++){
coneColorX.push(0.6, 0.1, 0.0, 1.0);
}
var coneIndexX = cone.faces;
bindBuffer(conePositionX, coneNormalX, coneColorX, coneIndexX);
// 使用索引進行繪圖,畫三角面
gl.drawElements(gl.TRIANGLES, coneIndexX.length, gl.UNSIGNED_SHORT, 0);
/**
* z軸
* */
//空間變換
mMatrix = m.identity(m.create());
m.rotate(mMatrix, 90*Math.PI/180, [1, 0, 0], mMatrix);
//圓錐姿態
m.rotate(mMatrix, rad, [x, y, z], mMatrix);
// 得到mvpMatrix定位坐標矩陣
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
//圓柱模型
var cylinderPositionZ = cylinder.vertices;
var cylinderNormalZ = cylinder.normals;
var cylinderColorZ = [];
for(var i=0; i<cylinderPositionZ.length/3; i++){
cylinderColorZ.push(0.0, 0.1, 0.6, 1.0);
}
var cylinderIndexZ = cylinder.faces;
//寫緩存綁定vertex-shader
bindBuffer(cylinderPositionZ, cylinderNormalZ, cylinderColorZ, cylinderIndexZ);
// 使用索引進行繪圖,畫三角面
gl.drawElements(gl.TRIANGLES, cylinderIndexZ.length, gl.UNSIGNED_SHORT, 0);
//空間變換
mMatrix = m.identity(m.create());
m.translate(mMatrix, [0, 0, 0.5], mMatrix);
m.rotate(mMatrix, 90*Math.PI/180, [1, 0, 0], mMatrix);
m.rotate(mMatrix, rad, [x, y, z], mMatrix);
// 得到mvpMatrix定位坐標矩陣
m.multiply(tmpMatrix, mMatrix, mvpMatrix);
// mMatrix的逆矩陣
m.inverse(mMatrix, invMatrix);
// 向uniformLocation中傳入坐標變換矩陣
gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);
gl.uniformMatrix4fv(uniLocation[1], false, invMatrix);
//圓錐模型
var conePositionZ = cone.vertices;
var coneNormalZ = cone.normals;
var coneColorZ = [];
for(var i=0; i<conePositionZ.length/3; i++){
coneColorZ.push(0.0, 0.1, 0.6, 1.0);
}
var coneIndexZ = cone.faces;
bindBuffer(conePositionZ, coneNormalZ, coneColorZ, coneIndexZ);
// 使用索引進行繪圖,畫三角面
gl.drawElements(gl.TRIANGLES, coneIndexZ.length, gl.UNSIGNED_SHORT, 0);
}
};
</script>
<script src="polyhedron.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<br/>
轉動前X坐標:<input id="cordX">轉動前Y坐標:<input id="cordY">轉動前Z坐標:<input id="cordZ">和轉軸的點積:<input id="00">
<br/>
轉動后X坐標:<input id="cordX1">轉動后Y坐標:<input id="cordY1">轉動后Z坐標:<input id="cordZ1">和轉軸的點積:<input id="01">
<br/>
旋轉軸X坐標分量:<input id="axisX">旋轉軸Y坐標分量:<input id="axisY">旋轉軸Z坐標分量:<input id="axisZ">
<br/>
旋轉角度:<input id="rad">
<br/>
===============================================旋轉操作參數===============================================
<br/>
旋轉軸X坐標分量:<input class='axisX'>旋轉軸Y坐標分量:<input class="axisY">旋轉軸Z坐標分量:<input class="axisZ">
旋轉角度:<input class="rad">
<button id="rotate">旋轉</button>
</body>
</html>
以上是渲染場景的html代碼,我們先來看一下最終結果,如下圖

如圖可以看到,我們成功將3個圓柱,3個圓錐繪制到場景中去了,那么這個實現的核心部分在哪里呢,我們來分析一下,其中繪圖是采用索引緩存的方式寫入的,而和頂點着色器attribute類型的變量進行傳值的數組每次繪制一個幾何體對象都會被覆寫,然后重新寫入索引緩存,然后重新gl.drawElements繪制。這就是向一個場景中繪入多個模型的核心思想,每次要寫入一個幾何體,就重新向頂點着色器的attribute變量傳值,重新寫入索引緩存,重新繪圖,而每次繪圖都不會將之前繪制完成的幾何體從場景中擦除(這就是增量渲染)。
通過記錄這個工程案例,對gl的緩存機制又有新的認識。引用本文請注明出處https://www.cnblogs.com/ccentry/p/9864847.html
