WebGL入门教程4 - 使用纹理贴图(Texture Map)
3D建模和纹理贴图的关系就好比人体和皮肤(或着装)的关系,3D建模用来处理空间属性,而贴图适合用来处理细腻的表面属性。
如果不使用贴图,而想在表面达到足够的细节感受,会使得建模任务变得异常复杂而得不偿失。
注:本文混用贴图(texture map)、texture和纹理这3个中英文词汇,因为它们代表同样的含义,用来确定物体的表面观感(纹路/光滑度等)。
我们在基础知识教程中已经说明过,顶点的属性除了位置、颜色外,还有纹理。
我们可以把纹理看成是一种特殊的颜色,那么我们就可以使用类似的方式来处理。
总体过程是,加载外部图片,把贴图映射到3D对象的表面即建立贴图的模型化数据,关联缓存并完成绘制。
创建纹理并加载图片
var eyeTexture; function initTexture() { eyeTexture = gl.createTexture(); eyeTexture.image = new Image(); eyeTexture.image.onload = function() { handleLoadedTexture(eyeTexture) } eyeTexture.image.src = "eyeball.png"; }
上述代码创建了一个Texture对象,并设置其图像属性的源为eyeball.png图片文件。
纹理使用异步方式加载图像文件,当加载完成时,调用回调函数handleLoadedTexture。
function handleLoadedTexture(texture) { gl.bindTexture(gl.TEXTURE_2D, texture); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.bindTexture(gl.TEXTURE_2D, null); }
第1行代码bindTexture功能和bindBuffer类似,用来把texture设定为当前2D类型的纹理;
第2行用来完成电脑屏幕图像坐标到3D空间坐标的转换,把Y轴翻转;
第3行代码把图像加载到显卡上的纹理空间中去,参数分别是类型(0)、数据存储格式(rgba,重复2次)、数据类型(用来存放rgba的数据类型),以及图像对象本身。
接下来的2行代码,分别设置纹理的放大/缩小参数,也就是当纹理图像和屏幕尺寸不匹配时的处理方式,NEAREST表示保持原样。
最后1行属于整理性质的代码,不是必须的。
纹理图片映射和初始化缓存
接下来我们需要把纹理贴图映射到物体表面,前面提到过,我们把贴图当成特殊的颜色属性和顶点关联起来。
function initBuffers() { var latitudeBands = 60; var longitudeBands = 60; var radius = 2; var vertexPositionData = []; var normalData = []; var textureCoordData = []; for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) { var theta = latNumber * Math.PI / latitudeBands; var sinTheta = Math.sin(theta); var cosTheta = Math.cos(theta); for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) { var phi = longNumber * 2 * Math.PI / longitudeBands; var sinPhi = Math.sin(phi); var cosPhi = Math.cos(phi); var x = cosPhi * sinTheta; var y = cosTheta; var z = sinPhi * sinTheta; var u = x * 0.5 + 0.5; var v = y * 0.5 + 0.5; normalData.push(x); normalData.push(y); normalData.push(z); textureCoordData.push(u); textureCoordData.push(v); vertexPositionData.push(radius * x); vertexPositionData.push(radius * y); vertexPositionData.push(radius * z); } } //...... }
上述代码中xyz是3D球体中某点所在位置的法线,uv是(x,y,z)所映射的贴图上的平面坐标。这里u/v的计算公式为:
faceVertexUvs[ face ][ j ].x = face.vertexNormals[ j ].x * 0.5 + 0.5; faceVertexUvs[ face ][ j ].y = face.vertexNormals[ j ].y * 0.5 + 0.5;
原因是我们所使用的图片(eyeball.png)是一张用于球面环境投影的MatCap(材质捕捉)图,映射到平面坐标时,需要做如上转换才不会出现变形。先不用深究这一点,总之我们记住要把2D纹理贴到3D模型上,需要做一个UV到XYZ的映射。
eyeballVertexNormalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, eyeballVertexNormalBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normalData), gl.STATIC_DRAW); eyeballVertexNormalBuffer.itemSize = 3; eyeballVertexNormalBuffer.numItems = normalData.length / 3; eyeballVertexTextureCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, eyeballVertexTextureCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordData), gl.STATIC_DRAW); eyeballVertexTextureCoordBuffer.itemSize = 2; eyeballVertexTextureCoordBuffer.numItems = textureCoordData.length / 2; eyeballVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, eyeballVertexPositionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexPositionData), gl.STATIC_DRAW); eyeballVertexPositionBuffer.itemSize = 3; eyeballVertexPositionBuffer.numItems = vertexPositionData.length / 3;
上述代码完成顶点和纹理数据缓存方面的处理。
绘制眼球
function drawScene() { //...... gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, eyeballTexture); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.bindBuffer(gl.ARRAY_BUFFER, eyeballVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, eyeballVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, eyeballVertexTextureCoordBuffer); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, eyeballVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, eyeballVertexNormalBuffer); gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, eyeballVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, eyeballVertexIndexBuffer); setMatrixUniforms(); gl.drawElements(gl.TRIANGLES, eyeballVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); //...... }
在绘制元素(调用gl.drawElements接口)之前,我们需要设置当前激活的纹理为TEXTURE0,并告诉着色器程序,我们使用纹理0(WebGL最多可以处理32个纹理)。接着我们把着色器属性(Attribute)和数据缓存关联起来,以便依次读取和绘制顶点数据。
最后,我们还需要在片段着色器程序中添加纹理的处理逻辑:
precision mediump float; varying vec2 vTextureCoord; varying vec3 vLightWeighting; uniform sampler2D uSampler; void main(void) { vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a); }
可以看到,现在球体对象表面的颜色被纹理贴图的颜色所替代了。
我们再加上一些简单的鼠标操作,就完成了一个可以旋转的3D眼球作品。
你可以自己在线试试。
最新评论
- 相关文章
常用光照类型基本概念工作原理及其计算公式
在三维场景中,原理上物体的渲染效果取决于光照与物体表面的相互作用,对于渲染程序而言,可以通过把一些数学公式应用于像素着色来实现,从而模拟出真实生活中的...
JavaScript事件模型图解
在JavaScript中用户交互的核心部分就是事件处理。本文为对事件模型和处理机制的总体性描述。Event是什么?
event是用户操作网页时发生的交互动作,比如clic...常见面试题JavaScript闭包(ES5语法)
JavaScript闭包(Closure)是常见的JS面试题,是否理解闭包是一个简单的区分JS初级和高级程序员的判例。几乎每个JS程序员都在使用闭包,有意或无意间。比如编写一个jQuery鼠标点击处理函数:$(function()
NodeJS、Java和PHP性能考量和若干参考结论
首先需要说明的是,严格而言NodeJS和Java、PHP并非对等概念,NodeJS是基于JS的一个应用程序,而Java/PHP是语言。我们这里实际指的是分别使用node、java和php来实...
HTML5动画背后的数学 - 粒子群仿生算法简介
本站收录了多个算法可视化动画,如模拟鸟群运动:http://wow.techbrood.com/fiddle/30529等等。这里面除...
Three.js入门教程5 - 10个必须知道的编程技巧
作者为Google的Paul,关于如何写出好的WebGL代码的文章。和很多开发者一样,我通过实践学习,但同时我也向其他更有经验的开发者们学习。在过去的几个月中,我在c...
Three.js入门教程4 - 创建粒子系统动画
嗨,又见面了。这么说我们已经开始学习Three.js了,如果你还没有看过之前三篇教程,建议你先读完。如果你已经读完前面的教程了,你可能会想做一些关于粒子的东西。让我们直面这个话题吧,每个人都爱粒子效果。不管你是否知道,你可以很轻易地创建它们。
S3TC(S3 Texture Compression)纹理压缩格式详解
使用S3TC格式存储的压缩纹理是以4X4的纹理单元块(texel blocks)为基本单位存储的,每纹理单元块(texel blocks)有64bit或者128bit的纹理单元数据(texel data)。这...
WebGL入门教程6 - 光照效果和Phong光照模型
正是因为有了光,世界才能被我们看见,在3D的世界里,光照给物体带来真实的视觉感受。当光照射在某一表面上时,它可能被吸收、反射或投射。其中入射到表面上的一...
如何使用WebGL实现空气高温热变形动画特效
我们在炎炎夏日,或者在火堆旁,经常会观察到热源周围空气的不稳定波动现象。本文将讲解如何通过WebGL来实现这个特效。该效果可用于热变形、波浪、水面波光等场...
如何使用WebGL创建一个逼真的下雨动画
之前写过文章来分别讲解如何使用CSS3和Canvas2D实现过雨滴和下雨动画。通过背景处理看起来也有视觉上的3D效果,但并非真正的3D场景,如果要加入用户交互,进行36...
使用Canvas绘制完美的不完美圆形
真实世界是不完美的,当我们需要模拟真实世界时,经常需要引入不完美/不规则的形状。比如陨石、雨滴、行星、树叶、绵延的海岸线、云朵等。本文介绍如何基于Canva...
使用CSS3实现流星雨动画教程
很多营销页面中需要实现类似流星雨的动画背景,营造节日浪漫的气氛。要实现这样的效果,有两种方法,一个是使用Canvas,一个是使用纯CSS3,我们这里介绍第2种方...
更多...