- 最后登录
- 2018-12-19
- 注册时间
- 2012-8-20
- 阅读权限
- 90
- 积分
- 54706
- 纳金币
- 32328
- 精华
- 41
|
欢迎来到WebGL教程的第12课,这是第二节不是基于NeHe的OpenGL教程的WebGL课程。在这节课中,我们将介绍点光源,这节课很简单,但是却很重要,而且会引出将来一些有趣的内容。点光源,顾名思义,指的就是来自一个场景内特殊的点的光源,这与我们一直使用的平行光不一样。
下面的视频就是我们这节课将会完成的最终效果。
你将会看到一个绕轨道旋转的球体和立方体,在加载纹理的时候,他们会是白色的,当加载完成后,你们会看到一个月亮和一个木条箱。他们两个都是由位于他们之间的一个点光源照亮的。如果你希望改变光源的位置和颜色,你可以使用canvas下方的文本输入框。
下面我们来看看它是怎么工作的……
惯例声明:本系列的教程是针对那些已经具备相应编程知识但没有实际3D图形经验的人的;目标是让学习者创建并运行代码,并且明白代码其中的含义,从而可以快速地创建自己的3D Web页面。如果你还没有阅读前一课,请先阅读前一课的内容吧。因为本课中我只会讲解那些与前一课中不同的新知识。
另外,我编写这套教程是因为我在独立学习WebGL,所以教程中可能(非常可能)会有错误,所以还请风险自担。尽管如此,我还是会不断的修正bug和改正其中的错误的,所以如果你发现了教程中的错误,请告诉我。
有两种方法可以获得上面实例的代码:在实例的独立页面中选择“查看源代码”;或者点击这里下载我们为您准备好的压缩包。
首先,让我们来解释一下用点光源做些什么;点光源与平行光源之间的不同在于点光源中的光线来自场景中的一个点。仔细想想你就能搞清楚,这意味着,对于场景中的每一个点,每束光线射入的角度都是不同的。所以,处理它的最好方法就是计算每个顶点朝着光线所在位置的方向,接着进行我们对平行光线所做的同样的运算。这就是我们所要做的。
(在这里你们可能会想,计算顶点之间的点的光的方向 – 也就是计算每个片元上光的方向,会比仅仅计算每个顶点上的光的方向来的更好。没错,这样想是正确的。这样的计算方法对于显卡来说,比较困难,但是,它给出的效果会更好。在下一节课,我将会详细说明)
现在,既然我们知道了我们需要做的是什么,我们可以再回到示例页面看一下,你会发现在场景中,射出光源的那个点上没有一个实际的物体。如果你希望有一个看上去发出光源的物体(例如在太阳系中的太阳),你就需要分别定义光源和物体。根据我们前几课所学,绘制一个新物体现在对我们是来说十分简单了。所以在教程中,我仅仅会介绍点光源是如何作用的。在以上的说明中你一定可以看出整个过程十分简单;和第十一课中所述内容的最大不同在于需要绘制一个立方体,并且让立方体和球体一起沿着轨道转动。
和以前一样我们从源代码底部向上看起,找出本课代码与第十一课代码中的不同。第一个不同在于HTML的 body标签中,这里用来输入光源方向的字段已经改为光源位置。这个改动十分简单,所以就不再解释了。接着让我们来看一下webGLStart函数。这里的改动依然很简单,这节课上,我们不会用到鼠标控制,所以我们也不需要这部分代码;并且,之前称作initTexture的函数现在被更名为initTextures,这是因为函数需要同时加载一个立方体和一个球体。很无聊的理由吧……
往上一点,tick函数现在有了一个新的调用,让物体能够动起来,这样场景就会随着时间推移不断更新。
546 function tick() {
547 okRequestAnimationFrame(tick);
548 drawScene();
549 animate();
550 }
再往上就是animate函数,这个函数将会更新两个全局变量,用来描述立方体和球体在以每秒50度的速度围绕轨道旋转时,他们围绕轨道走了多远。
531 var lastTime = 0;
532
533 function animate() {
534 var timeNow = new Date().getTime();
535 if (lastTime != 0) {
536 var elapsed = timeNow - lastTime;
537
538 moonAngle += 0.05 * elapsed;
539 cubeAngle += 0.05 * elapsed;
540 }
541 lastTime = timeNow;
542 }
再往上是drawScene函数,这里你会发现一些有趣的变化。函数由样板化的代码开始,清除我们的画布并且设置***,接着是与11课相同的代码,检查光线复选框是否被勾选,并且将环境光的颜色传递给显卡:
454 function drawScene() {
455 gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
456 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
457
458 pMatrix = okMat4Proj(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
459
460 var lighting = document.getElementById("lighting").checked;
461 gl.uniform1i(shaderProgram.useLightingUniform, lighting);
462 if (lighting) {
463 gl.uniform3f(
464 shaderProgram.ambientColorUniform,
465 parseFloat(document.getElementById("ambientR").value),
466 parseFloat(document.getElementById("ambientG").value),
467 parseFloat(document.getElementById("ambientB").value)
468 );
然后,我们通过uniform变量将点光源的位置推送给显卡。这里的代码和我们在11课中用来推送光源方向的代码是完全一样的,不同之处在于我们去掉了一些东西,而不是加入了一些新的东西。在第11课中,当我们把光源方向发送给显卡时,我们需要将它转化为一个单位矢量 (也就是说,将它的长度缩放为1)并且将它的方向倒转。但对于点光源来说,我们不需要这样做,我们只需要直接将光源坐标传递给显卡就行。
470 gl.uniform3f(
471 shaderProgram.pointLightingLocationUniform,
472 parseFloat(document.getElementById("lightPositionX").value),
473 parseFloat(document.getElementById("lightPositionY").value),
474 parseFloat(document.getElementById("lightPositionZ").value)
475 );
接着,我们针对点光源的颜色做相同的操作,drawScene中的光源代码大概就是这样了。
477 gl.uniform3f(
478 shaderProgram.pointLightingColorUniform,
479 parseFloat(document.getElementById("pointR").value),
480 parseFloat(document.getElementById("pointG").value),
481 parseFloat(document.getElementById("pointB").value)
482 );
483 }
下一步,我们将在合适的位置绘制球体和立方体。
mvMatrix = okMat4Trans(0.0, 0.0, -20.0);
mvPushMatrix();
485 mvMatrix.rotY(OAK.SPACE_LOCAL, moonAngle, ***e);
486 mvMatrix.translate(OAK.SPACE_LOCAL, 5, 0, 0, ***e);
487 gl.activeTexture(gl.TEXTURE0);
488 gl.bindTexture(gl.TEXTURE_2D, moonTexture);
489 gl.uniform1i(shaderProgram.samplerUniform, 0);
490
491 gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexPositionBuffer);
492 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
493 moonVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
494
495 gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexTextureCoordBuffer);
496 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
497 moonVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
498
499 gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexNormalBuffer);
500 gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute,
501 moonVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);
502
503 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, moonVertexIndexBuffer);
504 setMatrixUniforms();
505 gl.drawElements(gl.TRIANGLES, moonVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
506 mvPopMatrix();
507
508 mvPushMatrix();
509 mvMatrix.rotY(OAK.SPACE_LOCAL, cubeAngle, ***e);
510 mvMatrix.translate(OAK.SPACE_LOCAL, 5, 0, 0, ***e);
511 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
512 gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,
513 cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
514
515 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexNormalBuffer);
516 gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute,
517 cubeVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);
518
519 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
520 gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,
521 cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
522
523 gl.activeTexture(gl.TEXTURE0);
524 gl.bindTexture(gl.TEXTURE_2D, crateTexture);
525 gl.uniform1i(shaderProgram.samplerUniform, 0);
526
527 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
528 setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
mvPopMatrix();
}
关于drawScene就这么多了。接着往上看,我们会看到initBuffers函数,其中增加了立方体和球体的数组对象的代码,这些代码都很标准。再往上,在initTextures函数中,我们载入了两个纹理,而不是一个。
接着往上,我们将会遇到最后一个,也是最重要的一个变化。找到顶点着色器的部分,你会发现这里有一些小的变动。我们从上往下看,注意发生变动的代码位于第35行和第36行。
25 attribute vec3 aVertexPosition;
26 attribute vec3 aVertexNormal;
27 attribute vec2 aTextureCoord;
28
29 uniform mat4 uMVMatrix;
30 uniform mat4 uPMatrix;
31 uniform mat4 uNMatrix;
32
33 uniform vec3 uAmbientColor;
34
35 uniform vec3 uPointLightingLocation;
36 uniform vec3 uPointLightingColor;
这样我们就设置好了光源位置和颜色的uniform变量,用来替换原先的光照方向和颜色。接下来:
38 uniform bool uUseLighting;
39
40 varying vec2 vTextureCoord;
41 varying vec3 vLightWeighting;
42
43 void main(void) {
44 vec4 mvPosition = uMVMatrix * vec4(aVertexPosition, 1.0);
45 gl_Position = uPMatrix * mvPosition;
我们在这里所做的是将原先的代码分成两个部分。到目前为止,在我们对顶点着色器的所有的应用中,我们都是一次性把模型视图矩阵和投影矩阵配置在顶点位置中,就像下面这样:
// Code from lesson 11
2 gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
但是在这里,我们将会储存一个中间值,这个顶点位置的值已经应用了当前模型视图矩阵,但是还未根据***进行调整。在下面的代码中我们用到了这个值。
46 vTextureCoord = aTextureCoord;
47
48 if (!uUseLighting) {
49 vLightWeighting = vec3(1.0, 1.0, 1.0);
50 } else {
51 vec3 lightDirection = normalize(uPointLightingLocation - mvPosition.xyz);
光源位置是通过世界空间坐标表现的,顶点坐标也是一样,模型视图矩阵乘以顶点坐标以后,也将会通过世界空间坐标表现。我们需要利用这些坐标算出点光源射入当前顶点的方向,还需要算出一点到另一点的方向,我们要做的仅仅是把他们相除。除过之后,我们需要做的是使方向向量归一化,就像我们之前对平行光的矢量所做的一样,把它的长度调整为1。 设置完成后,将所有的东西组合起来进行一次运算,这和我们对平行光源所做的是完全一样的,只有几处变量名称的变化。
53 vec3 transformedNormal = (uNMatrix * vec4(aVertexNormal,1.0)).xyz;
54 float directionalLightWeighting = max(dot(transformedNormal, lightDirection), 0.0);
55 vLightWeighting = uAmbientColor + uPointLightingColor * directionalLightWeighting;
好了,现在你学会了如何在着色器中编写代码来创建一个点光源。
这节课讲完了,下节课上,我们还是会讲光线,通过逐片元光照而不是逐顶点光照,让场景看起来更加真实。 |
|