[WebGL] 简介,流程及示例
WebGL是一个附加的渲染上下文(context),支持HTML5的canvas对象。这个上下文允许通过一种与OpenGL ES 2.0 API非常相似的API来进行图像渲染。
创建WebGL上下文
通过使用canvas的getContext(“experimental-webgl”)方法来获取一个WebGLRenderingContext。
创建Shaders
Shaders是用来将图形信息(shape data)转换为屏幕上的像素。当使用GLSL这种shader格式时,我们会用到两种不同的shaders。
Vertex shader 使用在被渲染的三角形的每个顶点(corner)上。这个shader会转换点信息,传入贴图对其信息并且使用每个三角形的normals来计算光照。GLSL提供给用户一个特殊的变量gl_Position来存储经过转换的顶点信息。WebGL使用三角形每个顶点存储的信息来生成并填充其他所有需要输出的像素。贴图对齐和光照信息通过varying变量传入。
所有Vertex shader的信息都会传递到fragment shader中,此shader会在每个被传入的经过转换的三角形的每个点上运行,从贴图得出对应的像素,调整光照并且输出。GLSL为此定义了一个专用的变量gl_FragColor,此变量存储的信息即为像素的颜色。
下面这个例子说明了如何通过HTML script标签来声明vertex及fragment shader。
uniform mat4 u_modelViewProjMatrix;
uniform mat4 u_normalMatrix;
uniform vec3 lightDir; attribute vec3 vNormal;
attribute vec4 vTexCoord;
attribute vec4 vPosition; varying float v_Dot;
varying vec2 v_texCoord; void main()
{
gl_Position = u_modelViewProjMatrix * vPosition;
v_texCoord = vTexCoord.st;
vec4 transNormal = u_normalMatrix * vec4(vNormal, 1);
v_Dot = max(dot(transNormal.xyz, lightDir), 0.0);
}
</script>
<script id="fshader" type="x-shader/x-fragment">
uniform sampler2D sampler2d; varying float v_Dot;
varying vec2 v_texCoord; void main()
{
vec2 texCoord = vec2(v_texCoord.s, 1.0 - v_texCoord.t);
vec4 color = texture2D(sampler2d, texCoord);
color += vec4(0.1, 0.1, 0.1, 1);
gl_FragColor = vec4(color.xyz * v_Dot, color.a);
}
</script>
上例中的vertex shader只是简单的使用一个modelViewProjMatrix来定义如何转换vertex position,并且与vertex position即vPosition一起传给fragment shader。然后vertex shader会将贴图对齐信息vTexCoord与通过normal即vNormal生成的光照信息v_Dot一并传入fragment shader。
Fragment shader就更简单了。它仅仅是从贴图中得到像素信息,然后乘以通过vertex shader传入的光照信息。这使得面对我们的像素更亮,从而获得一个较为真实的光照效果。
初始化引擎
我们通过以下代码可以简单的初始化一个WebGL引擎。
{
// Initialize
var gl = initWebGL("example", "vshader", "fshader",
[ "vNormal", "vColor", "vPosition"], [ 0, 0, 0, 1 ], 10000); // Set some uniform variables for the shaders
gl.uniform3f(gl.getUniformLocation(gl.program, "lightDir"), 0, 0, 1);
gl.uniform1i(gl.getUniformLocation(gl.program, "sampler2d"), 0); // Create a box. On return 'gl' contains a 'box' property with
// the BufferObjects containing the arrays for vertices,
// normals, texture coords, and indices.
gl.box = makeBox(gl); // Load an image to use. Returns a WebGLTexture object
spiritTexture = loadImageTexture(gl, "resources/spirit.jpg"); // Create some matrices to use later and save their locations in the shaders
gl.mvMatrix = new CanvasMatrix4();
gl.u_normalMatrixLoc = gl.getUniformLocation(gl.program, "u_normalMatrix");
gl.normalMatrix = new CanvasMatrix4();
gl.u_modelViewProjMatrixLoc = gl.getUniformLocation(gl.program, "u_modelViewProjMatrix");
gl.mvpMatrix = new CanvasMatrix4(); // Enable all of the vertex attribute arrays.
gl.enableVertexAttribArray(0);
gl.enableVertexAttribArray(1);
gl.enableVertexAttribArray(2); // Set up all the vertex attributes for vertices, normals and texCoords
gl.bindBuffer(gl.ARRAY_BUFFER, gl.box.vertexObject);
gl.vertexAttribPointer(2, 3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, gl.box.normalObject);
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, gl.box.texCoordObject);
gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0); // Bind the index array
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.box.indexObject); return gl;
}
initWebGL()方法接受以下的参数:
“example” – canvas元素的Id
“vshader”,"fshader" – vertex/fragment shader对应的Id
“vNormal”, “vColor”, “vPosition” – 定义vertex shader属性的名称
[0,0,0,1],10000 - 底色与深度信息
上面这段初始化代码加在了shaders并且将其传入一个定义了shader接口信息的GLSL程序。shader上的uniform参数用来存储不会变化的值;vertex属性用来存储会发生变化的值,例如顶点。这段代码也告诉WebGL通过makeBox()方法来生成顶点,光照和贴图信息。
设置观察点
在渲染之前,我们必须告诉canvas如何在模型空间和屏幕空间之间映射对象。在最开始,一个对象的位置信息被描述为模型,而本地信息描述了对象的形状。这两个信息将会被转换为其他信息:
MODELING COORDINATES ->> WORLD COORDINATES ->> VIEW COORDINATES ->> VIEWPORT COORDINATES
简单的说,就是将模型信息转换为通过世界来描述的信息(世界包含了所有需要渲染的对象)。然后通过设置观察点,即你从哪里观看这个世界来生成观察点信息,也就是最终渲染在屏幕上的像素点(再次步骤会进行3D至2D的转换。canvas只能显示2D信息。)。
我们会使用转换矩阵(transformation matrix)来完成从一个coordinates到下一个的转换。在上例中,从modeling coordinates到view coordinates的转换使用过model-view matrix进行的,这个matrix合并了modeling->world, world->view两个步骤。然后通过透视矩阵pMatrix来进行最后一步view->viewport的转换。透视矩阵在reshape()方法中生成并存储到转换流水线的最后。
{
var canvas = document.getElementById('example');
if (canvas.clientWidth == width && canvas.clientHeight == height)
return; width = canvas.clientWidth;
height = canvas.clientHeight; // Set the viewport and projection matrix for the scene
gl.viewport(0, 0, width, height);
gl.perspectiveMatrix = new CanvasMatrix4();
gl.perspectiveMatrix.lookat(0, 0, 7, 0, 0, 0, 0, 1, 0);
gl.perspectiveMatrix.perspective(30, width/height, 1, 10000);
}
绘制立方体
经过上述步骤,我们已经可以最终绘制了。我们要求在最终效果中,我们的立方体是可以转动的。我们应该在model-view matrix中加入这个操作,因为正是在这个转换矩阵中生成了在哪里和哪个角度来显示立方体。然后我们把model-view矩阵与透视矩阵相乘来完成所有步骤。请注意因为矩阵的相乘是不符合交换律的,所以顺序很重要。你也可以把一个model-view矩阵变为光照矩阵,这样你就能为此立方体得到合适的光照:
{
//Make sure the canvas is sized correctly.
reshape(gl); // Clear the canvas
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Make a model/view matrix.
gl.mvMatrix.makeIdentity();
gl.mvMatrix.rotate(currentAngle, 0,1,0);
gl.mvMatrix.rotate(20, 1,0,0); // Construct the normal matrix from the model-view matrix and pass it in
gl.normalMatrix.load(gl.mvMatrix);
gl.normalMatrix.invert();
gl.normalMatrix.transpose();
gl.uniformMatrix4fv(gl.u_normalMatrixLoc, false, gl.normalMatrix.getAsWebGLFloatArray()); // Construct the model-view * projection matrix and pass it in
gl.mvpMatrix.load(gl.mvMatrix);
gl.mvpMatrix.multRight(gl.perspectiveMatrix);
gl.uniformMatrix4fv(gl.u_modelViewProjMatrixLoc, false, gl.mvpMatrix.getAsWebGLFloatArray()); // Bind the texture to use
gl.bindTexture(gl.TEXTURE_2D, spiritTexture); // Draw the cube
gl.drawElements(gl.TRIANGLES, gl.box.numIndices, gl.UNSIGNED_BYTE, 0); // Finish up.
gl.flush(); // Show the framerate
framerate.snapshot(); currentAngle += incAngle;
if (currentAngle > 360)
currentAngle -= 360;
}
最终,我们完成了这个例子。生成了一个带贴图的持续旋转的立方体。相信你已经可以通过这个例子完整的理解WebGL的工作模式与步骤。个人感觉还是非常简明易用的。
WebGL的支持现状
在最新版的Apple WebKit / Google Chrome / Mozilla Firfox / Opera中,均对WebGL提供了支持。但此标准依然是draft状态,各浏览器的支持也仅处于测试阶段,并不完整。