本系列教程的代码将开源到该仓库,前几篇文章的代码也会陆续补上,欢迎大家 Star:github.com/DesertsX/th...
本系列的代码同时放到了 Codepen Collection,欢迎学习:codepen.io/collection/...
此外,之前和之后的所有文章的例子都将更新到这里,方便大家和古柳一起见证本系列内容的不断壮大与完善过程 www.canva.com/design/DAF3...
继续放新加上的群友的赞扬!看到群友把前面七篇全认真看了并每篇都点赞,甚至发现了此前没人发现的 bug,值得表扬👍!
正文
在前两篇关于顶点着色器的文章里,古柳教大家应用 sin、random、noise 等函数来移动顶点、改变几何体形状,并介绍了将 noise 数值作为颜色的一些方法。
本文古柳将教大家如何对顶点进行旋转,不过因为球体的顶点怎么旋转球体形状都不变,所以我们用长方体讲解更直观。通过对顶点旋转不同角度可以实现类似"扭麻花"的效果。
长方体
下面是简单的一些设置,我们使用 BoxGeometry
并且在 shader 里用 vUv 作为颜色,相机在 z=3 的位置看向场景中心、正对长方体。
js
const camera = new THREE.PerspectiveCamera(75, 1, 0.01, 100);
camera.position.set(0, 0, 3);
const vertexShader = /* GLSL */ `
uniform float uTime;
varying vec2 vUv;
void main() {
vUv = uv;
vec3 newPos = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);
}
`;
const fragmentShader = /* GLSL */ `
varying vec2 vUv;
void main() {
gl_FragColor = vec4(vUv, 0.0, 1.0);
}
`;
// const geometry = new THREE.BoxGeometry(1, 1, 1);
const geometry = new THREE.BoxGeometry(3, 1, 1);
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
},
vertexShader,
fragmentShader,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const clock = new THREE.Clock();
function render() {
const time = clock.getElapsedTime();
material.uniforms.uTime.value = time;
// mesh.rotation.y = time;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
当长宽高都为1时,可以看到立方体每个面都有各自的 (0,0)-(1,1) uv 值,看着像由6个 plane 组成。不同几何体的 uv 效果会很不一样,这点大家替换几何体看看就会发现,不过数值范围都是0-1不会变化。
将长度设为3,方便后续沿x轴扭麻花。这里可以看到长度拉长后,uv 青红颜色效果也只是相应的拉长,4个角的颜色不变,背后数值范围也是不变的。
之前讲解片元着色器时选用 1:1 的 plane 作为例子是为了不使大家对 uv 产生困惑,实际上不论是这里的 3:1,还是任何比例,uv 各自都是0-1的范围,只是图形不是 1:1 时咋看起来会有些奇怪。
条纹效果
知道长方体上 uv 的效果后,我们可以将 vUv.y 剩以3.0再通过 fract 取小数然后对0.5取 step 从而使得数值在0/1/0/1/0/1之间变化,最后用该数值来 mix 插值两种颜色从而实现重复条纹效果。
这里的红色是 #d8345f
,可以通过该链接将16进制格式直接转化成 GLSL 里的 rgb 数值,即 vec3(0.847, 0.204, 0.373)
。链接不用记也没事,要用时直接谷歌 glsl hex to rgb
即可。
C#
varying vec2 vUv;
void main() {
vec3 color1 = vec3(0.847, 0.204, 0.373);
vec3 color2 = vec3(1.0);
float mixer = step(0.5, fract(vUv.y * 3.0));
// vec3 color = vec3(mixer);
vec3 color = mix(color1, color2, mixer);
gl_FragColor = vec4(color, 1.0);
}
如果大家是跟着本系列教程一篇篇学下来的,相信这里红白条纹的实现闭着眼睛也能写出来了吧。如果有不理解的地方,可以看前面文章复习下:「手把手带你入门 Three.js Shader 系列(二) - 牛衣古柳 - 20230716」。
旋转顶点
完成上述准备工作后,我们就可以开始对顶点进行旋转。谷歌搜索 glsl rotate
后从这个链接拷贝 glsl-rotation-3d
部分的函数到顶点着色器里。rotate() 函数接收待旋转的三维点坐标、旋绕的轴、旋转角度这三个参数,返回旋转后点的坐标位置。
C#
// glsl-rotation-3d
mat4 rotationMatrix(vec3 axis, float angle) {
axis = normalize(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0);
}
vec3 rotate(vec3 v, vec3 axis, float angle) {
mat4 m = rotationMatrix(axis, angle);
return (m * vec4(v, 1.0)).xyz;
}
长方体沿x轴拉长,那么就让顶点 position 都绕x轴旋转,即 axis 用 (1.0, 0.0, 0.0) 表示;旋转角度用弧度值表示,需要用到 PI 值,可以通过 const float PI
声明和赋值。先让所有顶点统一绕x轴旋转 PI/4.0 即45度,看起来已经成功生效。
C#
uniform float uTime;
varying vec2 vUv;
const float PI = 3.1415925;
// vec3 rotate(...){ ... }
void main() {
vUv = uv;
// vec3 newPos = position;
vec3 axis = vec3(1.0, 0.0, 0.0);
float angle = PI / 4.0;
vec3 newPos = rotate(position, axis, angle);
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);
}
让角度随时间变化,就能实现 mesh.rotation.y=time 同样的效果。
C#
// float angle = PI / 4.0;
float angle = uTime;
改变 axis 就能绕其他轴旋转。
C#
// vec3 axis = vec3(1.0, 0.0, 0.0);
vec3 axis = vec3(0.0, 1.0, 0.0);
// vec3 axis = vec3(0.0, 0.0, 1.0);
// vec3 axis = vec3(1.0, 1.0, 1.0);
旋转角度随x值而变化
当然这里还是绕x轴旋转所以 axis 不用变。为了实现前文所说的沿x轴扭麻花的效果,我们需要使旋转的角度随顶点坐标里的x值而变化,而不是所有顶点都用统一的数值。此时会发现长方体形状变得挺别扭、挺奇怪,中间区域过渡地不够丝滑。
C#
float angle = position.x;
原因想来大家也能猜到,就是长方体的细分数不够、顶点不够多,中间没有顶点、没有操作的空间,只有两端才能旋转,所以扭转得很生硬。通过设置 material 的 wireframe:true 就能应证我们的想法。
js
const geometry = new THREE.BoxGeometry(3, 1, 1);
const material = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
},
vertexShader,
fragmentShader,
wireframe: true,
});
增加长方体的细分数
我们以2、4、8、16、32、64......等2的倍数来增加长方体的细分数,会发现差不多在32以后中间扭转过渡就很丝滑了。毕竟图中长方体就这么点长度,切成32份已经很细了。这里采用64也没什么特别的缘故,反正 shader 里都是同样轻松拿捏,用大些的数值也无妨。
js
// const geometry = new THREE.BoxGeometry(3, 1, 1);
// const geometry = new THREE.BoxGeometry(3, 1, 1, 4, 4, 4);
const geometry = new THREE.BoxGeometry(3, 1, 1, 64, 64, 64);
随着细分数增加、顶点数增多,中间顶点随着x坐标的变化以不同角度发生旋转,从而有了丝滑的扭曲效果。当我们把 position.x + uTime
作为 angle 时就能使扭转效果随之动起来。
C#
// ...
void main() {
vUv = uv;
vec3 axis = vec3(1.0, 0.0, 0.0);
// float angle = position.x;
float angle = position.x + uTime;
vec3 newPos = rotate(position, axis, angle);
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);
}
其他变体
除此之外我们还可以随意在 angle 加些内容来实现不同的扭曲动画效果,大家可自由发挥,没准会有意外之喜。
C#
float angle = position.x + sin(uTime) * 3.0 + uTime;
C#
float angle = position.x * sin(uTime) - uTime;
Kinetic Typography
以上就是"扭麻花"效果的全部内容,并不复杂、并不难懂。
古柳之所以想讲这个效果,一方面是因为想教大家顶点旋转,一方面是想到这个 Kinetic Typography
动态文字排版/文字动效的实际例子(也这是上面 #d8345f
红色的出处,并非凭空冒出来的)。
扭转长方体后再结合文字会使人眼前一亮,后续讲解到 shader 里的纹理贴图、纹理采样时,古柳会再带大家把结合文字部分补上,敬请期待!
其他文字效果同样不错,有机会古柳会在教程里多讲些例子。
暂时舍弃的 WebGL Blob
其实一开始古柳设想的讲解顶点旋转的例子是下面这个效果,即在 noise 值偏移顶点后,加上旋转顶点,再加上 Cosine Palette
(另一种 shader 里的配色方法),从而实现出这种酷炫的 WebGL Blob 效果。不过一些细节古柳觉得解释不好,远不如长方体"扭麻花"直观好懂,故而进行了取舍。当然其中的 Cosine Palette
古柳后续还是会教大家的。
小结
本文的内容比较简单,没啥好总结的。最后照旧是本文的所有例子合集,大家要好好复习哈。
相关阅读
「手把手带你入门 Three.js Shader 系列」目录如下:
- 「断更19个月,携 Three.js Shader 归来!(上) - 牛衣古柳 - 20230416」
- 「断更19个月,携 Three.js Shader 归来!(下) - 牛衣古柳 - 20230421」
- 「手把手带你入门 Three.js Shader 系列(七) - 牛衣古柳 - 20230206」
- 「手把手带你入门 Three.js Shader 系列(六) - 牛衣古柳 - 20231220」
- 「手把手带你入门 Three.js Shader 系列(五) - 牛衣古柳 - 20231126」
- 「手把手带你入门 Three.js Shader 系列(四) - 牛衣古柳 - 20231121」
- 「手把手带你入门 Three.js Shader 系列(三) - 牛衣古柳 - 20230725」
- 「手把手带你入门 Three.js Shader 系列(二) - 牛衣古柳 - 20230716」
- 「手把手带你入门 Three.js Shader 系列(一) - 牛衣古柳 - 20230515」
照例
如果你喜欢本文内容,欢迎以各种方式支持,这也是对古柳输出教程的一种正向鼓励!
最后欢迎加入「可视化交流群」
,进群多多交流,对本文任何地方有疑惑的可以群里提问。加古柳微信:xiaoaizhj
,备注「可视化加群」
即可。
欢迎关注古柳的公众号「牛衣古柳」
,并设置星标,以便第一时间收到更新。