更新于 

Shader Tutorial 着色器

稍稍修shader绘制的顶点坐标

一个10*10的网格平面,正常绘制时,其VertexShader如下:

1
2
3
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

但是如果稍稍做一些修改,将使用三角函数对position进行变换:

sin变换
1
2
3
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(sin(position), 1.0);
}
cos变换
1
2
3
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(cos(position), 1.0);
}
tan变换
1
2
3
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(tan(position), 1.0);
}

传入uniform变量

vertex shader中使用uniform变量

shader存在两种可以从js传入的变量类型,
一种是attribute,
另一种是uniform,
attribute针对不同顶点提供不同的值,
uniform类型的变量能够作用于每一个顶点,
使用threejs传入uniform类型的变量的位置,和传入shader程序的位置相同,
也是在定义Shader材质的时候作为属性传入:

1
2
3
4
5
6
7
8
9
const uniforms = {
u_time:{type:'f',value:0}
}
const material = new THREE.ShaderMaterial({
vertexShader:document.getElementById("vertex-shader").textContent,
fragmentShader:document.getElementById('fragment-shader').textContent,
wireframe:true,
uniforms // 传入shader
});

动画的关键属性就是时间
因此还需要考虑属性随时间的变化,
可以在animate函数中对属性进行修改:

1
2
3
4
5
function animate(){
uniforms.u_time.value = clock.getElapsedTime()
//...
renderer.render(scene, camera)
}

以上是在js中的配置,着色器程序中也需要定义需要接收的uniform变量:

1
2
3
4
5
6
7
8
<script id="vertex-shader" type="x-vertex">
uniform float u_time;
void main(){
float newX = sin(position.x * u_time) * sin(position.y * u_time);
vec3 newPosition = vec3(newX, position.y, position.z);
gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0);
}
</script>

shader中参与计算的数据附着上时间属性后,构成了动画:

fragment shader中使用uniform变量

要实现片元颜色沿x轴显现出渐变色,
首先需要知道该片元的x轴坐标,
这个值在FragmentShader中是提供的:gl_FragCoord.x
这是这个片元的绝对位置,
除此之外还需要知道这个x值相对整个画布的位置,
因为只有这样才能把x值归一化到-1和1之间,
因此画布大小这个变量就需要从js中传入:

1
2
3
4
5
6
7
8
9
const uniforms = {
// ...
u_resolution :{
type:'v2', // vec2 类型
// 按照设备像素比例计算尺寸
value: new THREE.Vector2(window.innerWidth,window.innerHeight)
.multiplyScalar(window.devicePixelRatio)
}
}

在fragment shader中接收画布分辨率之后,
计算出片元的相对位置,
再根据这个相对位置设置片元颜色:

1
2
3
4
5
6
7
<script id="fragment-shader" type="x-fragment">
uniform vec2 u_resolution;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
gl_FragColor = vec4(0.0,st.x, 0.0, 1.0);
}
</script>

最终绘制的结果呈现渐变效果:

鼠标控制颜色变化

鼠标坐标需要从js中传入,

需要注意的是鼠标依赖的是浏览器坐标系,
从左上角开始到右下角,
其y轴方向与webgl中的y轴方向相反

因此一方面需要对鼠标坐标进行归一化处理,
另一方面需要对y轴反向

1
2
3
4
5
6
7
8
9
10
11
12
13
const uniforms = {
// ...
u_mouse:{
type:'v2',
value:new THREE.Vector2(0,0)
}
}
window.onmousemove= e=>{
uniforms.u_mouse.value.set(
e.offsetX/window.innerWidth,
1-e.offsetY/window.innerHeight
)
}
1
2
3
4
5
6
uniform vec2 u_mouse;
uniform vec2 u_resolution;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
gl_FragColor = vec4(st.x, u_mouse, 1.0);
}

shader绘制纹理

纹理的传入使用的也是uniform类型变量,
纹理类型需要限制为sampler2D取样器类型,

gles中的取样方法为texture2D
这个方法接收两个参数:

  • sampler2D类型的图片取样器
  • vec2类型的图片坐标

在Vertex Shader中,
ThreeJs提供了uv变量作为当前被渲染物体的纹理坐标
可以直接拿来使用

第一步:将纹理图片作为uniform变量传入

1
2
3
4
5
6
7
8
import nebula from '../img/nebula.jpg';
const uniforms = {
//...
image :{
type:'t',
value:textLoader.load(nebula)
}
}

第二步:将uv纹理坐标使用varing变量从vertex shader传入fragment shader中

1
2
3
4
5
varying vec2 v_uv;
void main(){
v_uv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0);
}

第三步: 在fragment shader中读入传入的纹理图片,然后绘制

1
2
3
4
5
6
7
8
9
10
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
uniform sampler2D image;
varying vec2 v_uv;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
vec4 texture = texture2D(image, v_uv);
gl_FragColor = vec4(vec3(texture),1.0);
}

GLSL Snacks

视口大小变化导致光标偏移

使用ShaderMaterial创建一个材质,
要求材质的颜色以 距离鼠标的位置 为标准变化,

v-shader
1
2
3
4
5
<script type="x-vertex" id="v-shader">
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
f-shader
1
2
3
4
5
6
7
8
9
10
<script type="x-fragment" id="f-shader">
uniform vec2 u_resolution;
uniform vec2 u_mouse;
void main(){
vec2 st = gl_FragCoord.xy/u_resolution;
float c = 1.0 - distance(st, u_mouse);
float color = pow(c, 10.0);
gl_FragColor = vec4(vec3(color), 1.0);
}
</script>

需要向Fragment Shader中传入两个vec2类型的uniform变量:

  • u_resolution 分辨率
  • u_mouse 鼠标坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const uniforms = {
u_resolution: {
type: 'v2',
value:new THREE.Vector2(window.innerWidth, window.innerHeight)
},
u_mouse: {
type: 'v2',
value:new THREE.Vector2()
}
}
window.onmousemove = e => {
uniforms.u_mouse.value.x = e.offsetX / window.innerWidth;
uniforms.u_mouse.value.y = 1 - e.offsetY / window.innerHeight;
}
const planeGeo = new THREE.PlaneGeometry(4, 4)
const planeMat = new THREE.ShaderMaterial({
uniforms,
vertexShader: document.getElementById('v-shader').textContent,
fragmentShader: document.getElementById('f-shader').textContent
})
const planeMesh = new THREE.Mesh(planeGeo, planeMat)
scene.add(planeMesh)
实现效果
实现效果

但是在屏幕尺寸改变时,会发现对鼠标位置的计算出现偏差,
对于这个问题,可以在resize函数中更新分辨率变量u_resolution的值:

1
2
3
4
5
6
window.addEventListener('resize', function() {
// ...
uniforms.u_resolution.value.x = window.innerWidth;
uniforms.u_resolution.value.y = window.innerHeight;
// ...
});

纹理镂空效果

这里的纹理镂空指的是对纹理进行动态映射,
根据 片元坐标相对视口位置 来获取 纹理的映射范围

vertex shader
1
2
3
4
5
<script type="x-vertex" id="v-shader">
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
fragment shader
1
2
3
4
5
6
7
8
9
<script type="x-fragment" id="f-shader">
uniform vec2 u_resolution;
uniform sampler2D image;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
vec4 texture = texture2D(image, st);
gl_FragColor = vec4(vec3(texture), 1.0);
}
</script>

使用ShaderMaterial传入uniform变量:

1
2
3
4
5
6
7
8
9
10
const uniforms = {
u_resolution:{
type:'v2',
value:new THREE.Vector2(window.innerWidth, window.innerHeight)
},
image:{
type:'t',
value:new THREE.TextureLoader().load('./assets/ice.png')
}
}

实现镂空效果:

纹理镂空 效果转换为 纹理映射 效果,只需要对 texture2D 采样使用的坐标进行修改,
这里传入的uVu(uv)参数,通常代表的是 当前顶点的纹理坐标

1
2
3
4
5
6
7
  <script type="x-vertex" id="v-shader">
varying vec2 uVu;
void main(){
uVu = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
1
2
3
4
5
6
7
8
9
10
<script type="x-fragment" id="f-shader">
varying vec2 uVu;
uniform vec2 u_resolution;
uniform sampler2D image;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
vec4 texture = texture2D(image, uVu);
gl_FragColor = vec4(vec3(texture), 1.0);
}
</script>

纹理动画效果

动画的实现需要引入一个重要的变量:时间

步骤1:实现半透明蒙版效果

首先有一张渐变灰度图:

将这张渐变灰度图映射到目标物体表面,
为了实现半透明的效果:

  1. 将纹理的r通道映射到alpha通道中
  2. 开启纹理材质的transparent属性
1
2
3
4
5
6
7
8
<script type="x-fragment" id="f-shader">
uniform sampler2D image;
varying vec2 vUv;
void main(){
vec4 textureColor = texture2D(image, vUv);
gl_FragColor = vec4(vec3(0.3, 0.2, 0.6), textureColor.r);
}
<script/>

这样能够实现半透明的渐变效果:

步骤2:实现纹理旋转效果

要做出旋转的效果:

  1. 传入u_time时间参数
  2. 在纹理映射的时候,对uv坐标进行旋转处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="x-fragment" id="f-shader">
uniform float u_time;
uniform sampler2D image;
varying vec2 vUv;

vec2 rotate(vec2 uv, float rotation){
return vec2(
cos(rotation) * uv.x + sin(rotation) * uv.y,
cos(rotation) * uv.y - sin(rotation) * uv.x
);
}

void main(){
vec2 vUv = vUv;

vUv -= vec2(0.5); // 图像中心移动到左上角(旋转中心)
vec2 vUv_rotate = rotate(vUv, u_time);
vUv_rotate += vec2(0.5); // 将图像中心移回

vec4 textureColor = texture2D(image, vUv_rotate);
gl_FragColor = vec4(vec3(0.3, 0.2, 0.6), textureColor.r);
}
</script>

u_time的刷新:

1
2
3
4
function animate(time) {
uniforms.u_time.value = time.toFixed(2)/1000
renderer.render(scene, camera);
}

步骤3:实现纹理叠加效果

将gl_FragColor的rgb通道,换成需要映射的纹理即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script type="x-fragment" id="f-shader">
uniform float u_time;
uniform sampler2D image;
uniform sampler2D golconda;
varying vec2 vUv;

vec2 rotate(vec2 uv, float rotation){
// ...
}

void main(){
vec2 vUv2 = vUv; // 保存原始纹理坐标
vec2 vUv = vUv;

vUv -= vec2(0.5);
vec2 vUv_rotate = rotate(vUv, u_time);
vUv_rotate += vec2(0.5);

vec4 textureColor = texture2D(image, vUv_rotate);
vec4 imageTexture = texture2D(golconda, vUv2);
gl_FragColor = vec4(vec3(imageTexture), textureColor.r);
}
</script>

过滤效果

两张纹理之间的过渡,
如果不引入任何效果,
只需要使用 mix 函数就能实现。

1
gl_FragColor = mix(i1Texel, i2Texel, mixRatio);

想要实现其他过渡效果,
需要引入一张纹理材质图片:

使用 clump 函数将纹理图片映射到 mixer 处理过的图片的alpha通道上:

1
2
3
4
5
6
7
vec4 transitionTexel = texture2D(transition, vUv); // 纹理
vec4 i1Texel = texture2D(image, vUv);
vec4 i2Texel = texture2D(image2, vUv);
// mixRatio 代表混合程度
float r = mixRatio * 1.6 - 0.3; // [-0.3, 1.3]
float mixF = clamp((transitionTexel.r - r) * 3.33, 0.0, 1.0);
gl_FragColor = mix(i1Texel, i2Texel, mixF);

需要传入的uniform有这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="x-fragment" id="f-shader">
uniform sampler2D image;
uniform sampler2D image2;
uniform sampler2D transition;
uniform float mixRatio;
varying vec2 vUv;
void main(){
vec4 transitionTexel = texture2D(transition, vUv);
vec4 i1Texel = texture2D(image, vUv);
vec4 i2Texel = texture2D(image2, vUv);

float r = mixRatio * 1.6 - 0.3;
float mixF = clamp((transitionTexel.r - r) * 3.33, 0.0, 1.0);
gl_FragColor = mix(i1Texel, i2Texel, mixF);
}
</script>

Perlin Noise 噪点动画

讲到这里, Wael Yasmina 又甩出来一个很强的网站:

是一位大佬的技术分享网站

噪点,需要在原物体顶点坐标的基础上进行偏移,
能够实现光滑物体表面粗糙不平的效果,
如果这种偏移是震荡连续的,
就能够实现波浪形的动画效果。

噪点的实现需要借助 Parcel Noise:Ashima写好的webgl噪点程序

准备好基础球体,然后就开始…..复制粘贴,
webgl-noise/src下提供了各种噪点程序,
复制粘贴需要的到vertex shader中,
这里以 classicnoise3D 为例,里面提供了两种噪点程序:

  • cnoise
  • pnoise

选用pnoise噪点程序,最后的vertex shader如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

<script type="x-vertex" id="v-shader">
uniform float u_time;

// 辅助函数
vec3 mod289(vec3 x){
return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 mod289(vec4 x){
return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x){
return mod289(((x*34.0)+10.0)*x);
}

vec4 taylorInvSqrt(vec4 r){
return 1.79284291400159 - 0.85373472095314 * r;
}

vec3 fade(vec3 t) {
return t*t*t*(t*(t*6.0-15.0)+10.0);
}

// Classic Perlin noise, periodic variant
float pnoise(vec3 P, vec3 rep)
{
vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period
vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period
Pi0 = mod289(Pi0);
Pi1 = mod289(Pi1);
vec3 Pf0 = fract(P); // Fractional part for interpolation
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = Pi0.zzzz;
vec4 iz1 = Pi1.zzzz;

vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);

vec4 gx0 = ixy0 * (1.0 / 7.0);
vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
gx0 = fract(gx0);
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
vec4 sz0 = step(gz0, vec4(0.0));
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
gy0 -= sz0 * (step(0.0, gy0) - 0.5);

vec4 gx1 = ixy1 * (1.0 / 7.0);
vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
gx1 = fract(gx1);
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
vec4 sz1 = step(gz1, vec4(0.0));
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
gy1 -= sz1 * (step(0.0, gy1) - 0.5);

vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);

vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
g000 *= norm0.x;
g010 *= norm0.y;
g100 *= norm0.z;
g110 *= norm0.w;
vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
g001 *= norm1.x;
g011 *= norm1.y;
g101 *= norm1.z;
g111 *= norm1.w;

float n000 = dot(g000, Pf0);
float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
float n111 = dot(g111, Pf1);

vec3 fade_xyz = fade(Pf0);
vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
return 2.2 * n_xyz;
}

// 主体
void main(){
float noise = 5.0 * pnoise(position + u_time, vec3(10.0));
float displacement = noise / 10.0;
vec3 newPosition = position + normal * displacement;

gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
</script>

fragment shader如下:

1
2
3
4
5
6
7
<script type="x-fragment" id="f-shader">
uniform vec2 u_resolution;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
gl_FragColor = vec4(vec3(st.x, st.y, 1.0),1.0);
}
</script>
classicnoise3D/perlin_noise
classicnoise3D/perlin_noise
classicnoise3D/classic_noise
classicnoise3D/classic_noise

Audio Visualize Animation 音频可视化

音频可视化在threejs中并不难实现,
关键是: 音频值加入噪点的计算

步骤1:创建网格物体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const uniforms = {
u_time: {
type: 'f',
value: 0.0
}
}
const mat = new THREE.ShaderMaterial({
uniforms,
wireframe:true,
vertexShader: document.getElementById('v-shader').textContent,
fragmentShader:document.getElementById('f-shader').textContent
})
const geo = new THREE.IcosahedronGeometry(4, 20)
const mesh = new THREE.Mesh(geo, mat)
scene.add(mesh)
1
2
3
4
5
6
7
8
9
10
11
<script type="x-vertex" id="v-shader">
uniform float u_time;
void main(){
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>
<script type="x-fragment" id="f-shader">
void main(){
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>
步骤2:加入噪点
1
2
3
4
5
6
7
8
9
10
<script type="x-vertex" id="v-shader">
// 噪点部分省略
uniform float u_time;
void main(){
float noise = 5.0 * pnoise( position + u_time, vec3(10.0));
float displacement = noise / 10.0;
vec3 newPosition = position + displacement;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
</script>
步骤3:加入音频

音频作为一个实体,具有坐标属性,
这里可以将音频加入camera中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建一个AudioListener,放入camera
const listener = new THREE.AudioListener();
camera.add(listener)

// 创建一个全局音频资源
const sound = new THREE.Audio(listener)

// AudioLoader加载音频, 设置进Audio的buffer中
const audioLoader = new THREE.AudioLoader();
audioLoader.load('./assets/audio/ili.mp3', function (buffer) {
sound.setBuffer(buffer);
sound.loop = true
sound.play()
})

准备好音乐,需要创建AudioAnalyser对音频进行解析

1
const analyser = new THREE.AudioAnalyser(sound, 32)

在animation中提取出每一帧的平均频率,
作为变量传入vertex shader:

1
2
3
4
5
6
7
const uniforms = {
// ....
u_frequency: {
type: 'f',
value: 0.0
}
}
1
2
3
4
function animate() {
// ...
uniforms.u_frequency.value = analyser.getAverageFrequency()
}

在顶点着色器中,加入u_frequency计算顶点偏移量

1
2
3
4
5
6
7
8
9
10
11
<script type="x-vertex" id="v-shader">
// ...
uniform float u_frequency;
void main(){
float noise = 5.0 * pnoise(position + u_time, vec3(10.0));
// 加入u_frequency计算
float displacement = (u_frequency / 30.0) * (noise / 10.0);
vec3 newPosition = position + normal * displacement;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
</script>
此处无声胜有声,没有声音凑合听
此处无声胜有声,没有声音凑合听
步骤4:gui改变颜色

引入gui,将r、g、b三个通道的颜色作为float类型变量传入。

1
2
3
4
5
6
7
8
9
10
11
12
13
const uniforms = {
// ...
u_red: {type:'f', value:1.0},
u_green: {type:'f', value:1.0},
u_blue: {type:'f', value:1.0},
}

const gui = new GUI()
const colorFolder = gui.addFolder('color')
colorFolder.add(uniforms.u_red, 'value').min(0).max(1).name('red')
colorFolder.add(uniforms.u_green, 'value').min(0).max(1).name('green')
colorFolder.add(uniforms.u_blue, 'value').min(0).max(1).name('blue')

1
2
3
4
5
6
7
8
<script type="x-fragment" id="f-shader">
uniform float u_red;
uniform float u_green;
uniform float u_blue;
void main(){
gl_FragColor = vec4(u_red, u_green, u_blue, 1.0);
}
</script>
步骤5:加入unrealBloom特效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { RenderPass } from 'three/examples/jsm/postprocessing/renderpass';
import { EffectComposer } from 'three/examples/jsm/postprocessing/effectcomposer';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/unrealbloompass';
import { OutputPass } from 'three/examples/jsm/postprocessing/outputpass';

// ...
const renderScene = new RenderPass(scene, camera)
const effectComposer = new EffectComposer(renderer)
const unrealBloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
0.5, 0.2, 0.5
)
effectComposer.addPass(renderScene)
effectComposer.addPass(unrealBloomPass)

const outputPass = new OutputPass()
effectComposer.addPass(outputPass)

const unrealBloomFolder = gui.addFolder('unreal bloom')
unrealBloomFolder.add(unrealBloomPass,'strength',0,3)
unrealBloomFolder.add(unrealBloomPass,'radius',0,3)
unrealBloomFolder.add(unrealBloomPass,'threshold',0,1)
unrealBloomFolder.add(renderer,'toneMappingExposure',0,3)
//...
function animation(){
//...
effectComposer.render()
}
是特效!我加了特效!
是特效!我加了特效!

受光照影响的ShaderMaterial

想让自定义的ShaderMaterial受光照影响,
有2种方式:

  • 在shader程序中经过计算实现光照效果
  • 重写其它受光照影响的材质的部分shader

已经有实现第二种方式的库:

引入customShaderMaterial

npm安装:

1
npm install three-custom-shader-material
1
2
3
4
5
6
7
8
9
import CustomShaderMaterial from 'three-custom-shader-material/vanilla'
const custom_mat = new CustomShaderMaterial({
baseMaterial: THREE.MeshPhysicalMaterial, // 需要修改的材质
metalness: 0.6,
roughness:0.3,
uniforms,
vertexShader: document.getElementById('v-shader').textContent,
fragmentShader: document.getElementById('f-shader').textContent
})

CustomShaderMaterial中使用shader,不能仅仅给原本GLSL输出变量进行修改,
还需要修改一些以csm(custom shader material)开头的输出变量,
比如:

  • gl_Position → csm_Position
  • gl_FragColor → csm_FragColor
引入vertex/fragment shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const mat = new THREE.ShaderMaterial({
uniforms,
vertexShader: document.getElementById('v-shader').textContent,
fragmentShader: document.getElementById('f-shader').textContent
})

const custom_mat = new CustomShaderMaterial({
baseMaterial: THREE.MeshPhysicalMaterial,
metalness: 0.6,
roughness:0.3,
uniforms,
vertexShader: document.getElementById('v-shader').textContent,
fragmentShader: document.getElementById('f-shader').textContent
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="x-vertex" id="v-shader">
uniform float u_time;
void main(){
vec3 newPosition = position * sin(u_time);
// csm_Position 必须修改
csm_Position = newPosition;
glayerl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
</script>
<script type="x-fragment" id="f-shader">
uniform vec2 u_resolution;
void main(){
vec2 st = gl_FragCoord.xy / u_resolution;
// csm_DiffuseColor 表示材质的原始颜色
csm_DiffuseColor = vec4(st.x / 0.6, st.y / 1.8, (st.x + st.y)/ 1.2, 1.0);
}
</script>