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.x
和 position.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]
写入画布。
我们仍然没有从评论中提到三件大事。纹理,变化和制服。每个都需要它自己的主题。