通过 gl = canvas.getContext('webgl') 来获取 WebGL 绘图上下文之后,一般可以写一个 initShader() 函数来直接调用, 从而初始化着色器。initShader() 函数是 WebGL 原生 API 是如何将 GLSL ES 代码编译为显卡中运行的着色器程序。分7个步骤:

  1. 创建着色器对象 gl.createShader()
  2. 指定着色器 GLSL ES 代码 gl.shaderSource()
  3. 编译着色器 gl.compileShader()
  4. 创建程序对象 gl.createProgram()
  5. 为程序对象分配着色器 gl.attachShader()
  6. 连接程序对象 gl.linkProgram()
  7. 使用程序对象 gl.useProgram()

1. 创建着色器对象: 使用 gl.createShader()
(1) 着色器代码写在js文件里,var 出来,GLSL ES 里的每一行都需要用引号把内容括起来 var VSHADER_SOURCE = 'void main(){' + 'gl_Position = vec4( 0.0, 0.0, 0.0, 1.0 );' + 'gl_PointSize = 10.0;' + '}'; var FSHADER_SOURCE = 'void main(){' + 'gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );' + '}'; var vertexShader = loadShader( gl, gl.VERTEX_SHADER, VSHADER_SOURCE ); var fragmentShader = loadShader( gl, gl.FRAGMENT_SHADER, FSHADER_SOURCE ); function loadShader( gl, type, source ) { var shader = gl.createShader( type ); if (shader == null) { console.log('unable to create shader'); return null; } } (2) 写在HTML的 <script> 标签里,加上id,从 DOM 中加载着色器 <script id="vertexShader" type="x-shader/x-vertex"> void main(void) { gl_Position = vec4( 0.0, 0.0, 0.0, 1.0 ); } </script> <script id="fragmentShader" type="x-shader/x-fragment"> void main(void) { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } </script> var vertexShader = getShader(gl, "vertexShader", gl.VERTEX_SHADER), fragmentShader = getShader(gl, "fragmentShader", gl.FRAGMENT_SHADER); function getShader(gl, id, type) { var shaderScript = document.getElementById(id); if(!shaderScript){ console.log('unable to get the element from DOM'); return; } var source = shaderScript.textContent; var shader = gl.createShader(type); if (shader == null) { console.log('unable to create shader'); return null; } } 2. 指定着色器 GLSL ES 代码:
gl.shaderSource( shader, source ); 3. 编译着色器: GLSL ES 更接近 C 或 C++,在使用之前需要编译成二进制可执行格式,WebGL 系统真正使用的是这种可执行格式。如果 GLSL ES 代码中存在错误,那么就会出现编译错误。可用 gl.getShaderParameter() 来检查着色器的状态。 gl.compileShader( shader ); var compiled = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); if (!compiled) { console.log('failed to compile shader: ' + gl.getShaderInfoLog( shader )); gl.deleteShader(shader); return null; } 4. 创建程序对象: 一个程序对象必须包含一个顶点着色器和一个片元着色器,WebGLProgram 负责将两个着色器使用在一个 WebGL 程序上。 var program = gl.createProgram(); if (!program) { return null; } 5. 为程序对象分配着色器: 为一个程序对象分配两个着色器 gl.attachShader( program, vertexShader ); gl.attachShader( program, fragmentShader ); 6. 连接程序对象: 将顶点着色器和片元着色器连接起来。以保证:
(1)顶点着色器和片元着色器的 varying 变量同名同类型,且一一对应
(2)顶点着色器对每一个 varying 变量赋了值
(3)顶点着色器和片元着色器的同名 uniform 变量同类型,无需一一对应
(4)attribute, uniform, varying 变量的个数没有超过着色器的上限
在连接着色器之后,应该坚持是否连接成功。调用 gl.getProgramParameter() 来实现。如果程序连接成功, 我们就得到一个二进制的可执行模块来供 WebGL系统使用;如果连接失败,也可调用 gl.getProgramInfoLog() 来获取出错信息。 gl.linkProgram( program ); var linked = gl.getProgramParameter( program, gl.LINK_STATUS ); if (!linked) { console.log( 'failed to link program: ' + gl.getProgramInfoLog( program ) ); gl.deleteProgram(program); gl.deleteShader( fragmentShader ); gl.deleteShader( vertexShader ); return null; } 7. 使用程序对象: WebGL 可在绘制器准备多个程序对象,然后在绘制的时候根据需要切换程序对象。 gl.useProgram( program ); gl.program = program; 总体代码: GLSL 写在HTML的 <script> 标签里,加上id function initShaders(gl, "vertexShader", "fragmentShader") { var vertexShader = getShader(gl, "vertexShader", gl.VERTEX_SHADER), fragmentShader = getShader(gl, "fragmentShader", gl.FRAGMENT_SHADER); var program = createProgram(gl, vertexShader, fragmentShader); if (!program) { console.log('failed to create program'); return false; } gl.useProgram(program); gl.program = program; return true; } // 从 DOM 中加载着色器 function getShader(gl, id, type) { var shaderScript = document.getElementById(id); if(!shaderScript){ console.log('unable to get the element from DOM'); return; } var source = shaderScript.textContent; var shader = gl.createShader(type); if (shader == null) { console.log('unable to create shader'); return null; } gl.shaderSource(shader, source); gl.compileShader(shader); 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; } function createProgram(gl, vertexShader, fragmentShader) { if (!vertexShader || !fragmentShader) { return null; } var program = gl.createProgram(); if (!program) { return null; } gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); 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; }