预言已显,新月将至!Threejs复刻原神绝美空月之歌场景
点击关注公众号,“技术干货”及时达!
??一、前言最近老米又整了个大活,上线了三月传说的剧情。
不得不说原神的美工这一块真的没话说,身为一个前端切图仔,马上被他的 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 进行加载:
consttextureLoader=newTHREE.TextureLoader();
constbackgroundTexture=textureLoader.load('path/to/background.jpg');
scene.background=backgroundTexture;使用 threejs 加载后,为了符合深空的背景,我整体使用紫色和暗色调。在使用 gui 调整整体的透明度,旋转、位置等参数后,得到了初步的效果。
背景中还有一些散落的星星,我在抓包的时候看到老米使用的背景星星好像是使用贴图时不断放大缩小实现的,我觉得这样还是太敷衍了。所以我决定使用 Threejs 中的网格自己实现一个。
首先,创建了一个包含大量点的 THREE.Points 网格每里面大量的点的位置是随机的。
//创建星星点点
functioncreateStarField(){
constvertices=
for(leti=0;i10000;i++){
constx=(Math.random()-0.5)*2000;
consty=(Math.random()-0.5)*2000;
constz=(Math.random()-0.5)*2000;
vertices.push(x,y,
}
constgeometry=newTHREE.BufferGeometry();
geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices,3));
constmaterial=newTHREE.PointsMaterial({
color:0xffffff,
size:1,
transparent:true
returnnewTHREE.Points(geometry,material);
}然后对于每个点我们再使用 shader 进行优化,为了让星星具有闪烁效果,使用了自定义的 ShaderMaterial,通过在顶点着色器和片段着色器中添加时间变量,控制星星的透明度和亮度变化。以下是部分着色器代码:
//顶点着色器
varyingvec3vPosition;
voidmain(){
vPosition=position;
gl_PointSize=1.0;
gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);
}
//片段着色器
uniformfloattime;
varyingvec3vPosition;
voidmain(){
floatbrightness=sin(time+length(vPosition))*0.5+0.5;
gl_FragColor=vec4(vec3(brightness),brightness);
}通过不断调整着色器中的参数,最终实现了星星的闪烁效果,使其更加贴近原神中的视觉表现。
2.2 星环实现星环是整个场景中最复杂的部分之一。为了实现星环的动态旋转效果,需要创建了多个同心圆环,并为每个圆环添加了不同的旋转速度和方向。每个圆环由一系列点组成,这些点同样使用了自定义的着色器来实现发光和闪烁效果。
exportfunctioncreateCircle(
imagePath=CirclePath,
circleName="circlename",
defaultValue={
circleSize:3.5,
rotationSpeed:0.5,//默认旋转速度
opacity:0.5,
}
){
//添加图片到圆环中心
consttextureLoader=newTHREE.TextureLoader();
constcircleTexture=textureLoader.load(imagePath);
constcircleGeometry=newTHREE.PlaneGeometry(1,1);//平面大小根据图片调整
constcircleMaterial=newTHREE.MeshBasicMaterial({
map:circleTexture,
transparent:true,
side:THREE.DoubleSide,
alphaMap:alphaTexture,
opacity:defaultValue.opacity,
alphaTest:0.1,
constcircleMesh=newTHREE.Mesh(circleGeometry,circleMaterial);
circleMesh.scale.set(Number(defaultValue.circleSize),Number(defaultValue.circleSize),1);
circleMesh.position.z=0.1;//稍微调整z轴避免与星星重叠
constfolder=gui.addFolder(circleName);
folder.close();//默认收起面板
constcontrols={
...defaultValue,
//新增图片大小控制
folder.add(controls,"circleSize",1,10).onChange((value)={
circleMesh.scale.set(Number(value),Number(value),1);
//添加旋转速度控制
folder.add(controls,"rotationSpeed",-0.1,0.1).name("旋转速度");
functionanimate(){
//return
requestAnimationFrame(animate);
//更新圆环旋转
circleMesh.rotation.z+=controls.rotationSpeed*0.01;
}
animate();
returncircleMesh;
}
如果直接加载图片的话会显得不那么真实,看到老米的星环是有一种模糊的质感,于是通过分析发现老米应该是用了贴图纹理,找到资源中相关的图片,将星环加上质感。
//加载星环纹理
conststarRingTexture=textureLoader.load('path/to/star-ring-texture.png');
starRingTexture.wrapS=THREE.RepeatWrapping;
starRingTexture.wrapT=THREE.RepeatWrapping;
2.3 、坐标轴图片场景还有一个坐标轴,我们直接使用 Point 将点随机分布在坐标轴上就行。
exportfunctioncreateAxisStars(){
constgeometry=newTHREE.BufferGeometry();
constvertices=
for(leti=0;i100;i++){
constx=-500;
consty=(Math.random()-0.5)*1000;
constz=(Math.random()-0.5)*1000;
vertices.push(x,y,
}
geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices,3));
constmaterial=newTHREE.PointsMaterial({
color:0xffffff,
size:2
returnnewTHREE.Points(geometry,material);}三、最中相机视角确定目前我们已经完成了需要使用到的所有组件,但是目前我们还是在一个三维的视角中。
现在需要完成最终相机的位置确定,就是用户最终看到的画面。为了方便,我们「将背景的坐标固定在 xy 平面上,将中间倾斜的主体放到一个 group中进行整体旋转」
constgalaxyGroup=newTHREE.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;
然再调整相关的参数(炼丹),就大差不差了:
四、整体优化最后,再对整个场景进行了优化,以确保在不同设备上都能流畅运行。
「性能优化」:通过减少星星和星环的数量、降低着色器的复杂度等方式,在保证视觉效果的同时提高渲染性能。「响应式设计」:添加了窗口大小变化的监听器,使场景能够自适应不同的屏幕尺寸。「参数调整」:使用 dat.GUI 添加了调试界面,方便在运行时调整各个元素的参数,如星星的大小、星环的旋转速度等。constgui=newdat.GUI();
gui.add(material.uniforms.size,'value',0.1,5).name('星星大小');
gui.add(material.uniforms.glowIntensity,'value',0,2).name('发光强度');
通过不断调试和优化,最终实现了与原神空月之歌场景相似的视觉效果。
GitHub 地址:
https://github.com/qirong77/genshin-impact-moon喜欢的点个??吧 谢谢~
在线访问地址:
https://qirong77.github.io/genshin-impact-moon/
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线