使用WebGL 繪圖的基本步驟 - 3D 網站開發入門筆記

文章推薦指數: 80 %
投票人數:10人

目前有兩個主要的GPU 繪圖介面:OpenGL 和微軟的DirectX。

WebGL 可以把JavaScript 和OpenGL ES 2.0 結合在一起,為HTML5 Canvas 提供硬體3D 加速繪製,就可借助系統 ... 介紹 WebGL(WebGraphicsLibrary的縮寫)用於瀏覽器中呈現3D影像的技術標準,透過WebGL技術,只需撰寫JavaScript程式(加少許的著色器程式)即可實現3D影像展示,WebGL使用HTML5Canvas來呈現影像。

WebGL標準由非營利的KhronosGroup管理,主要的瀏覽器廠商都是WebGL標準的參與者。

WebGL基於OpenGLES2.0,OpenGLES(OpenGLforEmbeddedSystems的縮寫)是OpenGL3D圖形API的子集,針對手機、PDA和遊戲主機等嵌入式裝置而設計,去除了OpenGL中許多不是必須存在的特性,例如四邊形、多邊形繪製模式等操作。

Google應用WebGL範例: https://www.zygotebody.com/ https://web.chemdoodle.com/ WebGL應用不能在模擬器或一些虛擬機器上執行,必須使用設定了GPU的系統才可以,如果不確定目前的瀏覽器是否支援WebGL,可造訪下面網址: http://webglreport.com/ 請參閱以下兩個範例,感受一下原生寫法~本章不多贅述WebGL原生寫法,以概念為主,有興趣請參考原書籍! 範例:1_WebGL/01_畫一個三角形並複製一個 範例:1_WebGL/02_畫一個四邊形並複製一個  1OpenGL和GPU OpenGL(OpenGraphicsLibrary的縮寫,即開放式圖形函數庫)定義一個跨程式語言、跨平台的應用程式介面標準,用於產生2D、3D影像。

目前有兩個主要的GPU繪圖介面:OpenGL和微軟的DirectX。

WebGL可以把JavaScript和OpenGLES2.0結合在一起,為HTML5Canvas提供硬體3D加速繪製,就可借助系統顯示卡在瀏覽器流暢展示3D場景和模型。

硬體3D加速繪製也被稱為GPU(GraphicProcessingUnit,圖形處理器)加速,所謂GPU就是使用程式來操作GPU和顯示記憶體(也可以認為是執行在GPU上的程式)。

普通2D繪圖只要建立繪圖物件就可直接畫出,但WebGL只是提供操作顯示卡介面,撰寫WebGL程式就是操作GPU和顯示記憶體,所以WebGL就比傳統的2D繪圖多很多流程。

WebGL有賴GPU實現繪圖、實現硬體加速,因此需硬體支援,需要一個OpenGL2.0顯示卡,從2005年起,應該沒有不支援OpenGL2.0的顯卡了!  2GPU和顯示卡的關係 雖然顯示卡已經成為電腦不可或缺的元件,但它不是必須的。

早期電腦因為輸出的圖像資料很單一(通常都是黑白兩色,也沒有複雜圖形),所以CPU就可以完成影像的處理並輸出到顯示裝置。

但隨著圖形化處理要求越來越高,CPU已經無法對許多圖形函數進行處理,而最根本的解決方法就是增加一個顯示卡,讓顯示卡來對圖形函數進行處理,這時的顯示卡也被稱為圖形加速卡(GraphicsPerformanceAccelerator,簡稱GPA,也被譯為圖形效能加速卡)。

最早的顯示卡其實不具備圖形函數運算能力,只是作為一個快取來加速圖形的輸出。

後來CPU發展逐漸顯得後勁不足(由於發熱緣故,效能和頻率都遇到瓶頸),而顯示卡逐漸獲得發展,開始加入運算能力,可對圖形進行一些處理,進一步分擔CPU的一些運算。

舉例要畫一個圓圈,如果單單讓CPU做這個工作,它就要考慮需要多少個像素實現、用什麼顏色,但如果是圖形加速卡具有畫圈這個函數,CPU只需告訴它「給我畫個圈」,剩下的工作就由圖形加速卡來進行,這樣CPU就可以執行其他更多的工作,加強了電腦整體效能。

後來,顯示卡設計公司NVIDIA在發佈GEForce256圖形處理晶片時提出了GPU概念,此後人們在使用顯示卡的功能來分擔CPU運算時都會說是使用GPU加速。

 3使用WebGL繪圖的基本步驟 使用HTML5Canvas元素,需為HTMLCanvasElement.getContext()方法傳遞參數“webgl”就可以傳回一個WebGLRenderingContext物件用於3D繪圖,這就是3D繪圖的上下文環境,出於相容性考慮,可使用以下程式: vargl=canvas.getContext('webgl')|| canvas.getContext('webkit-3d')|| canvas.getContext('experimenal-webgl')|| canvas.getContext('moz-3d'); 當成功建立WebGLRenderingContext物件後,我們就下顯示記憶體中建立了一個繪圖快取,並不是直接在canvas元素上繪圖,而是首先在這個繪圖快取中工作,最後將繪圖結果呈現在canvas元素上,WebGLRenderingContext物件也被稱為WebGL物件。

基本步驟如下: 設定3D繪製環境 撰寫GPU程式 GPU程式碼使用著色語言(OpenGLESShadingLanguage,簡稱GLSL)撰寫,做的工作一般就是矩陣與向量的處理,複雜的也有邏輯處理,又分為兩個部分:處理座標的「頂點著色器」和處理顏色的「部分著色器」。

傳遞資料 繪製 (以下省略原生WebGL的撰寫......)  4Canvas在專案上的實用功能 讓Canvas鋪滿瀏覽器視窗 經常需要將Canvas填滿瀏覽器視窗,需用CSS設定canvas為block,然後用JavaScript來動態調整畫布長寬及resize: //該函數用來改變畫布大小 functionresizeCanvas(){ varcanvas=document.getElementById('glCanvas');//獲得canvas canvas.setAttribute('width',window.innerWidth);//改變寬度 canvas.setAttribute('height',window.innerHeight);//改變高度 } //每當視窗改變時也要呼叫resizeCanvas()函數用來改變畫布的大小 window.onresize=resizeCanvas; 禁用選取Canvas 通常還需禁止選取Canvas功能,避免像手機觸控時選取到DOM,而阻礙互動進行: //禁止滑鼠選取DOM元素 document.onselectstart=function(){ returnfalse; }; 儲存Canvas圖像資料 一般在用戶端預設右鍵另存圖片都是PNG格式,若要傳到伺服端需用canvas.toDataURL()方法取得base64編碼的圖片資料: //影像輸出為base64壓縮字串,預設為image/png vardata=canvas.toDataURL(); //刪除字串前的提示訊息"data:image/png;base64" varbase64=data.subString(22);  5繪製管線(Pipeline) 繪製管線(Pipeline)就是GPU上執行的圖形處理程式的執行過程,GPU是一種串流處理器,作用是處理由CPU傳輸過來的資料,處理後轉化為顯示器可以辨識的訊號,繪製的實際過程步驟: 頂點快取物件 初始化階段將頂點資料經過基本處理後送入頂點快取,GPU直接從快取讀取資料,包含頂點位置座標、頂點索引、頂點法線、頂點顏色、紋理座標。

頂點著色器(VertexShader) 負責處理每頂點,將頂點的空間位置投影在螢幕上,即計算頂點的2D座標,也負責頂點的深度快取(又稱Z-Buffer)計算,掌控頂點的位置、顏色和紋理座標等屬性,但無法產生新的頂點,所以需將這些頂點數據傳進頂點著色器。

像素配裝 像素組裝和像素處理。

光柵化(Rasterize) 將物體的數學描述和相關的顏色資訊轉為螢幕像素,被稱為光柵化,雖然3D世界中的幾何資訊是3D的,但由於目前用於顯示的裝置都是2D,因此在真正執行光柵化之前,需將虛擬3D世界中的物體投影到視平面上,這過程是透過OpenGL將所有有關3D物件的資訊處理後建立一個2D影像。

部分著色器(FragmaentShader) 用來處理光柵化後的資料。

多邊形上的每個像素被成為一個部分,光柵化操作已經將多邊形填滿並透過管線傳送至部分著色器,部分著色器像素計算顏色。

在部分著色器裡,我們將曲面相關的一切綜合起來,例如紋理材質、凹凸效果、陰影和燈光效果、反射、折射...等等,部分著色器最後輸出的是一個像素格式RGBA顏色,也可以捨棄部分,也就是沒有輸出。

部分處理 針對每個部分都要進行一系列處理,包含剪裁測試、深度測試、混合、抖動等。

影格快取 部分處理的結果直接輸出給影格快取,OpenGLES中的物體繪製並不是在螢幕上進行,而是會預先在影格快取中進行繪製,每繪完1影格再將繪製的結果交換到螢幕上,因此每次繪製的新影格時都需清除快取區中的相關資料,否則可能產生不正確效果。

繪製器快取 部分處理的結果輸出給繪製器快取,然後由繪製器快取輸出給影格快取,繪製器快取用於儲存影格快取中所用到的個單獨的快取,有幾個基本繪製器快取:顏色快取、深度快取、模板快取。

這個過程中,像素裝配、光柵化、部分處理這三個步驟(點化線框部分)我們是無法透過程式設計來控制的,我們能夠撰寫的就是快取和兩個著色器,以及紋理物件,著色器是這個圖形處理程式中一小區塊可程式化的單元。

 6暫存器(Buffer) 是一種臨時的儲存最佳化,表示下次繪製使你仍可以使用暫存器中的資料,而無需重新建立這些資料,進一步加強處理速度,OpenGL有三種類型的暫存器可用: 頂點快取(VertexBuffer) 也被稱為快取物件,包含頂點的屬性資料,例如頂點位置座標、頂點索引、頂點法線、頂點顏色、紋理座標等。

頂點快取X頂點顏色、紋理座標等,頂點快取也稱VBO(VertexBufferObject)。

影格快取(FrameBuffer) 一般情況下,影格快取完全由系統產生和管理,此時繪製結果將自動呈現在裝置的螢幕上,此為預設情況,就無法使用OpenGLAPI控制了;另外也可選擇發送給一個自訂的影格快取,所以在繪製時可以根據需求選擇使用哪個輸出,影格快取最後也被傳遞給繪圖快取,影格快取是顏色繪製器快取、深度繪製器快取、模板繪製器快取的集合,通常都是被用來繪製用作紋理的圖片。

影格快取也稱FBO(FrameBufferObject)。

繪製器快取(RenderBuffer) 用於儲存影格快取中所用到的個單獨的快取,有幾個基本的繪製器快取:顏色繪製器快取、深度繪製器快取、模板繪製器快取,繪製器快取也稱RBO(RenderBufferObject)。

顏色快取儲存最後OpenGL繪製產生RGBA影像。

深度快取儲存最後的Z深度,Z深度是物體在3D空間中的Z位置,灰階影像全白代表最附近的可見物件,黑色代表最深遠的物件。

模板快取區處理物件的可見部分。

使用快取物件(頂點快取)、使用索引陣列繪製 一個立方體有6個面、8個頂點,轉換OpenGL就是12個三角形構成,但每個面會重複2個頂點,有點浪費,所以OpenGL建議建立一個陣列,稱為索引陣列,於是可以建立一個有8個頂點的陣列,在索引陣列每個元素表示一個頂點,每3個元素的組合代表一個三角面。

有了這個功能,我們可以寫一次頂點資訊,然後使用索引陣列重用頂點資訊。

繪製像素 在3D繪圖中,像素是一個重要的概念,任何3D元素都是由像素組成的,在WebGL中基本像素只有三種類型,分別是點、線和三角形,無論需要畫什麼圖形都只能用這些像素去拼湊。

像素被描述為頂點(包含頂點、位置、顏色、紋理座標和法線)的集合。

3D空間中的一點 定義x、y、z座標的3D座標點,在空間用作一個粒子。

3D空間中的一條直線 由兩個3D座標點組成,一個單一的線,也可用作一個3D向量。

3D空間中的三角形 由三個3D座標點組成,三角形是組成3D物體表面的基本形狀,一個物體表面可能有一個或成千上萬個三角形組成。

*所有的3D物體都是由三角形組成的,三角形越多就越接近真實。

 73D環境基本概念 透視(Perspective) 2D平面上將平行線表示成聚合於一個消失點,進一步獲得深度和距離(3D)的視覺效果。

觀察點(ViewPoint)/視點/攝影點/照相點 觀察者眼睛的位置,由於很多時候都是使用攝影機來拍攝物體,因此攝影機的鏡頭也就相當於人的眼睛。

透視角度/角度 觀察者觀察事物的角度,常用的有平視圖、仰視圖、俯視圖。

消失點/滅點 用線性透視法表示時,它呈現為逐漸遠離的平行線看似相交的點。

投影 多維物件產生2D影像,3D投影將3D點對映到2D平面。

變形 在3D空間中旋轉一個物件,透過按圓周運動的方向移動物件內的每個點來更改物件的方向(通常也會更改其位置)。

平移 在3D空間中移動一個物件,也就是透過將物件內的每個點往同一方向移動相同的距離來更改物件的位置。

向量 3D向量使用笛卡爾座標x,y和z表示3D空間中的點或位置。

頂點 轉角點。

Mesh 由一些平直面(通常是一些三角形)來描述2D與3D的曲面。

(以下省略著色器、矩陣、向量筆記......)  8投影轉換 投影將3D物件呈現在二維空間內,有兩種基本投影模式:透視投影和正交投影。

投影的改變不但是某個物體的改變,而且連同整個場景都會發生改變。

透視投影使用四凌錐來建模並將3D世界及其物件投影到二維螢幕上,原點可以是針對螢幕的攝影機或觀察者的眼睛。

隨視點遠離原點(觀察點),四凌錐會變得越來越寬。

投影透視會在深度和距離上產生3D錯覺,其中接近螢幕的物件比遠離螢幕的物件要大得多。

從觀察點向前看,會有一個最近的剪裁平面(近裁剪平面),比這個剪裁平面更近的物體,我們看不見;也有另一個最遠的剪裁平面(遠裁剪平面),比這個剪裁平面更遠的物體,我們也看不見。

我們僅能看到兩個裁剪平面中間的東西,這兩個剪裁平面與觀察點形成一個四凌錐體,四凌錐被截取為一個四凌台,這個四凌台被稱為視截體(也稱視景體、平截頭體),只有在這個四凌台內的物體才可視。

兩個平面之間距離越長,看到的就越多。

但在電腦螢幕上,我們要在螢幕中顯示看到的內容,所以還需要有一個投影視窗。

四凌台共有6個面組成,利用相交和法線方向確定四凌台的方程式。

觀察點的原點位於Z=0處,針對正Z軸,法線指向內部,左右平面對稱,上下平面對稱,近、遠平面與Z軸垂直。

透過公式計算,可取得四凌台矩陣,也就是透視投影矩陣,最常使用這個公式計算透視投影矩陣: mat4.perspective(out,fovy,aspect,near,far) 參數out是計算後獲得的投影視圖矩陣 參數fovy是視野角度 參數aspect是縱橫比,通常就是WebGL視點的寬度與高度的比率(width/height) 參數near是近裁平面離觀察點的距離 參數far是遠裁平面離觀察點的距離 在3D專案中,透視至關重要,如果沒有透視技術,那麼3D世界看起就不真實。

視野 視野也被稱角度,fovy(fieldOfViewY,y方向上的角度),指定一個介於0~180度的角度,該角度確定透視投影的角度,該值越大越接近廣角,沿Z軸移動的3D物體的扭曲程度就越強。

若fovy值接近於0,則表示畫布的二維x和y座標與3D的x、y、z座標大致相同,只不過具有少許扭曲或無扭曲,也意指沿z軸下移的3D物體看起來大小不變且移動距離很小。

若fovy值接近於180,則會產生魚眼鏡頭效果,導致較大的扭曲,也意指沿z軸下移的3D物體看起來大小不變且移動距離很大。

如果是也設定為0或180,螢幕上不會顯示任何內容。

焦距 焦距表示觀察點原點(0,0,0)與3D物體在z軸上的位置之間的距離,較長焦距相當於視野較窄、物距經過壓縮的攝遠鏡頭;較短的焦距相當於廣角鏡頭,可獲得較寬的視野,但成像出現較大的扭曲;中等焦距相當於肉眼所見的效果,比較還原真實,視野範圍也會跟畫布大小有關,如果畫布有限,就相當於「看不到在此視野之外的物體」,一般來說當3D物體移動時,焦距會在透視轉換過程中動態重新計算,但多數時候不需這樣做,因為修改fovy的值將自動修改焦距,兩者屬性互相依賴: 焦距=canvasHeight/2*(cos(fovy/2)/sin(fovy/2)  9紋理對映圖 紋理對映(Texturemapping)是指將紋理影像應用於幾何圖形的過程,在3D處理中,也被稱為「為3D模型賦紋理」、「為3D模型貼圖」。

注意紋理與材質不一樣!紋理也稱紋理貼圖、貼圖,是用於物體曲面增加的顏色和影像。

而材質不包含紋理,而且還描述了物體表面的實體特性,主要是表面如何反射或透射光線。

紋理位於材質中,透過物體表面的特性可以模擬反射、折射和其他效果。

關於紋理圖片尺寸 OpenGL2.0以前的版本,要求紋理的寬度和高度都必須是2的整數次方,並且有最大值、最小值的限制,如果指定不符合的大小,則指定是無效的。

OpenGL2.0及以後版本就沒有這個限制,但是如果使用mipmap對映貼圖還是要遵守規定,紋理影像的高度寬度都必須是2的n次方大小,只有滿足這個條件,這個紋理影像才是有效的,如果不想使用2的n次方大小圖片作紋理,必須修改MIP對映貼圖的設定。

10UV對映 紋理對映(Texturemapping)是指紋理影像應用於幾何圖形的過程,因為紋理影像由像素組成,因此紋理對映也是將紋理空間中的紋理像素對映到螢幕空間中的像素過程。

紋理被認為是由紋理像素(texel)組成的陣列,而圖片是由像素組成的陣列。

一張紋理是由一系列的像素組成的(這些像素被稱為texel,翻譯為「紋理像素」或「紋理元素」),每個像素基於紋理的寬和高對應一個紋理座標,這些紋理座標沿著uv座標(u為寬,v為高)被對映到[0,1]範圍內,這個過程叫做UV對映,座標被稱為UV座標。

UV對映 UV對映是一種紋理化物件的方法,依賴兩個值:U水平(x)值和V垂直(y)值,這兩個值不是基於像素值,而是基於百分比。

(0,0)是點陣圖左上角,(1,1)是點陣圖右下角。

三角形的頂點移動且點陣圖發生扭曲,但是每個頂點的UV值都保持不變。

由於3D轉換應用於與點陣圖連結的三角形,點陣圖影像將根據UV值應用於三角形,因此不需透過矩陣運算,只需透過設定或調整UV值就可以實現立體效果! 使用UV座標 當應用紋理對映時,需指定紋理元素需要放置在幾何圖形頂端的確切位置,基本上,當使用文理影像對幾何圖形進行覆蓋時,要建立一個精確的對映,用來定義每一個紋理影像的像素應該落在3D幾何圖形上的準確位置。

將一種紋理在3D幾何圖形上進行合理的排列,需要對每個頂點指定對應的對映:對於每一個頂點,指定一對2D座標,用(U,V)表示該座標定義與該特定頂點相對應你紋理影像的點。

紋理影像首先必須上傳至GPU記憶體以便在繪製過程中使用!注意OpenGL紋理預設是從左下角開始讀取到右上角結束,正常情況下,我們都是從左上角到右下角觀看,因此通常我們需要將紋理圖片垂直翻轉。

11立方體紋理貼圖 立方體有6面2D紋理,注意立方圖紋理有一個限制:影像的寬度和高度必須相等,這樣才能組成一個正方體,另外立方體紋理的座標範圍也不再是[0,1]之間的(u,v),而是一個3D向量(u,v,w)。

取樣過程為:首先把立方圖紋理想像成一個6面是影像的立方體,立方體的中心就是紋理座標軸的原點,所指定的紋理座標對應著一個從立方體的中心點開始的向量,該向量和該立方體的焦點處的影像像素,就是最後的取樣值,紋理皆由「紋理採樣座標」實現。

以球體來說,從中心點開始一個向量,該向量一定跟球體的頂點垂直,因此它是頂點的法向量,這個法向量和立方圖所在立方體的交點處的影像像素就是紋理採樣(texel),這個texel就作為球體的像素(pixel)。

天空盒和天空穹 天空盒和天空穹也是利用貼圖造成真實天空的感覺,天空盒是用立方體、天空穹是用半球來實現,二者技術原理是相同,都是使用立方圖作為紋理採樣,要實現一個天空盒需要: 立方體足夠大,最好與遠裁平面距離相同。

立方體必須總是繪製在觀察點周圍,而觀察點應該總處於立方體中心,只有這樣才能讓在移動觀察點時,保持觀察點和立方體之間的距離不便,使場景給人一種在無窮遠處的印象。

天空盒的素材可以搜尋skyboxtexture: https://93i.de/p/free-skybox-texture-set/ 12貼圖品質和MIP影射貼圖 如果對多邊形來說紋理會太大或太小,那麼紋理需要被過濾以比對空間。

兩種基本過濾方式:放大和縮小,放大通常會獲得一張模糊影像;而縮小原理較複雜,不正確的縮小會導致鋸齒。

縮放過濾 紋理操作時有兩種不同的過濾類型發生:縮小和放大。

縮小,在投影到螢幕上的多邊形小於紋理時發生;放大,則在投影到螢幕上的多邊形大於紋理時發生。

決定使用哪種過濾類型完全由實現自動完成,但API提供了對各種情況下使用何種拓綠類型的控制: NEAREST 表示點過濾,把原圖縮放後對映過去的像素直接使用,也就是對對映座標取近似值,讓每一個座標都對映到像素上,確保顏色表不被破壞,縮放後使用的顏色表絕不會超出原圖的顏色表。

這種演算法實際上只是簡單放大了原始影像的像素,並沒有做其他任何最佳化,當紋理按比例放大或縮小時,WebGL會自原始影像上尋找最近的點來決定指定點的顏色。

不縮放還不錯;縮小後還過得去;放大後會有很多馬賽克。

*點過濾計算量小,速度很快,執行效率高 LINEAR 表示線性過濾,對鄰近的4個紋理像素的採樣進行線性內插並產生一個平均值,縮放後的顏色表和原圖不同,但由於取了平均值,使顏色的過度更加自然,有效消除鋸齒,但原本銳利的邊緣就會看起來有些模糊。

*線性過濾要計算顏色平均值,計算量變高,速度較慢 MIP影射貼圖 在使用縮小過濾時,可以應用MIP影射貼圖,也稱多級漸進紋理過濾。

進行紋理貼圖時,紋理大小是固定的,但虛擬世界的物體沒有一定形狀和尺寸,經過幾何的透視轉換,同一物體的不同部分在顯示時大小也有變化,距離近的看起來大,反之則小,為此提供同一紋理的不同解析度版本,例如256x256紋理,通常會有一個128x128和一個64x64的紋理版本,這些紋理版本就叫MIP(拉丁語的MultumInParvo字首,意指放置很多東西的小空間),一個紋理對映出不同大小的多個紋理,繪製之前,程式選出符合的版本,描繪在多邊形上。

MIPmap(稱mipmap)是組合一起並與紋理連結的點陣圖,可改善執行時期顯示品質和效能,MIPmap中的每個點陣圖影像分別是主點陣圖影像的版本,但與主影像相比,其他詳細程度有所降低,例如,MIPmap包含64*64像素的最高品質的主影像,較低品質影像將為32x32、16x16、8x8、4x4、2x2、1x1像素。

「紋理流式處理」是首先載入最低品質的點陣圖,然後在載入點陣圖時逐漸顯示較高品質點陣圖的功能,因低品質的點陣圖較小、載入速度快,使用者可載高品質的主點陣圖載入之前,在應用程式中檢視影像。

Mipmap處理是自動執行的,可遵循以下準則,確保影像利用此最佳化技術: 對於二維影像,請使用可被4或8整除的點陣圖大小(如640x128,可按以下方式遞減:320x64>160x32>80x16>40x8>20x4>10x2>5x1)。

對3D紋理,使用其中每個影像的解析度為2的冪(即2^n)的MIPmap,例如主影像的解析度為1024x1024px,Mipmap中較低影像將為512x512、256x256、128x128...1x1,共11個影像。

請注意,不會對寬度或高度為奇數的點陣圖內容執行Mipmap處理。

因多邊形每一面大小有差異,常常每面採用紋理都不同,高低解析紋理的連接處會出現一邊清晰一邊模糊的問題,必須進行過濾處理,使兩者之間的過濾變得平順,才會讓畫質提升。

13光源的計算 在OpenGL中,有專門來設定光源的API,但WebGL中沒有用來計算光的相關API,因此想為模型打光,就必須自己進行計算。

實現光源的基本原理:按照光源的實體機制針對性地更新每個像素的顏色值,該像素顏色值的計算由光源的性質和物體表面顏色兩種因素決定。

光是一種顏色,白光是RGB三種顏色重疊而成,原色(PrimaryColor)又被稱為基色,是色彩中不能再分解的基本色,也不能透過其他顏色混合而成,但反過來,當按照不同的組合就可以看到光譜中的所有顏色。

黑暗中無法看到不發光的物體,所有物體都不是光源,他們具有顏色是因為這些物體的表面對可見光的反射具有選擇性,因此打光已經由光源色變成了對光的反射率。

光源 光源有四種,而且物體本身的材質也有顏色,稱為放射光(Emissivelight),光源設在物體上還會產生反射、折射等: 環境光(Ambientlight) 沒有方向、無處不在、瀰漫在場景中、會均衡的影響場景中的每個面。

平行光(Directionallight) 這種光源發出的光線是平行的,它也是從一個特定的點發出的光,但這個點來自很遙遠的地方,因此光線可看成是平行的,如同太陽發出的太陽光。

點光源(Pointlight) 點光源發出的光從一個點開始,向所有方向輻射,這是許多光源真實的工作方式,如燈泡發出的光。

聚光燈(Spotlight) 它發出的是一束光,中間最亮,向週邊逐漸衰減。

光源強度 材質與燈光組合一起有作用,光源照在物體表面的光源強度決定了顯示的顏色強度,以下三種因素決定了燈光源在物體上的光源強度: 燈光強度 燈光在發射點的原始強度,初始點的燈光強度影響光源亮物件的亮度,投射在明亮顏色物件上的暗光將使這些顏色發暗。

入射角 物體表面與光源所成的傾斜角度愈大,物體接收的燈光越少、越暗,光線和物體表面的法線之間的角度就該面的入射角,入射角為0度(即燈光垂直照射該面),在光線不衰減的情況下,燈光以完全強度對該面進行照明,隨入射角增大,表面照明強度會減小。

距離 光源隨距離減弱,這種效果稱衰減,衰減的幅度根據實際情況會有很大不同。

現實世界中,遠離光源的物件看起來更暗,距離光源較近的物件看起來更亮。

一般會以平方反比速率衰減,其強度的減小與到光源距離的平方成反比: 光源強度=原始強度/(length*length) 但光線在塵埃較多的大氣中時,衰減幅度更大,例如大霧天,按照線性衰減,其強度的減小與到光源距離的成反比: 光源強度=原始強度/length 光源計算 在WebGL中,光的計算是透過光的來源和強度進行計算,根據一定規律有多種不同的計算方式。

當光照射物體上會反射到觀察者的眼睛中,有兩種反射方式: 鏡面反射 嚴格的鏡面反射定律是「入射角等於反射角」。

漫反射(Diffuse) 投射粗糙表面上的光向各方向反射的現象,觀察者無論從哪個方向觀察,漫反射光源強度都是相同的。

環境光計算,由於環境光在所有方向上的顏色和強度一樣,所以只需簡單的進行分量相乘就可以。

平行光計算採用「朗伯餘弦定律」讓表面的光源強度由入射光的方向和垂直於表面的法線的夾角的餘弦值成正比,這是在200年前由約翰・海因裡希・朗伯得出。

點光源計算,對每一個頂點,法線向量和光源方向向量都是變化的,因此不僅物體運動會改變法線與光源的角度,還會改變頂點與光源的距離,與平行光相比,要多一個步驟計算頂點與光源的距離。

(跳過模板測試和深度測試......) 14實現陰影 光源被物體遮擋投射在其他物體上才能產生陰影,理論上,只有追蹤光源的軌跡才能準確地確定陰影,但這種光線追蹤技術需複雜的計算量,針對每部分都要光線追蹤,加上光反射所導致的遞迴,計算量之大,所以在目前硬體情況下動態繪製非實用範圍,而在靜態會中目前被大量使用,目前廣泛使用的陰影技術有三種,任何一種都需要很大的計算量,因此在使用時需仔細考慮: 平面陰影(PlanarShadow) 原理就是把3D物體壓扁到一個面上,所有被繪製的頂點都將位於這個平面的二維世界,平面陰影的實現主要有賴於一個轉換矩陣,它可以將模型壓扁到指定的面上,這個轉換矩陣也稱為陰影矩陣。

關鍵為需仰賴陰影矩陣的產生和模板快取區的使用。

平面陰影使用有很大侷限,它不能把陰影對映在曲面上,在環境簡單時非常不錯,不過被對映的物體形狀越複雜,計算效率越低。

陰影椎 陰影對映 (跳過影格快取、離線繪製和繪製到紋理......) 15效能最佳化、偵錯和例外處理 出錯資訊的處理 WebGLAPI公開了幾個方法可以取得著色器、GPU程式的記錄檔資訊,使用getShaderInfoLog()可以取得著色器編譯的情況,取得錯誤使用getError()。

事件處理 canvas元素可以監聽以下三個事件: webglcontextlost事件 當與WebGLRenderingContext繪圖上下文環境連結在一起的繪圖快取遺失就會觸發該事件。

webglcontextrestored事件 當與WebGLRenderingContext繪圖上下文環境連結在一起的繪圖快取恢復時就會觸發該事件。

webglcontextcreationerror事件 當建立WebGLRenderingContext繪圖上下文環境出錯時觸發該事件。

每個事件都會產生WebGLContextEvent事件物件,該物件有一個屬性statusMessage,用於描述事件。

關於繪圖上下文環境遺失 WebGL可以使GPU用JavaScript實現,但GPU是一個共用資源,因此有時可能會掛掉,例如一個網站長時間佔用GPU,瀏覽器或作業系統可能會重置GPU來獲得控制權,進一步掛掉程式,使程式失去WebGL繪圖上下文,如果多個頁面使用太多資源,瀏覽器也有可能決定掛掉所有網頁,失去WebGL繪圖上下文,當恢復時僅恢復目前頁面的WebGL繪圖上下文;在一些桌面系統中,使用者還可切換顯示卡或更新驅動程式,導致上下文遺失;或當行動裝置電池變動時(例如電池即將耗盡),也可能發生,所以需有「當發生遺失時的對應處理」。

上下文環境遺失不一定會發生,要手動觸發有一定難度,這對開發過程中偵錯是一大問題,所以khronos提供一個工具可以模擬上下文環境遺失: https://github.com/vorg/webgl-debug 使用WebGL偵錯工具 WebGLInspector是一個Chrome擴充,提供一個介面幫助偵錯: http://benvanik.github.io/WebGL-Inspector/ 點擊UI按鈕就可出現WebGLInspectorGUI介面。

點擊Programs可以檢視程式和著色器,包含現在的uniform變數和attribute變數。

點擊Buffers可以檢視上傳的所有快取、快取歷史、用途、內容和推斷結構。

點擊Textures可以瀏覽目前使用的所有紋理,查看修改歷史、用法和附加的資訊。

點擊State可以檢視現在的GL的狀態,也就是所有使用getParameter()方法取得的資訊。

點擊Timeline可以看到的GL各種即時的活動,使用曲線表示,預設是沒有啟用的,你需要點擊右上角的連結啟用它(也可點擊切換來關閉它)。

點擊Capture按鈕可以捕捉運動的影格,然後點擊Tracing就可以追蹤影格的繪製過程。

WebGL效能最佳化 由於WebGL有關執行在CPU上的JavaScript程式和執行在CPU上的繪圖程式,所以要注意的方面尤為多: 充分使用GPU快取 GPU應用程式的特點就是必須等待CPU為其下達執行指令才會被動執行,不但是傳遞執行指令,還要向GPU顯示記憶體傳遞執行指令所需的資料,這是一個複雜的過程,雖然傳遞速度很快,也趕不上使用顯示記憶體本機快取的資料,因此要盡量使用快取資料。

快取的資料主要是頂點相關的資料,包含頂點座標、頂點索引、紋理座標,這些都可以重複使用頂點快取物件來加強使用效率。

在CPU和GPU之間尋找平衡點 在3D應用中,經常會有關到3D轉換,包含各種矩陣的計算、歸一化座標、向量計算等,這些運算可以在CPU上計算過後傳進GPU,也可直接傳進GPU,讓GPU做這些運算,因此在發佈網站前應做大量測試來平衡。

但要注意JavaScript的執行效率,有些運算最好放在WebWorkers中執行。

平面陰影(PlanarShadow) 原理就是把3D物體壓扁到一個面上,所有被繪製的頂點都將位於這個平面的二維世界,平面陰影的實現主要有賴於一個轉換矩陣,它可以將模型壓扁到指定的面上,這個轉換矩陣也稱為陰影矩陣。

關鍵為需仰賴陰影矩陣的產生和模板快取區的使用。

平面陰影使用有很大侷限,它不能把陰影對映在曲面上,在環境簡單時非常不錯,不過被對映的物體形狀越複雜,計算效率越低。

陰影椎 陰影對映 (跳過影格快取、離線繪製和繪製到紋理......) 16進階動畫計時器 傳統的JavaScript動畫無非就是用setInterval()方法或setTimeout()方法實現,但在看不到網頁的情況下(特別當網頁標籤不是目前瀏覽的標籤或瀏覽器以最小化時),動畫還是會不停繪製,為解決此問題,W3C提供了一個能夠對動畫影格統一管理,並提供監聽的API––WindowAnimationTiming,有以下兩種優勢: 同一影格中,對DOM的所有操作只進行一次版面配置和繪製,把原本要多次進行的版面配置和繪製最佳化成1次。

如果發生動畫的元素被隱藏了,那麼就不再去繪製,減少了CPU和記憶體消耗。

基本方法就是呼叫window.requestAnimationFrame()方法: varanimationStartTime; functionanimate(timeStamp){ //執行一個操作 document.getElementById('animated').style.left=(timeStamp-animationStartTime)%500+'px'; //再次呼叫requestAnimationFram()方法循環執行,進一步實現動畫 window.requestAnimationFrame(animate); } functionstart(){ animationStartTime=Date.now(); window.requestAnimationFrame(animate); } 瀏覽器支援的相容性: (function(){ varrequestAnimationFrame=window.requestAnimationFrame ||window.mozRequestAnimationFrame ||window.webkitRequestAnimationFrame ||function(callback){ returnwindow.setTimeout(callback,1000/24); }; varcancelAnimationFrame=window.cancelAnimationFrame ||window.mozCancelAnimationFrame ||window.webkitCancelAnimationFrame ||window.clearTimout; window.requestAnimationFrame=requestAnimationFrame; window.cancelAnimationFrame=cancelAnimationFrame; })(); 高精確度時間 可以對SVG、Canvas、CSS...設定回呼函數動畫,回呼函數有一個傳入的timeStamp參數,該參數表示呼叫回呼函數的時間,它是一個DOMTimeStamp類型的值,是從導覽開始時開始測量的時間值,此時間值可以直接與Data.now()進行比較,但大多瀏覽器將timeStamp參數實現為DOMHighResTimeStamp類型的值,表示是從導覽開始時開始測量的高精確度時間值,DOMHighResTimeStamp以毫秒為單位,精確到千分之一毫秒,此時間值不直接與Date.now()進行比較,應使用window.performance.now取得目前的高精確度時間值。

下面示範如何回呼函數中的時間戳記參數: functionanimate(timeStamp){ varprogress; if(start===null)start=timestamp; progress=timestamp-start; if(對progress一些比較){ requestAnimationFrame(step); } } 17建立人機互動 DOM事件處理方法在Canvas畫布上已不再適用,無論Canvas上繪製多少圖形,Canvas都是一個整體,圖形本身實際都是Canvas的一部份,不可單獨取得。

滑鼠控制 由於Canvas像素繪圖的本質,只能在canvas元素節點去處理,因此只能為canvas元素節點定義事件,然後識別滑鼠指標發生在Canvas內部的哪一個物體上,給canvas元素綁定事件,當事件發生時,檢查滑鼠指標的位置,然後檢查哪些物體覆蓋了該位置,且還要注意,多個物體可能覆蓋同一個位置,因此還需判斷哪個物體位於最頂層(一般來說會用「射線投射」方式來確定)。

單純講滑鼠控制需用clientX、clientY、screenX、screenY四個屬性來判斷計算鼠標的位置,然後用mousedown、mouseup、mousemove、mouseover、click動作監聽。

鍵盤控制 使用keydown動作監聽。

(以下省略RenderMonkey進行著色器開發和偵錯......) 最後請參閱程式碼的兩個範例,感覺一下原生寫法~ 本文不贅述WebGL原生寫法,有興趣請參考原書籍! DEMOMEMO



請為這篇文章評分?