threejs 下雨场景
介绍
该实例是threejs制作的下雨场景,使用Points创建一个粒子集合,PointsMaterial加贴图方式美化粒子,项目是一个练习demo,若有错误请指正,感谢大佬。
实现
场景创建
let scene: THREE.Scene = new THREE.Scene();
相机
透视相机:PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
- fov — 摄像机视锥体垂直视野角度
- aspect — 摄像机视锥体长宽比
- near — 摄像机视锥体近端面
- far — 摄像机视锥体远端面
const initCamera = (width: number, height: number): void => {
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 40);
// 设置相机位置
camera.position.set(0, 0, 40);
// 将相机添加到场景中
scene.add(camera);
};
坐标辅助器
const initAxesHelper = (): void => {
const axesHelper: THREE.AxesHelper = new THREE.AxesHelper(50);
scene.add(axesHelper);
};
状态检测器
const initStats = (): void => {
stats = new Stats();
canvas.value.appendChild(stats.dom);
};
渲染器
这里说一下,有些同学在网上看的教程会有使用 WebGL1Renderer 来创建渲染器,那到底是使用WebGLRenderer还是WebGL1Renderer,接下来我们来了解下这两的区别
如果看过WebGL的同学应该知道WebGL有1和2两个版本,从r118起,WebGLRenderer会自动使用WebGL2来做渲染
那么这两个版本有何区别,简单说就是多了更多纹理格式、内置函数、3D 纹理贴图,同时还支持了非2的整数次方大小的图片。同时,WebGL2 与 WebGL1 在对浏览器的兼容性上有很大的差异,这两者对浏览器兼容产生的巨大差异会导致陈旧的WebGL1的系统崩溃,所以threejs给我们提供了WebGL1Renderer来进行适配兼容
const initRenderer = (width: number, height: number): void => {
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
canvas.value.appendChild(renderer.domElement);
renderer.render(scene, camera);
};
轨道控制器
const initControls = (): void => {
controls = new OrbitControls(camera, renderer.domElement);
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = false;
//是否开启右键拖拽
controls.enablePan = true;
};
创建雨水粒子
const createRaining = (
size: number,
transparent: boolean,
opacity: number,
vertexColors: boolean,
sizeAttenuation: boolean,
color: number,
): void => {
let geometry: THREE.BufferGeometry = new THREE.BufferGeometry();
// pointCount * 3 共pointCount个点,每个点有x,y,z三个坐标,所以需要*3
let positions: Float32Array = new Float32Array(pointCount * 3);
for (let i = 0; i < pointCount * 3; i++) {
positions[i] = (Math.random() - 0.5) * 100;
}
// 设置顶点
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
// 加载雨滴贴图
let textureLoader: THREE.TextureLoader = new THREE.TextureLoader();
let rainTexture: THREE.Texture = textureLoader.load(rainingPng);
// 点材质
let material: THREE.PointsMaterial = new THREE.PointsMaterial({
size: size, // 大小
transparent: transparent, // 材质是否透明,配合opacity设置透明度
opacity: opacity, // 透明度
vertexColors: vertexColors, // 顶点着色
sizeAttenuation: sizeAttenuation, // 指定点的大小是否因相机深度而衰减
color: color, // 颜色
depthTest: true, // 渲染此材质时启用深度测试
depthWrite: false, // 渲染此材质是否对深度缓冲区有任何影响
map: rainTexture, // 贴图
alphaMap: rainTexture, // 贴图灰度
blending: THREE.AdditiveBlending, // 材质混合模式
});
// 生成点
point = new THREE.Points(geometry, material);
// 添加到场景
scene.add(point);
};
调试器
创建调试器控制粒子数据
const initGui = (): void => {
const datGui = new dat.GUI();
const guiConfig = {
size: 1,
transparent: true,
opacity: 0.5,
vertexColors: false,
sizeAttenuation: true,
color: 0xededed,
rotateSystem: false,
reDraw: () => {
// 点存在时移除掉,避免创建相同的point导致性能变差
if (point) {
scene.remove(point);
}
createRaining(
guiConfig.size,
guiConfig.transparent,
guiConfig.opacity,
guiConfig.vertexColors,
guiConfig.sizeAttenuation,
guiConfig.color,
);
controls.autoRotate = guiConfig.rotateSystem;
},
};
datGui.add(guiConfig, "size", 0.1, 3).onChange(guiConfig.reDraw);
datGui.add(guiConfig, "transparent").onChange(guiConfig.reDraw);
datGui.add(guiConfig, "opacity", 0.1, 1).onChange(guiConfig.reDraw);
datGui.add(guiConfig, "vertexColors").onChange(guiConfig.reDraw);
datGui.add(guiConfig, "sizeAttenuation").onChange(guiConfig.reDraw);
datGui.addColor(guiConfig, "color").onChange(guiConfig.reDraw);
datGui.add(guiConfig, "rotateSystem").onChange(guiConfig.reDraw);
guiConfig.reDraw();
};
渲染
const render = (): void => {
controls.update();
renderer.render(scene, camera);
if (stats) {
stats.update();
}
if (point) {
// 雨水动画,修改顶点位置来模拟雨水降落效果
const positions = (point.geometry.getAttribute("position") as any).array;
for (let i = 0; i < pointCount * 3; i += 3) {
positions[i + 1] -= Math.random() * 1;
if (positions[i + 1] < -40) {
positions[i + 1] = 40;
}
}
// 更新顶点
point.geometry.getAttribute("position").needsUpdate = true;
}
// 使用requestAnimationFrame函数进行帧渲染
requestAnimationFrame(render);
};
完整代码 raining.vue
<template>
<div id="canvas" ref="canvas"></div>
</template>
<script lang="ts" setup>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import dat from "dat.gui";
import Stats from "stats.js";
import { nextTick, ref } from "vue";
import rainingPng from "../assets/rain.png";
const canvas = ref<any>(null);
let scene: THREE.Scene = new THREE.Scene();
let camera: THREE.PerspectiveCamera;
let renderer: THREE.WebGLRenderer;
let controls: any;
let stats: any;
let point: THREE.Points;
const pointCount: number = 5000;
nextTick(() => {
initCamera(canvas.value.clientWidth, canvas.value.clientHeight);
initRenderer(canvas.value.clientWidth, canvas.value.clientHeight);
// initAxesHelper();
initControls();
render();
initStats();
initGui();
});
const initCamera = (width: number, height: number): void => {
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 40);
camera.position.set(0, 0, 40);
scene.add(camera);
};
const initRenderer = (width: number, height: number): void => {
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
canvas.value.appendChild(renderer.domElement);
renderer.render(scene, camera);
};
const initStats = (): void => {
stats = new Stats();
canvas.value.appendChild(stats.dom);
};
const initAxesHelper = (): void => {
const axesHelper: THREE.AxesHelper = new THREE.AxesHelper(50);
scene.add(axesHelper);
};
const initControls = (): void => {
controls = new OrbitControls(camera, renderer.domElement);
// 使动画循环使用时阻尼或自转 意思是否有惯性
controls.enableDamping = true;
//是否可以缩放
controls.enableZoom = true;
//是否自动旋转
controls.autoRotate = false;
//是否开启右键拖拽
controls.enablePan = true;
};
const render = (): void => {
controls.update();
renderer.render(scene, camera);
if (stats) {
stats.update();
}
if (point) {
const positions = (point.geometry.getAttribute("position") as any).array;
for (let i = 0; i < pointCount * 3; i += 3) {
positions[i + 1] -= Math.random() * 1;
if (positions[i + 1] < -40) {
positions[i + 1] = 40;
}
}
point.geometry.getAttribute("position").needsUpdate = true;
}
requestAnimationFrame(render);
};
const initGui = (): void => {
const datGui = new dat.GUI();
const guiConfig = {
size: 1,
transparent: true,
opacity: 0.5,
vertexColors: false,
sizeAttenuation: true,
color: 0xededed,
rotateSystem: false,
reDraw: () => {
if (point) {
scene.remove(point);
}
createRaining(
guiConfig.size,
guiConfig.transparent,
guiConfig.opacity,
guiConfig.vertexColors,
guiConfig.sizeAttenuation,
guiConfig.color,
);
controls.autoRotate = guiConfig.rotateSystem;
},
};
datGui.add(guiConfig, "size", 0.1, 3).onChange(guiConfig.reDraw);
datGui.add(guiConfig, "transparent").onChange(guiConfig.reDraw);
datGui.add(guiConfig, "opacity", 0.1, 1).onChange(guiConfig.reDraw);
datGui.add(guiConfig, "vertexColors").onChange(guiConfig.reDraw);
datGui.add(guiConfig, "sizeAttenuation").onChange(guiConfig.reDraw);
datGui.addColor(guiConfig, "color").onChange(guiConfig.reDraw);
datGui.add(guiConfig, "rotateSystem").onChange(guiConfig.reDraw);
guiConfig.reDraw();
};
const createRaining = (
size: number,
transparent: boolean,
opacity: number,
vertexColors: boolean,
sizeAttenuation: boolean,
color: number,
): void => {
let geometry: THREE.BufferGeometry = new THREE.BufferGeometry();
let positions: Float32Array = new Float32Array(pointCount * 3);
for (let i = 0; i < pointCount * 3; i++) {
positions[i] = (Math.random() - 0.5) * 100;
}
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
let textureLoader: THREE.TextureLoader = new THREE.TextureLoader();
let rainTexture: THREE.Texture = textureLoader.load(rainingPng);
let material: THREE.PointsMaterial = new THREE.PointsMaterial({
size: size,
transparent: transparent,
opacity: opacity,
vertexColors: vertexColors,
sizeAttenuation: sizeAttenuation,
color: color,
depthTest: true,
depthWrite: false,
map: rainTexture,
alphaMap: rainTexture,
blending: THREE.AdditiveBlending,
});
point = new THREE.Points(geometry, material);
scene.add(point);
};
// 监听画面变化更新渲染画面
window.addEventListener("resize", () => {
// 更新摄像机
camera.aspect = canvas.value.clientWidth / canvas.value.clientHeight;
// 更新摄像机投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(canvas.value.clientWidth, canvas.value.clientHeight);
// 设置渲染器的像素比
renderer.setPixelRatio(window.devicePixelRatio);
});
</script>
转载自:https://juejin.cn/post/7197414501006999610