WebGL 理论基础 - 基础概念 中 - 图1

前文我们对 WebGL 需要的着色器程序进行了简单介绍,本章将使用 WebGL 做一个简单的示例,这篇教程将编写一个简单的着色器程序对,并链接成一个着色器程序。

创建着色器程序

WebGL 只关心两件事:裁剪空间中的坐标值和颜色值,使用 WebGL 就是提供这两个信息。你需要提供两个着色器来做这两件事,一个顶点着色器提供裁剪空间坐标值,一个片断着色器提供颜色值。

无论画布有多大,裁剪空间的坐标范围永远是 -1 到 1 。这里有一个简单的 WebGL 示例,展示 WebGL 的简单用法。

让我们从顶点着色器开始:

  1. // 一个属性值,将会从缓冲中获取数据
  2. attribute vec4 a_position;
  3. // 所有着色器都有一个main方法
  4. void main() {
  5. // gl_Position 是一个顶点着色器主要设置的变量
  6. gl_Position = a_position;
  7. }

如果用 JavaScript 模拟 GLSL,当它运行的时候,你可以想象它做了类似以下的事情:

  1. // *** 伪代码!! ***
  2. var positionBuffer = [
  3. 0, 0, 0, 0,
  4. 0, 0.5, 0, 0,
  5. 0.7, 0, 0, 0,
  6. ];
  7. var attributes = {};
  8. var gl_Position;
  9. drawArrays(..., offset, count) {
  10. var stride = 4;
  11. var size = 4;
  12. for (var i = 0; i < count; ++i) {
  13. // 从positionBuffer复制接下来4个值给a_position属性
  14. attributes.a_position = positionBuffer.slice((offset + i) * stride, size);
  15. runVertexShader();// 运行顶点着色器
  16. ...
  17. doSomethingWith_gl_Position();
  18. }

实际情况没有那么简单,因为 positionBuffer 将会被转换成二进制数据(见后文),所以真实情况下从缓冲中读取数据有些麻烦,但是希望这个例子能够让你想象出顶点着色器是怎么执行的。

接下来我们需要一个片段着色器:

  1. // 片断着色器没有默认精度,所以我们需要设置一个精度
  2. // mediump是一个不错的默认值,代表“medium precision”(中等精度)
  3. precision mediump float;
  4. void main() {
  5. // gl_FragColor是一个片断着色器主要设置的变量
  6. gl_FragColor = vec4(1.0, 0.0, 0.5, 1.0); // 返回“瑞迪施紫色”
  7. }

上方我们设置 gl_FragColor1.0, 0.0, 0.5, 1.0,其中 1.0 代表红色通道的值,0.0 代表绿色通道的值,0.5 代表蓝色通道值,最后一个 1.0 表示阿尔法通道值。WebGL 中的颜色值范围是 0.0 到 1.0 。

现在有了两个着色器方法,让我们开始使用 WebGL。

首先需要一个 HTML 中的 canvas 对象:

  1. <canvas id="webgl-canvas"></canvas>

然后可以用 JavaScript 获取它:

  1. var canvas = document.getElementById("webgl-canvas");

创建一个 WebGL 渲染上下文(WebGLRenderingContext):

  1. var gl = canvas.getContext("webgl");
  2. if (!gl) {
  3. // 你不能使用WebGL!
  4. ...

现在需要编译着色器对,提交到 GPU。你可以利用 JavaScript 中创建字符串的方式创建 GLSL 字符串:用串联的方式(concatenating),用AJAX下载,用多行模板数据。或者在这个例子里,将它们放在非 JavaScript 类型的标签中。

  1. <script id="2d-vertex-shader" type="notjs">
  2. // 一个属性变量,将会从缓冲中获取数据
  3. attribute vec4 a_position;
  4. // 所有着色器都有一个main方法
  5. void main() {
  6. // gl_Position 是一个顶点着色器主要设置的变量
  7. gl_Position = a_position;
  8. }
  9. </script>
  10. <script id="2d-fragment-shader" type="notjs">
  11. // 片断着色器没有默认精度,所以我们需要设置一个精度
  12. // mediump是一个不错的默认值,代表“medium precision”(中等精度)
  13. precision mediump float;
  14. void main() {
  15. // gl_FragColor是一个片断着色器主要设置的变量
  16. gl_FragColor = vec4(1, 0, 0.5, 1); // 返回“瑞迪施紫色”
  17. }
  18. </script>

事实上,大多数三维引擎在运行时利用模板,串联等方式创建GLSL。对于这个教程中的例子来说,没有复杂到要在运行时创建 GLSL 的程度。

接下来我们使用的方法将会创建一个着色器,只需要上传 GLSL 数据,编译成着色器。你可能注意到这段代码没有任何注释,因为可以从方法名很清楚的了解方法的作用(这里作为翻译版本我还是稍微注释一下)。

  1. // 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
  2. function createShader(gl, type, source) {
  3. var shader = gl.createShader(type); // 创建着色器对象
  4. gl.shaderSource(shader, source); // 提供数据源
  5. gl.compileShader(shader); // 编译 -> 生成着色器
  6. var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  7. if (success) {
  8. return shader;
  9. }
  10. console.log(gl.getShaderInfoLog(shader));
  11. gl.deleteShader(shader);
  12. }

现在可以使用以上方法创建两个着色器:

  1. var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
  2. var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;
  3. var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
  4. var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

然后我们将这两个着色器 link(链接)到一个 program(着色程序):

  1. function createProgram(gl, vertexShader, fragmentShader) {
  2. var program = gl.createProgram();
  3. gl.attachShader(program, vertexShader);
  4. gl.attachShader(program, fragmentShader);
  5. gl.linkProgram(program);
  6. var success = gl.getProgramParameter(program, gl.LINK_STATUS);
  7. if (success) {
  8. return program;
  9. }
  10. console.log(gl.getProgramInfoLog(program));
  11. gl.deleteProgram(program);
  12. }

然后调用它:

  1. var program = createProgram(gl, vertexShader, fragmentShader);

到此为止我们已经创建了一个着色器程序,下一篇将完成这个 WebGL 示例。