1.如何實現霧化
實現霧化的方式由多種,這里使用最簡單的一種:線性霧化(linear fog)。在線性霧化中,某一點的霧化程度取決於它與視點之間的距離,距離越遠霧化程度越高。線性霧化有起點和終點,起點表示開始霧化之處,終點表示完全霧化之處兩點之間某一點的霧化程度與該點與視點的距離呈線性關系。比終點更遠的點完全霧化了,即完全看不見了。
某一點霧化的程度可以被定義為霧化因子(fog factor),並在線性霧化公式中被計算出來:
<霧化因子> = (<終點> - <當前點與視點間的距離>) / (<終點> - <起點>)
這里:
<起點> <= <當前點與視點間的距離> <= <終點>
霧化因子為1.0,表示該點完全沒有被霧化,可以很清晰地看到此處的物體。如果為0.0, 就表示改點完全被霧化了。起點因子的線形圖如下所示:
2.片元着色器中包含霧化因子的片元顏色計算
計算公式如下:
<片元顏色> = <物體表面顏色> * <霧化因子> + <霧化顏色> * (1 - <霧化因子>)
3.使用頂點的w分量作為當前點與視點間的距離
之前,我們並未顯示使用過gl_Position的w分量,實際上,這個w分量的值就是頂點的視圖坐標的z分量乘以-1。在視圖坐標系中,視點在原點,視線沿着Z軸負方向,觀察者看到的物體其視圖坐標系值z分量都是負的,而gl_Position的w分量正好是z分量值乘以-1,所以可以直接使用該值來近似頂點和視點建的距離。
4.繪制圓形的點
為了將矩形削成圓形,需要知道每個片元在光柵化過程中的坐標。在第5章的一個示例程序中,在片元着色器中通過內置變量gl_FragCoord來訪問片元的坐標。實際上,片元着色器還提供了另外一個內置變量gl_PointCoord。這個變量可以幫助我們繪制圓形的點。片元着色器內置變量:
變量類型和名稱/描述
vec4 gl_FragCoord/片元在窗口坐標
vec4 gl_PointCoord/片元在被繪制的點內的坐標(從0.0到1.0)
gl_PointCoord坐標值的區間從0.0到1.0,如下圖所示。為了將矩形削成圓形,需要將與中心點(0.5, 0.5)距離超過0.5,也就是將圓外的片元剔除掉。在片元着色器中,可以使用discard語句來放棄當前片元。
在片元着色器中的代碼實現方式如下:
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'void main(){\n' +
' float distance = distance(gl_PointCoord, vec2(0.5, 0.5));\n' +
' if (distance <= 0.5){\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
' }else{\n' +
' discard;' +
' }\n' +
'}\n';
5.α混合
我們要讓物體實現半透明,實現這種效果需要用到顏色的a分量。該功能被稱為a混合(alpha blending)或混合(blending),WebGL已經內置了該功能,值需要開啟即可。
如何實現a混合?
1.開啟混合功能:gl.enable(gl.BLEND)。
2.指定混合函數:gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)。
如果我們只設置了顏色的第四個分量,是看不到透明效果的,必須要執行上面兩個步驟。
6.gl.blendFunc(src_factor, dst_factor)
通過參數src_factor和dst_factor指定進行混合操作的函數,混合后的顏色如下計算:
<混合后的顏色> = <源顏色> * src_factor + <目標顏色> * dst_factor
參數:
src_factor:指定源顏色在混合顏色重的權重因子,如下表所示
dst_factor:指定目標顏色在混合后顏色重的權重因子,如下表所示
常量/R分量的系數/G分量的系數/B分量的系數
gl.ZERO/0.0/0.0/0.0
gl.ONE/1.0/1.0/1.0
gl.SRC_COLOR/Rs/Gs/Bs
gl.ONE_MINUS_SRC_COLOR/(1-Rs)/(1-Gs)/(1-Bs)
gl.DST_COLOR/Rd/Gd/Bd
gl.ONE_MINUS_DST_COLOR/(1-Rd)/(1-Gd)/(1-Bd)
gl.SRC_ALPHA/As/As/As
gl.ONE_MINUS_SRC_ALPHA/(1-As)/(1-As)/(1-As)
gl.DST_ALPHA/Ad/Ad/Ad
gl.ONE_MINUS_DST_ALPHA/(1-Ad)/(1-Ad)/(1-Ad)
gl.SRC_ALPHA_SATUREATE/min(As,Ad)/min(As,Ad)/min(As,Ad)
上表中,(Rs,Gs,Bs, As)和(Rd,Gd,Bd,Ad)表示源顏色和目標顏色的各個分量。
假如源顏色是半透明的綠色(0.0, 1.0, 0.0, 0.4),目標顏色是普通的黃色(1.0, 1.0, 0.0, 1.0),調用函數:
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
計算過程如下圖所示:
在使用a混合功能時,我們屏蔽掉了隱藏面消除功能(去掉了代碼gl.enable(gl.DEPTH_TEST)。關閉隱藏面消除功能只是一個粗暴的解決方案,並不能滿足實際的需求。實際上,通過某些機制,可以同時實現隱藏面消除和半透明效果。我們只需要:
1.開啟隱藏面消除功能:gl.enable(gl.DEPTH_TEST)。
2.繪制所有不透明的物體(a為1.0)。
3.鎖定用於進行隱藏面消除的深度緩沖區的寫入操作,使之只讀。調用:
gl.depthMask(false);
4.繪制所有半透明的物體(a小於1.0),注意他們應當按照深度排序,然后從后向前繪制。
5.釋放深度緩沖區,使之可讀可寫。調用:
gl.depthMask(true)
8.gl.depthMask(mask)
鎖定或釋放深度緩沖區的寫入操作。參數:
mask:指定是鎖定深度緩沖區的寫入操作(false),還是釋放之(true)
9.渲染到文理
WebGL可以把渲染的三維圖形作為紋理貼到另一個三維物體上去。要實現這個功能,需要提到兩個新的對象:幀緩沖區對象和渲染緩沖區對象。
幀緩沖區對象(framebuffer object)可以用來代替顏色緩沖區或深度緩沖區,如下圖所示。繪制在幀緩沖區中的對象並不會直接顯示canvas上,可以先對幀緩沖區中的內容進行一些處理再顯示,或者直接用其中的內容作為紋理圖像。在幀緩沖區中進行繪制的過程又稱為離屏繪制(offscreen drawing)。
幀緩沖區對象的結構如下圖所示。繪制操作並不是直接發生在幀緩沖區中的,而是發生在幀緩沖區所關聯的對象(attachment)上。一個幀緩沖區有3個關聯對象:顏色緩沖區(color attachment)、深度緩沖區(depth attachment)、模板關聯對象(stencil attachment),分別用來代碼顏色緩沖區、深度緩沖區、模板緩沖區。
每個關聯對象又可以是兩種類型的:紋理對象或渲染緩沖區對象(renderbuffer object)。
10.渲染到紋理,幀緩沖區配置步驟
1.創建幀緩沖區對象(gl.createFramebffer())。
2.創建文理對象並設置其尺寸和參數(gl.createTexture()、gl.bindTexture()、gl.texImage2D()、gl.Parameteri())。
3.創建渲染緩沖區對象(gl.createRenderbuffer()).
4.綁定渲染緩沖區對象並設置其尺寸(gl.bindRenderBuffer()、gl.renderbufferStorage())。
5.將幀緩沖區的顏色關聯對象指定為一個文理對象(gl.frambufferTexture2D())。
6.將幀緩沖區的深度關聯對象指定為一個渲染緩沖區對象(gl.framebufferRenderbuffer())。
7.檢查幀緩沖區是否正確配置(gl.checkFramebufferStatus())。
8.在幀緩沖區中進行繪制(gl.bindFramebuffer())。
11.gl.createFramebuffer()
創建幀緩沖區對象。
12.gl.deleteFramebuffer(framebuffer)
刪除幀緩沖區對象。參數:
framebuffer:將要被刪除的幀緩沖區對象
13.gl.createRenderbuffer()
創建渲染緩沖區對象
14.deleteRenderbuffer(renderbuffer)
刪除渲染緩沖區對象。參數:
renderbuffer:將要被刪除的幀緩沖區對象
15.gl.bindRenderbuffer(target, renderbuffer)
將renderbuffer指定的渲染緩沖區對象綁定在target目標上。如果renderbuffer為null,則將已經綁定在target目標上的渲染緩沖區對象解除綁定。參數:
target:必須為gl.RENDERBUFFER
renderbuffer:指定被綁定的渲染緩沖區
16.gl.renderbufferStorage(target, internalformat, width, height)
創建並初始化渲染緩沖區的數據區。作為深度關聯對象的渲染緩沖區,其寬度和高度必須與作為顏色關聯對象的文理緩沖區一致。參數:
target:必須為gl.RENDERBUFFER
internalformat:指定渲染緩沖區中的數據格式。格式包括:gl.DEPTH_COMPONENT16,表示渲染緩沖區將代替深度緩沖區;gl.STENCIL_INDEX8,表示渲染緩沖區將替代模板緩沖區;gl.RGBA4,表示渲染緩沖區將替代顏色緩沖區,表示這4個分量各占據4個比特;gl.RGB5_A1,表示RGB個占據5個比特而A占據1個比特;gl.RGB565,表示RGB分別占據5、6、5個比特
width和height:指定渲染緩沖區的寬度和高度以像素為單位
17.gl.bindFramebuffer(target, framebuffer)
將framebuffer指定的幀緩沖區對象綁定到target目標上。如果framebuffer為null,那么已經綁定到target目標上的幀緩沖區對象將被解除綁定。參數:
target:必須是gl.FRAMEBUFFER
framebuffer:指定被綁定的幀緩沖區對象
18.gl.framebufferTexture2D(target, attachment, textarget, texture, level)
將texture指定的文理對象關聯到綁定在target目標上的幀緩沖區。參數:
target:必須是gl.FRAMEBUFFER
attachment:指定關聯的類型。包括:gl.COLOR_ATTACHMENT0,表示texture是顏色關聯對象;gl.DEPTH_ATTACHMENT,表示texture是深度關聯對象
textarget:同textureImage2D()函數的第一個參數(gl.TEXTURE_2D或gl.TEXTURE_CUBE)
texture:指定關聯的文理對象
level:指定為0(在使用MIPMAP紋理時指定紋理的層級)
attachment參數的取值之一gl.COLOR_ATTACHMENT0,其中有個0。這是因為,在OpenGL中,幀緩沖區可以具有多個顏色關聯對象(gl.COLOR_ATTACHMENT0, gl.COLOER_ATTACHMENT1等等),但WebGL中只可以有一個。
19.gl.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer)
將renderbuffer指定的渲染緩沖區對象關聯到綁定在target上的幀緩沖區對象。參數:
target:必須是gl.FRAMEBUFFER
attachment:指定關聯的類型。gl.COLOR_ATTACHMENT0,表示renderbuffer是顏色關聯對象;gl.DEPTH_ATTACHMENT,表示renderbuffer是深度關聯對象;gl.STENCIL_ATTACHMENT,表示renderbuffer是模板關聯對象
renderbuffertarget:必須是gl.RENDERBUFFER
renderbuffer:指定被關聯的渲染緩沖區對象
20.gl.checkFramebufferStatus(target)
檢查綁定在target上的幀緩沖區對象的配置狀態。參數:
target:必須為gl.FRAMEBUFFER
返回值:0,target不是gl.FRAMEBUFFER。其他包括:gl.FRAMEBUFFER_COMPLETE,幀緩沖區對象正確配置;gl.FRAMEBUFFER_INCPOMPLETE_ATTACHMENT,某一個關聯對象為空,或者關聯對象不合法;gl.FRAMEBUFFER_INCOMPOLETE_DIMENSIOUS,顏色關聯對象和深度關聯對象的尺寸不一致;gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT,幀緩沖區尚未關聯任何一個關聯對象
21.gl.viewport(x, y, width, height)
設置gl.drawArrays()和gl.drawElements函數的繪圖區域。在cavans上繪圖時,x和y就是canvas中的坐標。參數:
x,y:指定繪圖區域的左上角,以像素為單位
width,height:指定繪圖區域的寬度和高度
22.繪制陰影
實現陰影有若干不同的方法,經常采用的是陰影貼圖(shadow map),或稱深度貼圖(depth map)。該方法具有較好的表現力,在多種計算機圖形學的場合,甚至電影特效總都有所使用。
我們需要用到光源和物體之間的距離(實際上也就是物體在光源坐標系下的深度z值)來決定物體是否可見。如下圖所示,同一條光線上有兩個點P1和P2,由於P2的z值大於P1,所以P2在陰影中。
我們需要使用兩對着色器以實現陰影:(1)一對着色器用來計算光源到物體的距離,(2)另一對着色器根據(1)中計算出的距離繪制場景。使用一張文理圖像吧(1)的結果傳入(2)中,這張文理圖像就被稱為陰影貼圖(shadow map),而通過陰影貼圖實現陰影的方法就被稱為陰影映射(shadow mapping)。
23.讀取OBJ模型文件步驟
1.准備Float32Array類型的數組vertices,從文件中讀取模型的頂點坐標數據並保存到其中;
2.准備Float32Array類型的colors,從文件中讀取模型的頂點顏色數據並保存到其中;
3.准備Float32Array類型的數組normals,從文件中讀取模型的頂點法線數據並保存到其中;
4.准備Unit32Array(或Uint8Array)類型的數組indices,從文件中讀取頂點索引數據並保存在其中,頂點索引數據定義了組成整個模型的三角序列;
5.將前4步驟獲取的數據寫入緩沖區中,調用gl.drawElements()以繪制出整個立方體;
24.OBJ文件格式
OBJ格式的文件由若干個部分組成,包括頂點坐標部分、表面定義部分、材質(material,即表面的樣式,可能是單色或漸變色,也可能貼有紋理)定義部分等。每個部分定義了多個頂點、法線、表面等等。下面是一個OBJ文件。
已#號開頭的行表示注釋,如圖10.26中的第1行到第2行就是Blender軟件根據其自身版本創建出來的注釋;
第3行引用了一個外部材質文件。OBJ格式將模型的材質信息爆粗能在外部的MTL格式的文件中。
mtllib <外部材質文件名>
例如mtllib cube.mtl,外部材質文件是cube.mtl。
第4行按照如下格式自定了模型的名稱:
o <模型名稱>
第5行到第12行按照如下格式定義了頂點的坐標,其中w是可選的,如果沒有就默認為1.0:
v x y z [w]
該模型是一個標准的立方體,共有8個頂點。
第13行到第20行先指定了某個材質,然后列舉了使用這個材質的表面。第13行指定了材質名稱,該材質被定義在第3行引用的MTL文件中。
usemtl <材質名>
第14行到18行定義了使用這個材質的表面。每個表面是由頂點、紋理坐標和法線的索引序列定義的。
f v1 v2 v3 v4 …
其中v1、v2、v3、v4是之前定義的頂點的索引值。注意,這里頂點的索引值從1開始,而不是從0開始。本例為了簡單,沒有包含法線,如果包含法線向量,就需要遵照如下格式:
f v1//vn1 v2//vn2 v3//vn3 ...
其中,vn1、vn2等是法線向量的索引值,也是從1開始。
第19行到第20行定義了使用了另一個材質的表面,即橘黃色的表面。
25.MTL文件格式
MTL文件定義了多個文件,下圖顯示了cube.mtl中的內容。
第1行和第2行是注釋。
第3行使用newmtl定義一個新材質,格式如下:
newmtl <材質名>
材質名被OBJ文件引用,如材質Material就被cube.obj的第13行引用了。
第4行到第6行,分別使用Ka、Kd、Ks定義了表面的環境色、漫射色、高亮色。顏色使用RGB格式定義的,每個分量值得區間為[0.0, 1.0],本例只用到漫射色,也就是物體表面本來的顏色。我們不用管其他兩個顏色的含義。
第7行使用Ns指定了高光亮的權重,第8行用Ni指定了表面光學密度,第9行使用d指定了透明度,第10行用illum指定了光照模型。本例沒有用到這些信息。
第11行到第18行以同樣的方法定義了另外一種材質material.001。
16.根據頂點坐標,通過交叉操作計算法線方向
比如,三角形的頂點為v0、v1、v2,從v0到v1的矢量是(x1,y1,z1),而從v0到v2的矢量是(x2,y2,z2),那么這兩個矢量的叉乘結果為矢量(y1*z2*z1*y2, z1*x2*x1*z2,x1*y2*y1*x2),也就是三角形的法向量。
17.結構化OBJ文件
下圖是結構化OBJ文件的結構圖,在解析OBJ文件時,可按照此設計解析