webGL教程4:实现三维效果

欢迎来到WebGL教学系列的第四课。这次我们来显示一些3D物体。本课是基于NeHe OpenGL教学系列的第五课。



如果你的浏览器已经支持WebGL,请点击此处,你将看到本课WebGL的现场版;如果不支持,你从此处可以获取一个支持WebGL的浏览器。

本课代码与前一课代码的不同之处完全集中于initBuffers和drawScene这两个函数。如果你现在滚动到drawScene函数,你将在第一行的地方看到细微的改变:保留场景中两个物体当前旋转状态的变量被更名了,它们以前是rTri和 rSquare,现在是:
var rPyramid = 0;
var rCube = 0;
设置好代码之后,我们来看看如何绘制锥体。就像在前面的课程里对三角形所做的那样,我们让它围绕Y轴旋转:

mvRotate(rPyramid, [0, 1, 0]);
然后绘制它。在上一课里,绘制彩色三角形的代码与绘制同样漂亮的锥体的新代码之间的唯一区别在于后者有更为丰富的矢量和颜色,这一切将在 initbuffers函数(我们稍后将看到)中进行处理。这就意味着除了缓冲区名字不同之外,代码是完全相同的。

gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);    
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, pyramidVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, pyramidVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
setMatrixUniforms();
gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer.numItems);
  
很简单吧。现在让我们来看看关于立方体的代码。第一步先旋转它,这一次和仅仅绕x轴旋转不同,我们将绕一个(从观察者的角度看)向上、向右并朝向你的轴上旋转立方体:    
mvRotate(rCube, [1, 1, 1]);
下一步绘制立方体。这里涉及的东西多一些,我们有三种方法来绘制一个立方体:   
1. 使用一个单一的三角形带。如果整个立方体都是同一种颜色,这将相当容易——我们利用一直使用的顶点位置来绘制立方体的一个面,接着添加另外两个点绘制另一个面,然后再添加两个点绘制第三个面,以此类推。这非常有效。不幸的是,我们想要立方体的每个面有不同的颜色。因为每个顶点指定了立方体的一个内角,同时每个内角又被三个面所共有,我们需要指定每个顶点三次。这样做如此复杂,以至于我甚至不想试图去解释它…… 
2. 我们可以通过绘制六个独自的正方形来绘制立方体,它们中的每个面包含各自的顶点位置和颜色集合。本课的第一个版本就是这样做的,而且它运行得很好。然而,这并非最佳的实现方式,因为每次在你的场景中指示WebGL绘制另一个对象时会耗费大量的时间。如果能较少地去调用drawArrays函数的话,那它将是比较好的方法。   
3. 最后的选择是将立方体指定为六个正方形,每一个正方形由两个三角形构成,不过要将所有这些图形送给WebGL让它一次绘制完成。这和我们处理三角形带的方法有点相似,但是由于现在我们每次都完整地定义三角形,而不是像以前那样简单地通过在前一个三角形上添加一个单独的点来定义一个三角形,因此很容易指定每条边的颜色。它还有一个好处就是编码方式最为干净优美。让我来介绍一个新函数drawElements——它就是我们要去实现的方法:-)首先,我们将包含立方体顶点位置的缓冲区和在initBuffers函数中用合适的属性产生的颜色缓冲区关联起来,这就像我们处理锥体那样:    
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);    
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);    
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);    
gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
  
接着我们绘制三角形。这里有点小问题。我们考虑一下立方体的正面;我们现在有正面的四个顶点位置,同时它们每一个都有一个与之相关的颜色。然而,它需要使用两个三角形来绘制。由于我们现在使用的是各自指定自身顶点的简单三角形,而不是共享顶点的三角形带,所以我们不得不为此总共指定六个顶点。但问题是我们只能从缓冲区中为它提供四个。

我们所需要做的是指定如下方法:“绘制一个由缓冲区数组中前三个顶点缓冲区构成的三角形,接着绘制另一个由数组中第一个、第三个和第四个顶点缓冲区构成的三角形”。这样我们就能绘制出立方体的正面;然后我们使用类似的方法来绘制余下的部分。这就是我们要做的。

在这里,我们使用“元素缓冲区数组”和一个新函数drawElements。就像我们一直使用的缓冲区数组那样,元素缓冲区数组将在 initBuffers函数中填充相应的值,同时使用基于零点索引顶点位置和颜色的数组方法来保留一份顶点集合的列表(稍后我们会看到)。

我们利用立方体的元素数组保存当前的缓冲区(WebGL可以保留不同的缓冲区数组和元素缓冲区数组,因此我们在调用gl.bindBuffer时必须指定我们需要绑定哪一个缓冲区),接着我们将模型视图矩阵传到图形卡上,最后调用drawElements函数绘制三角形。    
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);    
setMatrixUniforms();    
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);

  
这就是drawScene函数。余下的代码都在initBuffers函数中,浅显易懂。我们用新名字来定义缓冲区以反映正在处理新的一类物体,同时我们为立方体的顶点索引缓冲区新增一项:    
var pyramidVertexPositionBuffer;    
var pyramidVertexColorBuffer;    
var cubeVertexPositionBuffer;    
var cubeVertexColorBuffer;   
var cubeVertexIndexBuffer;
我们对锥体所有正面的顶点位置缓冲区填充数值,同时 numItems有相应的变化:   
pyramidVertexPositionBuffer = gl.createBuffer();   
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer);   

var vertices = [            // Front face             0.0,  1.0,  0.0,            -1.0, -1.0,  1.0,             1.0, -1.0,  1.0,            // Right face             0.0,  1.0,  0.0,             1.0, -1.0,  1.0,             1.0, -1.0, -1.0,            // Back face             0.0,  1.0,  0.0,             1.0, -1.0, -1.0,            -1.0, -1.0, -1.0,            // Left face             0.0,  1.0,  0.0,            -1.0, -1.0, -1.0,            -1.0, -1.0,  1.0        ];    
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);    
pyramidVertexPositionBuffer.itemSize = 3;    
pyramidVertexPositionBuffer.numItems = 12;…   

锥体的顶点颜色缓冲区也如此:   
pyramidVertexColorBuffer = gl.createBuffer();       
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer);   

var colors = [                
   // Front face           

   1.0, 0.0, 0.0, 1.0,            
   0.0,1.0, 0.0, 1.0,            
   0.0, 0.0, 1.0, 1.0,            
   // Right face            
   1.0, 0.0, 0.0, 1.0,            
   0.0, 0.0, 1.0, 1.0,            
   0.0, 1.0, 0.0, 1.0,            
   // Back face            
   1.0, 0.0, 0.0, 1.0,            
   0.0, 1.0, 0.0, 1.0,            
   0.0, 0.0, 1.0, 1.0,            
   // Left face            
   1.0, 0.0, 0.0, 1.0,            
   0.0, 0.0, 1.0, 1.0,            
   0.0, 1.0, 0.0, 1.0        
   ];    
gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(colors), gl.STATIC_DRAW);

pyramidVertexColorBuffer.itemSize = 4;       
pyramidVertexColorBuffer.numItems = 12;…

对于立方体的顶点位置缓冲区的处理如下:      
cubeVertexPositionBuffer = gl.createBuffer();       
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);   

vertices = [

   // Front face           
   -1.0, -1.0,  1.0,          

   1.0, -1.0,  1.0,  
   1.0,  1.0,  1.0,         

   -1.0,  1.0,  1.0,          
   // Back face          
   -1.0, -1.0, -1.0,          
   -1.0,  1.0, -1.0,           
   1.0,  1.0, -1.0,          
   1.0, -1.0, -1.0,          
   // Top face          
   -1.0,  1.0, -1.0,          
   -1.0,  1.0,  1.0,           
   1.0,  1.0,  1.0,           
   1.0,  1.0, -1.0,          
   // Bottom face          
   -1.0, -1.0, -1.0,           
   1.0, -1.0, -1.0,           
   1.0, -1.0,  1.0,          
   -1.0, -1.0,  1.0,          
   // Right face           
   1.0, -1.0, -1.0,           
   1.0,  1.0, -1.0,           
   1.0,  1.0,  1.0,           
   1.0, -1.0,  1.0,          
   // Left face          
   -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.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(vertices), gl.STATIC_DRAW);    
cubeVertexPositionBuffer.itemSize = 3;  

  cubeVertexPositionBuffer.numItems = 24;  
颜色缓冲区稍微复杂些,因为我们使用一个循环来产生一个顶点颜色的列表,所以我们不得不指定每种颜色四次,每个顶点一次。   

    cubeVertexColorBuffer = gl.createBuffer();    
   gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);    
   var colors = [          
   [1.0, 0.0, 0.0, 1.0],     
   // Front face          
   [1.0, 1.0, 0.0, 1.0],     
   // Back face          
   [0.0, 1.0, 0.0, 1.0],     
   // Top face          
   [1.0, 0.5, 0.5, 1.0],     
   // Bottom face          
   [1.0, 0.0, 1.0, 1.0],     
   // Right face          
   [0.0, 0.0, 1.0, 1.0],     
   // Left face       
];    
   var unpackedColors = []    for (var i in colors) 
{
    var color = colors[i];       
   for (var j=0; j < 4; j++) {  
         unpackedColors = unpackedColors.concat(color);        }    }  
   gl.bufferData(gl.ARRAY_BUFFER, new WebGLFloatArray(unpackedColors), gl.STATIC_DRAW);    
   cubeVertexColorBuffer.itemSize = 4; 
   cubeVertexColorBuffer.numItems = 24;
  
最后,我们定义元素缓冲区数组(提示:gl.bindBuffer和gl.bufferData函数的第一个参数是不同的):   
  cubeVertexIndexBuffer = gl.createBuffer();    
   gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);    
   var cubeVertexIndices = [          
   0, 1, 2,      
   0, 2, 3,    
   // Front face          
   4, 5, 6,      
   4, 6, 7,    
   // Back face          
   8, 9, 10,     
   8, 10, 11,  
   // Top face          
   12, 13, 14,   
   12, 14, 15, 
   // Bottom face          
   16, 17, 18,   
   16, 18, 19, 
   // Right face          
   20, 21, 22,   
   20, 22, 23  
   // Left face        
   ]    
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new WebGLUnsignedShortArray(cubeVertexIndices), gl.STATIC_DRAW);    
cubeVertexIndexBuffer.itemSize = 1;   

cubeVertexIndexBuffer.numItems = 36;  
记住,在这个缓冲区中的每个数字是顶点位置和颜色缓冲区的一个索引。因此,结合drawScene函数绘制三角形的指令,第一行代码意味着我们利用顶点0,1和2获得一个三角形。接着利用顶点 0,2和3获得另一个三角形。由于两个三角形颜色相同且相邻,于是利用顶点0,1,2和3就可以获得一个正方形。重复这样的操作就可以获得立方体的所有面,最后立方体也就绘制好了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值