- WebGL 3D開發實戰詳解(第2版)
- 吳亞峰 于復興 索依娜
- 5275字
- 2020-07-07 17:17:04
2.2 初識WebGL 2.0應用
2.1節簡單介紹了3D繪圖標準WebGL 2.0,相信讀者已經對WebGL 2.0的發展歷程和作用有了一定的了解。上面的介紹偏重基礎,讀者無法深入學習具體編程技巧。為了使讀者能夠保持學習熱情,在實踐中提升編程能力,本章將給出一個WebGL 2.0的基礎案例并對相關的運行步驟進行詳細介紹。
2.2.1 WebGL 2.0應用案例部署步驟
由于WebGL 2.0應用程序是以瀏覽器為平臺進行工作的,因此一般情況下開發完畢后都需要將應用部署到Web服務器上以供瀏覽器訪問。在具體開發各個案例之前,本節先簡要介紹一下如何在Tomcat上部署WebGL 2.0應用,具體步驟如下。
(1)登錄Tomcat的官方網站下載Tomcat的壓縮包(如“apache-tomcat-6.0.14.zip”),然后解壓縮到本地磁盤。解壓完成后對環境變量中的JAVA_HOME進行配置。
(2)找到解壓后apache-tomcat-6.0.14目錄下bin子目錄中的startup.bat文件,雙擊此文件啟動Tomcat服務器,如圖2-3所示。

圖2-3 打開Tomcat服務器
提示
Tomcat的配置過程難度較小。本書篇幅有限,對于Tomcat的配置過程不再贅述。不熟悉的讀者可以參考其他的書籍或資料。
(3)將開發完畢的WebGL 2.0案例(如Sample2_1)復制到解壓后的apache-tomcat-6.0.14目錄下的webapps子目錄中,如圖2-4所示。

圖2-4 WebGL 2.0案例位置
(4)查找并記錄本機的IP地址,然后打開Firefox等可以支持WebGL 2.0的瀏覽器,在地址欄中輸入指定網址,按下回車鍵即可。例如運行案例Sample2_1時輸入的網址為:
http://10.16.189.15:8080/Sample2_1/Sample2_1.html
說明
讀者只需找到“:8080”前的IP地址并替換為自己機器的IP地址即可在自己的計算機上成功運行本案例。
提示
作者運行本章案例時使用的都是Tomcat服務器,讀者也可以選擇其他的Web服務器。作者運行本章案例時使用的是Firefox瀏覽器,讀者也可以使用其他支持WebGL 2.0的瀏覽器(如UC瀏覽器、淘寶瀏覽器等)。
2.2.2 初識WebGL 2.0應用程序
2.2.1節中簡單地介紹了WebGL 2.0應用案例的運行步驟,讀者可以參考給出的案例進行高效學習。接下來將通過一個旋轉正方體的案例向讀者介紹如何開發3D場景。具體運行效果如圖2-5、圖2-6所示。

圖2-5 初始狀態效果圖

圖2-6 繞y軸旋轉大約120°的效果圖
前面給出了本案例的運行效果圖,有興趣的讀者可在自己的設備上運行本案例。下面將通過本案例的詳細代碼講解具體開發步驟。
在開發與本案例直接相關的類之前,首先需要介紹在網絡端讀取著色器(shader)腳本的工具類。此類讀取著色器的方法主要分為幾步:發送一個打開指定URL的請求,接收文本并進行切分,新建著色器對象并加載。具體代碼如下。
代碼位置:隨書源代碼/第2章/Sample2_1/js目錄下的LoadShaderUtil.js文件。
1 function shaderObject(typeIn, textIn){ //聲明shaderObject類 2 this.type=typeIn; //初始化type成員變量 3 this.text=textIn; //初始化text成員變量 4 } 5 var shaderStrArray=["a", "a"]; //存儲著色器數組 6 var shaderNumberCount=0; //數組索引值 7 var shaderTypeName=["vertex", "fragment"]; //著色器名稱數組 8 function processLoadShader(req, index){ //處理著色器腳本內容的回調函數 9 if (req.readyState == 4){ //數據接收 10 var shaderStr = req.responseText; //獲取響應文本 11 //根據不同的數組索引值創建不同的著色器,并存入著色器數組 12 shaderStrArray[shaderNumberCount]=new 13 shaderObject(shaderTypeName[shaderNumberCount], shaderStr); 14 shaderNumberCount++; //數組索引值加1 15 if(shaderNumberCount>1){ //如果兩個著色器內容均不為空,則 16 //加載著色器 17 shaderProgArray[index]=loadShaderSerial(gl, shaderStrArray[0], shaderStrArray[1]); 18 } 19 }} 20 function loadShaderFile(url, index){ //從服務器加載著色器腳本的函數 21 var req = new XMLHttpRequest(); //創建XMLHttpRequest對象 22 req.onreadystatechange = function () //設置響應回調函數 23 { processLoadShader(req, index) }; //調用processLoadShader處理響應 24 req.open("GET", url, true); //用GET方式打開指定URL 25 req.responseType = "text"; //設置響應類型 26 req.send(null); //發送HTTP請求 27 }
? 第1~4行聲明了著色器類shaderObject,其中有type和text兩個成員變量,分別為著色器的類型和其中的內容文本。著色器類型又分為兩種,“vertex”表示頂點著色器,“fragment”表示片元著色器。
? 第5~19行為處理著色器腳本內容的回調函數。程序接收到文本和數值后,根據數組索引值新建著色器對象并創建著色器程序,同時存入單獨的數組中以供使用。
? 第20~27行為從指定地址讀取著色器腳本并加載函數。
上文已經將網絡端讀取著色器的工具類介紹完畢,接下來介紹的是用于初始化WebGL 2.0 Canvas的JavaScript腳本文件——GLUtil.js,首先給出的是其中的initWebGLCanvas方法,具體代碼如下。
代碼位置:隨書源代碼/第2章/Sample2_1/js目錄下的GLUtil.js文件。
1 function initWebGLCanvas(canvasName) { //初始化WebGL Canvas的方法 2 canvas = document.getElementById(canvasName); //獲取Canvas對象 3 var context = canvas.getContext('webgl2', { antialias: true }); //獲取GL上下文 4 return context; //返回GL上下文對象 5 }
說明
在渲染WebGL 2.0之前,必須進行Canvas的初始化工作。在本方法中首先通過Canvas的名字找到對應的Canvas。然后獲取GL上下文的操作,找到后返回上下文對象。
初始化WebGL 2.0 Canvas的方法已經介紹完畢。一般在較復雜的項目中為了實現更加酷炫的渲染效果需要多套著色器。為了使管理更加便捷,GLUtil.js中還開發了對著色器進行管理的方法,具體代碼如下。
代碼位置:隨書源代碼/第2章/Sample2_1/js目錄下的GLUtil.js文件。
1 function loadSingleShader(ctx, shaderScript){ //加載單個著色器的方法 2 if (shaderScript.type == "vertex") //若為頂點著色器 3 var shaderType = ctx.VERTEX_SHADER; //頂點著色器類型 4 else if (shaderScript.type == "fragment") //若為片元著色器 5 var shaderType = ctx.FRAGMENT_SHADER; //片元著色器類型 6 else { //否則打印錯誤信息 7 console.log("*** Error: shader script of undefined type '"+shaderScript.type+"'"); 8 return null; 9 } 10 var shader = ctx.createShader(shaderType); //根據類型創建著色器程序 11 ctx.shaderSource(shader, shaderScript.text); //加載著色器腳本 12 ctx.compileShader(shader); //編譯著色器 13 var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS); //檢查編譯狀態 14 if (! compiled && ! ctx.isContextLost()){ //若編譯出錯 15 var error = ctx.getShaderInfoLog(shader); //獲取錯誤信息 16 console.log("*** Error compiling shader '"+shaderId+"':"+error); //打印錯誤信息 17 ctx.deleteShader(shader); //刪除著色器程序 18 return null; //返回空 19 } 20 return shader; //返回著色器程序 21 } 22 function loadShaderSerial(gl, vshader, fshader){ //加載鏈接頂點、片元著色器的方法 23 var vertexShader = loadSingleShader(gl, vshader); //加載頂點著色器 24 var fragmentShader = loadSingleShader(gl, fshader); //加載片元著色器 25 var program = gl.createProgram(); //創建著色器程序 26 gl.attachShader (program, vertexShader); //將頂點著色器添加到著色器程序中 27 gl.attachShader (program, fragmentShader); //將片元著色器添加到著色器程序中 28 gl.linkProgram(program); //鏈接著色器程序 29 var linked = gl.getProgramParameter(program, gl.LINK_STATUS); //檢查鏈接是否成功 30 if (! linked && ! gl.isContextLost()){ //若鏈接不成功 31 var error = gl.getProgramInfoLog (program); //獲取錯誤信息 32 console.log("Error in program linking:"+error); //打印錯誤信息 33 gl.deleteProgram(program); //刪除著色器程序 34 gl.deleteProgram(fragmentShader); //刪除片元著色器 35 gl.deleteProgram(vertexShader); //刪除頂點著色器 36 return null; //返回空 37 } //返回著色器程序 38 gl.useProgram(program); //指明使用的著色器編號 39 gl.enable(gl.DEPTH_TEST); //打開深度檢測 40 return program; //返回著色器程序 41 }
? 第1~21行為加載單個著色器的loadSingleShader方法。向此方法傳入著色器類型,程序會新建一個同類型的著色器并加載著色器腳本。加載完成后,程序會對著色器進行編譯。如果編譯出錯則打印錯誤信息。
? 第22~41行為對一套著色器進行加載和鏈接操作的loadShaderSerial方法。向此方法傳入頂點著色器和片元著色器后,程序會分別加載頂點著色器和片元著色器并鏈接著色器程序。因為鏈接操作過程中可能會出現錯誤,所以此方法還進行鏈接檢查。如果鏈接不成功則刪除著色器及程序。如果鏈接成功則返回著色器程序。
上文已經詳細介紹了著色器的操作、管理工具類和初始化上下文的相關方法,接下來將介紹經常使用的MatrixState工具類。此類的作用是對各種變換矩陣進行管理。在執行各種變換時,本類對矩陣進行計算,其具體代碼如下。
代碼位置:隨書源代碼/第2章/Sample2_1/js目錄下的MatrixState.js文件。
1 function MatrixState(){ 2 this.mProjMatrix = new Array(16); //投影矩陣 3 this.mVMatrix = new Array(16); //攝像機矩陣 4 this.currMatrix=new Array(16); //基本變換矩陣 5 this.mStack=new Array(100); //矩陣棧 6 this.setInitStack=function(){ //初始化矩陣的方法 7 this.currMatrix=new Array(16); //創建存儲矩陣元素的數組 8 setIdentityM(this.currMatrix,0); //將元素填充為單位陣的元素值 9 } 10 this.pushMatrix=function(){ //保護變換矩陣,當前矩陣入棧 11 this.mStack.push(this.currMatrix.slice(0)); 12 } 13 this.popMatrix=function(){ //恢復變換矩陣,當前矩陣出棧 14 this.currMatrix=this.mStack.pop(); 15 } 16 this.translate=function(x, y, z){ //平移變換 17 translateM(this.currMatrix, 0, x, y, z); //將平移變換記錄進矩陣 18 } 19 this.rotate=function(angle, x, y, z) { //旋轉變換 20 rotateM(this.currMatrix,0, angle, x, y, z); //將旋轉變換記錄進矩陣 21 } 22 ……//此處省略了部分方法,讀者可參見隨書源代碼 23 }
? 第2~5行為對投影矩陣、攝像機矩陣、基本變換矩陣進行聲明的相關代碼。另外新建一個棧用來存儲基本變換矩陣,在繪制時進行進棧和出棧的操作。
? 第6~21行為初始化矩陣、保護變換矩陣、恢復變換矩陣,以及進行平移旋轉變換的相關代碼。保護變換矩陣方法的實質是對當前變換矩陣執行入棧操作。恢復變換矩陣的方法的實質是對矩陣棧執行出棧操作,刪除棧頂的矩陣。
提示
此類中還有對投影矩陣、攝像機矩陣及變換矩陣操作的相關方法。本書篇幅有限,不再贅述,有興趣的讀者可自行查閱隨書源代碼。
上文已經將項目開發中常用的幾個工具類全部介紹完畢。下面介紹與本案例直接相關的三角形繪制腳本Triangle.js。此腳本中包含三角形頂點坐標信息和頂點顏色信息,在繪制時將這些信息傳入渲染管線中。具體代碼如下。
代碼位置:隨書源代碼/第2章/Sample2_1/js目錄下的Triangle.js文件。
1 function Triangle( //聲明繪制物體對象所屬類 2 gl, //GL上下文 3 programIn //著色器程序id 4 ){ 5 this.vertexData= [3.0,0.0,0.0, 0.0,0.0,0.0, 0.0,3.0,0.0]; //三角形頂點的x、y、z坐標 6 this.vcount=this.vertexData.length/3; //得到頂點數量 7 this.vertexBuffer=gl.createBuffer(); //創建頂點坐標數據緩沖 8 gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); //綁定頂點坐標數據緩沖 9 //將頂點坐標數據送入緩沖 10 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.vertexData), gl.STATIC_DRAW); 11 this.colorsData=[1.0,1.0,1.0,1.0, 0.0,0.0,1.0,1.0, 0.0,1.0,0.0,1.0]; //初始化頂點顏色數據 12 this.colorBuffer=gl.createBuffer(); //創建顏色數據緩沖 13 gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); //綁定顏色數據緩沖 14 //將顏色數據送入緩沖 15 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.colorsData), gl.STATIC_DRAW); 16 this.program=programIn; //初始化著色器程序id 17 this.drawSelf=function(ms) { //繪制三角形的方法 18 gl.useProgram(this.program); //指定使用某套著色器程序 19 //獲取總變換矩陣引用id 20 var uMVPMatrixHandle=gl.getUniformLocation(this.program, "uMVPMatrix"); 21 //將總變換矩陣送入渲染管線 22 gl.uniformMatrix4fv(uMVPMatrixHandle, false, new Float32Array(ms.getFinalMatrix())); 23 //啟用頂點坐標數據 24 gl.enableVertexAttribArray(gl.getAttribLocation(this.program, "aPosition")); 25 gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); //綁定頂點坐標數據緩沖 26 //給管線指定頂點坐標數據 27 gl.vertexAttribPointer(gl.getAttribLocation(this.program, "aPosition"), 3, gl.FLOAT, false,0, 0); 28 //啟用顏色坐標數據 29 gl.enableVertexAttribArray(gl.getAttribLocation(this.program, "aColor")); 30 gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); //綁定顏色數據緩沖 31 //給管線指定顏色數據 32 gl.vertexAttribPointer(gl.getAttribLocation(this.program, "aColor"), 4, gl.FLOAT, false,0, 0); 33 gl.drawArrays(gl.TRIANGLES, 0, this.vcount); //用頂點法繪制物體 34 }}
? 第5行為初始化三角形坐標。數組中每3個數為一組,分別代表頂點的x、y、z坐標。
? 第6~10行為創建并綁定頂點坐標數據緩沖。在綁定完成之后,將頂點坐標數據送入緩沖中,為之后的繪制做好準備。
? 第11行為初始化頂點顏色數據。數組與頂點坐標數組不同,每4個數為一組,分別代表每個頂點的R、G、B、A數據。
? 第17~34行為繪制三角形。主要將總變換矩陣、頂點和顏色數據傳給渲染管線。
上文已經詳細介紹了三角形類的相關代碼。可能讀者看到繪制三角形方法中的uMVPMatrix、aPosition等變量會感到疑惑,實際上這些變量是著色器中定義的相關變量。接下來將會對本案例中與著色器相關的代碼進行簡要介紹,首先來看頂點著色器的相關代碼。
代碼位置:隨書源代碼/第2章/Sample2_1/shader目錄下的vtrtex.bns文件。
1 #version 300 es //使用WebGL2.0著色器 2 uniform mat4 uMVPMatrix; //總變換矩陣 3 layout (location = 0) in vec3 aPosition; //頂點位置 4 layout (location = 1) in vec4 aColor; //頂點顏色 5 out vec4 vColor; //傳遞給片元著色器的變量 6 void main(){ 7 gl_Position = uMVPMatrix * vec4(aPosition,1); //根據總變換矩陣計算此次繪制的頂點位置 8 vColor = aColor; //將接收的顏色傳遞給片元著色器 9 }
說明
第1~9行為頂點著色器的相關代碼。頂點著色器的主要作用是執行頂點變換、紋理坐標變換等與頂點相關的操作。此頂點著色器只計算了繪制點的位置并將接收到的顏色數據傳入片元著色器。
前面已經詳細介紹了頂點著色器的相關代碼。可以看到頂點著色器需要傳值給片元著色器,下面來對片元著色器的相關代碼進行簡要介紹。
代碼位置:隨書源代碼/第2章/Sample2_1/shader目錄下的fragment.bns文件。
1 #version 300 es //使用WebGL2.0著色器 2 precision mediump float; //使用默認精度 3 in vec4 vColor; //接收從頂點著色器傳過來的參數 4 out vec4 fragColor; //輸出到片元顏色 5 void main(){ 6 fragColor = vColor; //給此片元顏色賦值 7 }
第1~7行為片元著色器的相關代碼。片元著色器的作用為執行紋理訪問、顏色匯總和霧效等操作。此片元著色器的作用是將接收到的顏色數據作為此頂點的顏色。
說明
可能有些讀者這時對著色器的認識還是一頭霧水。其實不必擔心,后面的章節中會對著色語言以及著色器的邏輯和作用進行詳細講解。讀者只需要對以上代碼有基本的認識即可。
所有基礎開發工作都已經結束,接下來將詳細介紹呈現3D場景的網頁的相關代碼。此網頁中的代碼主要作用為初始化上下文及相關參數,為繪制工作做好準備,并且使用setInterval()函數對繪制方法進行定時調用。具體代碼如下。
代碼位置:隨書源代碼/第2章/Sample2_1目錄下的Sample2_1.html文件。
1 <html> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 4 <title>Triangle</title> <! --標題--> 5 <script type="text/javascript" src="js/Matrix.js"></script> 6 ……//此處省略了導入其他JavaScript腳本文件的代碼,讀者可自行查閱隨書源代碼 7 <script> 8 var gl; //GL上下文 9 var ms=new MatrixState(); //變換矩陣管理類對象 10 var ooTri; //要繪制的三角形 11 var shaderProgArray=new Array(); //著色器程序列表,集中管理 12 var currentAngle; //旋轉角度 13 var incAngle; //旋轉角度增量 14 var canvas; //圖形容器 15 function start(){ //初始化的方法 16 gl = initWebGLCanvas("bncanvas"); //獲取GL上下文 17 if (! gl){ //若獲取GL上下文失敗 18 alert("創建GLES上下文失敗,不支持WebGL2.0! "); //顯示錯誤提示信息 19 return; 20 } 21 gl.viewport(0, 0, canvas.width, canvas.height); //設置視口大小 22 gl.clearColor(0.0,0.0,0.0,1.0); //設置屏幕背景色 R、G、B、A 23 ms.setInitStack(); //初始化變換矩陣 24 ms.setCamera(0,0, -5,0,0,0,0,1,0); //設置攝像機 25 ms.setProjectFrustum(-1.5,1.5, -1,1,1,100); //設置投影參數 26 gl.enable(gl.DEPTH_TEST); //開啟深度檢測 27 loadShaderFile("shader/vtrtex.bns",0); //加載頂點著色器程序 28 loadShaderFile("shader/fragment.bns",0); //加載片元著色器程序 29 if(shaderProgArray[0]){ //如果著色器已加載完畢 30 ooTri=new Triangle(gl, shaderProgArray[0]); //則創建三角形繪制對象 31 }else{ 32 //休息10ms后再創建三角形繪制對象 33 setTimeout(function(){ooTri=new Triangle(gl, shaderProgArray[0]); },10); 34 } 35 currentAngle = 0; //初始化旋轉角度 36 incAngle = 0.4; //初始化角度步進值 37 setInterval("drawFrame(); ",16.6); //定時繪制畫面 38 } 39 function drawFrame(){ //繪制一幀畫面的方法 40 if(! ooTri){ //如果三角形沒有加載成功 41 console.log("加載未完成!"); //則提示信息 42 return; 43 } 44 //清除著色緩沖與深度緩沖 45 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 46 ms.pushMatrix(); //保護現場 47 ms.rotate(currentAngle,0,1,0); //執行旋轉 48 ooTri.drawSelf(ms); //繪制物體 49 ms.popMatrix(); //恢復現場 50 currentAngle += incAngle; //修改旋轉角度 51 if (currentAngle > 360){currentAngle -= 360; } //保證角度范圍不超過360° 52 } 53 </script> 54 </head> 55 <body onload="start(); "> 56 <canvas height="800" width="1200" id="bncanvas"> 57 若看到這個文字,則說明瀏覽器不支持WebGL! 58 </canvas> 59 </body> 60 </html>
? 第1~7行為常用的一些標簽。它設置了本頁面的名稱、內容類型以及編碼格式等。另外,第5行為引入外部腳本文件的代碼,在此處將本案例需要的腳本文件都引入了操作。
? 第8~14行為聲明多個全局變量的相關代碼。其中包括GLES上下文、變換矩陣管理、三角形的繪制、著色器程序列表、旋轉角度、旋轉角度增量、圖形容器。這些變量在后面的程序中會多次使用。
? 第15~38行為對繪制工作進行初始化的相關代碼。首先獲取上下文。如果獲取上下文失敗則顯示錯誤信息。接下來設置視口大小、屏幕背景色、攝像機、投影參數等基本信息。最后進行著色器的加載并以定時回調的方式進行繪制。
? 第39~52行為繪制每一幀的方法。在進行繪制之前要先檢查三角形的繪制是否完成。若沒有加載完成,則彈出錯誤信息。接下來清除著色緩沖和深度緩沖。前面的工作完成后進行三角形的繪制。
項目中的Matrix.js文件是作者針對項目而開發的一個工具文件。此文件已經進行了加密操作,所以此處不進行深入介紹。MatrixState類中對矩陣執行操作的各種方法就是基于此文件進行開發的。