全部章节
06.frameBuffer-将webgl绘制的正方体作为材质
在正方体上使用贴图
上章我们绘制了一个彩色的正方体,本章我们学习一下贴图,然后基于上章的正方体把颜色修改成贴图
效果
代码
<canvas id="cvs" style="width: 400px; height: 400px" height="400px" width="400px"></canvas>
<script src="./05customApi.js"></script>
<script>
const VS = `
attribute vec4 a_Position;
uniform mat4 v_ModelMatrix;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = v_ModelMatrix * a_Position;
v_TexCoord = a_TexCoord;
}
`;
const FS = `
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
`;
</script>
<script>
const cvs = document.getElementById('cvs')
const gl = cvs.getContext('webgl')
initShader(gl, VS, FS)
bindBuffer(gl, [
{
name: 'a_Position',
size: 3,
type: gl.FLOAT,
stride: 5,
offset: 0
},
{
name: 'a_TexCoord',
size: 2,
type: gl.FLOAT,
stride: 5,
offset: 3
},
], new Float32Array([
0.5, 0.5, 0.5, 1, 1,
-0.5, 0.5, 0.5, 0, 1,
-0.5, -0.5, 0.5, 0, 0,
0.5, -0.5, 0.5, 1, 0, // v0-v1-v2-v3 front
0.5, 0.5, 0.5, 0, 1,
0.5, -0.5, 0.5, 0, 0,
0.5, -0.5, -0.5, 1, 0,
0.5, 0.5, -0.5, 1, 1, // v0-v3-v4-v5 right
0.5, 0.5, 0.5, 1, 0,
0.5, 0.5, -0.5, 1, 1,
-0.5, 0.5, -0.5, 0, 1,
-0.5, 0.5, 0.5, 0, 0, // v0-v5-v6-v1 up
-0.5, 0.5, 0.5, 1, 1,
-0.5, 0.5, -0.5, 0, 1,
-0.5, -0.5, -0.5, 0, 0,
-0.5, -0.5, 0.5, 1, 0, // v1-v6-v7-v2 left
-0.5, -0.5, -0.5, 0, 0,
0.5, -0.5, -0.5, 1, 0,
0.5, -0.5, 0.5, 1, 1,
-0.5, -0.5, 0.5, 0, 1, // v7-v4-v3-v2 down
0.5, -0.5, -0.5, 0, 0,
-0.5, -0.5, -0.5, 1, 0,
-0.5, 0.5, -0.5, 1, 1,
0.5, 0.5, -0.5, 0, 1, // v4-v7-v6-v5 back
]))
const n = bindElementsBuffer(gl, new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9, 10, 8, 10, 11, // up
12, 13, 14, 12, 14, 15, // left
16, 17, 18, 16, 18, 19, // down
20, 21, 22, 20, 22, 23 // back
]))
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT )
gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
0.7071067690849304, 0.5, -0.5, 0,
-0.5, 0.8535534143447876, 0.1464466154575348, 0,
0.5, 0.1464466154575348, 0.8535534143447876, 0,
0, 0, 0, 1
]));
const url = '图片地址,自己整一张,懒得起web服务,所以这里我放的base64,太长去掉了'
initTexture(gl, 'u_Sampler', url).then(() => {
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
})
function initTexture(gl, uniform, url) {
return new Promise(resolve => {
const image = new Image();
image.onload = () => {
loadTexture(gl, gl.getUniformLocation(gl.program, uniform), image)
resolve();
};
image.src = url
})
}
function loadTexture (gl, uniform, image) {
const texture = gl.createTexture();
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(uniform, 0);
}
</script>
解释
首先我们需要了解在webgl中,贴图是怎么映射到片元着色器上的。
这就不得不提到一个概念,UV坐标
UV坐标
UV就是一个二维的坐标系,横轴为U,竖轴为V,他们的区间都是[0,1]
。
这个坐标系可以关联贴图和模型,当我们将某个顶点的UV值设置成1,1,那么它就对应在了贴图的右上角
我们用图来直观的解释一下。
当我们把front面的四个顶点的UV坐标定义在四个角,那么使用的贴图会整个铺入front面,未被定义的像素区域,会被自动赋值两点之间的贴图像素
我们可以把right面(因为front在这个旋转角度看不到)的0.5,-0.5,0.5的UV坐标从0,0改成0,0.5,把0.5,-0.5,-0.5的UV坐标从1,0改成1,0.5,会发现绿色和粉色就没有的。
着色器代码
const VS = `
attribute vec4 a_Position;
uniform mat4 v_ModelMatrix;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = v_ModelMatrix * a_Position;
v_TexCoord = a_TexCoord;
}
`;
const FS = `
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
`;
相比较上章的着色器程序,本例中移除了color的获取和传递,而改成texCoord的传递,texCoord代表了UV坐标,所以它是vec2类型。
最终在片元着色器中使用内置函数texture2D来完成贴图的赋值。
attribute,uniform,varying
第二章的时候有提到过attribute和varying这两个限定词,本章在单独解释一下这三个限定词的区别。
- attribute 顶点着色器中的全局变量,储存逐顶点信息
- uniform 两个着色器中的全局变量,非逐顶点信息,逐片元
- varying 顶点着色器向片元着色器传递数据的全局变量
我对他们的理解是这样的
attribute定义的变量,是根据顶点数量来的,我需要绘制多少个顶点,attribute的变量就会接收多少个数据
比如我绘制两个POINTS,那么我定义的attribute a_Position就会获取到两次数据
uniform定义的变量,是整个着色器程序共用的,比如说形变矩阵,它虽然作用于每个顶点,但是它的值不会因为是不同顶点而不一样,是恒定的。
varying定义的变量,是用于在两个着色器直接传递数据的,只需要定义相同名称的变量就可以完成数据的传递。常用于attribute变量的传递,我的理解是因为在片元着色器中没有顶点的概念,所以不能定义attribute的变量。
js代码
除了UV传值和贴图相关的地方,其他的基本和上章相同。
我们直接来看webgl关于贴图相关的api
initTexture(gl, 'u_Sampler', url).then(() => {
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
})
function initTexture(gl, uniform, url) {
return new Promise(resolve => {
const image = new Image();
image.onload = () => {
loadTexture(gl, gl.getUniformLocation(gl.program, uniform), image)
resolve();
};
image.src = url
})
}
function loadTexture (gl, uniform, image) {
const texture = gl.createTexture();
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(uniform, 0);
}
initTexture
我们先看initTexture函数,定义了一个Promise,然后创建了一个HTMLImageElement加载图片,加载完了以后执行loadTexture,执行完了以后resolve。
这个函数相对比较简单,只有两个点,一个是异步,另一个是image标签。
异步是因为,图片加载时需要时间的,如果没有加载完就传入到贴图中,是不存在贴图的,而且没有加载完贴图就运行drawElements绘制出来的正方体是黑色的。
image标签是因为,可以提供给webgl当做贴图的一种数据源是image标签。
loadTexture
直接看一下流程
- 创建一个贴图对象
- 激活一个贴图单元
- 绑定贴图对象
- 配置贴图的属性
- 指定贴图的图像
- 将贴图单元传递给webgl中的uniform变量
流程和创建buffer差不多,都是创建然后绑定,再给处于绑定状态的贴图,设置属性,传值。
有一个区别是激活贴图单元这个过程,即gl.activeTexture
贴图是需要一个贴图单元是暂存图像信息的,而不同浏览器提供的贴图单元数量是不同的,可以通过gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS查看。
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
翻转Y轴坐标,正常UV的Y轴是向下的,而webgl中的y轴是向上的,所以惯性思维会用上方的点赋值(x,1)的UV,比如0,1,0点和0,0,0点,常理来说肯定把1,1赋值给0,1,0,把1,0赋值给0,0,0。
但是由于UV的Y轴是向下的,所以这样赋值会使得图像显示是反的。
gl.texParameteri
是设置贴图的一些参数配置,如本例中的填充方式等,gl.pixelStorei
其实也是类似的方法,设置的是图像里一些参数。
然后使用gl.texImage2D
指定图像,最后将激活的贴图单元的所在index传给着色器程序
总结
本章简单讲述了一下纹理的使用过程,其中只简单涉及到的每个api的少部分内容。
贴图配置的api(pexelStorei,texParameteri)可选参数,多贴图的使用(activeTexture(gl.TEXTURE0), gl.TEXTURE1 ...)这些需要大家自己去在demo中修改尝试,查看变化