一、星空
1、搭建一个基本的 Three.js 场景。
首先创建场景、相机、渲染器、控制器。基础可参考这篇。
import * as THREE from 'three';
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 创建场景
const scene = new THREE.Scene()
// 创建相机
const camera = new THREE.PerspectiveCamera(75,window.innerWidth/ window.innerHeight,0.1,1000)
// 相机位置
camera.position.set(0,10,10)
// 将相机添加到场景
scene.add(camera);
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染器尺寸
renderer.setSize(window.innerWidth,window.innerHeight)
// 将canvas添加到body中
document.body.appendChild(renderer.domElement)
//允许在场景中使用阴影贴图
renderer.shadowMap.enabled = true;
// 创建控制器
const controls = new OrbitControls(camera,renderer.domElement);
// 轨道控制器阻尼
controls.enableDamping = true
window.addEventListener('resize',() => {
// 更新相机宽高比
camera.aspect = window.innerWidth / window.innerHeight;
// 修改相机矩阵(就是摄像机的视野以及渲染画面的范围)
camera.updateProjectionMatrix();
// 更新渲染器尺寸
renderer.setSize(window.innerWidth,window.innerHeight)
// 设置渲染器的像素比
renderer.setPixelRatio(window.devicePixelRatio)
})
const clock = new THREE.Clock()
//渲染函数
function render() {
let time = clock.getElapsedTime()
// 阻尼
controls.update()
renderer.render(scene,camera)
requestAnimationFrame(render)
}
// 初始化渲染函数
render()
2、创建一个点。
先看看如何创建一个点。
使用threejs的BufferGeometry
和Points
类创建点。BufferGeometry
是 Three.js 中用于存储顶点数据(如顶点位置、法线、颜色、纹理坐标等)的一种类。与传统的 Geometry
类相比,BufferGeometry
使用了更紧凑和高效的数据结构,可以更快的将数据传到GPU(图形处理器),提高渲染性能。
要创建星空,需要创建很多的点,也就是有大量的顶点数据,并且如果让这些点运动起来,数据会实时变化。对于这种场景,使用 BufferGeometry
可以显著提高渲染性能。
// 创建BufferGeometry对象
const pointBuffer = new THREE.BufferGeometry();
// 定义顶点位置。有x,y,z坐标
const particlePositions = new Float32Array([1,1,1])
// 将顶点数据添加到BufferGeometry。第二个参数itemSize=3,因为每个顶点都是一个三元组。
geometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));
//创建一个 PointsMaterial 材质,设置其颜色和大小
const pointMaterial= new THREE.PointsMaterial({ color: 0xffffff, size: 0.1});
// 创建一个 Points 对象,并将其添加到场景中
const point = new THREE.Points(pointBuffer, pointMaterial);
scene.add(point);
效果:
3、创建多个点
通过循环往顶点数据数组里面添加多个点坐标。坐标位置随机生成。同时给每个粒子添加一个颜色。
// 星星数量,可随意设置。
const count = 1000;
// 设置顶点数组。每个粒子都是个三元组(x,y,z坐标)
let particlePositions = new Float32Array(count * 3);
// 设置粒子颜色。设置rgb。
let colors = new Float32Array(count * 3);
for(let i=1;i<=count *3;i++) {
// 范围:-50 到 50。这个可根据需求设置。
particlePositions[i] = (Math.random() - 0.5) * 100;
colors[i] = Math.random()
}
// 设置属性
pointBuffer.setAttribute('position',new THREE.BufferAttribute(particlePositions,3))
pointBuffer.setAttribute('color',new THREE.BufferAttribute(colors,3));
效果:
生成了很多五颜六色的点。但跟星星没有关系。给这些点添加贴图。让它更像星星。图可以去一些素材库下载,比如这个。
完整代码:
const pointBuffer = new THREE.BufferGeometry();
const count = 1000;
// 设置顶点数组
let particlePositions = new Float32Array(count * 3);
// 设置粒子颜色
let colors = new Float32Array(count * 3);
for(let i=1;i<=count *3;i++) {
particlePositions[i] = (Math.random() - 0.5) * 100;
colors[i] = Math.random()
}
// 设置属性
pointBuffer.setAttribute('position',new THREE.BufferAttribute(particlePositions,3))
pointBuffer.setAttribute('color',new THREE.BufferAttribute(colors,3))
// 创建一个点材质
const pointMaterial = new THREE.PointsMaterial()
pointMaterial.size = 1
// pointMaterial.color.set(0xff0000)
const textureLoader = new THREE.TextureLoader()
const texture = textureLoader.load(
require("../../assets/img/textures/particles/1.png")
);
// 创建点
const points = new THREE.Points(pointBuffer,pointMaterial);
// 贴图
pointMaterial.map = texture
pointMaterial.alphaMap = texture
pointMaterial.transparent = true
// 控制是否将对象的深度值写入深度缓冲区
pointMaterial.depthWrite = false;
//重叠部分 混合模式
pointMaterial.blending = THREE.AdditiveBlending;
// 是否使用顶点着色器,默认值为false
pointMaterial.vertexColors = true;
效果:
也可以让星星运动起来。想到运动,一般都会想到时间。
通过Threejs的Clock
获取时间。
var clock = new THREE.Clock();
通过clock.getDelta()每一帧的间隔时间,或者使用clock.getElapsedTime() 方法来获取从创建 Clock 开始到到现在经过的时间(单位:s)。
//渲染函数
function render() {
let time = clock.getDelta()
points.position.x += time;
// 加个边界判断,不然一会就都飞出去了。
if(points.position.x > 50) {
points.position.x = 0;
}
points.rotation.x += time * 0.1;
// 阻尼
controls.update()
renderer.render(scene,camera)
requestAnimationFrame(render)
}
效果:
优化:我们可以把创建星星的代码提取出来,放到一个函数或类中。这样可以重复使用,更灵活,比如可以将贴图,尺寸作为参数传进去,这样就给星星帖不同的图,设置不同的尺寸。
二、星系
旋涡状星系大概长这样:
有几条悬臂,悬臂上星星的分布是越往外越稀释,星系中心星星密集,外围稀疏,大概就是这样一个特征。
1、创建由点组成的线
我们可以把悬臂看成是弯曲的线,这条线是由点组成的,调节线上点的分布来实现悬臂上星星的分布特征。这里用到了一些数学函数(可以看看这本书),来实现这些分布特征。嗯,果然,宇宙的尽头是数学。
创建直线:
// 定义一些需要的参数
const params = {
// 每条分支的点的数量
count: 10000,
// 点的尺寸
size: 0.1,
// 分支的数量
branch: 4,
// 半径
radius: 5,
// 弯曲度
rotateScale: 0.3,
//定义中心颜色
color:'#ff6030',
//结束(末端)颜色
endColor:'#2859b8'
}
let bufferGeometry = null
let material = null;
let points = null;
// 中心颜色
const centerColor = new THREE.Color(params.color)
// 边缘颜色
const endColor = new THREE.Color(params.endColor)
// 点的坐标
const pointsPosition = new Float32Array(params.count * 3)
// 点的颜色
const colors = new Float32Array(params.count * 3);
// 创建branch条由点组成的线
for (let i = 1; i < params.count; i++) {
// 当前点在哪个分支,分支间的弧度,比如四个分支,每个分支就是90°,转化为弧度。
const pointBranch = (i % params.branch) * (Math.PI * 2 / params.branch);
// 半径 原点附近的点密集,越往外越稀疏。 Math.pow数学函数
const distance = Math.random() * params.radius * Math.pow(Math.random() ,3)
bufferGeometry = new THREE.BufferGeometry();
const current = i * 3;
// x
pointsPosition[current] = Math.cos(pointBranch) * distance
// y
pointsPosition[current + 1] = 0;
// z
pointsPosition[current + 2] = Math.sin(pointBranch) * distance
// 混合颜色
const mixColor = centerColor.clone()
mixColor.lerp(endColor,distance / params.radius)
colors[current] = mixColor.r
colors[current + 1] = mixColor.g
colors[current + 2] = mixColor.b
}
// 设置顶点位置和颜色属性
bufferGeometry.setAttribute('position',new THREE.BufferAttribute(pointsPosition,3))
bufferGeometry.setAttribute('color',new THREE.BufferAttribute(colors,3))
给点添加贴图
// 创建材质
material = new THREE.PointsMaterial({
size: params.size,
// 纹理贴图
map:texture,
// 确保透明区域不会干扰深度缓存的计算
depthWrite:false,
//渲染顶点颜色
vertexColors:true,
//指定点的大小是否会被相机的深度影响
sizeAttenuation:true,
// 混合模式 叠加
blending: THREE.AdditiveBlending,
});
points = new THREE.Points(bufferGeometry,material)
// 添加到场景
scene.add(points)
效果:得到了四条点组成的直线,且颜色和点的密集程度随着半径变化。
接下来给线添加弧度,也就是改变每个点的坐标,修改如下:
pointsPosition[current] = Math.cos(pointBranch + distance * params.rotateScale) * distance
pointsPosition[current + 1] = 0;
pointsPosition[current + 2] = Math.sin(pointBranch + distance * params.rotateScale) * distance
效果:
接着,让这些点分布在线的四周。给每个点加一个偏移距离,且距离线越远,点越稀疏,用到了一个数学函数,y=x^3,这个函数的曲线就是随着x的增大,y增大的越来越快,可以想象下那个曲线。用到这里就是值小的地方比较密集,大的地方就比较稀疏:
const randomX = (Math.pow(Math.random() * 2 - 1,3))
const randomY = (Math.pow(Math.random() * 2 - 1,3))
const randomZ = (Math.pow(Math.random() * 2 - 1,3))
pointsPosition[current] = Math.cos(pointBranch + distance * params.rotateScale) * distance + randomX
pointsPosition[current + 1] = 0 + randomY;
pointsPosition[current + 2] = Math.sin(pointBranch + distance * params.rotateScale) * distance + randomZ
效果:
再加上上面的星空,效果如下: