之前對着色器的初始化都是使用的initShaders()
這個方法,部分代碼在文章最後,其內部分執行細節沒有去清晰的瞭解過,下面做進一步的瞭解和分析,才能更深入的瞭解webgl
的原理
initShaders()
方法的主要作用是編譯GLSL ES
代碼,創建和初始化着色器,以便供WebgGL
使用,具體初始化分爲以下七步;
- 1、創建着色器對象
gl.createShader()
- 2、向着色器對象中填充着色器程序的源代碼
gl.shaderSource()
- 3、編譯着色器
gl.compileShader()
- 4、創建程序對象
gl.createProgram()
- 5、爲程序對象分配着色器
gl.attachShader()
- 6、連接程序對象
gl.linkProgram()
- 7、使用程序對象
gl.useProgram()
1、兩個對象
着色器對象:着色器對象管理一個頂點着色器或者一個片元着色器
程序對象:程序對象是管理着着色器對象的容器
2、具體方法介紹
1、創建着色器對象 gl.createShader()
通過gl.createShader()
來創建着色器對象,gl.VERTEX_SHADER
或者gl.FRAGMENT
gl.createShader(type)
是WebGL
提供的一個方法,MDN
文檔這樣解釋
WebGLRenderingContext.createShader()
用於創建一個WebGLShader
着色器對象,該對象可以使用WebGLRenderingContext.shaderSource()
和WebGLRenderingContext.compileShader()
方法配置着色器代碼.
該方法接收一個參數type
type
參數爲gl.VERTEX_SHADER
或gl.FRAGMENT_SHADER
兩者中的一個
如果不再需要這個着色器可以使用gl.deleteShader()
將其刪除,但是如果着色器對象還在使用,那麼gl.deleteShader()
並不會立刻將其刪除,而是要等到程序對象不再使用該着色器後,纔將其刪除
2、指定着色器對象的代碼gl.shaderSource()
gl.shaderSource(shader, source)
方法,向着色器指定GLSL ES
源代碼,在js
中源代碼以字符串的形式存在;
3、編譯着色器gl.compileshader()
使用WebGLRenderingContext.compileShader()
用於編譯一個GLSL
着色器,使其成爲二進制數據,然後就可以被WebGLProgram
對象所使用
語法
void gl.compileShader(shader)
var shader = gl.createShader(gl.VERTEX_SHADER);// 創建着色器對象
gl.shaderSource(shader, shaderSource);// 指定着色器對象的代碼
gl.compileShader(shader);// 編譯着色器
當使用gl.compileShader()
方法時,如果着色器源代碼中存在錯誤,那麼就會出現編譯錯誤,可以使用gl.getShaderParameter()
來檢查着色器的狀態
有以下參數
gl.DELETE_STATUS
:標示着色器是否被刪除,刪除(GL_TRUE
)未刪除(GL_FALSE
).gl.COMPILE_STATUS
: 標示着色器是否編譯成功,是(GL_TRUE
)不是(GL_FALSE
)gl.SHADER_TYPE
: 標示着色器類型,是頂點着色器(gl.VERTEX_SHADER
)還是片段着色器(gl.FRAGMENT_SHADER
)
返回true
或者false
如果編譯失敗,返回false
,此時webgl
系統會把編譯錯誤的具體內容寫入着色器的 信息日誌,可以通過 gl.getShaderInfoLog()
來獲取報錯信息
4、創建程序對象gl.createProgram()
程序對象包含頂點着色器和片元着色器,可以調用gl.createProfram()
來創建程序對象,
該方法沒有參數,返回一個WebGLProgram
對象
一個
WebGLProgram
對象,由兩個編譯過後的WebGLShader
組成,頂點着色器和片元着色器,這些組合成一個可用的webgl
着色器程序
示例:
var program = gl.createProgram();
// 添加預先定義好的頂點着色器和片段着色器
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if ( !gl.getProgramParameter( program, gl.LINK_STATUS) ) {
var info = gl.getProgramInfoLog(program);
throw "Could not compile WebGL program. \n\n" + info;
}
類似的可以使用gl.deleteProgram()
方法來刪除程序對象
一旦程序對象被創建之後,需要向程序附上兩個着色器
5、爲程序對象分配着色器對象gl.attachShader()
webgl
要運行起來必須要有兩個着色器,頂點着色器和片元着色器,使用gl.attachShader()
方法,爲程序對象分配這兩個着色器
如剛纔上面的那段代碼,就是在程序對象被創建之後開始分配着色器對象
着色器在附給程序對象前,並不一定要爲其指定代碼或進行編譯,也就是說把空的着色器附給程序對象也是可以的,也可以使用gl.detachShader()
來解除分配給程序對象的着色器
6、連接程序對象gl.linkProgram()
在爲程序對象分配了兩個着色器對象後,還需要將着色器連接起來,使用gl.linkProgram()
方法來執行
程序對象連接操作目的時保證以下幾點:
- 頂點着色器和片元着色器的
varying
變量同名同類型,且一一對應 - 頂點着色器對每個
varying
變量賦了值 - 頂點着色器和片元着色器中的同名
uniform
變量也是同類型的 - 着色器中的
attribute
、uniform
、和varying
變量的個數沒有超過着色器的上限
如果程序已經連接成功了,那麼就會得到一個二進制的可執行模塊,供webgl
系統使用
需要注意的是,程序對象即使連接成功了,也有可能運行失敗,比如沒有爲取樣器分配紋理單元,這些錯誤是在運行階段而不是連接階段產生的,在運行階段錯誤檢查的開銷很大,所以通常只在調式程序時這樣做;
7、使用程序對象gl.useProgram()
最後通過gl.useProgram()
告知webgl
系統使用哪個對象
WebGLRenderingContext.useProgram()
方法將定義好的WebGLProgram
對象添加到當前的渲染狀態中
這個方法使得webgl
具有了一個強大的特性,那就是在繪製前準備多個程序對象,然後在繪製的時候根據需要切換程序對象
工具方法代碼
// cuon-utils.js (c) 2012 kanda and matsuda
/**
* Create a program object and make current
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return true, if the program object was created and successfully made current
*/
function initShaders(gl, vshader, fshader) {
var program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
}
// 使用程序對象
gl.useProgram(program);
// 將此時的程序對象夫hi給
gl.program = program;
return true;
}
/**
* Create the linked program object
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return created program object, or null if the creation has failed
*/
function createProgram(gl, vshader, fshader) {
// 創建着色器對象
var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
return null;
}
// 創建程序對象
var program = gl.createProgram();
if (!program) {
return null;
}
// 爲程序對象分配着色器對象
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 連接程序對象
gl.linkProgram(program);
// Check the result of linking
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var error = gl.getProgramInfoLog(program);
console.log('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
return program;
}
/**
* Create a shader object
* @param gl GL context
* @param type the type of the shader object to be created
* @param source shader program (string)
* @return created shader object, or null if the creation has failed.
*/
function loadShader(gl, type, source) {
// 創建着色器對象
var shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source);
// Compile the shader
gl.compileShader(shader);
// Check the result of compilation
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var error = gl.getShaderInfoLog(shader);
console.log('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
/**
* Initialize and get the rendering for WebGL
* @param canvas <cavnas> element
* @param opt_debug flag to initialize the context for debugging
* @return the rendering context for WebGL
*/
function getWebGLContext(canvas, opt_debug) {
// Get the rendering context for WebGL
var gl = WebGLUtils.setupWebGL(canvas);
if (!gl) return null;
// if opt_debug is explicitly false, create the context for debugging
if (arguments.length < 2 || opt_debug) {
gl = WebGLDebugUtils.makeDebugContext(gl);
}
return gl;
}