在WebGL环境中添加2D内容
本系列文章翻译自:https://developer.mozilla.org/en/WebGL
本文地址:https://developer.mozilla.org/en/WebGL/Adding_2D_content_to_a_WebGL_context
翻译说明:水平有限并且不会逐字逐句翻译,只保证符合原文意思。
一旦你成功创建WebGL环境,就可以开始在里面渲染内容了。最简单的莫过于画一个没有任何纹理的2D图形,所以我们将从这里开始,编写代码来画一个方形。
设置场景的光源
在开始之前你需要理解这一点:即使我们画的是二维图形,也是在三维空间绘制的。所以我们除了绘制形状以外仍然需要建立着色器(shader)来照亮这个简单的场景。
初始化着色器
着色器是通过OpenGL ES着色语言来定义的。为了使我们的内容更方便的维护和更新,我们可以写一段代码来加载着色器,这样在整个HTML文档中都可以使用。下面就是initShaders()函数的内容,它来负责加载着色器。
function initShaders() {
var fragmentShader = getShader(gl, "shader-fs");
var vertexShader = getShader(gl, "shader-vs");
// 创建着色器程序
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 如果失败则进行提示
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Unable to initialize the shader program.");
}
gl.useProgram(shaderProgram);
vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(vertexPositionAttribute);
}
这里,代码首先加载了两个着色器程序;第一个是片段着色器,它是从一个id为“shader-fs”的script元素中加载的。第二个是顶点着色器,从id为“shader-vs”的script元素加载。下一节我们将看到getShader()函数的内容,它用来从DOM中获取着色器程序。
接着调用WebGL对象的createProgram()方法来创建着色器程序,并将之前创建的两个着色器附加到WebGL对象上,再连接到着色器程序中。之后,我们检测gl对象的LINK_STATUS参数确保程序连接成功。如果连接成功,我们就激活了这个新的着色程序。
从DOM中加载着色器
getShader()函数通过id名从DOM中获取着色器并返回给调用者,如果无法找到着色器或者编译失败则返回null。
function getShader(gl, id) {
var shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
var theSource = "";
var currentChild = shaderScript.firstChild;
while(currentChild) {
if (currentChild.nodeType == 3) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
一旦找到元素,其中的内容就会被读到变量theSource中。
var shader;
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null; // 未知着色器类型
}
当着色器的代码读取完毕,我们查看着色器对象的MIME类型,由此决定是顶点着色器(MIME类型为“x-shader/x-vertex”)还是片段着色器(MIME类型为“x-shader/x-fragment”),接着创建与此对应的着色器。
gl.shaderSource(shader, theSource);
// 编译着色器
gl.compileShader(shader);
// 检查编译是否成功
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
最后,theSource变量的内容被传到着色器中进行编译。如果失败则提示用户并返回null,否则将着色器返回给调用者。
着色器
下面我们需要添加着色器程序本身到HTML中。着色器通过着色器语言的语法进行描述,其工作原理的细节已经超出本文范围。
片段着色器
在GL术语中,多边形的每一个像素被称作一个片段。片段着色器的工作就是为每一个像素添加颜色。这里我们简单地设置颜色为白色。
gl_FragColor是一个内置的GL变量。通过它可以设置像素的颜色。
<script id="shader-fs" type="x-shader/x-fragment">
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
顶点着色器
顶点着色器定义每个顶点的位置和形状。
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
</script>
创建对象
在开始渲染方形之前,我们需要创建一个容纳顶点的缓冲区,这将通过调用函数initBuffers()来完成;随着我们逐渐深入WebGL,这个过程将被扩充用以创建更多的3D对象。
var horizAspect = 480.0/640.0;
function initBuffers() {
squareVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
var vertices = [
1.0, 1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
}
函数内容非常简单。开始它调用gl的createBuffer()方法获得一个缓冲区,我们会在其中放入顶点。该缓冲区通过bindBuffer()方法会被绑定到环境中。
下面,我们创建一个Javascript数组用来保存矩形的每个顶点。这些信息会被转换为WebGL浮点数并传递给gl的bufferData()方法,用来建立对象的顶点。
绘制场景
着色器建立好之后,我们就可以真正渲染场景了。因为本例中没有任何动画,函数drawScene()函数非常简单。它使用了几个工具库,稍后会介绍。
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0);
loadIdentity();
mvTranslate([-0.0, 0.0, -6.0]);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer);
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
第一步清除环境只保留背景色;下面建立摄像机视角。这里设置为45度,宽高比为640/480。另外我们规定距离摄像机0.1到100个单位之间的物体才会被渲染。
接下来加载位置信息并将其放置在距离摄像机6个单位的位置。之后再将方形的顶点缓冲区绑定到环境中并进行配置,最后调用drawArrays()方法来绘制对象。
矩阵工具库
矩阵操作很复杂。没有人愿意完全编写所有相关的代码。好在有个叫做Sylvester的库,可以在JavaScript中处理向量和矩阵操作。
这个范例中的glUtils.js文件也在其他的WebGL范例中引用。没人知道它从何而来,但是它添加了许多建立特殊矩阵的方法,确实比Sylvester用起来更简单,另外它还可以通过HTML格式进行输出。
此外,这个范例定义了几个有用的方法。这些方法的细节已经超出范例的范围,网上有很多关于矩阵的参考资料,可以查看后面的参考资料部分。
function loadIdentity() {
mvMatrix = Matrix.I(4);
}
function multMatrix(m) {
mvMatrix = mvMatrix.x(m);
}
function mvTranslate(v) {
multMatrix(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4());
}
function setMatrixUniforms() {
var pUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
gl.uniformMatrix4fv(pUniform, false, new Float32Array(perspectiveMatrix.flatten()));
var mvUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
gl.uniformMatrix4fv(mvUniform, false, new Float32Array(mvMatrix.flatten()));
}
参考资料
Matrices Wolfram MathWorld
Matrix on Wikipedia