1. 程式人生 > >WebGL---1.第一個三角形

WebGL---1.第一個三角形

最近無意中接觸到WebGL,之前上半年學了一些opengl方面的知識,但知識這東西就是這樣只有溫故才能知新,打算正好藉助學習WebGL來複習一下opengl方面的知識,順便比較兩者之間的區別。


一、獲取WebGL上下文

一般都把WebGL上下文物件命名為gl。這樣做也可以保證 JavaScript 程式碼與 OpenGL 程式更相近。取得了 WebGL 上下文之後,就可以開始 3D 繪圖了。

首先,新建一個html檔案並加入以下程式碼:

<html>
	<canvas id='c' width='480' height='320'></canvas>
	<script>
		var canvas = document.getElementById('c');
        var names = ["webgl",
                  "experimental-webgl",
                  "webkit-3d", 
                  "moz-webgl"];

		for (var i = 0; i < names.length; ++i) {
			try {
			  	gl = canvas.getContext(names[i]);
			} 
			catch(e) {}
			if (gl) break;
		}

		if (gl == null){
		 	alert("WebGL is not available");
		}else{
			gl.clearColor(0,0,0.8,1);
			gl.clear(gl.COLOR_BUFFER_BIT);
		}
	</script>
</html>

使用瀏覽器執行,將會顯示一塊藍色區域。


二、緩衝區

頂點資訊儲存在js型別化陣列中,使用之前必須轉換到WebGL的緩衝區。
要建立緩衝區,可以呼叫gl.createBuffer() ,然後呼叫**gl.bindbuffer()**繫結到WebGL上下文。
緩衝是傳送到GPU的一些二進位制資料序列,通常情況下緩衝資料包括位置,法向量,紋理座標,頂點顏色值等。 你可以儲存任何資料。

建立VBO並且準備頂點資料:

// 1.建立緩衝區
var buffer = gl.createBuffer();

// 2.將buffer設定為上下文的當前緩衝區,此後,所有緩衝區的操作都直接在buffer中執行
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

// 3.準備頂點資料
var vertices = [-0.5, -0.5, 0.5, -0.5, 0, 0.5];

// 4.把頂點資料從CPU複製到當前繫結的GPU緩衝區
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),gl.STATIC_DRAW)

三、著色器

WebGL中有兩種著色器:頂點著色器和片段(或畫素)著色器。
頂點著色器用於將3D頂點轉換為渲染的2D頂點。
片段著色器用於準確計算要繪製的每個畫素的顏色。

向著色器傳遞資料的4種方式:

1. 屬性(attribute)

屬性用來指明怎麼從緩衝中獲取所需資料並將它提供給頂點著色器。 例如你可能在緩衝中用三個32位的浮點型資料儲存一個位置值。 對於一個確切的屬性你需要告訴它從哪個緩衝中獲取資料,獲取什麼型別的資料(三個32位的浮點資料), 起始偏移值是多少,到下一個位置的位元組數是多少。

緩衝不是隨意讀取的。事實上頂點著色器執行的次數是一個指定的確切數字, 每一次執行屬性會從指定的緩衝中按照指定規則依次獲取下一個值。

2.全域性變數(uniform)
全域性變數在著色程式執行前賦值,在執行過程中全域性有效。

3.可變數(varying)
可變數是一種頂點著色器給片斷著色器傳值的方式,依照渲染的圖元是點, 線還是三角形,頂點著色器中設定的可變數會在片斷著色器執行中獲取不同的插值。

4.紋理(texture)
紋理是一個數據序列,可以在著色程式執行中隨意讀取其中的資料。 大多數情況存放的是影象資料,但是紋理僅僅是資料序列, 你也可以隨意存放除了顏色資料以外的其它資料。

編寫Vertex Shader和Fragment Shader

var vs = 'attribute vec2 pos;' +
'void main(){ gl_Position = vec4(pos,0,1);}';
var fs = 'precision mediump float;' +
'void main(){ gl_FragColor = vec4(0,0.8,0,1);}';

編寫Shader Program
接下來,我們需要編譯,連結並最終生成我們的shader program,具體程式碼如下:

function createShader(str, type){
  var shader = gl.createShader(type);
  gl.shaderSource(shader,str);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw gl.getShaderInfoLog(shader);
  }
  return shader;
}

function createProgram(vstr,fstr){
  var program = gl.createProgram();
  var vshader = createShader(vstr,gl.VERTEX_SHADER);
  var fshader = createShader(fstr, gl.FRAGMENT_SHADER);
  gl.attachShader(program,vshader);
  gl.attachShader(program,fshader);
  gl.linkProgram(program);
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    throw gl.getProgramInfoLog(program);
  }
  return program;
}

//建立shader program
var program = createProgram(vs,fs);
gl.useProgram(program);

傳送頂點資料給Vertex Shader
首先,我們要獲取Vertex attribute的location。接著,啟用該attribute,最後呼叫vertexAttribPointer來傳遞頂點資料。

program.vertexPosAttrib = gl.getAttribLocation(program,'pos');
gl.enableVertexAttribArray(program.vertexPosAttrib);

var step = Float32Array.BYTES_PER_ELEMENT;
gl.vertexAttribPointer(program.vertexPosAttrib, 2, gl.FLOAT, false, 2*step, 0);

glVertexAttribPointer是一個專門用來解析頂點資料的函式。
第一個引數指定我們要配置的頂點屬性,這裡傳的是頂點屬性的位置值。
第二個引數指定頂點屬性的大小,這裡的頂點由x軸和y軸組成,所以大小是2。
第三個引數指定資料的型別,這裡是gl.FLOAT。
第四個個引數定義我們是否希望資料被標準化(Normalize)。如果我們設定為true,所有資料都會被對映到0(對於有符號型signed資料是-1)到1之間。我們把它設定為false。
第五個引數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由於下個組位置資料在2個float之後,我們把步長設定為2 * step。
第六個引數是偏移量,它表示資料在緩衝中起始位置的偏移量(Offset)。由於位置資料在陣列的開頭,所以這裡是0。

uniform變數傳值

var uColor = gl.getUniformLocation(program, "uColor");
gl.uniform4fv(uColor, [0, 0, 0, 1]);

四、繪製三角形

WebGL 只能繪製三種形狀:點、線和三角。其他所有形狀都是由這三種基本形狀合成之後,再繪 制到三維空間中的。執行繪圖操作要呼叫 gl.drawArrays()或 gl.drawElements()方法,前者用於 陣列緩衝區,後者用於元素陣列緩衝區。

gl.drawArrays()或 gl.drawElements()的第一個引數都是一個常量,表示要繪製的形狀。可 取值的常量範圍包括以下這些:

  • gl.POINTS:將每個頂點當成一個點來繪製。

  • gl.LINES:將陣列當成一系列頂點,在這些頂點間畫線。每個頂點既是起點也是終點,因此數
    組中必須包含偶數個頂點才能完成繪製。

  • gl.LINE_LOOP:將陣列當成一系列頂點,在這些頂點間畫線。線條從第一個頂點到第二個頂點,
    再從第二個頂點到第三個頂點,依此類推,直至最後一個頂點。然後再從最後一個頂點到第一
    個頂點畫一條線。結果就是一個形狀的輪廓。

  • gl.LINE_STRIP:除了不畫最後一個頂點與第一個頂點之間的線之外,其他與 gl.LINE_LOOP
    相同。

  • gl.TRIANGLES:將陣列當成一系列頂點,在這些頂點間繪製三角形。除非明確指定,每個三角
    形都單獨繪製,不與其他三角形共享頂點。

  • gl.TRIANGLES_STRIP:除了將前三個頂點之後的頂點當作第三個頂點與前兩個頂點共同構成一個新三角形外,其他都與 gl.TRIANGLES 相同。例如,如果陣列中包含 A、B、C、D 四個頂
    點,則第一個三角形連線 ABC,而第二個三角形連線 BCD。

  • gl.TRIANGLES_FAN:除了將前三個頂點之後的頂點當作第三個頂點與前一個頂點及第一個頂
    點共同構成一個新三角形外,其他都與 gl.TRIANGLES 相同。例如,如果陣列中包含 A、B、C、D 四個頂點,則第一個三角形連線 ABC,而第二個三角形連線 ACD。

      gl.drawArrays(gl.TRIANGLES,0,3);
    

第一個引數選擇上面的常量作為繪製型別。
第二個引數是陣列緩衝區中的起始索引。
第三個引數是陣列緩衝區中包含的頂點數(點的集合數)


五、例項程式碼

<html>
	<canvas id='c' width='480' height='320'></canvas>
	<script>
		var canvas = document.getElementById('c');
        var names = ["webgl",
                  "experimental-webgl",
                  "webkit-3d", 
                  "moz-webgl"];

		for (var i = 0; i < names.length; ++i) {
			try {
			  	gl = canvas.getContext(names[i]);
			} 
			catch(e) {}
			if (gl) break;
		}

		if (gl == null){
		 	alert("WebGL is not available");
		}else{
			gl.clearColor(0,0,0.8,1);
			gl.clear(gl.COLOR_BUFFER_BIT);
		}

		// 1.建立緩衝區
		var buffer = gl.createBuffer();

		// 2.將buffer設定為上下文的當前緩衝區,此後,所有緩衝區的操作都直接在buffer中執行
		gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

		// 3.準備頂點資料
		var vertices = [-0.5, -0.5, 0.5, -0.5, 0, 0.5];

		// 4.把頂點資料從CPU複製到當前繫結的GPU緩衝區
		gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices),gl.STATIC_DRAW)

		var vs = 'attribute vec2 pos;' +
		'void main(){ gl_Position = vec4(pos,0,1);}';
		var fs = 'precision mediump float;' +
		'void main(){ gl_FragColor = vec4(0,0.8,0,1);}';

		function createShader(str, type){
		  var shader = gl.createShader(type);
		  gl.shaderSource(shader,str);
		  gl.compileShader(shader);
		  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
		    throw gl.getShaderInfoLog(shader);
		  }
		  return shader;
		}

		function createProgram(vstr,fstr){
		  var program = gl.createProgram();
		  var vshader = createShader(vstr,gl.VERTEX_SHADER);
		  var fshader = createShader(fstr, gl.FRAGMENT_SHADER);
		  gl.attachShader(program,vshader);
		  gl.attachShader(program,fshader);
		  gl.linkProgram(program);
		  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
		    throw gl.getProgramInfoLog(program);
		  }
		  return program;
		}

		//建立shader program
		var program = createProgram(vs,fs);
		gl.useProgram(program);

		program.vertexPosAttrib = gl.getAttribLocation(program,'pos');
		gl.enableVertexAttribArray(program.vertexPosAttrib);

		var step = Float32Array.BYTES_PER_ELEMENT;
		gl.vertexAttribPointer(program.vertexPosAttrib, 2, gl.FLOAT, false, 2*step, 0);

		gl.drawArrays(gl.TRIANGLES,0,3);


	</script>
</html>