稀土掘金技术社区 03月22日
预言已显,新月将至!Threejs复刻原神绝美空月之歌场景
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

作者学习Three.js后,尝试复刻原神活动场景,介绍了主体背景、星环、坐标轴的实现及相机视角确定、整体优化等内容。

🎇主体背景:抓取背景图片,用Three.js加载,创建闪烁星星

💫星环实现:创建多个同心圆环,添加旋转速度和方向

📏坐标轴图片:使用Point将点随机分布在坐标轴上

📷相机视角确定:将背景坐标固定,主体旋转

⚙️整体优化:进行性能、响应式设计及参数调整

原创 qirong 2025-03-22 09:01 重庆

点击关注公众号,“技术干货” 及时达!

点击关注公众号,“技术干货” 及时达!

一、前言

最近老米又整了个大活,上线了三月传说的剧情。

不得不说原神的美工这一块真的没话说,身为一个前端切图仔,马上被他的 web 特效吸引。刚好最近在学习 Three.js,想着能否通过复刻一个新活动场景来练练手。于是,我开始了这场 Three.js 的奇妙之旅。

最终效果展示

GitHub 地址:

https://github.com/qirong77/genshin-impact-moon 喜欢的话点个🌟 谢谢~

在线访问地址

https://qirong77.github.io/genshin-impact-moon/


个人精力有限,只做了一个页面,历时五天。差不多还原了这个页面的 90% 的功能。

二、场景分析

这个页面其实不太难,在动手之前,我先仔细观察了原神空月之歌场景,发现主要有以下几个部分:

2.1 主体背景实现

主体背景是一个静态的图片,要实现的话直接导入就可以。

我直接从原神的活动页面抓取了背景图片资源。通过分析网络请求,找到了背景图片的链接,并将其下载到本地项目中。在 Three.js 中,使用 THREE.TextureLoader 加载背景图片,并将其设置为场景的背景。

    勾选请求类型 image

    右侧下载

可以看到除了背景也有很多其他的资源,我们全部 copy 下来放到我们的项目中。使用 Threejs 进行加载:

const textureLoader = new THREE.TextureLoader();
const backgroundTexture = textureLoader.load('path/to/background.jpg');
scene.background = backgroundTexture;

使用 threejs 加载后,为了符合深空的背景,我整体使用紫色和暗色调。在使用 gui 调整整体的透明度,旋转、位置等参数后,得到了初步的效果。

背景中还有一些散落的星星,我在抓包的时候看到老米使用的背景星星好像是使用贴图时不断放大缩小实现的,我觉得这样还是太敷衍了。所以我决定使用 Threejs 中的网格自己实现一个。

首先,创建了一个包含大量点的 THREE.Points 网格每里面大量的点的位置是随机的。

// 创建星星点点
function createStarField({
const vertices = [];
for (let i = 0; i < 10000; i++) {
const x = (Math.random() - 0.5) * 2000;
const y = (Math.random() - 0.5) * 2000;
const z = (Math.random() - 0.5) * 2000;
    vertices.push(x, y, z);
  }
const geometry = new THREE.BufferGeometry();
  geometry.setAttribute('position'new THREE.Float32BufferAttribute(vertices, 3));
const material = new THREE.PointsMaterial({
color0xffffff,
size1,
transparenttrue
  });
returnnew THREE.Points(geometry, material);
}

然后对于每个点我们再使用 shader 进行优化,为了让星星具有闪烁效果,使用了自定义的 ShaderMaterial,通过在顶点着色器和片段着色器中添加时间变量,控制星星的透明度和亮度变化。以下是部分着色器代码:

// 顶点着色器
varying vec3 vPosition;
void main() {
  vPosition = position;
  gl_PointSize = 1.0;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// 片段着色器
uniform float time;
varying vec3 vPosition;
void main() {
  float brightness = sin(time + length(vPosition)) * 0.5 + 0.5;
  gl_FragColor = vec4(vec3(brightness), brightness);
}

通过不断调整着色器中的参数,最终实现了星星的闪烁效果,使其更加贴近原神中的视觉表现。

2.2 星环实现

星环是整个场景中最复杂的部分之一。为了实现星环的动态旋转效果,需要创建了多个同心圆环,并为每个圆环添加了不同的旋转速度和方向。每个圆环由一系列点组成,这些点同样使用了自定义的着色器来实现发光和闪烁效果。

export function createCircle(
    imagePath = CirclePath,
    circleName = "circlename",
    defaultValue = {
        circleSize: 3.5,
        rotationSpeed: 0.5// 默认旋转速度
        opacity: 0.5,
    }
{
    // 添加图片到圆环中心
    const textureLoader = new THREE.TextureLoader();
    const circleTexture = textureLoader.load(imagePath);
    const circleGeometry = new THREE.PlaneGeometry(11); // 平面大小根据图片调整
    const circleMaterial = new THREE.MeshBasicMaterial({
        map: circleTexture,
        transparenttrue,
        side: THREE.DoubleSide,
        alphaMap: alphaTexture,
        opacity: defaultValue.opacity,
        alphaTest0.1,
    });
    const circleMesh = new THREE.Mesh(circleGeometry, circleMaterial);
    circleMesh.scale.set(Number(defaultValue.circleSize), Number(defaultValue.circleSize), 1);
    circleMesh.position.z = 0.1// 稍微调整z轴避免与星星重叠
    const folder = gui.addFolder(circleName);
    folder.close(); // 默认收起面板
    const controls = {
        ...defaultValue,
    };
    // 新增图片大小控制
    folder.add(controls, "circleSize"110).onChange((value) => {
        circleMesh.scale.set(Number(value), Number(value), 1);
    });
    // 添加旋转速度控制
    folder.add(controls, "rotationSpeed"-0.10.1).name("旋转速度");
    function animate({
        // return
        requestAnimationFrame(animate);
        // 更新圆环旋转
        circleMesh.rotation.z += controls.rotationSpeed * 0.01;
    }
    animate();
    return circleMesh;
}

如果直接加载图片的话会显得不那么真实,看到老米的星环是有一种模糊的质感,于是通过分析发现老米应该是用了贴图纹理,找到资源中相关的图片,将星环加上质感。

// 加载星环纹理
const starRingTexture = textureLoader.load('path/to/star-ring-texture.png');
starRingTexture.wrapS = THREE.RepeatWrapping;
starRingTexture.wrapT = THREE.RepeatWrapping;

2.3 、坐标轴

图片场景还有一个坐标轴,我们直接使用 Point 将点随机分布在坐标轴上就行。

export function createAxisStars({
  const geometry = new THREE.BufferGeometry();
  const vertices = [];
  for (let i = 0; i < 100; i++) {
    const x = -500;
    const y = (Math.random() - 0.5) * 1000;
    const z = (Math.random() - 0.5) * 1000;
    vertices.push(x, y, z);
  }
  geometry.setAttribute('position'new THREE.Float32BufferAttribute(vertices, 3));
  const material = new THREE.PointsMaterial({
    color0xffffff,
    size2
  });
  return new THREE.Points(geometry, material);}

三、最中相机视角确定

目前我们已经完成了需要使用到的所有组件,但是目前我们还是在一个三维的视角中。

现在需要完成最终相机的位置确定,就是用户最终看到的画面。为了方便,我们「将背景的坐标固定在 xy 平面上,将中间倾斜的主体放到一个 group中进行整体旋转」

const galaxyGroup = new THREE.Group();
galaxyGroup.add(ringItem1);
galaxyGroup.add(ringItem2);
galaxyGroup.add(startRing1);
galaxyGroup.add(startRing2);
galaxyGroup.add(startRing3);
galaxyGroup.add(startRing4);
galaxyGroup.add(circle1);
galaxyGroup.add(circle2);
galaxyGroup.add(circle3);
galaxyGroup.add(circle4);
galaxyGroup.add(circle5);
galaxyGroup.add(axisStar);
galaxyGroup.rotation.x = -0.8;
galaxyGroup.rotation.y = -0.21;
galaxyGroup.rotation.z = -0.18;

然再调整相关的参数(炼丹),就大差不差了:

四、整体优化

最后,再对整个场景进行了优化,以确保在不同设备上都能流畅运行。

const gui = new dat.GUI();
gui.add(material.uniforms.size, 'value'0.15).name('星星大小');
gui.add(material.uniforms.glowIntensity, 'value'02).name('发光强度');

通过不断调试和优化,最终实现了与原神空月之歌场景相似的视觉效果。

GitHub 地址:

https://github.com/qirong77/genshin-impact-moon喜欢的点个🌟吧 谢谢~

在线访问地址:

https://qirong77.github.io/genshin-impact-moon/

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Three.js 原神场景 技术实现 整体优化 创意实践
相关文章