Skip to content
Zheng Ping edited this page Jun 10, 2015 · 4 revisions

WebGL基础

WebGL可以在你的浏览器里显示迷人的实时3D图形,但是很多人却不知道WebGL实际上是一个光栅化API,并非3D API。

WebGL只关心两个东西:剪切空间坐标和颜色。使用WebGL的程序员的工作就是用这两个东西来提供WebGL。你用两个阴影来实现它:一个顶点阴影提供剪切空间坐标,一个碎片阴影提供颜色。

不管你的canvas是什么尺寸,剪切空间坐标总是从-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(progam, 'a_position');

// 创建一个缓冲区然后把一个剪切空间矩形(两个三角形)放进去
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>

上面的代码就会在整个canvas区域里画一个绿色的矩形。

再强调一遍,剪切空间坐标总是从-1到1,它和canvas的尺寸没有关系。上面的例子我们除了把位置数据传了进去外什么也没做,你可能想用像素而不是剪切空间来绘制2D图形,下面是另一种顶点阴影:

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;

void main() {
  // 把矩形像素从0.0转化到1.0
  vec2 zeroToOne = a_position / u_resolution;
  // 把0->1转化成0->2
  vec2 zeroToTwo = zeroToOne * 2.0;
  // 把0->2转化成-1->1(也就是剪切空间)
  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);
// 把矩形的大小设置为10,20到80,30像素
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    10, 20,
    80, 20,
    10, 30,
    10, 30,
    80, 20,
    80, 30]), gl.STATIC_DRAW);

你可能会注意到矩形被绘制在了canvas区域的底部,WebGL把左下角作为坐标0,0。为了让它符合常见的以左上角为原点的2D绘图坐标,我们可以这样翻转坐标系:

gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

现在,让我们把定义矩形的代码放到一个函数中,这样我们就可以调用它来绘制各种大小的矩形。同样我们也可以把颜色参数化。我们需要一个定义了颜色输入的碎片阴影:

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision medium 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);
// 用随机颜色绘制50个矩形
for (var ii = 0; ii < 50; ++ii) {
  // 设置一个随机的矩形
  setRectangle(gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
  // 设置一个随机颜色
  gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);
  // 绘制矩形
  gl.drawArrays(gl.TRANGLES, 0, 6);
}
function randomInt(range) {
  return Math.floor(Math.random() * range);
}
// 用矩形的定义值来填充缓冲区
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本身是相当简单的2D API

如果你在WebGL方面还是新手,并且不知道GLSL是什么东西,你需要了解一些WebGL工作的原理。

type="x-shader/x-vertex"type="x-shader/x-fragment"是用来做什么的呢?对于<script>标签中的内容,如果没有指定type属性,或者type="javascript"或是type="text/javascript",浏览器默认会按照javascript来进行解析,如果type被指定为其它值,浏览器不会对之进行解析。

我们可以用这个特性来把阴影放在标签中,还可以通过type的值来决定是把阴影编译成顶点阴影还是碎片阴影。

上面例子中的createProgramFromScripts就会用指定的id查找脚本,并根据标签的type来决定要创建什么样的阴影。

Clone this wiki locally