在八青妹学习webgl基础的过程中,第一次觉得难受的时候,就是在实现webgl贴图。N多次觉得不就展示一个图片嘛,在h5当中就是用
<img>标签,引入图片路径,标签上直接改改外观属性,就很好实现。但是在webgl中,需要用到坐标系的转换,还需要注意图片自身的尺寸,还需要对贴图的参数进行设置等等。但是,总归是要啃下这块的,所以慢慢梳理下吧。
tips⭐️:在准备贴图前,要知道webgl贴图的像素尺寸要保证为2的n次幂,比如16、512、256、1024等,可以自由组合出256x256、128x512等像素尺寸图片。
一、坐标系的种类
在webgl中使用贴图前,需要了解的是坐标系的种类。
canvas坐标系统:左上角为坐标原点,往右是X轴正方向,往下是Y轴正方向,如下图所示:
ps:八青妹在后面的threejs系列中,会给个用canvas做3D贴图的案例哟。
顶点坐标系统:右手坐标系,拇指X方向,食指Y方向,中指为Z方向。
纹理坐标系统:纹理坐标为UV,U代表水平坐标,V代表垂直坐标。
二、使用uv纹理贴图
在webgl中,uv贴图是一种常见的纹理贴图技术,通过将2D的纹理图像映射到3D模型表面上来实现纹理渲染。值域范围均为0到1。
1.uv坐标通常作为顶点属性存储在顶点数据中。
let vertexShader = `
attribute vec3 a_position;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;//v_uv计算最终的纹理坐标,传递给片元着色器
gl_Position = vec4(a_position, 1.0);
}
`
2.在顶点着色器中,uv坐标被用于计算最终的纹理坐标
形状的四个顶点跟图片的四个顶点对应上。
// 4个点的坐标信息-形状的4个顶点
let positions = new Float32Array([
-0.5,-1.5,0.0,
0.5,-0.5,0.0,
0.5,0.5,0.0,
-0.5,0.5,0.0,
])
// 4个点的信息-图片的4个顶点
let uvs = new Float32Array([
0.0,0.0,
0.5,0.0,
0.5,1.0,
0.0,1.0,
])
let FSIZE = positions.BYTES_PER_ELEMENT // Float32 Size = 4
let positionsBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER,positionsBuffer)
gl.bufferData(gl.ARRAY_BUFFER,positions,gl.STATIC_DRAW)
let a_position = gl.getAttribLocation(gl.program,'a_position')
gl.vertexAttribPointer(a_position,3,gl.FLOAT,false,FSIZE * 3,0)
gl.enableVertexAttribArray(a_position)
let uvsBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER,uvsBuffer)
gl.bufferData(gl.ARRAY_BUFFER,uvs,gl.STATIC_DRAW)
let a_uv = gl.getAttribLocation(gl.program,'a_uv')
gl.vertexAttribPointer(a_uv,2,gl.FLOAT,false,FSIZE * 2,0)
gl.enableVertexAttribArray(a_uv)
3. 片元着色器接受最终的纹理坐标。
let fragmentShader = `
precision mediump float;
varying vec2 v_uv;
uniform sampler2D u_sampler;
void main() {
vec4 color = texture2D(u_sampler, v_uv);
gl_FragColor = color;
}
`
4. 在片元着色器中,使用计算得到的纹理坐标通过texture2D()函数采样纹理颜色。
let texture = gl.createTexture()
let u_sampler = gl.getUniformLocation(gl.program, 'u_sampler')
let image = new Image()
image.src = '/demo.png'
// 异步的过程:图片加载完成之后执行这个函数里的任务
image.onload = function () {
// 翻转图片的Y轴,默认是不翻转
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
gl.activeTexture(gl.TEXTURE0) //激活贴图,放在第0个单元上(最少可以支持8个单元)
gl.bindTexture(gl.TEXTURE_2D, texture) //绑定贴图:哪种贴图和哪个贴图
// 对贴图的参数进行设置gl.texParameteri(贴图的种类,参数的名称,具体值)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 贴图用哪张图片,即用image作为texture
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
gl.uniform1i(u_sampler, 0)
draw(gl)
}
如果改变四个顶点的坐标,会发现图像会变形,也就是对于不完全对应顶点的片元,webgl会进行纹理坐标的插值计算,得到最终的纹理颜色。
完整的代码与效果如下所示:
代码参考github-jinhuafe
更改uv的顶点坐标也会发生有趣的变化,例如将uv坐标中的1.0改为2.0,会发现图片变成了2x2的四个图片;将例如将uv坐标中的1.0改为0.5,发现图片放大了两倍,只有左下方的四分之一展示出来。
三、对uv贴图的参数进行设置
对贴图的参数进行设置gl.texParameteri(贴图的种类,参数的名称,具体值)
texParameterf(target, pname, param)
-
target:
- 指定要设置参数的纹理对象的目标。
- 常用的目标有
gl.TEXTURE_2D(2D纹理)和gl.TEXTURE_CUBE_MAP(立方体纹理)。
-
pname:
- 指定要设置的参数名称。
- 常用的参数有:
gl.TEXTURE_MIN_FILTER: 纹理缩小时的过滤方式。gl.TEXTURE_MAG_FILTER: 纹理放大时的过滤方式。gl.TEXTURE_WRAP_S: 纹理水平方向的环绕方式。gl.TEXTURE_WRAP_T: 纹理垂直方向的环绕方式。
-
param:
- 指定要设置的参数值。
- 可以是以下预定义值:
- 环绕方式:
gl.REPEAT、gl.CLAMP_TO_EDGE、gl.MIRRORED_REPEAT - 过滤方式:
gl.NEAREST、gl.LINEAR、gl.NEAREST_MIPMAP_NEAREST、gl.LINEAR_MIPMAP_NEAREST、gl.NEAREST_MIPMAP_LINEAR、gl.LINEAR_MIPMAP_LINEAR
- 环绕方式:
纹理过滤方式:
// 大的图片贴到小的形状上去
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 大的图片贴到小的形状上去
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
// 小的图片贴到大的形状上去
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// 小的图片贴到大的形状上去
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
纹理环绕方式:
// 水平方向上采用重复环绕方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
//水平方向上采用边缘拉伸方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
// 水平方向上采用镜像重复方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT)
//垂直方向上采用边缘拉伸方式
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
采用哪种方式还是取决于需求。环绕方式的选择则取决于希望如何处理纹理超出UV坐标范围的部分,比如重复、边缘拉伸或镜像重复等。平铺背景或重复纹理的场景中,gl.REPEAT模式通常是首选;而在需要避免纹理拉伸的场景中,gl.CLAMP_TO_EDGE模式则更合适。