標籤:webgl
註:文章譯自http://wgld.org/,原作者杉本雅広(doxas),文章中如果有我的額外說明,我會加上[lufy:],另外,鄙人webgl研究還不夠深入,一些專業詞語,如果翻譯有誤,歡迎大家指正。
本次的demo的運行結果
點光源上次介紹了Gouraud Shading和補色著色。
使用補色著色的手法,可以渲染更加自然的陰影,3D效果更加真實。但是會有計算量比較大的缺點。這個只能case by case,根據不同的情況來處理了,是個挺煩人的地方。
那麼,這次,還是講光源。我貌似聽到了“不會吧......”此類的聲音了......
這次的話題是點光源的封裝。點光源就像它的名字一樣,和頂點一樣,光源就是一個點。
到目前為止,所有的光源處理都是使用了平行光源,平行光源可以看作是從無限遠的地方發出的方向固定的光的光源,三維空間中的所有的模型都受到同樣方向的光的照射。而點光源的處理,光源的位置在三維空間中是固定的,三維空間中的模型根據它所在的位置,受到不同方向的光照。
現實世界中類似電光源的有燈泡等。但是,實際上燈泡的光是會變弱的,距離越遠光的強度就越弱。而這次封裝的電光源的處理並沒有考慮光的減弱,無論對象距離光源有多元,都受到同樣的強度的光的影響,所以,並不是完全類比現實世界中的電光源。
電光源的考慮方法電光源的封裝其實也並不難。
平行光源的時候是光向量,就是說光的方向是固定的。點光源的時候,光源的位置是確定的,需要算出從光源到頂點的向量作為光向量,用這個光向量來計算陰影。
因為必須計算出光源到頂點的向量,所以比平行光源的計算量要大,但是解決了光向量的計算之後,就可以沿用之前平行光源的計算,所以也不會太難。
頂點著色器的修改這次和上次一樣,使用補色著色來進行著色,雖然大部分修改都是在片段著色器中進行的,但是頂點著色器中也有少許修改。
點光源的處理,就想剛才說的那樣,需要計算光源到頂點的向量,而要計算光向量的話,那就必然需要頂點的位置情報了。
要將頂點著色器中的位置情報傳給片段著色器,那肯定需要新的varying變數了,但是只傳頂點的位置情報的話,還有一些問題。
頂點的位置情報通常以局部座標的形式傳給頂點著色器,所以如果用模型座標變換對模型進行了移動或者旋轉,頂點的位置就會發生變化了,就是說,即使局域座標是(1.0, 1.0, 1.0)的頂點經過移動,旋轉等變換,座標可能會變成(例 0.5, 2.0, 5.5 )等等。
從點光源發出的光的光向量,必須要考慮模型座標變換後的頂點的位置。所以,必須向頂點著色器中傳入新的模型座標變換矩陣,那麼來修改一下頂點著色器的代碼吧。
>頂點著色器代碼
attribute vec3 position;attribute vec3 normal;attribute vec4 color;uniform mat4 mvpMatrix;uniform mat4 mMatrix;varying vec3 vPosition;varying vec3 vNormal;varying vec4 vColor;void main(void){ vPosition = (mMatrix * vec4(position, 1.0)).xyz; vNormal = normal; vColor = color; gl_Position = mvpMatrix * vec4(position, 1.0);}
跟上次比較,變更點有兩個。
第一個變更點是為了向片段著色器傳入頂點的位置情報,追加了varying變數vPosition。因為表示的頂點的位置情報,所以定義了vec3類型。
第二個變更點是追加了新的uniform變數mMatrix。像剛才寫的那樣,因為頂點著色器中的頂點的位置座標是局域座標系,所以為了將模型座標變換矩陣變換成適當的形式(就是全局座標系),使用uniform修飾符定義的變數,以便在著色器一側接受到模型座標變換矩陣。
向片段著色器中傳遞頂點的位置情報的時候,表示模型座標變換矩陣的mMatrix和表示頂點的局部座標的position相乘,結果帶入到vPosition中。這樣的話,片段著色器就能使用模型座標變換後的頂點位置了。
片段著色器的修改接著是片段著色器一側的修改,片段著色器中需要使用頂點的位置和點光源的位置算出光向量。
這時的光向量的計算方法是非常簡單的,只需要單純的減法就可以了。
另外,這次是根據電光源做處理的,所以用表示電光源的位置的uniform變數lightPosition來代替表示光向量的uniform變數lightDirection。
>片段著色器代碼
precision mediump float;uniform mat4 invMatrix;uniform vec3 lightPosition;uniform vec3 eyeDirection;uniform vec4 ambientColor;varying vec3 vPosition;varying vec3 vNormal;varying vec4 vColor;void main(void){ vec3 lightVec = lightPosition - vPosition; vec3 invLight = normalize(invMatrix * vec4(lightVec, 0.0)).xyz; vec3 invEye = normalize(invMatrix * vec4(eyeDirection, 0.0)).xyz; vec3 halfLE = normalize(invLight + invEye); float diffuse = clamp(dot(vNormal, invLight), 0.0, 1.0) + 0.2; float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0); vec4 destColor = vColor * vec4(vec3(diffuse), 1.0) + vec4(vec3(specular), 1.0) + ambientColor; gl_FragColor = destColor;}
著色器中的main函數的第一行,將從點光源到頂點的光向量代入到變數lightVec中。像上面說的一樣,使用了單純的減法,很簡單吧。並且,使用這裡得到的光向量,和之前平行光源一樣求逆矩陣以及半向量,計算擴散光和反射光。
理解了結構,也就應該明白,其實和之前的demo也沒有多大的變化。主要是光向量的處理不同,光照的方法基本上大致相同。
javascript 的修正著色器修改完之後,下面就是主程式的javascript的修改了。
這次細節部分修改的比較多,一點點的開始解說。目前為止的demo都只渲染了一個圓環體,這次在圓環體之外加一個球體,看文章一開始的圖片就知道了。圓環體的頂點資料以及球體的頂點資料要另外準備。
球體模型的頂點資料的產生,使用以下的函數。和產生圓環體的頂點資料的函數是比較類似的。
>產生球體的頂點資料的函數
// 球體を產生する関數function sphere(row, column, rad, color){ var pos = new Array(), nor = new Array(), col = new Array(), idx = new Array(); for(var i = 0; i <= row; i++){ var r = Math.PI / row * i; var ry = Math.cos(r); var rr = Math.sin(r); for(var ii = 0; ii <= column; ii++){ var tr = Math.PI * 2 / column * ii; var tx = rr * rad * Math.cos(tr); var ty = ry * rad; var tz = rr * rad * Math.sin(tr); var rx = rr * Math.cos(tr); var rz = rr * Math.sin(tr); if(color){ var tc = color; }else{ tc = hsva(360 / row * i, 1, 1, 1); } pos.push(tx, ty, tz); nor.push(rx, ry, rz); col.push(tc[0], tc[1], tc[2], tc[3]); } } r = 0; for(i = 0; i < row; i++){ for(ii = 0; ii < column; ii++){ r = (column + 1) * i + ii; idx.push(r, r + 1, r + column + 2); idx.push(r, r + column + 2, r + column + 1); } } return {p : pos, n : nor, c : col, i : idx};}
形成球體的頂點,定義了一個用一個大的多邊形群組成的膜裹成球的形狀方法。這個sphere函數接受四個參數,第一個參數是形成球體的膜狀的多邊形板的縱向分割數(頂點數),用地球比喻的話就是緯度的方向。第二個參數則是橫向分割數,這裡用地球來說的話,就是經度的方向。第三個參數是球體的半徑。第四個參數是球體的顏色,這個顏色是包含四個元素的數組,如果沒有指定顏色,則會自動分配HSV顏色。
這個函數的使用方法,傳入適當的參數,然後接收傳回值。傳回值是一個對象,使用的時候就參照這個對象的適當的屬性。實際的代碼如下。
>函數sphere的使用部分
// 用球體的頂點資料產生VBO並儲存var sphereData = sphere(64, 64, 2.0, [0.25, 0.25, 0.75, 1.0]);var sPosition = create_vbo(sphereData.p);var sNormal = create_vbo(sphereData.n);var sColor = create_vbo(sphereData.c);var sVBOList = [sPosition, sNormal, sColor];// 球體用IBO的產生var sIndex = create_ibo(sphereData.i);
上面的代碼,產生一個縱向和橫向都是64個頂點的球體,半徑是2.0,這次指定的顏色是藍色。需要注意的是,為了後面的處理,將VBO儲存到了數組中,這樣做了之後,attributeLocation和VBO聯絡的工作就變的非常的方便了,這個後面會敘述。
接著是uniformLocation的擷取部分,這次是從平行光源變到電光源,指定光的方向的部分要換成指定光的位置。
>uniform的相關處理
// uniformLocationを配列に取得var uniLocation = new Array();uniLocation[0] = gl.getUniformLocation(prg, ‘mvpMatrix‘);uniLocation[1] = gl.getUniformLocation(prg, ‘mMatrix‘);uniLocation[2] = gl.getUniformLocation(prg, ‘invMatrix‘);uniLocation[3] = gl.getUniformLocation(prg, ‘lightPosition‘);uniLocation[4] = gl.getUniformLocation(prg, ‘eyeDirection‘);uniLocation[5] = gl.getUniformLocation(prg, ‘ambientColor‘);// 中略// 點光源的位置var lightPosition = [0.0, 0.0, 0.0];
著色器中進行的uniform修飾符變數的變更在這裡都詳細的反映出了,而且,demo的電光源的位置設定成了原點。
為了更容易明白點光源的效果,demo中的以點光源的位置為中心,圓環體和球體不斷的進行旋轉,裡麵包含了模型座標變換矩陣的產生。因為同時繪製兩個模型,在持續迴圈的過程中,使用適當的VBO和IBO進行模型的渲染。
代碼稍微長了點,仔細看的話就明白了。主要是剛才寫的那樣,使用儲存有VBO的數組,在自製的函數中對VBO進行綁定處理。
>持續迴圈的繪製處理
// カウンタをインクリメントするcount++;// カウンタを元にラジアンと各種座標を算出var rad = (count % 360) * Math.PI / 180;var tx = Math.cos(rad) * 3.5;var ty = Math.sin(rad) * 3.5;var tz = Math.sin(rad) * 3.5;// トーラスのVBOとIBOをセットset_attribute(tVBOList, attLocation, attStride);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, tIndex);// モデル座標変換行列の產生m.identity(mMatrix);m.translate(mMatrix, [tx, -ty, -tz], mMatrix);m.rotate(mMatrix, -rad, [0, 1, 1], mMatrix);m.multiply(tmpMatrix, mMatrix, mvpMatrix);m.inverse(mMatrix, invMatrix);// uniform変數の登録と描畫gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);gl.uniformMatrix4fv(uniLocation[1], false, mMatrix);gl.uniformMatrix4fv(uniLocation[2], false, invMatrix);gl.uniform3fv(uniLocation[3], lightPosition);gl.uniform3fv(uniLocation[4], eyeDirection);gl.uniform4fv(uniLocation[5], ambientColor);gl.drawElements(gl.TRIANGLES, torusData.i.length, gl.UNSIGNED_SHORT, 0);// 球體のVBOとIBOをセットset_attribute(sVBOList, attLocation, attStride);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sIndex);// モデル座標変換行列の產生m.identity(mMatrix);m.translate(mMatrix, [-tx, ty, tz], mMatrix);m.multiply(tmpMatrix, mMatrix, mvpMatrix);m.inverse(mMatrix, invMatrix);// uniform変數の登録と描畫gl.uniformMatrix4fv(uniLocation[0], false, mvpMatrix);gl.uniformMatrix4fv(uniLocation[1], false, mMatrix);gl.uniformMatrix4fv(uniLocation[2], false, invMatrix);gl.drawElements(gl.TRIANGLES, sphereData.i.length, gl.UNSIGNED_SHORT, 0);// コンテキストの再描畫gl.flush();
各種座標變換矩陣的產生,以及逆矩陣的產生結束後,將點光源的位置和視點向量等一起傳入著色器,以及VBO和IBO的綁定處理之後,發出繪圖命令。
為了繪製兩個模型而進行了一連串的處理,但是要注意不要重複,也沒進行特別難的處理。
總結用點光源的光照,概念基本上和平行光源一樣。根據擷取光向量和頂點的法線及視點向量的內積來添加陰影。和平行光源的不同之處,簡單的說就是光向量是否是一個固定值。點光源使用的是模型座標變換後的頂點的位置和光源的位置,這時再計算光向量,所以增加了若干的計算量。
平行光源的光的方向是一定的,整體都受到均等的光照。但是點光源根據實際頂點的座標要進行具體的光的碰撞。這次的demo和上次一樣在片段著色器中進行光的計算和補色著色,所以可以進行很漂亮的渲染。
這次的文章中只需要明白是進行了光照相關的基礎部分的封裝,WebGL中的著色根據功夫是否到家,可以渲染出各種效果,從現在開始要有應用的能力了,以後會進行一些特殊的技術的詳細的介紹。
那麼,這次也提供了實際的demo,請點擊串連進行測試。
下次,要開始圖片的渲染了,期待吧。
用點光源來渲染圓環體和球體
http://wgld.org/s/sample_013/
轉載請註明:轉自lufy_legend的部落格http://blog.csdn.net/lufy_legend
[WebGL入門]二十五,點光源的光照