10 WEBGL与GPU渲染原理(难)
10-1 渲染管线
-
渲染管线 Webgl的渲染依赖底层GPU的渲染能力。所以WEBGL 渲染流程和 GPU 内部的渲染管线是相符的。 渲染管线的作用是将3D模型转换为2维图像。 在早期,渲染管线是不可编程的,叫做固定渲染管线,工作的细节流程已经固定,修改的话需要调整一些参数。 现代的 GPU 所包含的渲染管线为可编程渲染管线,可以通过编程 GLSL 着色器语言 来控制一些渲染阶段的细节,。 简单来说: 就是使用shader,我们可以对画布中每个像素点做处理,然后就可以生成各种酷炫的效果了。
10-2 渲染过程
- 顶点着色器
- 图片装配
- 光栅化
- 片元着色器
- 裁剪测试
- 多重采样操作
- 背面剔除
- 模板测试
- 深度测试
- 融合
- 缓存
顶点着色器
WebGL就是和GPU打交道,在GPU上运行的代码是一对着色器,一个是顶点着色器,另一个是片元着色器。每次调用着色程序都会先执行顶点着色器,再执行片元着色器。
一个顶点着色器的工作是生成裁剪空间坐标值,通常是以下的形式:
const vertexShaderSource = `
attribute vec3 position;
void main() {
gl_Position = vex4(position, 1);
}
`
每个顶点调用一次(顶点)着色器,每次调用都需要设置一个特殊的全局变量 gl_Position。 该变量的值就是裁减
空间坐标值。这里有同学就问了,什么是裁剪空间的坐标值???
何为裁剪空间坐标?就是无论你的画布有多大,裁剪坐标的坐标范围永远是-1 到1。
看下面这张图:
如果运行一次顶点着色器, 那么gl_Position 就是 (-0.5,-0.5,Q,1) 记住他永远是个 Vec4, 简单理解就是对应x、y、z、w。 即使你没用其他的,也要设置默认值,这就是所谓的 3维模型转换到我们屏幕中。顶点着色器需要的数据,可以通过以下四种方式获得。
- attributes 属性(从缓冲读取数据)
- uniforms 全局变量 (一般用来对物体做整体变化、旋转、缩放)
- textures 纹理(从像素或者纹理获得数据)
- varyings 变量 (将顶点着色器的变量 传给 片元着色器)
图元装配和光栅化
什么是图元?
描述各种图形元素的函数叫做图元,描述几何元素的称为几何图元(点,线段或多边形)。点和线是最简单的
何图元 经过顶点着色器计算之后的坐标会被组装成组合图元。
通俗解释: 图元就是一个点、一条线段、或者是一个多边形。
什么是图元装配呢?
简单理解就是说将我们设置的顶点、颜色、纹理等内容组装称为一个可渲染的多边形的过程
组装的类型取决于: 你最后绘制选择的图形类型
gl.drawArray(gl.TRIANGLES, 0, 3);
如果是三角形的话,顶点着色器就执行三次
光栅化
什么是光栅化:
通过图元装配生成的多边形,计算像素并填充,剔除不可见的部分,剪裁掉不在可视范围内的部分。最终生成可见的带有颜色数据的图形并绘制。
光栅化流程图解:
剔除和裁剪
剔除: 在日常生活中,对于不透明物体,背面对于观察者来说是不可见的。同样,在webgl中,我们也可以设定物体的背面不可见,那么在渲染过程中,就会将不可见的部分剔除,不参与绘制。节省染开销。
剪裁: 日常生活中不论是在看电视还是观察物体,都会有一个可视范围,在可视范围之外的事物我们是看不到的。类似的图形生成后,有的部分可能位于可视范围之外,这一部分会被剪裁掉,不参与绘制。以此来提高性能。这个就是视椎体,在📷范围内能看到的东西,才进行绘制。
片元着色器
光珊化后,每一个像素点都包含了 颜色 、深度 、纹理数据, 这个我们叫做片元
小tips :每个像素的颜色由片元着色器的gl_Fragcolor提供
接收光栅化阶段生成的片元,在光栅化阶段中,已经计算出每个片元的颜色信息,这一阶段会将片元做逐片元挑选的操作处理过的片元会继续向后面的阶段传递。片元着色器运行的次数由图形有多少个片元决定的。
逐片元挑选
通过模板测试和深度测试来确定片元是否要显示,测试过程中会丢弃掉部分无用的片元内容,然后生成可绘制的二维图像绘制并显示。
- 深度测试: 就是对z轴的值做测试,值比较小的片元内容会覆盖值比较大的。深度测试:(类似于近处的物体会遮挡远处物体)
- 模板测试: 模拟观察者的观察行为,可以接为镜像观察。标记所有镜像中出现的片元,最后只绘制有标记的内容。
10-3 WEBGL绘制三角形
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// 获取canvas对象
const canvas = document.getElementById("canvas");
// canvas宽高
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 获取webgl绘图上下文
var gl = canvas.getContext("webgl");
// 第一次创建webgl上下文时,需要设置视口的大小
gl.viewport(0, 0, canvas.width, canvas.height);
// 创建顶点着色器
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 创建顶点着色器的源码,需要编写glsl代码
gl.shaderSource(
vertexShader,
`
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
`
);
// 编译顶点着色器
gl.compileShader(vertexShader);
// 创建片元着色器
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 创建片元着色器的源码,需要编写glsl代码
gl.shaderSource(
fragmentShader,
`
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
);
// 编译片元着色器
gl.compileShader(fragmentShader);
// 创建程序连接顶点着色器和片元着色器
var program = gl.createProgram();
// 链接顶点着色器和片元着色器
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 链接程序(将程序和上下文进行关联)
gl.linkProgram(program);
// 使用程序进行渲染
gl.useProgram(program);
// 创建顶点缓冲区对象
var vertexBuffer = gl.createBuffer();
// 绑定顶点缓冲区的对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向顶点缓冲区对象中写入数据
var vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);
// gl.STATIC_DRAW 表示数据不会或几乎不会改变, gl.DYNAMIC_DRAW 表示数据会被改变, gl.STREAM_DRAW 表示数据每次绘制时都会改变
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 获取顶点着色器中的a_Position变量的位置
var a_Position = gl.getAttribLocation(program, "a_Position");
// 将顶点缓冲区对象分配给a_Position变量,以两个值为一组数据传给a_Position变量,float类型,不进行数据归一化处理,顶点数据在缓冲区中的偏移量为0,顶点数据在缓冲区中的步长为0
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 启用顶点着色器中的a_Position变量
gl.enableVertexAttribArray(a_Position);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>
</body>
</html>
10-4 缩放矩阵与uniform变量和varying变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// 获取canvas对象
const canvas = document.getElementById("canvas");
// canvas宽高
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 获取webgl绘图上下文
var gl = canvas.getContext("webgl");
// 第一次创建webgl上下文时,需要设置视口的大小
gl.viewport(0, 0, canvas.width, canvas.height);
// 创建顶点着色器
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
// 创建顶点着色器的源码,需要编写glsl代码
gl.shaderSource(
vertexShader,
`
attribute vec4 a_Position;
uniform mat4 u_Mat;
varying vec4 v_Color;
void main() {
gl_Position = u_Mat * a_Position;
v_Color = gl_Position;
}
`
);
// 编译顶点着色器
gl.compileShader(vertexShader);
// 创建片元着色器
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// 创建片元着色器的源码,需要编写glsl代码
gl.shaderSource(
fragmentShader,
`
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
);
// 编译片元着色器
gl.compileShader(fragmentShader);
// 创建程序连接顶点着色器和片元着色器
var program = gl.createProgram();
// 链接顶点着色器和片元着色器
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// 链接程序(将程序和上下文进行关联)
gl.linkProgram(program);
// 使用程序进行渲染
gl.useProgram(program);
// 创建顶点缓冲区对象
var vertexBuffer = gl.createBuffer();
// 绑定顶点缓冲区的对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// 向顶点缓冲区对象中写入数据
var vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);
// gl.STATIC_DRAW 表示数据不会或几乎不会改变, gl.DYNAMIC_DRAW 表示数据会被改变, gl.STREAM_DRAW 表示数据每次绘制时都会改变
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
// 获取顶点着色器中的a_Position变量的位置
var a_Position = gl.getAttribLocation(program, "a_Position");
// 将顶点缓冲区对象分配给a_Position变量,以两个值为一组数据传给a_Position变量,float类型,不进行数据归一化处理,顶点数据在缓冲区中的偏移量为0,顶点数据在缓冲区中的步长为0
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
// 启用顶点着色器中的a_Position变量
gl.enableVertexAttribArray(a_Position);
// 清除canvas
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
let scale = {
x: 1.5,
y: 1.5,
z: 1.5,
};
function animate() {
// 清除canvas
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
scale.x -= 0.01;
if (scale.x < 0) {
scale.x = 1.5;
}
const mat = new Float32Array([
scale.x,
0.0,
0.0,
0.0,
0.0,
scale.x,
0.0,
0.0,
0.0,
0.0,
scale.x,
0.0,
0.0,
0.0,
0.0,
1.0,
]);
const u_Mat = gl.getUniformLocation(program, "u_Mat");
gl.uniformMatrix4fv(u_Mat, false, mat);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
11 精通着色器编程
11-1 初识着色器语言
着色器材质
使用自定义shader渲染的材质。shader是一个用GLSL 编写的小程序,在GPU上运行。我们可能需要使用自定义shader实现:
- 内置materials之外的效果
- 将许多对象组合成耽搁BufferGeometry以提高性能
变换与坐标系
所以常见的顶点着色器中,顶点变换过程通常用下面两种写法:
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
<投影矩阵> * <视图矩阵> * <模型矩阵> * <顶点坐标>
// const material = new THREE.MeshStandardMaterial({
// color: "#00ff00",
// });
// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: `
void main() {
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 64, 64),
shaderMaterial
);
scene.add(floor);
11-2 着色器插件安装与文件导入开发
-
将着色器文件单独抽离出来
-
有高亮,可以在vscode中安装Shader插件
-
修改代码
import basicVertexShader from "../shader/basic/vertex.glsl"; import basicFragmentShader from "../shader/basic/fragment.glsl"; // 创建着色器材质 const shaderMaterial = new THREE.ShaderMaterial({ vertexShader: basicVertexShader, fragmentShader: basicFragmentShader, }); // 创建平面 const floor = new THREE.Mesh( new THREE.PlaneBufferGeometry(1, 1, 64, 64), shaderMaterial ); scene.add(floor);
11-3 认识rawShaderMaterial与attribute_un
-
rawShaderMaterial 原始着色器材质
-
着色器材质的变量
每个着色器材质都可以指定两种不同类型的shaders,他们是顶点着色器和片元着色器(Vertex shaders and fragmentshaders).
- 顶点着色器首先运行;它接收attributes,计算/操纵每个单独顶点的位置,并将其他数据(varyings)传递给片元着色酯。
- 片元(或像素)着色器后运行;它设置渲染到屏幕的每个单独的“片元”(像素)的颜色。
shader中有三种类型的变量: uniforms, attributes, 和 varyings
- Uniforms是所有顶点都具有相同的值的变量。比如灯光,雾,和阴影贴图就是被储存在uniforms中的数据。uniforms可以通过顶点着色器和片元着色器来访问。
- Atributes 与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据。attributes 只可以在顶点着色器中访问。
- Varyings 是从顶点着色器传递到片元着色器的变量。对于每一个片元,每一个varying的值将是相邻顶点值的平滑插值。
注意:在shader 内部,uniforms和atributes就像常量;你只能使用JavaScript代码通过缓冲区来修改它们的值
// vertex.glsl
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
varying vec2 vUv;
// 高精度-中精度-低精度
// highp -2^16 - 2^16
// mediump -2^10 - 2^10
// lowp -2^8 - 2^8
void main() {
vUv = uv;
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
precision lowp float;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv, 0.0, 1.0);
}
import rawVertexShader from "../shader/raw/vertex.glsl";
import rawFragmentShader from "../shader/raw/fragment.glsl";
// 创建原始着色器材质
const rawShaderMaterial = new THREE.RawShaderMaterial({
vertexShader: rawVertexShader,
fragmentShader: rawFragmentShader,
side: THREE.DoubleSide,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 64, 64),
rawShaderMaterial
);
scene.add(floor);
11-4 控制顶点位置打造波浪形状
glsl内置函数
1 和角度相关的函数
| 函数 | 参数 | 描述 |
|---|---|---|
| sin(x) | 弧度 | 正弦函数 |
| cos(x) | 弧度 | 余弦函数 |
| tan(x) | 弧度 | 正切函数 |
| asin(x) | 弧度 | 反正弦函数 |
| acos(x) | 弧度 | 反余弦函数 |
| atan(x) | 弧度 | 反正切函数 |
| radians(x) | 角度 | 角度转换为弧度 |
| degrees(x) | 弧度 | 弧度转换为角度 |
2 数学函数
| 函数 | 描述 |
|---|---|
| pow(x, y) | x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。 |
| exp(x) | e的x次方 |
| log(x) | 计算满足x等于e的y次方的y的值。如果x的值小于0,结果是未定义的 |
| exp2(x) | 计算2的x次方 |
| log2(x) | 计算满足x等于2的y次方的y的值。如果x的值小于0,结果是未定义的 |
| sqrt(x) | 计算x的开方。如果x小于0,结果是未定义的 |
| inversesqrt(x) | 计算x的开方之一的值,如果x小于等于0,结果是未定义的 |
3 常用函数
| 函数 | 描述 |
|---|---|
| abs(x) | 返回x的绝对值 |
| sign(x) | 如果x>0,返回1.0;如果x=0,返回0;如果x<0,返回-1.0 |
| floor(x) | 返回小于等于x的最大正整数 |
| ceil(x) | 返回大于等于x的最小正整数 |
| fract(x) | 返回x-floor(x),即返回x的小数部分 |
| mod(x, y) | 返回x和y的模 |
| min(x, y) | 返回x和y的值较小的那个值 |
| max(x, y) | 返回x和y的值较大的那个值 |
| clamp(x, minVal, maxVal) | 将x值钳于minVal和maxVal之间,意思就是当x<minVal时返回minVal,当x>maxVal时返回maxVal,当x在minVal和maxVal之间时,返回x |
| mix(x, y, a) | 返回线性混合的x和y,如:x*(1-a)+y*a |
| step(edge, x) | 如果x<edge,返回0.0,否则返回1.0 |
| smoothstep(edge0, edge1, x) | 如果x<=edge0,返回0.0;如果x>=edge1返回1.0;如果edge0<x<edge1,则执行0~1之间的平滑艾尔米特差值。如果edge0>=edge1,结果是未定义的 |
4 几何函数
| length(x) | 返回向量x的长度 |
|---|---|
| distance(p0, p1) | 计算向量p0,p1之间的距离 |
| dot | 向量x,y之间的点积 |
| cross(x, y) | 向量x,y之间的叉积 |
| normalize(x) | 标准化向量,返回一个方向和x相同但长度为1的向量 |
| faceforward(N, I, Nref) | 如果Nref的点积和I的点积小于0,返回N;否则,返回-N; |
| reflect(I, N) | 返回反射向量 |
| refract(I, N, eta) | 返回折射向量 |
-
移动创建的平面的位置
// vertex.glsl precision lowp float; attribute vec3 position; attribute vec2 uv; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix; varying vec2 vUv; void main() { vUv = uv; // 将每一个顶点需要的公共部分提出来,再去设置位置 vec4 modelPosition = modelMatrix * vec4(position, 1.0); modelPosition.x += 1.0; modelPosition.z += 1.0; gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); }
-
用sin设置平面顶点的位置,同时给片元着色器传参数设置颜色
// vertex.glsl precision lowp float; attribute vec3 position; attribute vec2 uv; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix; varying vec2 vUv; // 创建变量 varying float vElevation; void main() { vUv = uv; vec4 modelPosition = modelMatrix * vec4(position, 1.0); // modelPosition.x += 1.0; // modelPosition.z += 1.0; // modelPosition.z += modelPosition.x; modelPosition.z = sin(modelPosition.x * 20.0) * 0.1; modelPosition.z += sin(modelPosition.y * 20.0) * 0.05; // 为变量赋值 vElevation = modelPosition.z; gl_Position = projectionMatrix * viewMatrix * modelPosition; }// fragment.glsl precision lowp float; varying vec2 vUv; // 接收变量 varying float vElevation; void main() { // gl_FragColor = vec4(vUv, 0.0, 1.0); // vElevation范围为为-0.5~0.5,颜色值的范围是0.0~1.0,因此加上0.05*10.0 // 效果:靠近眼睛的地方颜色亮,远离眼睛的地方颜色暗 float deep = vElevation + 0.05 * 10.0; gl_FragColor = vec4(1.0*deep, 0.0, 0.0, 1.0); }
11-5 uniform传递时间变量打造动画与通过uv采样纹理
-
让红色的波浪面动起来
// main.js const rawShaderMaterial = new THREE.RawShaderMaterial({ vertexShader: rawVertexShader, fragmentShader: rawFragmentShader, // wireframe: true, side: THREE.DoubleSide, // 创建uniforms变量 uniforms: { uTime: { value: 0, }, }, }); function render() { let elapsedTime = clock.getElapsedTime(); // 将获取到的运行时间传给uniform rawShaderMaterial.uniforms.uTime.value = elapsedTime; controls.update(); renderer.render(scene, camera); // 渲染下一帧的时候调用render函数 requestAnimationFrame(render); }// vertex.glsl precision lowp float; attribute vec3 position; attribute vec2 uv; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelMatrix; // 获取时间 uniform float uTime; varying vec2 vUv; varying float vElevation; void main() { vUv = uv; vec4 modelPosition = modelMatrix * vec4(position, 1.0); // 设置时间 modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05; modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05; vElevation = modelPosition.z; gl_Position = projectionMatrix * viewMatrix * modelPosition; }
-
让旗帜飘动起来
// main.js // 加载纹理 const textureLoader = new THREE.TextureLoader(); const texture = textureLoader.load("./texture/ca.jpeg"); const rawShaderMaterial = new THREE.RawShaderMaterial({ vertexShader: rawVertexShader, fragmentShader: rawFragmentShader, // wireframe: true, side: THREE.DoubleSide, uniforms: { uTime: { value: 0, }, // 传入纹理 uTexture: { value: texture, }, }, });// fragment.glsl precision lowp float; varying vec2 vUv; varying float vElevation; // 设置纹理 uniform sampler2D uTexture; void main() { float height = vElevation + 0.05 * 20.0; // 根据uv进行采样,去除对应的颜色 vec4 textureColor = texture2D(uTexture, vUv); textureColor.rgb *= height; gl_FragColor = textureColor; }
11-6 着色器编写各种类型的图案
// vertex.glsl
varying vec2 vUv;
precision highp float;
void main() {
vUv = uv;
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
// fragment.glsl
precision highp float;
varying vec2 vUv;
uniform float uTime;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
(1)通过顶点对应的uv,决定每一个像素在图像的位置,通过这个位置x,y决定颜色
gl_FragColor = vec4(vUv, 0.0, 1.0);
(2)对第一种变形
gl_FragColor = vec4(vUv, 1.0, 1.0);
(3)利用uv实现渐变效果 左到右渐变
float strength = vUv.x;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(4)从上到下渐变
float strength = 1.0 - vUv.y;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(5)从右到左渐变
float strength = 1.0 - vUv.x;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(6)从下到上渐变
float strength = vUv.y;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(7)变化剧烈一点
float strength = vUv.y * 10.0;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(8)通过取模达到反复的效果
float strength = mod(vUv.y * 10.0, 1.0);
gl_FragColor = vec4(strength, strength, strength, 1.0);
(9)使用step实现斑马线
float strength = step(0.5, mod(vUv.y * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(10)使用step实现黑色窄白色宽
float strength = step(0.3, mod(vUv.y * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(11)利用step实现竖条纹
float strength = step(0.5, mod(vUv.x * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(12)条纹相加
float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength += step(0.8, mod(vUv.y * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(13)条纹相乘
float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength *= step(0.8, mod(vUv.y * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(14)条纹相减
float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength -= step(0.8, mod(vUv.y * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(15)条纹偏移(方块图形)
float strength = step(0.2, mod(vUv.x * 10.0, 1.0));
strength *= step(0.2, mod(vUv.y * 10.0, 1.0));
gl_FragColor = vec4(strength, strength, strength, 1.0);
(16)x的条纹和y的条纹相加
float barX = step(0.4, mod(vUv.x * 10.0, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.4, mod(vUv.y * 10.0, 1.0)) * step(0.8, mod(vUv.x * 10.0, 1.0));
float strength = barX + barY;
// 设置透明度
gl_FragColor = vec4(vUv, 1.0, strength);
(17)让条纹动起来
float barX = step(0.4, mod((vUv.x + uTime * 0.1) * 10.0, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.4, mod((vUv.y + uTime * 0.1) * 10.0, 1.0)) * step(0.8, mod(vUv.x * 10.0, 1.0));
float strength = barX + barY;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(18)T形图
float barX = step(0.4, mod(vUv.x * 10.0 - 0.2, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
float barY = step(0.4, mod(vUv.y * 10.0, 1.0)) * step(0.8, mod(vUv.x * 10.0, 1.0));
float strength = barX + barY;
gl_FragColor = vec4(strength, strength, strength, 1.0);
(19)利用绝对值
float strength = abs(vUv.x - 0.5);
gl_FragColor = vec4(strength, strength, strength, 1.0);