欢迎大家来到了第五课的webGL教程,这一次要说的是要在3D对象上面添加纹理,我们将他与一个封面图像的文件结合。这个方法非常有用,可以增加你所描制的对象其复杂的细节,您的3d场景,让整个效果看起来更加美观。
在这一课,你所看到的webGL生成出來的景象,应该是这样的:
好了,如果你想要看到这个动画的话,你可以看看:http://learningwebgl.com/lessons/lesson05/index.html
不过你需要使用chrome浏览器浏览他。
下面我就来介绍工作原理了:
下面这些方法都是面向一些有编程经验的人,但是又没有3D图形实际经验的,目的是让你了解代码的运行,同时又可以很快地在网页上面实现3D的效果。如果你没有看到前面的教程,我们还是建议你看看,因为这样你才能够更好地理解第第五课的代码是如何运作的。
如果你发现这篇教程有什么错误的话,你可以尽快地告诉我,我会尽快地纠正它的。
如果你想要看到这次教程中网页的代码,你可以使用chrome用代码查看器查看详细的代码。
对于纹理的工作,其实从原理上面你可以这样理解,他们是利用了特殊的方式,给三维的物体的每一个点设置了颜色。大家还记得第二课上面,颜色指定到着色器(shader)上面嘛?其实我们在纹理这一步要做的就是加载图像并将其发送到着色器上面。
好了,让我们开始吧。首先来看看加载的代码,我们告诉我们的网页正确地执行一段js代码,你可以看看我们添加的地方,这段代码会添加在webGLStart中:
function webGLStart() { var canvas = document.getElementById("lesson05-canvas"); initGL(canvas); initShaders();
initTexture();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
让我们来看看initTexture-它的大约三分之一的位置,是新的代码:
var neheTexture; function initTexture() { neheTexture = gl.createTexture();
neheTexture.image = new Image();
neheTexture.image.onload = function() { handleLoadedTexture(neheTexture) } neheTexture.image.src = "nehe.gif"; }
所以,在全局变量中,我们也保存了纹理。(不过如果你建立的场景太大,是一个现实的世界,那么你就会用到太多太多的纹理,这样你可能不会使用全局的变量,不过现在我们为了编程的简单,而且需要的纹理不多,因此用到了全局变量来引入这些图片)。我们使用了gl.creareTexture 创建一个纹理放入了全局变量中,那么我们再创立一个js图像对象,并且把它纳入新的属性中。而这个属性,我们会标明其“质地”(textture)。然后,我们创建一个对象,用于处理纹理对象预设的图像,这个很此前的设置有点像,很方便的。下一个步骤就是让图像对象加载了实际的东西了,不过我们一定要给一个回调函数给他,这将会使得调用的时候图像已经加载了,这样的话就能够很好地实现加载的功能了。一旦我们设置了的话,我们就图像加载的步骤完成了。图像是异步加载的,所以你设置了其图形的src,后台就会加载web服务器的图像了。这样回调的时候,再调用
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);
}
刚刚说道,我门要做的是要告诉我们的webGL质地(texture)当前的情况。bindTexTure是我们设置当前之一,他是一个类似与gl.bindBuffer的模式。
下一步,我们就要告诉我们在webGL纹理加载到所有图像需要垂直翻转。首先是坐标,我们的纹理坐标,像你通常用的X,Y,Z轴,知道了坐标的位置。相比之下,其他大多数计算机图形系统,例如,我们的GIF格式的图像使用的纹理,使用坐标移动增加 downwords 的垂直轴,其横轴是和两个坐标系统相同的。这种差异在垂直轴,意味着 webGL 的角度来看,我们使用GIF图像纹理已经为我们的垂直翻转。(这段翻译得比较混乱,说说我的理解:它的意思就是webGL的垂直翻转和其他的图形系统有区别,他的垂直移动轴是向上的。)
截下来就是上传纹理到图形卡上面了,也就是我们假如的texImage2D 。不过这些参数引入后,我们希望其在储存卡上面多次使用(因为我们这次做的demo是每一个面的都一样的)。
接下来的两行中:指定的特殊的纹理缩放参数。第一个参数告诉了WebGL做什么的时候,纹理填充了相对于屏幕大小的图像,换句话,这个参数就是告诉他如果规范在模型中的应用。第二是它的提示下同等规模如何。还有各种尺寸的提示。它具有一定的优势,但是这个是相对你的电脑配置而言的,稍微弱一点的电脑会显得很不给力。在下一课,我们将会采用不同的尺寸提示,这样你可以比较在性能上,外观上的一些差别。
一旦做到了这一点,我们设置了当前纹理为空,虽然这个不是必要的做法,但是这个是一个良好的习惯。以后你就会明白。
所以到了这里为止,这是所有纹理加载所需的代码。接下来,让我们继续利用initBuffers。在这里,更有趣的事情是顶点颜色缓冲一个新的-立方体纹理坐标缓冲区。它看起来像是这样:
cubeVertexTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
var textureCoords = [ // Front face 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // Back face 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // Top face 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // Bottom face 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // Right face 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // Left face 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, ];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords),gl.STATIC_DRAW);
cubeVertexTextureCoordBuffer.itemSize = 2; cubeVertexTextureCoordBuffer.numItems = 24;
你应该会对于这种代码感觉到很舒服吧。看到所有我们要做的,是指定每一个新的顶点缓冲区属性数组,而这个属性有两个值。顶点纹理坐标在于cartesian x,y这里。对于这些坐标,我们设定好,texture高和宽分别为1.0 1.0(0,0)是在左下角,(1,1)是在右上角。这种把现实中的纹理图像处理,是通过我们的webGL来处理的。
这些变化当中,只有initBuffers,所以让我们转移到drawScene。在这个函数的变化最有趣的是它使用了纹理。不过,在我们编写过的代码中,也有一些不同的变化方式与一个非常简单的东西,如:去除了金字塔的工作。当然这些不是详细要说的范围,我们要讨论的是代码顶端的 drawScene 的功能:
var xRot = 0; var yRot = 0; var zRot = 0;
function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); mat4.identity(mvMatrix); mat4.translate(mvMatrix, [0.0, 0.0, -5.0]);
mat4.rotate(mvMatrix, degToRad(xRot), [1, 0, 0]); mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);
mat4.rotate(mvMatrix, degToRad(zRot), [0, 0, 1]); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
还有一些一起配套起来的功能,比如动画功能,xRot, yRot, zRot 。
让我们看看纹理的代码,在initBuffers我们设立了缓冲区,其中包含纹理坐标,所以在这里我们需要它绑定到相应的属性,以便可以看到它的颜色:
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer); gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);
而现在,webGL知道了每个顶点的纹理使用位,我们需要告诉它使用的纹理,我们装过,然后绘制立方体:
gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, neheTexture); gl.uniform1i(shaderProgram.samplerUniform, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
到底为什么这里如此复杂呢。原因是webGL可处理多大32个纹理,只要你指定到gl.drawElements,他们是从编号texture0 到 texture31。我们要做的就是传递值到shader上面,首先是在第一线表示两个 texture0,然后在第三个我们传递零值到一个统一的着色器。(翻译得不太好,英文原稿:What we're doing is saying in the first two lines that texture zero is the one we loaded earlier, and then in the third line we're passing the value zero up to a shader uniform (which, like the other uniforms that we use for the matrices, we extract from the shader program in initShaders ); this tells the shader that we're using texture zero. )
无论如何,一旦这三线执行了,我们就准备好下一步了。所以我们只用像以前绘制三角形的立方体构成相同的代码。
新代码剩下的唯一解释是对的着色变化,让我们看看顶点着色器吧:
attribute vec3 aVertexPosition; attribute vec2 aTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec2 vTextureCoord;
void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vTextureCoord = aTextureCoord; }
这些都是和颜色相关的东西,我们可以类比于我们在第二课中提及到的着色器,我们正在做的是纹理坐标(不是颜色,但是实现的原理和方法和颜色相似)都赋值于每个顶点上面,并且将它传递出一个不同的变量。
一旦这一值是每个顶点调用的,将会在webGL由顶点之间用线性插入。也就是说:着色坐标是 (1,0)和(0,0)那么纹理坐标则是 (0.5,0),你记得是一半就是了。
#ifdef GL_ES precision highp float; #endif varying vec2 vTextureCoord; uniform sampler2D uSampler; void main(void) { gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)); }
因此,我们拿起插纹理坐标,我们有一个类型的变量的 采样器 ,这是着色器的纹理方式代表。 在drawScene ,我们的纹理是必然 gl.TEXTURE0 ,和统一 uSampler 被设置为值为零,所以这是我们的纹理采样器。 所有的着色器所做的是使用功能 的Texture2D 使用的坐标得到相应的纹理的颜色。 纹理传统上使用的S和T的坐标,而不是X和Y和着色器语言支持别名这些作为,我们可以很容易地使用 vTextureCoord.x 和 vTextureCoord.y 。
一旦我们拥有了该片段的颜色,我们就大功告成了! 我们在屏幕上的纹理对象。
好了第五课的翻译我就展示到这里了,如果你有什么疑问或者发现错误的话,可以微博联系我 http://weibo.com/lbj96347/ @cashlee李秉骏
翻译原文的地址是: http://learningwebgl.com/blog/?p=507 因为我是业余的翻译者,不一定翻译得非常好,你如果感兴趣,可以参考原文进行学习,期待与你一起进步