為三角形加上顏色
上一章中,我講解了繪畫三角形的步驟。這個步驟也是我們繪畫其他任何圖形的步驟,無論繪畫的最終效果有多絢麗、有多複雜。
本章的目的,是給這個三角形上色。為此,我們需要為它指定顏色。為三角形指定顏色和指定頂點位置類似。在進行指定顏色之前,我首先說一下上章中著色器的源碼(這也是上章缺少的內容)。
先來看看頂點著色器的源碼:
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 v3Position;
void main(void)
{
gl_Position = vec4(v3Position, 1.0);
}
</script>
script的部分不屬於著色器源碼,不管它。attribute vec3 v3Position;這句,定義了一個類型為vec3的屬性,該屬性的名稱為v3Position。在上章中你可以看到,我們就是使用了這個“v3Position”,通過程式對象中的一個頂點屬性的索引,將該屬性和三角形的頂點數組資料關聯了起來,使得該屬效能夠訪問我們定義的三角形的各個頂點位置。頂點屬性的索引像是一個自來水管道,擁有開關、進水口和出水口。我們使用enableVertexAttribArray控制管道的開和關;使用vertexAttribPointer將資料流接到管道的進水口;使用bindAttribLocation,將出水口接到了我們的頂點著色器的屬性v3Position上。
接下來是一個入口函數,就象C、C++那般。函數體只有一條語句,就是將我們從自來水管道中接到的每一滴水由vec3類型轉換到vec4類型,然後賦值給gl_Position。此處的vec3和vec4是著色器中的內建類型(在後面你可能會在js中看到同樣的類型。但它們不是一回事。js中的vec3和vec4是我們自己定義的function)。gl_Position是頂點著色器中的特殊的內建變數。它的值直接影響到對應的像素點(我們稱之為片段)最終在螢幕上的顯示位置。
再來看看片段著色器的源碼:
<script id="shader-fs" type="x-shader/x-fragment">
void main(void)
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
這個比頂點著色器還要簡單。入口函數和頂點著色器相同。它沒有定義任何變數,只是簡單地給片段著色器的內建變數gl_FragColor賦值。gl_FragColor的值,直接影響到片段最終在螢幕上的顯示內容(顏色)。
注意,gl_Position是頂點著色器特有的,不能在片段著色器中使用。同樣gl_FragColor是片段著色器特有的,不能在頂點著色器中使用。另外,頂點屬性變數是頂點著色器特有的,在片段著色器中定義的話會導致錯誤。頂點著色器和片段著色器都可以使用的是uniform變數。和attribute不同,uniform變數無法手動綁定,只能由程式對象在連結時自動綁定到uniform索引。之後,使用WebGL函數getUniformLocation(programObject, name)擷取綁定的uniform索引。最後,使用WebGL的uniform*系列函數進行設值。uniform變數在著色器中是唯讀,它的值只能在著色器之外(我們的應用中)進行修改。attribute變數在頂點著色器中是可讀可寫的。除了attribute和uniform,還有一種特種的變數,叫做varying。它的作用是將頂點著色器中的值傳遞給片段著色器。要注意的是,傳遞雙方的varying變數的名稱和類型必須相同。
從varying變數的說明,不難看出,頂點著色器和片段著色器的執行是分特定順序的:頂點著色器在前,片段著色器在後。現在,你的腦中應當有一個全域的模型。我一邊說,你一邊想象,如果難以想象,就拿出紙和筆,一步一步畫出來。首先,WebGL是一個大管道。該大管道上,有兩個環節,前面的是頂點著色器,後面的是片段著色器。這兩個環節,各有一些小管道。其中,頂點著色器上,有0到(getParameter(MAX_VERTEX_ATTRIBS)-1)個頂點屬性小管道,0到(getParameter(MAX_VERTEX_UNIFORM_VECTORS)-1)個uniform變數;片段著色器上,有0到(getParameter(MAX_FRAGMENT_UNIFORM_VECTORS)-1)個uniform變數。此外,還有0-(getParameter(MAX_VARYING_VECTORS)-1)個varying變數直接從頂點著色器流向片段著色器。它們構成的整幅映像如下:
頂點著色器和片段著色器以及著色語言的更多資訊,可參考《OpenGL ES 2.0編程指南》的《第八章 頂點著色器》《第十章 片斷著色器》和《第五章 OpenGL ES著色語言》。
現在,回到正題。要給三角形上色,就要為它指定顏色。顏色和頂點位置一樣,我們用三個float數(分別代表rgb)表示:
var jsArrayColor = [
1.0, 0.0, 0.0,//上頂點
0.0, 1.0, 0.0,//左頂點
0.0, 0.0, 1.0];//右頂點
注意,用浮點數表示顏色分量時,範圍為[0.0, 1.0]。
由於頂點的屬性無法在直接片段著色器中訪問到,因此我們要象上面提到的那樣,繞點彎路,使用varying變數作為橋樑。首先在頂點著色器中定義一個屬性變數,用來接收我們指定的各個頂點的顏色;再定義個varying變數。在主處理函數中將接收到的顏色值賦給該varying變數。在片段著色器中定義一個同名同類型的varying變數,它的值就是我們指定的各個頂點的顏色值了。
修改後的片段著色器部分的代碼如下:
<script id="shader-fs" type="x-shader/x-fragment">
varying vec3 vv3Color;
void main(void)
{
gl_FragColor = vec4(vv3Color, 1.0);
}
</script>
如果你就以這樣的代碼執行的話,會發現編譯無法通過。此時,為了知道編譯失敗的原因,我們可以調用WebGL函數getShaderInfoLog(vertexShaderObject)。同樣,如果程式連結失敗,你也可以通過調用getProgramInfoLog(programObject)獲得連結失敗的具體原因。這兩個函數的傳回值都是js中的字串,我們將其alert之後,發現是說片段著色器中沒有指明float的精度。記住,頂點著色器提供了預設精度,而片段著色器沒有。關於精度的問題,請參考《OpenGL
ES 2.0 編程指南》的《第五章 OpenGL ES著色語言/精度修飾符》和《第十章 片斷著色器/精度修飾符》。
顏色屬性資料的其他動作都和位置類似。為了方便進行對比和提取通用模式,我作了一些整理,完整範例程式碼如下:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 v3Position;
attribute vec3 av3Color;
varying vec3 vv3Color;
void main(void)
{
vv3Color = av3Color;
gl_Position = vec4(v3Position, 1.0);
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
varying vec3 vv3Color;
void main(void)
{
gl_FragColor = vec4(vv3Color, 1.0);
}
</script>
<script>
function ShaderSourceFromScript(scriptID)
{
var shaderScript = document.getElementById(scriptID);
if (shaderScript == null) return "";
var sourceCode = "";
var child = shaderScript.firstChild;
while (child)
{
if (child.nodeType == child.TEXT_NODE ) sourceCode += child.textContent;
child = child.nextSibling;
}
return sourceCode;
}
var webgl = null;
var vertexShaderObject = null;
var fragmentShaderObject = null;
var programObject = null;
var triangleBuffer = null;
var v3PositionIndex = 0;
var triangleColorBuffer = null;
var v3ColorIndex = 1;
function Init()
{
var myCanvasObject = document.getElementById('myCanvas');
webgl = myCanvasObject.getContext("experimental-webgl");
webgl.viewport(0, 0, myCanvasObject.clientWidth, myCanvasObject.clientHeight);
vertexShaderObject = webgl.createShader(webgl.VERTEX_SHADER);
fragmentShaderObject = webgl.createShader(webgl.FRAGMENT_SHADER);
webgl.shaderSource(vertexShaderObject, ShaderSourceFromScript("shader-vs"));
webgl.shaderSource(fragmentShaderObject, ShaderSourceFromScript("shader-fs"));
webgl.compileShader(vertexShaderObject);
webgl.compileShader(fragmentShaderObject);
if(!webgl.getShaderParameter(vertexShaderObject, webgl.COMPILE_STATUS)){alert(webgl.getShaderInfoLog(vertexShaderObject));return;}
if(!webgl.getShaderParameter(fragmentShaderObject, webgl.COMPILE_STATUS)){alert(webgl.getShaderInfoLog(fragmentShaderObject));return;}
programObject = webgl.createProgram();
webgl.attachShader(programObject, vertexShaderObject);
webgl.attachShader(programObject, fragmentShaderObject);
webgl.bindAttribLocation(programObject, v3PositionIndex, "v3Position");
webgl.bindAttribLocation(programObject, v3ColorIndex, "av3Color");
webgl.linkProgram(programObject);
if(!webgl.getProgramParameter(programObject, webgl.LINK_STATUS)){alert(webgl.getProgramInfoLog(programObject));return;}
webgl.useProgram(programObject);
var jsArrayData = [
0.0, 1.0, 0.0,//上頂點
-1.0, -1.0, 0.0,//左頂點
1.0, 0.0, 0.0];//右頂點
triangleBuffer = webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayData), webgl.STATIC_DRAW);
var jsArrayColor = [
1.0, 0.0, 0.0,//上頂點
0.0, 1.0, 0.0,//左頂點
0.0, 0.0, 1.0];//右頂點
triangleColorBuffer = webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleColorBuffer);
webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayColor), webgl.STATIC_DRAW);
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
webgl.clear(webgl.COLOR_BUFFER_BIT);
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.enableVertexAttribArray(v3PositionIndex);
webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleColorBuffer);
webgl.enableVertexAttribArray(v3ColorIndex);
webgl.vertexAttribPointer(v3ColorIndex, 3, webgl.FLOAT, false, 0, 0);
webgl.drawArrays(webgl.TRIANGLES, 0, 3);
}
</script>
</head>
<body onload='Init()'>
<canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas>
</body>
</html>