Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

WebGL(伍) 最简单的贴图

  如果看过的之前的文章,那就对WebGL的基本工作方式就都比较了解了吧。这篇文章咱要来给立方体贴上图。这是个非常重要的东西,如果不会贴图,3D就只是有颜色的几何体而已,没有什么实用价值了。不过贴图还是很复杂的,这篇咱只说说最简单的贴图。
  如果完全理解了上一篇给立方体每个面上不同颜色的工作原理,这个贴图的工作原理也应该可以猜到吧?我们在给立方体每个面设置不同的颜色时就是给每个面的所有顶点设置上一样的颜色,这样光栅化处理后着色器就能给整个面都填上颜色。只要把这个添加颜色的步骤换成从某个图片上取坐标点的颜色就可以变成给片段贴图了嘛。这个思路是很正确的,但是光靠之前的那些知识显然无法把一张图片的所有颜色点一次性全部传入着色器中,这就无法完成这个贴图的操作。其实贴图操作关键就是把一张图片的数据直接传入着色器,至于如果把图片上的点映射到片段上这个靠之前的知识就可以完成。
  这篇文章的说明阶段我就不贴出完整代码了,因为着色器创建什么的之前的文章都已经介绍的很清楚了。不过不用担心,文章的最后会提供例子的完整代码。这个例子依然使用旋转的立方体,下面是着色器的代码。
//顶点着色器
attribute vec3 po;
attribute vec2 mp; //贴图坐标
uniform mat4 pro;
uniform mat4 rot;
uniform mat4 mov;
varying vec2 mp_v; //贴图坐标管道
void main(){
  mp_v=mp;
  gl_Position=pro*mov*rot*vec4(po,1.0);
}
//片段着色器
varying lowp vec2 mp_v; //贴图坐标管道
uniform sampler2D tex; //图片数据对象
void main(){
  //调用texture2D函数,取出图片数据对象在某个坐标点的颜色
  gl_FragColor=texture2D(tex,mp_v);
}
  以前有出现过的我就不再注释了。由于图片是2D的,定位图片上颜色的点只需要二阶向量(平面向量)就足够了,所以我们用vec2。这个变量的传递和上一篇的颜色传递一样,其实这个变量的作用和颜色也是差不多的。接着是片段着色器,这里使用了个uniform接口变量,类型是sampler2D。它就是贴图的关键,之前没有说过的东西。从这个类型的名字就可以看出,它是一个“2D样本”,可以理解为一堆颜色数据吧。使用它就是把一个图片的所有颜色数据直接传入着色器。但是它是一个句柄,并不是图片的颜色数组。要从这个句柄中取出颜色需要调用GLSL的内置函数texture2D。这个函数的第一个参数就是“2D样本”的句柄,第二个参数是一个二维的坐标,也就是平面向量。由于每个顶点在图片上对应的位置不同,我们把这个对应的位置通过attribute传入了顶点着色器,也就是之前使用vec2定义的接口。这些都比较简单吧,唯一要注意的是图片上的坐标是相对坐标,而且使用的是平面坐标系。从原点开始,整个图片在第一象限上。图片的宽度高度都为1,也就是100%。而我们的立方体使用的坐标是摄像机坐标,宽度什么的都是绝对的,所以要记得贴图时候处理成相对坐标。这个我是直接在传入贴图坐标数据源中计算的,后面的代码会出现。
  接着,创建着色器的代码我就不贴出来了,直接到指定顶点数据源的代码。
//指定顶点坐标的数据源
dat=new Float32Array(tmp=[
  -1,-1,-1, -1,1,-1,  -1,1,1, -1,-1,1,  //左面
  1,-1,-1,  1,1,-1,   1,1,1,  1,-1,1,   //右面
  -1,1,-1,  1,1,-1,   1,1,1,  -1,1,1,   //上面
  -1,-1,-1, 1,-1,-1,  1,-1,1, -1,-1,1,  //下面
  -1,-1,1,  1,-1,1,   1,1,1,  -1,1,1,   //前面
  -1,-1,-1, 1,-1,-1,  1,1,-1, -1,1,-1   //后面
]);
buf=webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER,buf);
webgl.bufferData(webgl.ARRAY_BUFFER,dat,webgl.STATIC_DRAW);
webgl.vertexAttribPointer(po,3,webgl.FLOAT,false,0,0);

//指定贴图坐标数据源
for(dat=[],i=0;i<tmp.length;i+=3)for(j=0;j<3;j++)
  if(j!=(i/24|0))dat.push((tmp[i+j]+1)/2);
dat=new Float32Array(dat);
buf=webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER,buf);
webgl.bufferData(webgl.ARRAY_BUFFER,dat,webgl.STATIC_DRAW);
webgl.vertexAttribPointer(mp,2,webgl.FLOAT,false,0,0);
  为什么我要把指定顶点坐标数据源的代码都贴出来呢?因为贴图坐标我是直接根据顶点坐标计算出来的。你会看到这个代码中用了循环来计算,其实这个计算很简单。比如左右这两个面,他们是在yz平面上的,而图片坐标只有xy,那么我们就需要把这个yz弄到xy上,这就相当于把左右面所有顶点的x坐标去掉。上下面是xz平面上的,所以去掉y坐标。前后面是xy平面上的,所以去掉z坐标。这个二重循环的功能就是这个,至于循环本身我就不说了。不过要注意的是,由于原坐标是从-1到1的,而贴图坐标上面说过了是从0到1的,所以这里做了个(n+1)/2的处理。接着是传入图片数据的uniform,其他uniform传入矩阵的代码和上一篇一样,我就不贴出来了。 //设置图片数据
var img;
img=new Image;
img.src="/images/signet-128.png";
img.onload=function(){
  //创建贴图(材质)对象句柄
  var texture=webgl.createTexture();
  //指定当前使用的贴图序号
  webgl.activeTexture(webgl.TEXTURE0);
  //设置为当前工作的贴图对象
  webgl.bindTexture(webgl.TEXTURE_2D,texture);
  //设置图片数据
  webgl.texImage2D(
    webgl.TEXTURE_2D,0,webgl.RGBA,webgl.RGBA,webgl.UNSIGNED_BYTE,img
  );
  //设置贴图参数
  webgl.texParameteri(
    webgl.TEXTURE_2D,webgl.TEXTURE_MIN_FILTER,webgl.NEAREST
  );
  //把预置缓冲区的序号传入uniform
  webgl.uniform1i(tex,0);
  //开始绘制(这个是自定义函数,在后面定义的)
  draw();
};
  既然需要图片数据,我们就必须先取得一张图片。由于WebGL支持IMG加载数据,所以我们直接创建个Image对象来加载。但是要注意WebGL是运行在CANVAS上的,CANVAS使用图片都有跨域安全性限制的,因为CANVAS有读取出图片二进制流的功能。所以,应该使用同域下的图片。这篇文章的例子如果下载到本地也要记得把图片带下去。对于图片的要求还有很重要的一点,它的尺寸必须是2的n次方。也就是32、64、128,等这样的尺寸,使用其他尺寸的图片会抛出异常。
  在图片加载完成之后,我们先调用createTexture创建一个贴图对象。根据以往的经验,当然是要调用bindTexture把它设置为当前工作对象了。可是贴图这东西并没那么简单,在调用bindTexture之前需要先设置本次bind使用的序号。也就是调用activeTexture来设置的。为什么需要做这样一个步骤呢?因为WebGL中贴图并不是可以无限制的贴,在每次绘制操作执行时可以使用的贴图数量最多只能有32个。这32个都是以常量的形式定义好的,比如上面的代码片段使用了TEXTURE0这个常量,代表第0个贴图,后面的数字以此类推。bindTextue这个操作则是在已经设置了activeTexture的大环境下工作的,每次使用activeTexture指定了不同的贴图需要都可以调用bindTexture绑定一个贴图对象。每个序号对应一个贴图对象,它们是同时绑定的。在绑定了贴图对象之后我们就可以往显存里放入数据,通过调用texImage2D这个函数来完成。之后是调用texParameteri来设置贴图的参数。texImage2D和texParameteri两个函数的参数列表比较复杂,以后的问中会单独介绍他们。这篇就不细说了。
  到这里,贴图的东西就说完了,上面这个代码最后还有一个draw函数,其实就是绘制的主函数。由于整个代码需要在这个图片加载完成后才开始执行,所以把绘制的工作也放入函数中,在这个事件中调用了。那个函数的代码我就不单独贴出来了,因为里面的东西之前都说过。最后是整个实例的代码:

  WebGL最简单的贴图
网名:
34.203.245.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^