欢迎来到第二节WebGL教程!这一次我们将要讨论如何给场景着色。这一节课的内容是基于NeHe的OpenGL教程第三课。
下面是本课内容在支持WebGL的浏览器中运行的样子:
点击此处,你将看到这个WebGL现场版。如果你还没有一个支持WebGL的浏览器,你可以从这里知道如何获取它。
一点提示:本教程是面向那些具有一定编程知识而缺乏实际3D图形开发经验的开发人员;其目的是为了让你更好地明白代码,以便于你能尽可能快地开发出你自己的三维网页。如果你已经看过第一节课的内容(你应该在开始本课之前去看一看第一节的内容),那么我现在就只是解释新旧代码间的区别。
本课的代码和第一节课的十分相似。从网页的顶部到底部开始运行,我们
使用具有"x-shader/x-vertex"
和"x-shader/x-fragment"类型的script标签
定义了顶点渲染器和片段渲染器;
在initGL函数中初始化了一个WebGL的上下文;
使用getShader和initShaders函数装载渲染器到一个WebGL对象中;
定义了模型视图矩阵以及操该矩阵的应用函数loadIdentity、multMatrix和mvTranslate;
定义了投影矩阵pMatrix和一个操作该矩阵的应用函数perspective;
定义setMatrixUniforms函数用于将模型视图矩阵和投影矩阵,以便渲染器能看见它们使用initBuffers加载包含场景对象信息的缓冲区;
在适当命名的drawScene函数中绘制场景;
定义WebGLStart函数在网页起始位置设置WebGL的环境;
最后,我们提供了所需的最少的html代码来显示它。
与第一课代码相比,唯一改变的地方在于渲染器,initBuffers函数和drawScene函数。为了解释它是如何工作的,你需要一些关于WebGL贴图管线的知识。下面是一个流程图:
这个流程图用十分简洁的方式显示了在drawScene函数中传入JavaScript函数的数据是如何转变为在场景画布中显示在WebGL上的像素点的。它仅显示了本课中需要说明的几个步骤;我们将在以后的课程中去讲解较为细节的地方。
在最高级别上,该过程工作如下:
你每次调用像drawArrays这样的函数,WebGL都要处理先前以属性
(如第一课中用于顶点的缓冲区)和统一变量(其用于投影矩阵和模型视图矩阵)的形式提交给它的数据,并将其传入到顶点渲染器。
对每个顶点调用一次顶点渲染器,每次都为该顶点设置合适的属性。同时,将统一变量传入到顶点渲染器。但是,统一变量正如它们的名字,在调用的过程并不发生改变。顶点渲染器用第一课中提到的这个数据来工作,它使用投影和模型视图矩阵以便顶点能根据当前的模型视图状态来将其置入场景中和在场景中移动,并将结果放入varying variables中。它也能输出一些varying variables。其中一个比较特殊的是gl_Position,它是必须的,它包含了渲染器完成顶点着色以后的顶点坐标。
一旦顶点渲染器完成其工作,WebGL就会从这些可变变量中将三维图像转化为二维图像,接着它在图像中为每一个像素调用一次片段渲染器。(由于这个原因,在一些三维图像系统中片段渲染器被认作为像素渲染器。)当然,这意味着片段渲染器用于那些没有顶点的像素——即,顶点结束的像素之间的像素。对于组成三角形的顶点位置,WebGL将通过线性插值的方法在两个顶点间填充一些像素。线性插值处理填充了组成可见三角形的顶点分隔的空间。片段渲染器的作用是返回每一个插值点的颜色,它在gl_FragColor可变变量中返回这个颜色值。
一旦片段渲染器处理完成,WebGL对其结果稍作混合(我们将在以后的课程中提及它),再将其放入帧缓冲区(frame buffer),此即屏幕上最终显示的东西。
很明显,这一课最重要的就是要教会你如何通过所有运行在片段渲染器上的JavaScript代码给顶点着色,我们不会从一个顶点到另一个顶点来直接获取。
我们所采用的方法是基于如下事实:我们能从顶点渲染器中传出可变变量(而不是位置信息),然后我们可以在片段渲染器中重新获得它们。因此,我们把颜色信息传入到顶点渲染器,然后顶点渲染器直接将该信息置入一个可以在片段渲染器中获得的可变变量中。
这种方法很方便地为我们提供了颜色的渐变,且不花费任何代价。当在顶点间生成片段时,除了位置信息,所有顶点渲染器设置的可变变量都是线性插值的。顶点间的线性颜色插值为我们提供了平滑渐变,就像你在图中的三角形看到的那样。
我们来看一看代码与第一课中的有什么区别。首先,来看看顶点渲染器。它有很大的变化,下面是代码:
attribute vec3 aVertexPosition; attribute vec4 aVertexColor;
uniform mat4 uMVMatrix; uniform mat4 uPMatrix; varying vec4 vColor;
void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = aVertexColor; }
这部分代码告诉我们有两个属性输入值:calledaVertexPosition和aVertexColor(表示从顶点到顶点的变化),两个不变同一变量:
uMVMatrix
和
uPMatrix,以及一个输出值:一个vColor的可变变量。
在渲染器中,我们用与第一课中相同的方法来计算
gl_Position(其被定义为一个针对每一个顶点渲染器的可变变量)。我们处理颜色所作就是的将颜色从输入的属性值直接传递到输出的可变变量上。
一旦每个顶点处理完这一过程,插值处理也就完成且生成片段,这些片段将传入到片段渲染器中。
varying vec4 vColor; void main(void) { gl_FragColor = vColor; }
这里,我们将输入的可变变量vColor
包含着从线性插值处理平滑混合得到的颜色,并将其立即返回作为该片段(即像素)的颜色。
这就是本课与第一课的渲染器之间所有的区别。这里还有其他两个变化。第一个变化很小:现在我们在
initShaders函数中得到两个而不是一个属性的引用;额外添加的代码我们使用高亮红色显示如下:
var shaderProgram;
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("Could not initialise shaders"); } gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); }
这段代码得到了属性的位置,在第一课中我们在某种程度上忽略了它,现在对它的理解应该变得十分清楚:
它们是关于我们如何针对每个顶点获得一个传入到顶点渲染器属性引用的处理。
在第一课中,我们只是获得了顶点的位置属性。现在,很明显,我们也获得了颜色属性。
其它变化在initBuffers函数和drawScene函数中,现在在initBuffers函数中我们不仅要为顶点的位置还要为顶点的颜色设置缓冲区,在drawScene函数中我们需要将顶点的位置和颜色都传入到WebGL。
首先来看看initBuffers函数,我们定义了新的全局变量来保留三角形和正方形的颜色缓冲区:
var triangleVertexPositionBuffer;
var triangleVertexColorBuffer;
var squareVertexPositionBuffer;
var squareVertexColorBuffer;
接着,我们在生成三角形的顶点位置缓冲区后,指定其顶点颜色:
function initBuffers() { triangleVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
var vertices = [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0 ];
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);
triangleVertexPositionBuffer.itemSize = 3;
triangleVertexPositionBuffer.numItems = 3;
triangleVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
var colors = [
1.0, 0.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0,
0.0, 0.0, 1.0, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(colors), gl.STATIC_DRAW);
triangleVertexColorBuffer.itemSize = 4;
triangleVertexColorBuffer.numItems = 3;
我们为颜色提供的数值放在一个列表中,一个针对每个顶点的数值集合,就像处理位置那样。然而这两个数值缓冲区有一个有意思的区别:每个顶
点的颜色分配了红、绿、蓝和alpha四个元素,每个顶点的位置被指定为X, Y和Z三个坐标值。如果你不知道alpha的话,这里可以简单地告诉你它一种表示物体不透明度的度量,在以后的课程中它会十分有用。缓冲区中每一项元素个数的变化使得与之相关联的itemSize发生变化变得十分有必要。
接下来,我们对正方形使用同样的代码;这次我们为每个顶点使用相同的颜色,
因此我们使用一个循环生成缓冲区:
squareVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
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 WebGLFloatArray(vertices), gl.STATIC_DRAW);
squareVertexPositionBuffer.itemSize = 3;
squareVertexPositionBuffer.numItems = 4;
squareVertexColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer);
colors = []
for (var i=0; i < 4; i++) {
colors = colors.concat([0.5, 0.5, 1.0, 1.0]);
}
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(colors), gl.STATIC_DRAW);
squareVertexColorBuffer.itemSize = 4;
squareVertexColorBuffer.numItems = 4;
现在我们在一个4缓冲区的集合中拥有了所有对象的数据,因此接下来的变化是让drawScene函数使用新的数据。新增加的代码用红色标示,应该很容易理解:
function drawScene() {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
perspective(45, 1.0, 0.1, 100.0);
loadIdentity();
mvTranslate([-1.5, 0.0, -7.0])
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);
mvTranslate([3.0, 0.0, 0.0])
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, squareVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}
接下来的变化......打住,已经没有其他的变化了!这就是所有添加颜色到WebGL场景中的代码。
现在你也很熟悉这些渲染器的基本知识以及如何在两个渲染器之间传输数据了。
这就是本课的内容——它比第一课要容易些!如果你有任何疑问、评论或者更正,
请在下面留言给我。
本文章转载于 HTML5 中文网 http://www.html5china.com/course/20110118_1521.html