Skip to content

36. 精灵模型 Sprite 和下雨下雪效果

Published:

有了顶点数据,可以构成各种几何体 geometry。

加上材质 material,就可以渲染成点模型 Points、线模型 Line、网格模型 Mesh。

其中最常用的是网格模型 Mesh。

除了这些之外,还有一种特殊的模型,精灵模型 Sprite。

它特殊在两个点:

提到矩形平面,大家会想到 PlaneGeometry,但 Sprite 和它不同,矩形平面几何体可以调整相机位置看到背面、侧面,而 Sprite 永远面向屏幕。

我们试一下就知道了:

npx create-vite sprite

image.png

进入项目,安装依赖:

npm install
npm install --save three
npm install --save-dev @types/three

改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();

scene.add(mesh);

const axesHelper = new THREE.AxesHelper(50);
scene.add(axesHelper);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Camera、Renderer。

因为 Sprite 是 1 * 1 的矩形平面,所以这里相机放在 0, 0, 5 的位置。

改下 style.css

body {
  margin: 0;
}

然后创建 src/mesh.js

import * as THREE from 'three';

const group = new THREE.Group();

const spriteMaterial = new THREE.SpriteMaterial({
    color: 'orange'
});

const sprite = new THREE.Sprite(spriteMaterial);

group.add(sprite);

export default group;

创建 Sprite,它只有材质,没有几何体(因为它固定是矩形平面)。

看看效果:

npm run dev

image.png

image.png

可以看到,确实是一个矩形平面。

那它和 PlaneGeometry 的区别在哪呢?

我们加个 PlaneGeometry 对比下:

image.png

const geometry = new  THREE.PlaneGeometry(1, 1);
const mesh = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
    color: 'lightblue'
}));
mesh.position.y = 3;

group.add(mesh);

2025-04-03 16.10.14.gif

可以看到,PlaneGeometry 是可以随着 camera 位置的变化,看到侧面、反面的,而 Sprite 永远正对相机。

那这个特性有什么用呢?

我们一般用 Sprite 来做一些标注。

类似这种:

image.png

给 Sprite 放大一下,贴上颜色贴图,悬浮在要标注的物体上方。

我们来试一下:

创建 src/mesh2.js

import * as THREE from 'three';

function createMesh(color, x) {
    const geometry = new THREE.DodecahedronGeometry(1);
    const material = new THREE.MeshBasicMaterial({
        color: color
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.x = x;
    return mesh;    
}

const mesh = createMesh('orange', 0);
const mesh2 = createMesh('skyblue', 5);
const mesh3 = createMesh('lightgreen', -5);

const group = new THREE.Group();
group.add(mesh);
group.add(mesh2);
group.add(mesh3);

export default group;

这里用上节的那三个十二面体,改成 MeshBasicMaterial,调一下大小。

image.png

image.png

然后在上面加三个 Sprite。

先找个贴图:

sprite.png

放到 public 目录下,然后创建 Sprite:

image.png

const loader = new THREE.TextureLoader();
function createSprite(x, y) {
    const texture = loader.load('./sprite.png');

    const spriteMaterial = new THREE.SpriteMaterial({
        map: texture
    });
    const sprite = new THREE.Sprite(spriteMaterial);
    sprite.position.x = x;
    sprite.position.y = y;
    return sprite;
}

const sprite1 = createSprite(0, 1.5);
const sprite2 = createSprite(5, 1.5);
const sprite3 = createSprite(-5, 1.5);
group.add(sprite1);
group.add(sprite2);
group.add(sprite3);

分别在 3 个 mesh 上添加 3 个 Sprite。

2025-04-03 16.52.14.gif

可以看到,不管怎么旋转,Sprite 的贴图都始终正对相机。

这样,把 Sprite 加到目标物体上,就可以实现这种标注效果:

image.png

此外,Sprite 还可以用来做下雨下雪效果:

首先,找个雪花的透明背景图片放在 public 目录下:

snow.png

创建 src/mesh3.js

import * as THREE from 'three';

const loader = new THREE.TextureLoader();
const texture = loader.load("./snow.png");
const spriteMaterial = new THREE.SpriteMaterial({
    map: texture
});

const group = new THREE.Group();

for (let i = 0; i < 10000; i ++) {
    const sprite = new THREE.Sprite(spriteMaterial);

    const x = 1000 * Math.random();
    const y = 1000 * Math.random();
    const z = 1000 * Math.random();
    sprite.position.set(x, y, z);

    group.add(sprite);
}

function render() {

    group.children.forEach(sprite => {
        sprite.position.y -= 0.1;

        if (sprite.position.y < 0) {
            sprite.position.y = 1000;
        }
    });

    requestAnimationFrame(render);
}
render();

export default group;

创建一万个 Sprite,放在在 0 到 1000 的随机位置。

然后在渲染循环里每一帧改变一下 y 的位置,就可以实现下落的效果。

在 main.js 里引入:

image.png

注释掉 AxesHelper,改一下相机位置。

camera.position.set(200, 200, 200);

看下效果:

2025-04-03 18.33.08.gif

可以看到,现在就有下雪的效果了。

偶尔会有一些特别大的雪花,这个改一下相机的近裁截面,把它设置大一点就好了。

image.png

这个 y 每次下降的高度还有另一种写法:

image.png

用 Clock 的 getDelta 可以拿到每次渲染的时间间隔,可以用它来做 y 的下降高度。

2025-04-03 18.43.42.gif

案例代码上传了小册仓库

总结

这节我们学了 Sprite,它没有几何体,是一个永远面向相机的 1 * 1 的矩形平面。

我们可以用它来做一些场景中物体的标注,也可以用它来实现下雨、下雪等效果。

不同于点模型、线模型、网格模型,Sprite 精灵模型是比较特殊的,在特定场景下会用到。

评论