webgl值得重視的基礎構建

psychewang發表於2019-02-19

此篇文章的主要目的是鞏固自己對於建立webgl的時候一些知識點,主要參考了《webgl程式設計指南》以及_Hahn_的webgl環境搭建這篇文章,在此附上鍊接,方便大家檢視( juejin.im/post/5baaf3… )。

我會分為三篇文章進行總結,為什麼不在一篇文章中寫完呢?
因為我個人不太喜歡技術的文章寫的很長,那樣在讀的時候感覺很煩。

《webgl程式設計指南》這本書中知識點介紹的很詳細,因此我就不在此贅述那些基礎的知識點了,直接上程式碼。

實現一個紅色的長方形

最終我們要實現的效果:

webgl值得重視的基礎構建

相應的程式碼

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<script src="./lib/webgl-debug.js"></script>
	<script src="./lib/webgl-utils.js"></script>
	<script src="./lib/cuon-matrix.js"></script>
	<script src="./lib/cuon-utils.js"></script>
	<style>*{padding:0;margin:0;}</style>
</head>
<body onload="main()">
	<canvas id="canvas" width="750" height="1334"></canvas>
	<script type="text/javascript">
		var VSHADER_SOURCE = `
			attribute vec4 a_Position;
			varying vec2 uv;

			void main(){
				gl_Position = vec4(vec2(a_Position), 0.0, 1.0);
				uv = vec2(0.5, 0.5) * (vec2(a_Position) + vec2(1.0, 1.0));
			}
		`
		var FSHADER_SOURCE = `
			precision mediump float;
			varying vec2 uv;

			void main(){
				gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
			}
		`

		function main(){
			var canvas = document.getElementById(`canvas`);
			canvas.width = 750;
			canvas.height = 1334;

			var gl = getWebGLContext(canvas);
			if(!gl){
				console.log("Failed to get the tendering context for WevGl");
				return;
			}

			if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
				console.log("Failed to set the vertex information");
				return;
			}
			//清空畫布
			gl.clearColor(0.0, 0.0, 0.0, 1.0);
			gl.clear(gl.COLOR_BUFFER_BIT);
			
			var n = initVertexBuffers(gl);
			//繪製頂點
			gl.drawArrays(gl.TRIANGLE_FAN, 0, n);
			
		}

		function initVertexBuffers(gl){
			var verticesTexCoords = new Float32Array([
				1.0, -1.0,
				1.0, 1.0,
				-1.0, 1.0,
				-1.0, -1.0
			]);
			
			var n = 4;
			
			var vertexTexCoordBuffer = gl.createBuffer();
			if(!vertexTexCoordBuffer){
				console.log("Failed to create the buffer object");
				return -1;
			}
			
			gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
			gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

			var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
			
			var a_Position = gl.getAttribLocation(gl.program, `a_Position`);
			if(a_Position < 0){
				console.log("Failed to get the stroage location og a_Position");
			}
			gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 2, 0);
			
			gl.enableVertexAttribArray(a_Position);

			return n;

		}
	</script>
</body>
</html>

複製程式碼

接下來我們做具體的理解分析。

首先我們可以看到在開始我們引用了幾個js的檔案,這幾個檔案是《webgl程式設計指南》中的一些輔助函式的檔案,暫時我們只是應用,不深入的探究。

<script src="./lib/webgl-debug.js"></script>
<script src="./lib/webgl-utils.js"></script>
<script src="./lib/cuon-matrix.js"></script>
<script src="./lib/cuon-utils.js"></script>
複製程式碼

我們在DOM中可以看到一個canvas的結構,這個DOM結構就是告訴瀏覽器我們要一個畫布。以後繪製的webgl都會出現在這個canvas中。
接下來就是js這個重點了,我們在js中首先定義了一個頂點著色器。

var VSHADER_SOURCE = `
    attribute vec4 a_Position;
    varying vec2 uv;

    void main(){
        gl_Position = vec4(vec2(a_Position), 0.0, 1.0);
        uv = vec2(0.5, 0.5) * (vec2(a_Position) + vec2(1.0, 1.0));
    }
    `
複製程式碼

什麼是頂點著色器呢?

頂點著色器就是用來描述頂點的特性(例如位置、顏色等)的程式。頂點是指二維或三維空間中的一個點,比如二維或者三維圖形的端點或焦點。

在上面我們首先定義了一個attribute vec4 a_Position;這個a_Position變數的作用就是js向頂點著色器傳入頂點位置的入口,通俗來說就是告訴瀏覽器我們要在哪幾個點的範圍內進行“作畫”,因為我們現在作畫都是在二維平面上面進行的因此呢
gl_Position = vec4(vec2(a_Position), 0.0, 1.0);
這個賦值操作的第三個引數就是0.0,第四個引數我們就設定為預設的1.0。

而我們定義的uv變數就是紋理的座標,這個uv變數一般會傳入片元著色器中進行操作,你可將其理解為我們“作畫”的座標。
uv = vec2(0.5, 0.5) * (vec2(a_Position) + vec2(1.0, 1.0));
但是這個計算是什麼鬼呢?
首先我們在initVertexBuffers函式中可以看到

var verticesTexCoords = new Float32Array([
	1.0, -1.0,
	1.0, 1.0,
	-1.0, 1.0,
	-1.0, -1.0
]);
複製程式碼

這四個座標就是我們想要畫的影像的座標,也就是紋理座標了。

webgl值得重視的基礎構建

上面紅色框內就是我們的紋理座標。

webgl值得重視的基礎構建

上面的藍色框內就是我們著色器中的座標了。
我們將紅色座標系的點帶入那個計算公式(注意是逐分量計算)

A => vec2(0.5, 0.5) * (vec2(1.0, -1.0) + vec2(1.0, 1.0)) => (1.0, 0.0)

B => vec2(0.5, 0.5) * (vec2(1.0, 1.0) + vec2(1.0, 1.0)) => (1.0, 1.0)

C => vec2(0.5, 0.5) * (vec2(-1.0, -1.0) + vec2(1.0, 1.0)) => (0.0, 1.0)

D => vec2(0.5, 0.5) * (vec2(-1.0, -1.0) + vec2(1.0, 1.0)) => (0.0, 0.0)

最後的結果是紅色座標系中的點變成了藍色座標系中對應的點,這就是那個計算公式的作用。

OK到這裡頂點著色器就研究完成了,接下來就是片元著色器了

什麼是片元著色器呢?

片元著色器就是進行逐片元處理過程的程式,片元是一個webgl術語,你可以將其理解為畫素。

var FSHADER_SOURCE = `
	precision mediump float;
	varying vec2 uv;

	void main(){
	    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
	}
`
複製程式碼

在片元著色器中我們首先定義了precision mediump float;
這句話的作用就是一個精度的規定,不加的話,片元著色器這裡就會報錯。
接著我們使用了和在頂點著色器中相同的varying vec2 uv;接收了來自頂點著色器中的紋理座標資訊(雖然在上面的程式碼中我們沒有使用紋理座標,但是你需要知道如何傳入)。

重點是在main函式中我們給gl_FragColor賦值一個vec4型別的變數,這個賦值的作用就是告訴gl_FragColor使用什麼樣的顏色來進行渲染,vec4的四個值分別對應rgba。

js進行初始化操作

我們寫好了頂點著色器和片元著色器以後瀏覽器並不能理解那是什麼,因為對於瀏覽器來說那就是一些字串,因此我們需要使用js進行一些列的操作。
首先第一步,先獲取DOM中的canvas,然後設定它的寬和高。

var gl = getWebGLContext(canvas);
if(!gl){
	console.log("Failed to get the tendering context for WevGl");
	return;
}
複製程式碼

第二步,我們使用上面的程式碼將我們獲取的canvas變為了gl的畫布(通俗意義上你可以這麼理解),並且我們做了一個差錯處理

if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
	console.log("Failed to set the vertex information");
	return;
}
複製程式碼

第三步,初始化shaders,這函式就是我們引用lib資料夾下面的js檔案中存在的,在此你暫時使用就可以,此函式需要將我們的“畫布”也就是gl傳入,同時需要傳入頂點著色器變數和片元著色器變數。

//清空畫布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

var n = initVertexBuffers(gl);
//繪製頂點
gl.drawArrays(gl.TRIANGLE_FAN, 0, n);
複製程式碼

第四步,清空畫布開始作畫,我們使用gl的函式先將畫布清空,然後使用我們自己寫的initVertexBuffers函式初始化緩衝區(這個函式稍後會介紹),並且此函式會返回一個頂點的個數,最後我們使用gl的drawArrays函式開始繪製。

最後就是詳解我們自己寫的initVertexBuffers函式了,我們需要知道緩衝區是什麼?

緩衝區物件是webgl系統中的一塊儲存區,你可以在緩衝區物件中儲存想要繪製的所有頂點的資料。
在上面我們就說過傳入紋理座標的事情。

var verticesTexCoords = new Float32Array([
	1.0, -1.0,
	1.0, 1.0,
	-1.0, 1.0,
	-1.0, -1.0
]);
var n = 4;
複製程式碼

在函式開始我們首先定義了一個特殊的陣列,這個陣列的作用就是定義紋理的頂點座標,並且我們定義了n變數,這個變數表示我們想要建立的頂點個數。
接下來我們需要建立出緩衝區,並且使用。

建立緩衝區物件

var vertexTexCoordBuffer = gl.createBuffer();
if(!vertexTexCoordBuffer){
	console.log("Failed to create the buffer object");
	return -1;
}
複製程式碼

在上面的程式碼中我們使用傳入函式的gl的createBuffer方法將建立的緩衝區賦值給了vertexTexCoordBuffer變數,並且做了一個差錯判斷。

繫結緩衝區物件

gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
複製程式碼

在此我們使用gl的bindBuffer函式將我們建立的緩衝區進行了繫結,bindBuffer的第一個參數列示緩衝區物件中包含了頂點的資料,第二個引數就是我們之前建立的緩衝區物件。

將資料寫入緩衝區物件

gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
複製程式碼

bufferData函式的作用就是將我們在上面定義的陣列verticesTexCoords寫入緩衝區物件,此函式的第一個引數需要是gl.ARRAY_BUFFER或gl.ELEMENT_ARRAY_BUFFER,第一種引數和bindBufferbuffer表示的一樣都是頂點的資料,第二種參數列示頂點資料的索引值,第二個引數就是我們紋理的頂點陣列了,第三個參數列示程式將如何使用儲存在緩衝區物件中的資料,STATIC_DRAW表示只會向緩衝區物件中寫入一次資料,但需要繪製很多次。

將緩衝區物件分配給一個attribute變數

var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;

var a_Position = gl.getAttribLocation(gl.program, `a_Position`);
if(a_Position < 0){
	console.log("Failed to get the stroage location og a_Position");
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 2, 0);
複製程式碼

在上面的程式碼中我們首先定義了一下FSIZE變數,這個變數只是為了我們在賦值的時候方便操作,接著我們定義了a_Position變數,並且使用了gl的方法獲取到了我們在頂點著色器中定義的attribute型別的a_Position變數,最後我們使用了vertexAttribPointer方法為這個attribute變數進行賦值。

vertexAttribPointer函式的第一個引數就是我們待分配的attribute變數的儲存位置;
第二個引數是指定緩衝區每個頂點的分量個數(此處我們使用的是2);
第三個引數是指定資料的格式。
第四個引數傳入true或false,表明是否將非浮點型的資料歸一化。
第五個引數指定相鄰兩個頂點間的位元組數(因為我們在陣列中定義的是每兩個數值表示一個頂點,所以此處傳入2)。
最後一個參數列示緩衝區物件中的偏移量。

開啟attribute變數

gl.enableVertexAttribArray(a_Position);
複製程式碼

在最後我們需要開啟我們要使用的attribute變數,這樣一個完整的buffer就建立完成了。

經過上面的操作,我們就得到了一個長方形了。
以上的知識都是比較基礎,但確實是很重要的東西。
程式碼地址:gitee.com/wangtao_it_…
我本次的事例是此地址下面的basics.html檔案。

相關文章