WebGL 基本原理
WebGL 的出现使得在浏览器上面实时显示 3D 图像成为,WebGL 本质上是基于光栅化的 API ,而不是基于 3D 的 API。
WebGL 只关注两个方面,即投影矩阵的坐标和投影矩阵的颜色。使用 WebGL 程序的任务就是实现具有投影矩阵坐标和颜色的 WebGL 对象即可。可以使用“着色器”来完成上述任务。顶点着色器可以提供投影矩阵的坐标,片段着色器可以提供投影矩阵的颜色。
无论要实现的图形尺寸有多大,其投影矩阵的坐标的范围始终是从 -1 到 1 。下面是一个关于实现 WebGL 对象的一个简单例子。
// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl");
// setup a GLSL program
var program = createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
下面是两个着色器。
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(0, 1, 0, 1); // green
}
</script>
它将绘出一个绿色的长方形来填充整个画板。
后面内容还会更精彩,我们继续:-P
我们再次降调一下,无论画板尺寸多大,投影矩阵坐标的范围只会在 -1 到 1 之间。从上面的例子中,我们可以看出我们只是将位置信息直接写在了程序里。 因为位置信息已经在投影矩阵中,所以并没有其他额外的工作要做。 如果想实现 3D 的效果,那么可以使用着色器来将 3D 转换为投影矩阵,这是因为 WebGL 是基于光栅的 API。
对于 2D 的图像,也许会使用像素而不是投影矩阵来表述尺寸,那么这里我们就更改这里的着色器,使得我们实现的矩形可以以像素的方式来度量,下面是新的顶点着色器。
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
}
</script>
下面我们将我们的数据从投影矩阵改为像素。
// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
10, 20,
80, 20,
10, 30,
10, 30,
80, 20,
80, 30]), gl.STATIC_DRAW);
上面例子矩阵位于底部边框,下面我们让它位于左上边框:
修改代码如下:
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
效果如下图:
下面我们将上述关于矩阵的实现写成函数以便可以以函数调用的方式来实现不同尺寸的矩阵。 然而,这里的颜色应该是可变的。
首先,我们为片段着色器设计一个关于颜色的输入。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script>
下面是实现绘画 50 个尺寸和颜色均随机的矩阵的代码。
var colorLocation = gl.getUniformLocation(program, "u_color");
...
// Create a buffer
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw 50 random rectangles in random colors
for (var ii = 0; ii < 50; ++ii) {
// Setup a random rectangle
setRectangle(
gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
// Set a random color.
gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// Fills the buffer with the values that define a rectangle.
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
下面就是实现出来的效果。
到此可以看出 WebGL 实质上是一种轻量级的 API。但是,它可以实现较为复杂的 3D 效果,其复杂性由程序定制。WebGL API 本身是 2D 的且相对比较简单。