第五章颜色和纹理
收获
当你学习完下面内容后你会有如下收获:
- 如何将顶点的其他(非坐标)数据传入顶点着色器中(如:颜色等)。
- 装配、光栅化、内插过程
- 如何将图像(纹理)映射到图形或三维对象的表面上。
1. 颜色
1.1 将非坐标数据传入顶点着色器
在我们之前的案例中,我们都是创建一个缓冲区对象,在其中存储顶点的坐标数据。然后将缓冲区对象传入给顶点着色器,但是我们每一个点的尺寸都是固定的统一的。这样并不好,所以我们接下来将实现给每个点不同的尺寸。
1.1.1 创建多个缓冲区
还记得,之前我们是如何将多个顶点坐标一次性传入着色器中的吗?需要遵循的步骤如下:
- 创建缓冲区对象。
- 将缓冲区对象绑定到
target
上。 - 将顶点坐标数据写入缓冲区对象中。
- 将缓冲区对象分配给对应的
attribute
变量。 - 开启
attribute
变量。
其实我们想实现给每个点不同的尺寸的话,只需要重复上面的操作即可。创建一个缓冲区对象用来传顶点坐标,创建一个缓冲区对象用来传顶点尺寸。 具体实现代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
}
</style>
<script src="./utils.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute float a_PointSize;
void main () {
gl_Position = a_Position;
gl_PointSize = a_PointSize;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
void main () {
gl_FragColor = vec4(1.0, 1.0, 0, 1.0);
}
</script>
<script>
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const vsSource = document.getElementById('vertexShader').innerHTML;
const fsSource = document.getElementById('fragmentShader').innerHTML;
const gl = canvas.getContext('webgl');
initShader(gl, vsSource, fsSource);
// 准备顶点坐标和顶点尺寸
const vertexPositions = new Float32Array([0, 0.3, -0.1, 0, 0.1, 0]);
const vertexSizes = new Float32Array([30.0, 60.0, 90.0]);
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
// 顶点坐标缓冲区
const vertexPositionsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexPositions, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
// 顶点尺寸缓冲区
const vertexSizeBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexSizeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexSizes, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_PointSize);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 3);
</script>
</body>
</html>
这里的utils.js
在第二章:WebGL入门这篇文章中有写到,暂时不需要我们理解里面的内容,用就好了。
上述步骤在WebGL
系统的内部状态如下图所示:
1.1.2 gl.vertexAttribPointer()的步进和偏移参数
上面使用多个缓冲区对象向着色器传递多种数据,比较适合数据量不大的情况。如果顶点的个数比较多的话就难以维护,所以WebGL
允许我们把顶点的坐标和尺寸数据打包到同一个缓冲区对象中。如下:
// 顶点坐标和点的尺寸
const verticesSize = new Float32Array([
0, 0.3, 30.0, // 第一个点
-0.1, 0, 60.0 // 第二个点
0.1, 0, 90.0 // 第三个点
])
我们可以将顶点坐标和点的尺寸放在一块并写入一个缓冲区对象中,但是WebGL
是无法智能的区分哪些是坐标数据,哪些是尺寸数据的。所以需要我们告诉它该如何区分,我们可以使用vertexAttribPointer()
方法的第5
和第6
个参数来实现。先回忆一下其方法的参数:
我们可以通过第5
个参数stride
告诉WebGL
我们的点是如何划分的,也就是相邻顶点之间的间隔字节数。
// 顶点坐标和点的尺寸
const verticesSize = new Float32Array([
0, 0.3, 30.0, // 第一个点
-0.1, 0, 60.0, // 第二个点
0.1, 0, 90.0 // 第三个点
])
// 获取数组中元素字节数
const FSIZE = verticesSize.BYTES_PER_ELEMENT;
...
// 告诉WebGL我们相邻顶点之间的字节数为:FSIZE * 3
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0)
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, 0)
然后通过第6
个参数offset
告诉WebGL
我们的每个顶点之间的数据类型(顶点坐标或顶点尺寸)是如何划分的。也就是每种数据类型的偏移字符大小。
// 顶点坐标和点的尺寸
const verticesSize = new Float32Array([
0, 0.3, 30.0, // 第一个点
-0.1, 0, 60.0, // 第二个点
0.1, 0, 90.0 // 第三个点
]);
// 获取数组中元素字节数
const FSIZE = verticesSize.BYTES_PER_ELEMENT;
...
// 告诉WebGL不同数据类型的偏移量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
至此就能够实现只创建一个缓冲区对象,并将各个顶点的数据放到一个数组中传入缓冲区中。也能实现上面的效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
}
</style>
<script src="./utils.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute float a_PointSize;
void main () {
gl_Position = a_Position;
gl_PointSize = a_PointSize;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
void main () {
gl_FragColor = vec4(1.0, 1.0, 0, 1.0);
}
</script>
<script>
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const vsSource = document.getElementById('vertexShader').innerHTML;
const fsSource = document.getElementById('fragmentShader').innerHTML;
const gl = canvas.getContext('webgl');
initShader(gl, vsSource, fsSource);
// 顶点坐标和点的尺寸
const verticesSize = new Float32Array([
0, 0.3, 30.0, // 第一个点
-0.1, 0, 60.0, // 第二个点
0.1, 0, 90.0 // 第三个点
]);
// 获取数组中元素字节数
const FSIZE = verticesSize.BYTES_PER_ELEMENT;
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
// 顶点缓冲区
const verticesSizeBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, verticesSizeBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesSize, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_PointSize);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 3);
</script>
</body>
</html>
WebGL
系统会根据stride
和offset
参数,从缓冲区中正确地抽取出数据,依次赋值给着色器中的各个attribute
变量,并进行绘制。WebGL系统内部行为如下:
1.1.3 修改颜色
至此,我们已经了解了将多种顶点数据信息传入顶点着色器的技术,下面我们将尝试修改各顶点的颜色。具体方法和步骤与之前相同,但是颜色是由片元着色器处理的属性,数据却在顶点着色器中,所以接下来我们将要了解顶点着色器与片元着色器之间如何传值。
我们可以使用一个新的变量varying
变量向片元着色器中传入数据,varying
变量的作用是从顶点着色器向片元着色器传输数据。
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Color;
varying vec4 v_Color;
void main () {
v_Color = a_Color;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 v_Color;
void main () {
gl_FragColor = v_Color;
}
</script>
设置不同位置
、大小
、颜色
的顶点,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
}
</style>
<script src="./utils.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec4 a_Color;
attribute float a_PointSize;
varying vec4 v_Color;
void main () {
gl_Position = a_Position;
gl_PointSize = a_PointSize;
v_Color = a_Color;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 v_Color;
void main () {
gl_FragColor = v_Color;
}
</script>
<script>
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const vsSource = document.getElementById('vertexShader').innerHTML;
const fsSource = document.getElementById('fragmentShader').innerHTML;
const gl = canvas.getContext('webgl');
initShader(gl, vsSource, fsSource);
// 顶点坐标、尺寸、颜色
const vertices = new Float32Array([
0.0, 0.5, 30.0, 1.0, 0.0, 0.0,
-0.5, -0.5, 60.0, 0.0, 1.0, 0.0,
0.5, -0.5, 90.0, 0.0, 0.0, 1.0,
]);
// 获取数组中元素字节数
const FSIZE = vertices.BYTES_PER_ELEMENT;
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
const a_Color = gl.getAttribLocation(gl.program, 'a_Color');
// 顶点缓冲区
const verticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 6, 0);
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 6, FSIZE * 2);
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_PointSize);
gl.enableVertexAttribArray(a_Color);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 3);
</script>
</body>
</html>
如果顶点着色器和片元着色器中有类型和变量名都相同的varying
变量,那么顶点着色器赋予该变量的值会自动传入给片元着色器。如下图:
当我们修改drawArrays
方法的一个参数类型为TRIANGLES
时,效果如下:
1.2 顶点着色器与片元着色器数据传输细节
我们通过上面的代码了解了可以通过varying
变量将颜色数据从顶点着色器传递到片元着色器中。但是我们并不了解这一传递过程中的细节,下面我们将好好了解一下这一过程。
1.2.1 几何形状的装配和光栅化
在顶点着色器和片元着色器之间,有这样两个步骤
- 图元装配过程:这一步的任务是,将孤立的顶点坐标装配成几何图形。几何图形的类别由
gl.drawArrays()
函数的第一个参数决定。 - 光栅化过程:这一步的任务是,将装配好的几何图形转化为片元。
宏观上:
微观上:
gl.drawArrays()
的参数n为3
,顶点着色器将被执行3
次。
第1步:执行顶点着色器,缓冲区对象的第一个坐标(0.0,0.5)被传递给attribute
变量a_Position
。一旦一个顶点的坐标被赋值给了gl_Position
,它就进入了图形装配区域,并暂时储存在那里。我们显示的赋予了a_Position
x分量和y分量,所以z与w分量都是默认值。实际上进入图形装配区域的坐标其实是(0.0,0.5,0.0,0.1)。
第2步:再次执行顶点着色器,类似地,将第2个坐标(-0.5,-0.5,0.0,1.0)传入并储存在装配区。
第3步:第3次执行顶点着色器,将第3个坐标(0.5,-0.5,0.0,1.0)传人并储存在装配区。现在,顶点着色器执行完毕,三个顶点坐标都已经处在装配区了。
第4步:开始装配图形。使用传入的点坐标,根据gl.drawArrays()的第一个参数信息(gl.TRIANGLES)来决定如何装配。
第5步:显示在屏幕上的三角形是由片元(像素)组成的,所以还需要将图形转化为片元,这个过程被称为光栅化。光栅化之后,你可以看到光栅化后得到的组成三角形的片元。
注:上面的示意图只显示了10个片元,实际上,片元的数目就是这个三角形最终在屏幕上所覆盖的像素数。
1.2.2 调用片元着色器
一旦光栅化过程结束后,程序就开始逐片元调用片元着色器。每调用一次,就处理一个片元。对于每个片元,片元着色器会计算出该片元的颜色,并写入颜色缓冲区。直到最后一个片元处理完成,浏览器就会显示出最终的结果。
1.2.3 varying变量的作用与内插过程
前面程序中指定的每个顶点的颜色不同,可最后绘制出来的是一个具有渐变色彩效果的三角形呢?
事实上,我们把顶点的颜色赋值给了顶点着色器中的varying
变量v_Color
,它的值被传给片元着色器中的同名、同类型变量如下图所示。但是,更准确地说,顶点着色器中的v_Color
变量在传入片元着色器之前经过了内插过程。所以,片元着色器中的v_Color
变量和顶点着色器中的v_Color
变量实际上并不是一回事,这也正是我们将这种变量称为“varying”(变化的)变量的原因。
我们在varying
变量中为三角形的3
个不同顶点指定了3
种不同颜色,而三角形表面上这些片元的颜色值都是WebGL
系统用这3个顶点的颜色内插出来的。
当两个顶点的的颜色不同时,他们之间的就会进行颜色值的内插。如下图所示:
在这个例子中RGBA
中的R
值从1.0
降低为0.0
,而B
值则从0.0
上升至1.0
,线段上的所有片元的颜色值都会被恰当地计算出来——这个过程就被称为内插过程
2.纹理
至此,我们了解了如何绘制彩色的图形,以及颜色的内插过程。虽然这些方法强大,但是更复杂的情况下仍然不够用。例如我们想要弄一个逼真的砌墙(如下),如果你试图创建很多个三角形并指定它们的位置和颜色来模拟墙面上的坑坑洼洼,那将使你陷入苦海。
在三维图形学中,有一项很重要的技术可以解决这个问题,那就是纹理映射
纹理映射:所谓纹理映射就是将一张图像映射到几何图形的表面上去。
纹理:映射的图像又称为纹理图像或纹理。
纹素:组成纹理图像的像素又称为纹素,每个纹素的颜色都是使用RGB
或RGBA
格式编码。
2.1 WebGL中如何做纹理映射
在WebGL中,要进行纹理映射,需要遵循以下四步:
- 准备好映射到几何图形上的纹理图像。
- 为几何图形配置纹理映射方式。
- 加载纹理图像,对其进行一些配置,以在 WebGL中使用它。
- 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元。
第1步很简单,就是准备一张图片即可。第2步的映射方式是指用那块纹理像素覆盖几何图形的那块片元,这里我们需要使用纹理坐标来确定纹理图像的哪部分将覆盖到几何图形上。
2.1.1 纹理坐标
纹理坐标是一套新的坐标体系,纹理坐标系是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹素颜色。WebGL
系统中的纹理坐标系统是二维的,如图下图所示。为了将纹理坐标和广泛使用的x
坐标和y
坐标区分开来,WebGL
使用s
和t
命名纹理坐标(st坐标系统)。
注:纹理坐标与纹理图像的大小无关,不管是128 × 128
还是128 × 258
的图像,右上角的坐标始终都是(1.0,1.0)
。
2.1.2 将纹理图像粘贴到几何图形上
如前所述,在WebGL中,我们通过纹理图像的纹理坐标与几何形体顶点坐标间的映射关系,来确认怎样将纹理图像贴上去,如下左图所示。
我们通过建立矩形四个顶点与纹理坐标对应关系,就获得了右图所示结果。
2.2 实现代码
现在,你应该已经大致了解纹理映射的原理了,接下来我们将用程序来实现。
2.2.1 设置纹理坐标
将纹理坐标传入顶点着色器,与将其他顶点数据(如颜色)传入顶点着色器的方法是相同的。
<!-- 顶点着色器 -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main () {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
</script>
<!-- 片元着色器 -->
<script id="fragmentShader" type="x-shader/x-fragment">
// 这里只需要关注v_TexCoord的传递,其他的后面会有讲
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main () {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
</script>
上面示例中v_TexCoord
是纹理的坐标,需要从顶点着色器中通过varying
传递给片元着色器。和前面传递顶点尺寸步骤一样。sampler2D u_Sampler
是定义了一个2D
的纹理采样器,texture2D
方法的作用是在片元着色器中获取纹理像素颜色。
<script>
const verticesTexCoords = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
]);
const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord);
</script>
上示例就是将程序中的纹理坐标传入缓冲区中。
2.2.2 配置和加载纹理
/**
* 配置和加载纹理
* @param {object} gl WebGL上下文对象
* @param {number} n 顶点个数
*/
const initTextures = (gl, n) => {
const texture = gl.createTexture();
const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
}
上面示例gl.createTexture()
方法可以创建纹理对象
调用该函数将会在WebGL
中创建一个纹理对象,如下图所示。其中gl.TEXTURE0
到gl.TEXTURE7
是管理纹理图像的8
个纹理单元,每一个都与gl.TEXTURE_2D
相关联,而后者也是绑定纹理时的纹理目标。
接着,请求浏览器加载纹理图像供WebGL使用,该纹理图像将会映射到矩形上。为此,我们需要使用Image
对象。
// utils.js
/**
* 配置和加载纹理
* @param {object} gl WebGL上下文对象
* @param {number} n 顶点个数
*/
const initTextures = (gl, n) => {
const texture = gl.createTexture();
const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
const image = new Image();
// 注册图像加载事件的响应函数
image.onload = () => {
loadTexture(gl, texture, n, u_Sampler, image);
}
image.src = './web.png';
}
当浏览器加载图像完成之后就会调用loadTexture()
方法,为WebGL
配置纹理。
2.2.3 为WebGL配置纹理
// utils.js
/**
* 为WebGL配置纹理
* @param {object} gl WebGL上下文对象
* @param {object} texture 纹理对象
* @param {number} n 顶点个数
* @param {object} u_Sampler 取样器
* @param {object} image image对象
*/
const loadTexture = (gl, texture, n, u_Sampler, image) => {
// 对纹理图像进行y轴反转
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
// 激活0号纹理单元
gl.activeTexture(gl.TEXTURE0);
// 向target绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 配置纹理参数
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);
// 将0号纹理传递给着色器
gl.uniform1i(u_Sampler, 0);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}
图像Y反转
在使用图像之前,你必须先进行Y轴上的反转,因为WebGL
坐标的Y
轴与纹理坐标的Y
轴刚好相反。如下图,
我们可以使用函数pixelStorei()
将纹理图像进行反转,函数介绍如下:
激活纹理单元
WebGL
通过一种称作纹理单元的机制来同时使用多个纹理。每个纹理单元有一个单元编号来管理一张纹理图像。即使你的程序只需要使用一张纹理图像,也得为其指定一个纹理单元。
系统纹理单元的个数取决于硬件和浏览器的WebGL
实现,默认情况下WebGL
至少支持8个纹理单元,内置的gl.TEXTRUE0
、gl.TEXTRUE1
...各代表一个纹理单元。
我们可以通过gl.activeTexture()
方法来激活指定单元,函数介绍如下:
上面的loadTexture
方法中的gl.activeTexture(gl.TEXTURE0)
就是激活0号单元,WebGL
内部如下图:
绑定纹理对象
我们还需要指定纹理对象使用哪种类型的纹理,并将这种类型的纹理绑定到纹理对象上。在WebGL
中纹理分为两种类型:
gl.TEXTURE_2D
:二维纹理。gl.TEXTURE_CUBE_MAP
:立方体纹理。
因为我们的示例是一张二维图像纹理,所以指定的类型也就是gl.TEXTURE_2D
了。然后通过gl.bindTexture()
进行绑定。
gl.bindTexture(gl.TEXTURE_2D, textrue);
WebGl
系统内部状态如下:
配置纹理对象的参数
接下来,还需要配置纹理对象的参数,以此来设置纹理图像映射到图形上的具体方式。我们可以通过gl.texParameteri()
来设置这些参数。
方法 | 值 | 描述 |
---|---|---|
方法方法 | gl.TEXTURE_MAG_FILTER |
当纹理的绘制范围比纹理本身更大时,会造成像素间的间隙,该参数就表示填充这些空隙的具体方法 |
缩小方法 | gl.TEXTURE_MIN_FILTER |
当纹理的绘制范围比纹理本身更小时,会造成部分像素需要剔除,该参数就表示具体的剔除像素的方法 |
水平填充方法 | gl.TEXTURE_WRAP_S |
这个参数表示,如何对纹理图像左侧或右侧的区域进行填充。 |
垂直填充方法 | gl.TEXTURE_WRAP_T |
这个参数表示,如何对纹理图像上方或下方的区域进行填充。 |
下面是四种纹理参数产生的效果:
以下是可以赋值给纹理参数的纹理参数值。
可以赋值给gl.TEXTURE_MAG_FILTER
与gl.TEXTURE_MIN_FILTER
的纹理参数值:
可以赋值给gl.TEXTURE_WRAP_S
与gl.TEXTURE_WRAP_T
的纹理参数值:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
配置好纹理参数后,WebGL
系统内部状态如下:
将纹理图像分配给纹理对象
我们通过方法gl.texImage2D()
方法将纹理图像分配给纹理对象。
这时,Image对象中的图像就从JS
传入WebGL
系统中,并存储在纹理对象中,如图所示
将纹理单元传递给片元着色器
我们通过指定纹理单元编号将纹理对象传给u_Sampler
。
gl.uniform1i(u_Sampler, 0);
WebGL
系统内部状态图,如下所示:
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
}
</style>
<script src="./utils.js"></script>
</head>
<body>
<canvas id="canvas"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main () {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main () {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
</script>
<script>
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const gl = canvas.getContext('webgl');
const vertexSource = document.getElementById('vertexShader').innerHTML;
const fragmentSource = document.getElementById('fragmentShader').innerHTML;
initShader(gl, vertexSource, fragmentSource);
// prettier-ignore
const verticesTexCoords = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
]);
const FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
const vertexTexCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_Position);
gl.enableVertexAttribArray(a_TexCoord);
initTextures(gl, 4);
</script>
</body>
</html>
注意:我们在贴图时,默认的图像源的尺寸只能是2的n次方才能显示。
当我将图像源的尺寸换成非2的n次方的时候,发现无法显示出来。
显示非2次幂图像
想要显示非二次幂图像需要添加纹理配置参数:
// utils.js
// 配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
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.TEXTURE_WRAP_S
和gl.TEXTURE_WRAP_T
: 这两个参数分别表示纹理的水平和垂直方向。S
表示水平方向,T
表示垂直方向。在二维纹理中,S
对应水平轴,T
对应垂直轴。gl.CLAMP_TO_EDGE
: 这个参数是 WebGL 纹理环绕模式的一种。gl.CLAMP_TO_EDGE
意味着当纹理坐标超出范围[0, 1]
时,纹理将被拉伸到边缘颜色。也就是说,超过边界的部分将会重复最边缘的像素颜色。
注:只有gl.CLAMP_TO_EDGE
参数能实现非2次幂图像源的显示。其他参数都不行。
参考
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgeeabf
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01