本节我们来讲WebGL里面很重要的纹理
纹理贴图在游戏开发中十分常见,但是纹理不单单只是一张贴图那么简单。
在WebGL中,纹理有多种,一维纹理:就是一条线;二维纹理:一张二维图片;二维纹理数组:由多张二维图片组成;还有三维纹理,立体纹理,本节主要介绍二维纹理
在早期的OpenGL中,只支持图片大小为2^n的纹理,但现在支持,但是最好用大小为2^n的纹理,这样效率会比较高
先看看效果:
var jsArrayData =
[
0, 0, 0, 0.0, 0.0,
400, 0, 0, 1.0, 0.0,
400, 400, 0, 1.0, 1.0,
0, 0, 0, 0.0, 0.0,
400, 400, 0, 1.0, 1.0,
0, 400, 0, 0.0, 1.0,
];
void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, TexImageSource? source):
第一个参数target:指定是什么类型的纹理(2选1),
第二个参数level:是纹理的级别,0是基本文件级别,之后再详细说,
第三个参数internalforma:t是纹理像素在显卡内存中存储的格式,GL_RGBA表示一个像素由4个分量(r,g,b,a)组成,
第四个参数format:是指我们传入的图片像素的存储格式,
第五个参数type:是像素每个分量的存储类型,webgl.UNSIGNED_BYTE表示r,g,b,a的存储类型是unsigned byte,1个字节,范围是[0,255],
最后一个参数source:是图片文件
void texParameteri(GLenum target, GLenum pname, GLint param) 是设置纹理参数用的,
第一个参数与前面一致,
第二个参数pname:是纹理的参数:只能是下列四个:
GL_TEXTURE_MIN_FILTER:指定纹理图片缩小时用到的算法
GL_TEXTURE_MAG_FILTER:指定纹理图片放大时用到的算法
GL_TEXTURE_WRAP_S :纹理包装算法,在s方向
GL_TEXTURE_WRAP_T :纹理包装算法,在t方向
第三个参数param:是第二个参数的值(value),eg: webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.NEAREST):表示纹理放大时用的算法是webgl.NEAREST,最近点采样,param有多个,具体的下面在介绍。
webgl.bindTexture(webgl.TEXTURE_2D, null);
这句代码的作用是将纹理target绑定一个空纹理,这样就不会影响webgl的纹理状态机,不会向下影响,否则一直会使用上面绑定的texture纹理
在shader里面也要有相应的改动,代码如下
vs中:attribute vec2 inUV:表示纹理的坐标,是一个二维坐标,每个顶点都应该指定一个UV(ST)坐标,UV(ST)的范围是[0,1]:
inUV的类型一定是attribute,是通过顶点带进来的,然后这个变量要传给fs,因为最终取纹理图片像素绘制给矩形是在fs进行的,所以要加varying vec2 outUV.
在fs中 uniform sampler2D texture 就是我们加载的纹理图片,varying vec2 outUV是接受从vs传过来的纹理UV坐标
gl_FragColor = texture2D(texture, vec2(outUV.x, outUV.y)):这句代码是将纹理图片(outUV.x,outUV.y)坐标位置的像素取出来赋值给gl_FragColor,用于随后的着色
下面就是为shader里面新添加的变量,attribute vec2 inUV和uniform sampler2D texture 赋值了,在function init()添加下面代码,
uniformTexture = webgl.getUniformLocation(programObject, "texture");
attrUV = webgl.getAttribLocation(programObject, "inUV");
为了使用我们加载进来的纹理图片,我们还需要在function init()里添加其他代码:
webgl.activeTexture(webgl.TEXTURE0);//激活level=0,第0阶段的纹理
webgl.bindTexture(webgl.TEXTURE_2D, textureHandle);//使用textureHandle指向的纹理
webgl.uniform1i(uniformTexture, 0);//为shader fs里的sampler2D texture 赋值,0表示:texture取第0阶段的纹理
其中void activeTexture(GLenum texture) 参数texture取值为下面所列,共32个,说明WebGL可以在一个三角形面上最多贴32张纹理,这32张纹理就是32个阶段,或32个寄存器,默认激活第0阶段的纹理
/* TextureUnit */
const GLenum TEXTURE0 = 0x84C0;顶点缓冲区要加上纹理UV坐标:
var jsArrayData =
[
x y z u v
0, 0, 0, 0.0, 0.0,
400, 0, 0, 1.0, 0.0,
400, 400, 0, 1.0, 1.0,
0, 0, 0, 0.0, 0.0,
400, 400, 0, 1.0, 1.0,
0, 400, 0, 0.0, 1.0,
];
xyz与uv的对应关系如下:
function renderScene()也要相应的调整:
function renderScene() {
//! 设置重绘背景的颜色
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
//! 执行绘制,即将背景清空成制定的颜色(clearColor)
webgl.clear(webgl.COLOR_BUFFER_BIT);
//! 指定绘制所使用的顶点数据 从 该缓冲区中获取
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.activeTexture(webgl.TEXTURE0);
webgl.bindTexture(webgl.TEXTURE_2D, textureHandle);
webgl.uniform1i(uniformTexture, 0);
webgl.uniformMatrix4fv(uniformProj, false, projectMat);
webgl.enableVertexAttribArray(v3PositionIndex);
webgl.enableVertexAttribArray(attrUV);
webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 4 * 5, 0);
webgl.vertexAttribPointer(attrUV, 2, webgl.FLOAT, false, 4 * 5, 12);
webgl.drawArrays(webgl.TRIANGLES, 0, 6);
}
纹理的基本流程就显示完了,现在来 深入讲一下纹理的参数设置,
纹理其实是一系列的二维图片,不是一张。
假设我在游戏里面离一张图片很近,那么我看的就很清楚,而离远了就很模糊,其实这是有多张纹理,这样做为了提升性能,离近了用清晰的纹理,远处用模糊的纹理,纹理的这种处理是LOD,层次细节
void texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, TexImageSource? source)现在我们仔细讲讲里面的参数,
这句代码的作用是讲图片里面的数据传给纹理对象
第二个参数是纹理的级别level,下面看一些图,你就明白是什么意思了:
最左边的是第0层,向右是1层,大小依次递减
void texParameteri(GLenum target, GLenum pname, GLint param) 是设置纹理参数用的,上面有介绍
第二个参数pname:是纹理的参数:只能是下列四个:
GL_TEXTURE_MIN_FILTER:指定纹理图片缩小时用到的算法
GL_TEXTURE_MAG_FILTER:指定纹理图片放大时用到的算法
GL_TEXTURE_WRAP_S :纹理包装算法,在s方向
GL_TEXTURE_WRAP_T :纹理包装算法,在t方向
放大和缩小所用的算法只有两个 NEAREST和LINEAR,(即第三个参数param的值是webgl.NEAREST或webgl.LINEAR)分别是最近点采样和线性采样,前者效率高单效果不好,后者效率不高单效果比较好。
GL_TEXTURE_WRAP_S和GL_TEXTURE_WRAP_T是纹理包装,之前我们说过纹理坐标是[0,1],但是如果纹理坐标超过1该怎么办
它们的算法(param的值)有:REPEAT,CLAMP_TO_EDGE,MIRRORED_REPEAT,分别是重复绘制,用边沿的像素填充和镜像重复(反过来)
下面我演示下你们就会明白,我先使纹理坐标超过1
var jsArrayData =
[
0, 0, 0, 0.0, 0.0
400, 0, 0, 2.0, 0.0,
400, 400, 0, 2.0, 2.0,
0, 0, 0, 0.0, 0.0,
400, 400, 0, 2.0, 2.0,
0, 400, 0, 0.0, 2.0
];
纹理参数设置如下:
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);
运行的效果:
左边是使用CLAMP_TO_EDGE,纹理坐标超过1,右边是纹理坐标没超过1,两者对比可以发现左边纹理坐标超过1的部分是由边缘像素填充的
当设置为REPAET时
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.REPAET);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.REPAET);
效果如下:
纹理坐标超过1的部分是重复绘制
当设置为MIRR0RED_REPAET时
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.MIRR0RED_REPAET);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.MIRR0RED_REPAET);
效果如下:
纹理坐标超过1的部分是重复反过来绘制
好, 本节内容完