使用Three.js、WebGPU和TSL实现交互式文字爆炸效果

本教程详细介绍了如何使用Three.js、WebGPU和Three Shader Language创建交互式3D文字爆炸效果,包括场景设置、几何体变形、弹簧物理模拟和后期处理等核心技术实现。

交互式文字爆炸效果:Three.js、WebGPU和TSL实战

项目概述

本教程将指导您创建一个交互式3D文字效果,其中字母会爆炸成动态形状。我们将使用Three.js、WebGPU和Three Shader Language(TSL)来实现这一效果。

项目结构

我们的脚本结构简单:一个函数用于预加载资源,另一个用于构建场景。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const Resources = {
    font: null
};

function preload() {
    const _font_loader = new FontLoader();
    _font_loader.load("../static/font/Times New Roman_Regular.json", (font) => {
        Resources.font = font;
        init();
    });
}

function init() {
    // 初始化场景
}

window.onload = preload;

场景设置与环境

经典的Three.js场景设置,需要注意的是我们使用TSL,因此渲染器需要是WebGPURenderer。

1
2
3
4
5
6
7
8
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGPURenderer({ antialias: true });

document.body.appendChild(renderer.domElement);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.position.z = 5;
scene.add(camera);

环境光照设置

使用Three.js自带的RoomEnvironment和DirectionalLight:

1
2
3
4
5
6
7
8
const environment = new RoomEnvironment();
const pmremGenerator = new THREE.PMREMGenerator(renderer);
scene.environment = pmremGenerator.fromSceneAsync(environment).texture;
scene.environmentIntensity = 0.8;

const light = new THREE.DirectionalLight("#e7e2ca", 5);
light.position.set(0.0, 1.2, 3.86);
scene.add(light);

文字几何体

使用TextGeometry创建3D文字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const text_geo = new TextGeometry("NUEVOS", {
    font: Resources.font,
    size: 1.0,
    depth: 0.2,
    bevelEnabled: true,
    bevelThickness: 0.1,
    bevelSize: 0.01,
    bevelOffset: 0,
    bevelSegments: 1
});

const mesh = new THREE.Mesh(
    text_geo,
    new THREE.MeshStandardMaterial({
        color: "#656565",
        metalness: 0.4,
        roughness: 0.3
    })
);

scene.add(mesh);

几何体居中处理

1
2
3
4
text_geo.computeBoundingBox();
const centerOffset = -0.5 * (text_geo.boundingBox.max.x - text_geo.boundingBox.min.x);
const centerOffsety = -0.5 * (text_geo.boundingBox.max.y - text_geo.boundingBox.min.y);
text_geo.translate(centerOffset, centerOffsety, 0);

Three Shader Language实现

初始化存储缓冲区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const initial_position = storage(text_geo.attributes.position, "vec3", count);
const normal_at = storage(text_geo.attributes.normal, "vec3", count);
const count = text_geo.attributes.position.count;

const position_storage_at = storage(new THREE.StorageBufferAttribute(count, 3), "vec3", count);

const compute_init = Fn(() => {
    position_storage_at.element(instanceIndex).assign(initial_position.element(instanceIndex));
})().compute(count);

renderer.computeAsync(compute_init);

更新计算函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const u_input_pos = uniform(new THREE.Vector3(0, 0, 0));
const u_input_pos_press = uniform(0.0);

const compute_update = Fn(() => {
    const base_position = initial_position.element(instanceIndex);
    const normal = normal_at.element(instanceIndex);
    const current_position = position_storage_at.element(instanceIndex);
    
    const distance = length(u_input_pos.sub(base_position));
    const pointer_influence = step(distance, 0.5).mul(1.0);
    
    const disorted_pos = base_position.add(normal.mul(pointer_influence));
    current_position.assign(disorted_pos);
})().compute(count);

弹簧物理实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const velocity_storage_at = storage(new THREE.StorageBufferAttribute(count, 3), "vec3", count);
const u_spring = uniform(0.05);
const u_friction = uniform(0.9);

const compute_update = Fn(() => {
    const base_position = initial_position.element(instanceIndex);
    const current_position = position_storage_at.element(instanceIndex);
    const current_velocity = velocity_storage_at.element(instanceIndex);
    const normal = normal_at.element(instanceIndex);
    
    const distance = length(u_input_pos.sub(base_position));
    const pointer_influence = step(distance, 0.5).mul(1.5);
    
    const disorted_pos = base_position.add(normal.mul(pointer_influence));
    disorted_pos.assign((mix(base_position, disorted_pos, u_input_pos_press)));
    
    current_velocity.addAssign(disorted_pos.sub(current_position).mul(u_spring));
    current_velocity.assign(current_velocity.mul(u_friction));
    current_position.addAssign(current_velocity);
})().compute(count);

添加噪声和旋转效果

1
2
3
4
5
6
7
8
9
const compute_update = Fn(() => {
    // ...之前的代码
    
    const noise = mx_noise_vec3(current_position.mul(0.5).add(vec3(0.0, time, 0.0)), 1.0).mul(u_noise_amp);
    const disorted_pos = base_position.add(noise.mul(normal.mul(pointer_influence)));
    disorted_pos.assign(rotate(disorted_pos, vec3(normal.mul(distance)).mul(pointer_influence)));
    
    // ...后续代码
})().compute(count);

材质和发光效果

1
2
3
4
5
6
const emissive_color = color(new THREE.Color("0000ff"));
const vel_at = velocity_storage_at.toAttribute();
const hue_rotated = vel_at.mul(Math.PI * 10.0);
const emission_factor = length(vel_at).mul(10.0);

mesh.material.emissiveNode = hue(emissive_color, hue_rotated).mul(emission_factor).mul(5.0);

场景雾化和背景

1
2
scene.fog = new THREE.Fog(new THREE.Color("#41444c"), 0.0, 8.5);
scene.background = scene.fog.color;

后期处理效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const composer = new THREE.PostProcessing(renderer);
const scene_pass = pass(scene, camera);

scene_pass.setMRT(mrt({
    output: output,
    normal: normalView
}));

const scene_color = scene_pass.getTextureNode("output");
const scene_depth = scene_pass.getTextureNode("depth");
const scene_normal = scene_pass.getTextureNode("normal");

const ao_pass = ao(scene_depth, scene_normal, camera);
ao_pass.resolutionScale = 1.0;

const ao_denoise = denoise(ao_pass.getTextureNode(), scene_depth, scene_normal, camera).mul(scene_color);
const bloom_pass = bloom(ao_denoise, 0.3, 0.2, 0.1);
const post_noise = (mx_noise_float(vec3(uv(), time.mul(0.1)).mul(sizes.width), 0.03)).mul(1.0);

composer.outputNode = ao_denoise.add(bloom_pass).add(post_noise);

动画循环

1
2
3
4
5
6
7
function animate() {
    renderer.computeAsync(compute_update);
    renderer.renderAsync(scene, camera);
    requestAnimationFrame(animate);
}

animate();

通过本教程,您已经学会了如何使用Three.js、WebGPU和TSL创建复杂的交互式3D文字效果,包括物理模拟、着色器编程和后期处理等高级技术。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计