Threejs-创建一个星空和星系

一、星空

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的BufferGeometryPoints类创建点。
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);

效果:

image.png

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));

效果:

image.png
生成了很多五颜六色的点。但跟星星没有关系。给这些点添加贴图。让它更像星星。图可以去一些素材库下载,比如这个
完整代码:

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;

效果:

image.png

也可以让星星运动起来。想到运动,一般都会想到时间。
通过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)
}

效果:

star.gif

优化:我们可以把创建星星的代码提取出来,放到一个函数或类中。这样可以重复使用,更灵活,比如可以将贴图,尺寸作为参数传进去,这样就给星星帖不同的图,设置不同的尺寸。

二、星系

旋涡状星系大概长这样:

image.png
有几条悬臂,悬臂上星星的分布是越往外越稀释,星系中心星星密集,外围稀疏,大概就是这样一个特征。

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)

效果:得到了四条点组成的直线,且颜色和点的密集程度随着半径变化。
image.png
接下来给线添加弧度,也就是改变每个点的坐标,修改如下:

pointsPosition[current] = Math.cos(pointBranch +  distance * params.rotateScale) * distance
pointsPosition[current + 1] = 0;
pointsPosition[current + 2] = Math.sin(pointBranch + distance * params.rotateScale) * distance

效果:

image.png
接着,让这些点分布在线的四周。给每个点加一个偏移距离,且距离线越远,点越稀疏,用到了一个数学函数,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

效果:

image.png

再加上上面的星空,效果如下:

image.png

完整代码。

三、结语

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYma5d9K' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片