參考
【《WebGL編程指南》讀書筆記】
【《WebGL編程指南》讀書筆記-WebGL概述】
【《WebGL編程指南》讀書筆記-WebGL入門】
關(guān)鍵詞:
- 頂點(diǎn)著色器
- 片元著色器
- 使用JS向著色器傳遞參數(shù)
一、使用Canvas畫個(gè)實(shí)心藍(lán)色矩形
//DrawRectangle.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Draw a blue rectangle (canvas version)</title>
</head>
<body onload="main()">
<canvas id="example" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
<script src="DrawRectangle.js"></script>
</body>
</html>
// DrawTriangle.js (c) 2012 matsuda
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('example');
if (!canvas) {
console.log('Failed to retrieve the <canvas> element');
return false;
}
// Get the rendering context for 2DCG
var ctx = canvas.getContext('2d');
// Draw a blue rectangle
ctx.fillStyle = 'rgba(0, 0, 255, 1.0)'; // Set color to blue
ctx.fillRect(120, 10, 150, 150); // Fill a rectangle with the color
}
二、清空繪圖區(qū)
上一個(gè)示例還不能嚴(yán)格認(rèn)為是WebGL的程序,而是<canvas>標(biāo)簽原本的2d方法和CanvasRenderingContext2D對(duì)象,下面進(jìn)入WebGL的簡(jiǎn)單示例。
//HelloCanvas.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Clear "canvas"</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="HelloCanvas.js"></script>
</body>
</html>
// HelloCanvas.js (c) 2012 matsuda
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Set clear color
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
}
1.getWebGLContext(canvas)
抹平不同瀏覽器之間的差異,返回一個(gè)3D的繪圖上下文。定義在cuon-utils.js中。也可以簡(jiǎn)單地使用這行代碼:
let gl = canvas.getContext('webgl');
2.gl.clearColor
在前面的二維圖形程序DrawRectangle中,顏色分量值在0到255之間。但是,由于WebGL是繼承自O(shè)penGL的,所以它遵循傳統(tǒng)的OpenGL顏色分量的取值范圍,即從0到1。RGB的值越高,顏色就越亮。類似地,第4分量值越高,顏色就越不透明。
一旦指定了背景色之后,背景色就會(huì)駐存在WebGL System中,在下一次調(diào)用gl.clearColor()方法前不會(huì)改變。換句話說,如果將來什么時(shí)候你還想用同一個(gè)顏色再清空一次繪圖區(qū),沒必要再指定一次背景色。
3.gl.clear(gl.COLOR_BUFF_BIT)
此方法繼承自O(shè)penGL,參數(shù)表示清空顏色緩沖區(qū)。除了顏色緩沖區(qū),還有深度緩沖區(qū)(gl.clearDepth)和模板緩沖區(qū)(gl.clearStencil)。
清空顏色緩沖區(qū)后,使用gl.clearColor指定的值,如果未指定,則使用默認(rèn)值(0.0,0.0,0.0,0.0)
三、繪制一個(gè)矩形點(diǎn)
在前面的DrawRectangle中,繪制一個(gè)矩形如下:
ctx.fillStyle = 'rgba(0, 0, 255, 1.0)';
ctx.fillRect(120, 10, 150, 150);
你可能會(huì)認(rèn)為WebGL也差不多,比如:
gl.drawColor(1.0,0.0,0.0,1.0);
gl.drawPoint(0,0,0,10);//點(diǎn)的位置和大小
不幸的是,事情沒這么簡(jiǎn)單。WebGL依賴于一種新的稱為著色器(shader)的繪圖機(jī)制。
//HelloPoint1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Draw a point (1)</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="HelloPoint1.js"></script>
</body>
</html>
// HelloPoint1.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'void main() {\n' +
// Set the vertex coordinates of the point
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' +
// Set the point size
' gl_PointSize = 10.0;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Specify the color for clearing <canvas>
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw a point
gl.drawArrays(gl.POINTS, 0, 1);
}
在代碼中,出現(xiàn)了頂點(diǎn)著色器和片元著色器。它們是以字符串的形式嵌入在js文件中。
1.頂點(diǎn)著色器 Vertex shader
用來描述頂點(diǎn)特性(如位置、顏色等)
2.片元著色器 Fragment shader
進(jìn)行逐片元處理過程如光照的程序。片元是一個(gè)WebGL術(shù)語(yǔ),可以理解為像素(圖像的單元)。
本書后續(xù)部分將仔細(xì)研究著色器。簡(jiǎn)單地說,在三維場(chǎng)景中,僅僅用線條和顏色把圖形畫出來是遠(yuǎn)遠(yuǎn)不夠的。你必須考慮,比如,光線照上去之后,或者觀察者的視角發(fā)生變化,對(duì)場(chǎng)景會(huì)有什么影響。著色器可以高度靈活地完成這些工作,提供各種渲染效果。這也是當(dāng)今計(jì)算機(jī)制作出的三維場(chǎng)景如此逼真和令人震撼的原因。
3.GLSL ES 著色器語(yǔ)言
因?yàn)橹鞔a必須預(yù)先處理成單個(gè)字符串的形式,所以我們用+號(hào)將多行字符串連成一個(gè)長(zhǎng)字符串。第一行以\n結(jié)束,這是由于當(dāng)著色器內(nèi)部出錯(cuò)時(shí),就能獲取出錯(cuò)的行號(hào),這對(duì)于檢查源代碼中的錯(cuò)誤很有幫助。但是,\n并不是必須的,也可不用它。
和C語(yǔ)言類似,必須包含一個(gè)main函數(shù)。gl_Position 和gl_PointSize 這兩個(gè)變量是內(nèi)置在頂點(diǎn)著色器中的,而且有著特殊的含義:一個(gè)表示位置,一個(gè)表示尺寸。
gl_Position 必須被賦值,gl_PointSize 并不是必須的,如果不賦值,會(huì)取默認(rèn)值1.0.
GLSL ES語(yǔ)言是強(qiáng)類型的,如果你將gl_PointSize = 10.0改為gl_PointSize = 10就會(huì)報(bào)錯(cuò)。因?yàn)橐髠魅胍粋€(gè)浮點(diǎn)數(shù),而10是一個(gè)整形數(shù)。
gl_Position = vec4(0.0, 0.0, 0.0, 1.0)需要傳入四個(gè)浮點(diǎn)數(shù)的分量,這被稱為齊次坐標(biāo)。
齊次坐標(biāo)能夠提高處理三維數(shù)據(jù)的效率,所以在三維圖形系統(tǒng)中被大量使用。齊次坐標(biāo)使用如下符號(hào)描述:(x,y,z,w),它等價(jià)于三維坐標(biāo)(x/w,y/w,z/w)。所以如果齊次坐標(biāo)的第4個(gè)分量是1,你就可以將它當(dāng)作三維坐標(biāo)來使用。w的值必須是大于等于0的。如果w趨近于0,那么它所表示的點(diǎn)將趨近無(wú)窮遠(yuǎn),所以在齊次坐標(biāo)系中可以有無(wú)窮的概念。齊次坐標(biāo)的存在,使得用矩陣乘法來描述頂點(diǎn)變換成為可能,三維圖形系統(tǒng)在計(jì)算過程中,通常使用齊次坐標(biāo)來表示頂點(diǎn)的三維坐標(biāo)。
4.initShaders()
對(duì)字符串形式的著色器進(jìn)行初始化,它被定義在cuon.util.js中,將在本書第9章研究?jī)?nèi)部細(xì)節(jié)。
5.繪制操作gl.drawArrays(gl.POINTS,0,1);
第一個(gè)參數(shù)為mode,指定了繪制方式,可接收以下常量符號(hào):
- gl.POINTS
- gl.LINES
- gl.LINE_STRIP
- gl.LINE_LOOP
- gl.TRIANGLES
- gl.TRIANGLE_STRIP
- gl.TRIANGLE_FAN
第二個(gè)參數(shù)為first,指定從哪個(gè)頂點(diǎn)開始繪制(整形數(shù))
第三個(gè)參數(shù)為count,指定繪制需要用到多少個(gè)頂點(diǎn)(整形數(shù))
因?yàn)槲覀兝L制的是單獨(dú)的點(diǎn),所以第一個(gè)參數(shù)設(shè)置成gl.POINTS;設(shè)置第二個(gè)參數(shù)為0,表示從第1個(gè)頂點(diǎn)開始畫起(雖然只有1個(gè)頂點(diǎn));設(shè)置第三個(gè)參數(shù)為1,表示僅繪制1個(gè)點(diǎn)
6.GLSL vs HLSL vs Cg
參考
GLSL vs HLSL vs Cg
GLSL 到 HLSL 參考
三大 Shader 編程語(yǔ)言(CG/HLSL/GLSL)
Shader language目前有3種主流語(yǔ)言:基于OpenGL的GLSL(OpenGL Shading Language,也稱為GLslang),基于Direct3D的HLSL(High Level Shading Language),還有NVIDIA公司的Cg (C for Graphic)語(yǔ)言。
GLSL與HLSL分別提基于OpenGL和Direct3D的接口,兩者不能混用,事實(shí)上OpenGL和Direct3D一直都是冤家對(duì)頭,曹操和劉備還有一段和平共處的甜美時(shí)光,但OpenGL和Direct3D各自的東家則從來都是爭(zhēng)斗不休。爭(zhēng)斗良久,既然沒有分出勝負(fù),那么必然是兩敗俱傷的局面。
首先ATI系列顯卡對(duì)OpenGL擴(kuò)展支持不夠,例如我在使用OSG(Open Scene Graphic)開源圖形引擎時(shí),由于該引擎完全基于OpenGL,導(dǎo)致其上編寫的3D仿真程序在較老的顯卡上常常出現(xiàn)紋理無(wú)法顯示的問題。其次GLSL 的語(yǔ)法體系自成一家,而HLSL和Cg語(yǔ)言的語(yǔ)法基本相同,這就意味著,只要學(xué)習(xí)HLSL和Cg中的任何一種,就等同于學(xué)習(xí)了兩種語(yǔ)言。不過OpenGL 畢竟圖形API的曾經(jīng)領(lǐng)袖,通常介紹OpenGL都會(huì)附加上一句“事實(shí)上的工業(yè)標(biāo)準(zhǔn)”,所以在其長(zhǎng)期發(fā)展中積累下的用戶群龐大,這些用戶當(dāng)然會(huì)選擇 GLSL學(xué)習(xí)。此外,GLSL繼承了OpenGL的良好移植性,一度在unix等操作系統(tǒng)上獨(dú)領(lǐng)風(fēng)騷(已是曾經(jīng)的往事)。
微軟的HLSL移植性較差,在windows平臺(tái)上可謂一家獨(dú)大,可一出自己的院子(還好院子夠大),就是落地鳳凰不如雞。這一點(diǎn)在很大程度上限制了 HLSL的推廣和發(fā)展。目前HLSL多半都是用于游戲領(lǐng)域。我可以負(fù)責(zé)任的斷言,在Shader language領(lǐng)域,HLSL可以憑借微軟的老本成為割據(jù)一方的諸侯,但,決不可能成為君臨天下的霸主。這和微軟現(xiàn)在的局面很像,就是一個(gè)被帶刺鮮花簇?fù)碇拇筘?cái)主,富貴已極,寸步難行。
Unity官方手冊(cè)上講Shader程序嵌入的小片段是用Cg/HLSL編寫的,從“CGPROGRAM”開始,到“CGEND”結(jié)束。所以,Unity官方主要是用Cg/HLSL編寫Shader程序片段。Unity官方手冊(cè)也說明對(duì)于Cg/HLSL程序進(jìn)行擴(kuò)展也可以使用GLSL,不過Unity官方建議使用原生的GLSL進(jìn)行編寫和測(cè)試。如果不使用原生GLSL,你就需要知道你的平臺(tái)必須是Mac OS X、OpenGL ES 2.0以上的移動(dòng)設(shè)備或者是Linux。在一般情況下Unity會(huì)把Cg/HLSL交叉編譯成優(yōu)化過的GLSL。因此我們有多種選擇,我們既可以考慮使用Cg/HLSL,也可以使用GLSL。不過由于Cg/HLSL更好的跨平臺(tái)性,更傾向于使用Cg/HLSL編寫Shader程序。
7.與unity中的shader對(duì)比
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/Chapter5-SimpleShader"
{
SubShader
{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION):SV_POSITION{
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
return UnityObjectToClipPos(v);
}
fixed4 frag():SV_Target{
return fixed4(1.0,1.0,1.0,1.0);
}
ENDCG
}
}
}
POSITION 和SV_POSITION都是CG/HLSL中的語(yǔ)義,是不可忽略的,POSITION告訴Unity把模型的頂點(diǎn)坐標(biāo)填充到輸入?yún)?shù)v中,SV_POSITION告訴unity頂點(diǎn)著色器的輸出是裁剪空間中的頂點(diǎn)坐標(biāo)。
UnityObjectToClipPos,是將頂點(diǎn)坐標(biāo)從模型空間轉(zhuǎn)換到剪裁空間中。
在本例中frag 函數(shù)沒有任何輸入,它的輸出是一個(gè)fixed4 類型的變量,并使用了SV_Target語(yǔ)義進(jìn)行限定,它等于告訴渲染器,把用戶的輸出顏色存儲(chǔ)到一個(gè)渲染 目標(biāo)(render target)中,這里將輸出到默認(rèn)的幀緩存中。片元著色器輸出的顏色的每個(gè)分量范圍在[0,1],其中(0,0,0)表示黑色,(1,1,1)表示白色。
現(xiàn)在來對(duì)比一下WEBGL的版本:
// Vertex shader program
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
' gl_PointSize = 10.0;\n' + // Set the point size
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
'}\n';
和POSITION 和SV_POSITION類似,GLSL中也有內(nèi)置變量gl_Position 和gl_PointSize。片元著色器寫法類似。
四、將頂點(diǎn)坐標(biāo)從JS傳到著色器程序中
在上面的HelloPoint1例子中,點(diǎn)的位置寫死在著色器程序中,缺乏擴(kuò)展性。有兩種方式可以把位置信息通過JS傳給著色器:
- attribute變量 傳輸?shù)氖悄切┡c頂點(diǎn)相關(guān)的數(shù)據(jù)
- uniform變量 傳輸那些對(duì)所有頂點(diǎn)都相同(或與頂點(diǎn)無(wú)關(guān))的數(shù)據(jù)
本例使用attribute變量來傳輸頂點(diǎn)坐標(biāo),顯然不同的頂點(diǎn)通常具有不同的坐標(biāo)。
// HelloPint2.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // attribute variable
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Get the storage location of a_Position
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}
// Pass vertex position to attribute variable
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
// Specify the color for clearing <canvas>
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw
gl.drawArrays(gl.POINTS, 0, 1);
}
1.著色器代碼
- 聲明一個(gè)attribute變量 叫a_Position(注:本書所有attribute變量都以a_前綴開始,同理uniform變量以u(píng)_前綴開始)
- 把a(bǔ)_Position賦值給gl_Position
- 這樣JS就可以向a_Position傳輸數(shù)據(jù)了
2.JS代碼
拿到暴露的變量:
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');返回的是個(gè)存儲(chǔ)地址,為了便于理解,本書中,存儲(chǔ)著色器變量地址的js變量名稱與著色器中的變量名稱保持一致。傳輸數(shù)據(jù):
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);第2、3、4個(gè)參數(shù)是三個(gè)浮點(diǎn)型數(shù)值,即點(diǎn)的xyz坐標(biāo)值。
3.gl.vertexAttrib3f的同族函數(shù)
你可能已經(jīng)注意到,第4行的a_Position變量是vec4類型的,但是gl.vertex-Attrib3f()僅傳了三個(gè)分量值。是不是漏掉了1個(gè)呢?實(shí)際上,如果你省略了第4個(gè)參數(shù),這個(gè)方法就會(huì)默認(rèn)地將第4個(gè)分量設(shè)置為1.0。
gl.vertexAttrib3f是一系列同族函數(shù)中的一個(gè),gl.vertexAttrib1f傳輸1個(gè)單精度值,相應(yīng)的23分量默認(rèn)為0.0,4分量默認(rèn)1.0。
gl.vertexAttrib4f則傳輸4個(gè)值。
你也可以使用這些方法的矢量版本,它們的名字以v(vector)結(jié)尾,并接受類型化數(shù)組作為參數(shù),函數(shù)名中的數(shù)字表示數(shù)組中的元素個(gè)數(shù),比如:
var position = new Float32Array([1.0, 2.0, 3.0, 1.0]);
gl.vertexAttrib4fv(a_Position, position);
4.改變點(diǎn)的大小
注意是float類型
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute float a_PointSize; \n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = a_PointSize;\n' +
'}\n';
var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
gl.vertexAttrib1f(a_PointSize, 5.0);
五、通過鼠標(biāo)點(diǎn)擊繪點(diǎn)
//ClickedPoints.js
...
// Register function (event handler) to be called on a mouse press
canvas.onmousedown = function(ev){ click(ev, gl, canvas, a_Position); };
// Specify the color for clearing <canvas>
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
}
var g_points = []; // The array for the position of a mouse press
function click(ev, gl, canvas, a_Position) {
var x = ev.clientX; // x coordinate of a mouse pointer
var y = ev.clientY; // y coordinate of a mouse pointer
var rect = ev.target.getBoundingClientRect() ;
x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);
// Store the coordinates to g_points array
g_points.push(x); g_points.push(y);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
var len = g_points.length;
for(var i = 0; i < len; i += 2) {
// Pass the position of a point to a_Position variable
gl.vertexAttrib3f(a_Position, g_points[i], g_points[i+1], 0.0);
// Draw
gl.drawArrays(gl.POINTS, 0, 1);
}
}
省略了部分代碼,主要難點(diǎn)是坐標(biāo)轉(zhuǎn)換和使用數(shù)組記錄點(diǎn)擊位置。WebGL使用的是顏色緩沖區(qū),繪制結(jié)束后系統(tǒng)將緩沖區(qū)內(nèi)容顯示在屏幕上,然后顏色緩沖區(qū)就會(huì)被重置,其中的內(nèi)容會(huì)丟失( 這是默認(rèn)操作,下一章將詳細(xì)討論)。因此,我們有必要將每次鼠標(biāo)點(diǎn)擊的位置都記錄下來,每次點(diǎn)擊后,程序都重新繪制了從第一次點(diǎn)擊到最近一次點(diǎn)擊中所有的點(diǎn)。
現(xiàn)在,讓我們看看,如果不執(zhí)行gl.clear(gl.COLOR_BUFFER_BIT);會(huì)怎么樣:
首先會(huì)看到黑色的背景,第一次點(diǎn)擊鼠標(biāo)后,背景就變成了白色,然后繪制了一個(gè)紅點(diǎn)。這是因?yàn)槔L制點(diǎn)之后,顏色緩沖區(qū)被WebGL重置為默認(rèn)的顏色(0.0,0.0,0.0,0.0)。這個(gè)默認(rèn)顏色的alpha分量是0.0,因此canvas就成了透明的了,可以看到網(wǎng)頁(yè)的背景顏色(這里還是白色)。如果你不希望這樣,應(yīng)當(dāng)在每次繪制之前都調(diào)用gl.clear來用指定的背景色清空。
1.瀏覽器、canvas、webgl坐標(biāo)系統(tǒng)相互轉(zhuǎn)換
通過event可以獲得鼠標(biāo)點(diǎn)擊的位置(瀏覽器頁(yè)面坐標(biāo)),該坐標(biāo)需要轉(zhuǎn)換到canvas坐標(biāo)系統(tǒng)下,再轉(zhuǎn)換到webgl坐標(biāo)系統(tǒng)下

var x = ev.clientX; // 鼠標(biāo)點(diǎn)擊處的x坐標(biāo)
var y = ev.clientY; // 鼠標(biāo)點(diǎn)擊處的y坐標(biāo)
var rect = ev.target.getBoundingClientRect();
ev.target表示canvas元素,getBoundingClientRect用于獲取某個(gè)元素相對(duì)于視窗的位置集合,返回包含top、left、bottom、right、width和height屬性的對(duì)象。

所以,轉(zhuǎn)換到canvas坐標(biāo)系下就是:
x - rect.left
y - rect.top
然后,看一下WEBGL坐標(biāo)系,位于屏幕中心點(diǎn),并且Y軸方向與canvas坐標(biāo)系相反

所以,轉(zhuǎn)到WEBGL坐標(biāo)系后就是:
x - rect.left - canvas.width/2
-(y - rect.top - canvas.height/2)
然后,WEBGL坐標(biāo)系要?dú)w一化,區(qū)間為[-1,1]。以x值舉例,最小為0,最大為canvas.width,轉(zhuǎn)到webgl坐標(biāo)系后,中心點(diǎn)變成canvas.width/2。所以,歸一化只要除以canvas.width/2即可。
[x - rect.left - canvas.width/2] / (canvas.width/2)
[-(y - rect.top - canvas.height/2)] / (canvas.height/2)
這個(gè)結(jié)果再化簡(jiǎn)一下,就是代碼常常看到的如下形式:
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
六、改變點(diǎn)的顏色
可以用uniform變量將顏色值傳給片元著色器,而不是頂點(diǎn)著色器。
// ColoredPoint.js (c) 2012 matsuda
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'precision mediump float;\n' +
'uniform vec4 u_FragColor;\n' + // uniform變量
'void main() {\n' +
' gl_FragColor = u_FragColor;\n' +
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// // Get the storage location of a_Position
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}
// Get the storage location of u_FragColor
var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
if (!u_FragColor) {
console.log('Failed to get the storage location of u_FragColor');
return;
}
// Register function (event handler) to be called on a mouse press
canvas.onmousedown = function(ev){ click(ev, gl, canvas, a_Position, u_FragColor) };
// Specify the color for clearing <canvas>
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
}
var g_points = []; // The array for the position of a mouse press
var g_colors = []; // The array to store the color of a point
function click(ev, gl, canvas, a_Position, u_FragColor) {
var x = ev.clientX; // x coordinate of a mouse pointer
var y = ev.clientY; // y coordinate of a mouse pointer
var rect = ev.target.getBoundingClientRect();
x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);
// Store the coordinates to g_points array
g_points.push([x, y]);
// Store the coordinates to g_points array
if (x >= 0.0 && y >= 0.0) { // 第一象限
g_colors.push([1.0, 0.0, 0.0, 1.0]); // Red
} else if (x < 0.0 && y < 0.0) { // 第三象限
g_colors.push([0.0, 1.0, 0.0, 1.0]); // Green
} else { // 其它
g_colors.push([1.0, 1.0, 1.0, 1.0]); // White
}
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
var len = g_points.length;
for(var i = 0; i < len; i++) {
var xy = g_points[i];
var rgba = g_colors[i];
// Pass the position of a point to a_Position variable
gl.vertexAttrib3f(a_Position, xy[0], xy[1], 0.0);
// Pass the color of a point to u_FragColor variable
gl.uniform4f(u_FragColor, rgba[0], rgba[1], rgba[2], rgba[3]);
// Draw
gl.drawArrays(gl.POINTS, 0, 1);
}
}
1.精度限定詞
'precision mediump float;\n'
用來指定變量的范圍(最大值與最小值)和精度,本例中為中等精度。第5章將會(huì)詳細(xì)討論精度的問題。
2. gl.uniform4f
與gl.vertexAttrib3f類似,也有同族函數(shù)