Hello World

就像它在备注部分提到的那样,我们需要提供两个功能。顶点着色器和片段着色器

让我们从顶点着色器开始

// an attribute will receive data from a buffer
attribute vec4 position;

// all shaders have a main function
void main() {

  // gl_Position is a special variable a vertex shader 
  // is responsible for setting
  gl_Position = position;
}

如果整个事情是用 JavaScript 而不是 GLSL 编写的,你可以想象它会像这样使用

// *** PSUEDO CODE!! ***

var positionBuffer = [
  0, 0, 0, 0,
  0, 0.5, 0, 0,
  0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;

drawArrays(..., offset, count) {
  for (var i = 0; i < count; ++i) {
     // copy the next 4 values from positionBuffer to the position attribute
     attributes.position = positionBuffer.slice((offset + i) * 4, 4); 
     runVertexShader();
     ...
     doSomethingWith_gl_Position();
}

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

// fragment shaders don't have a default precision so we need
// to pick one. mediump, short for medium precision, is a good default.
precision mediump float;

void main() {
  // gl_FragColor is a special variable a fragment shader
  // is responsible for setting
  gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple 
}

上面我们将 gl_FragColor 设置为 1, 0, 0.5, 1,红色为 1,绿色为 0,蓝色为 0.5,alpha 为 1。WebGL 中的颜色从 0 到 1。

现在我们已经编写了 2 个函数,让我们开始使用 WebGL

首先,我们需要一个 HTML canvas 元素

 <canvas id="c"></canvas>

然后在 JavaScript 中我们可以看一下

 var canvas = document.getElementById("c");

现在我们可以创建一个 WebGLRenderingContext

 var gl = canvas.getContext("webgl");
 if (!gl) {
    // no webgl for you!
    ...

现在我们需要编译这些着色器以将它们放在 GPU 上,所以首先我们需要将它们变成字符串。你可以以正常的方式获取字符串。通过使用 AJAX 连接,将它们放在非 JavaScript 类型的脚本标记中,或者在这种情况下使用多行模板文字

var vertexShaderSource = `
// an attribute will receive data from a buffer
attribute vec4 position;

// all shaders have a main function
void main() {

  // gl_Position is a special variable a vertex shader 
  // is responsible for setting
  gl_Position = position;
}
`;

var fragmentShaderSource = `
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default
precision mediump float;

void main() {
  // gl_FragColor is a special variable a fragment shader
  // is responsible for setting
  gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple 
}
`;

然后我们需要一个函数来创建着色器,上传源并编译着色器

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);  
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

我们现在可以调用该函数来创建 2 个着色器

var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

然后,我们需要将这两个着色器链接到一个程序中

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var sucesss = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
}

并称之为

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

现在我们已经在 GPU 上创建了一个 GLSL 程序,我们需要为它提供数据。大多数 WebGL API 都是关于设置状态以向我们的 GLSL 程序提供数据。在这种情况下,我们对 GLSL 程序的唯一输入是 position,这是一个属性。我们应该做的第一件事是查找我们刚刚创建的程序的属性位置

var positionAttributeLocation = gl.getAttribLocation(program, "position");

属性从缓冲区获取数据,因此我们需要创建缓冲区

var positionBuffer = gl.createBuffer();

WebGL 允许我们在全局绑定点上操作许多 WebGL 资源。你可以将绑定点视为 WebGL 内部的全局变量。首先,将绑定点设置为资源。然后,所有其他函数通过绑定点引用资源。所以,让我们绑定位置缓冲区。

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

现在我们可以通过绑定点引用数据将数据放入该缓冲区

// three 2d points
var positions = [
  0, 0, 
  0, 0.5,
  0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

这里有很多事情要做。第一件事是我们有 positions 这是一个 JavaScript 数组。另一方面,WebGL 需要强类型数据,因此 new Float32Array(positions) 行创建一个 32 位浮点数的新数组,并复制 positions 中的值。gl.bufferData 然后将该数据复制到 GPU 上的 positionBuffer。它使用位置缓冲区,因为我们将它绑定到上面的 ARRAY_BUFFER 绑定点。

最后一个参数 gl.STATIC_DRAW 是 WebGL 关于我们如何使用数据的提示。它可以尝试使用该信息来优化某些事物。gl.STATIC_DRAW 告诉 WebGL 我们不太可能更改这些数据。

既然我们已将数据放入缓冲区,我们需要告诉属性如何从中获取数据。首先,我们需要打开属性

gl.enableVertexAttribArray(positionAttributeLocation);

然后我们需要指定如何拉出数据

var size = 2;          // 2 components per iteration
var type = gl.FLOAT;   // the data is 32bit floats
var normalize = false; // use the data as is
var stride = 0;        // 0 = move size * sizeof(type) each iteration
var offset = 0;        // start at the beginning of the buffer
gl.vertexAttribPointer(
   positionAttributeLocation, size, type, normalize, stride, offset)

gl.vertexAttribPointer 的一个隐藏部分是它将当前的 ARRAY_BUFFER 绑定到属性。换句话说,现在这个属性绑定到 positionBuffer,我们可以自由地将其他东西绑定到 ARRAY_BUFFER 绑定点。

请注意,从我们的 GLSL 顶点着色器的角度来看,position 属性是一个 vec4

attribute vec4 position;

vec4 是 4 浮点值。在 JavaScript 中你可以想到它像 position = {x: 0, y: 0, z: 0, w: 0}。上面我们设置了 size = 2。属性默认为 0, 0, 0, 1,因此该属性将从缓冲区中获取其前 2 个值(x 和 y)。z 和 w 分别是默认值 0 和 1。

毕竟我们终于可以要求 WebGL 执行 GLSL 程序了。

var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);

这将执行我们的顶点着色器 3 次。我们的顶点着色器中的第一次 position.xposition.y 将被设置为 positionBuffer 的前 2 个值。第二次 position.xy 将被设置为第二个 2 值。最后一次将其设置为最后 2 个值。

因为我们将 primitiveType 设置为 gl.TRIANGLES,所以每次我们的顶点着色器运行 3 次时,WebGL 将根据我们设置 gl_Position 的 3 个值绘制一个三角形。无论我们的画布大小是多少,这些值都在剪辑空间坐标中,每个方向从 -1 到 1。

因为我们的顶点着色器只是将我们的 positionBuffer 值复制到 gl_Position,所以三角形将在剪辑空间坐标处绘制

  0, 0, 
  0, 0.5,
  0.7, 0,

这些值如何转换为像素取决于 gl.viewport 设置。gl.viewport 默认为画布的初始大小。由于我们没有为画布设置大小,因此默认大小为 300x150。从剪辑空间转换为像素(在 WebGL 和 OpenGL 文献中通常称为屏幕空间)WebGL 将绘制一个三角形

 clip space      screen space
   0, 0       ->   150, 75
   0, 0.5     ->   150, 112.5
 0.7, 0       ->   255, 75

WebGL 现在将渲染该三角形。对于要绘制它的每个像素,它将调用我们的片段着色器。我们的片段着色器只是将 gl_FragColor 设置为 1, 0, 0.5, 1。由于 Canvas 是每通道 8 位画布,这意味着 WebGL 将把值 [255, 0, 127, 255] 写入画布。

我们仍然没有从评论中提到三件大事。纹理,变化和制服。每个都需要它自己的主题。