之前看到有人在浏览器端复刻了原神的登录界面,效果非常还原。
本文就让我们一起来看看这种效果是如何实现的,主要分析Shader
相关的部分。
怀着学习的目的,我自己也写了一版,观看地址:https://genshin-replica.miaohezi.com/
背景
背景是一个渐变色,由 3 种颜色组成,可以用Shader
的smoothstep
函数配合UV
坐标的y
分量画出来。
1 | float y=1.-uv.y; |
整个Shader
代码我放到Shadertoy
上了,预览地址:https://www.shadertoy.com/view/dstBzj
背景云
背景云由两种平面网格组成,它们有属于自己的遮罩纹理。
在Shader
中,采样遮罩纹理,获取里面的透明度数据,将其作为alpha
通道的值,再定义一个主颜色即可。
1 | vec2 uv=vUv; |
这是其中一种平面的写法,它只有一种颜色。
另一个平面有两种颜色,是因为用了mix
函数将 2 种颜色混合到了一起。
1 | vec3 col=vec3(.090,.569,.980); |
柱子
柱子的原始信息是一大堆杂乱的网格数据,每条数据包含了模型名、位置、旋转、缩放数据,并且值都是随机的。
我们需要将这些信息分个组,大概分成这样:
每个组包含了模型名,实例网格列表和模型的组成网格列表。
在three.js
中,当我们需要同时创建大量的网格,并且每个网格的位置数据不同时,需要用到实例化网格THREE.InstancedMesh。
我们根据这些组,创建多个实例化网格,并且在渲染中不断地同步它们的位置数据。
云层
由于云层也有很多随机的位置数据,因此也要用到实例化网格。
材质方面,也用到了遮罩纹理。
在采样纹理前,我们要将UV
坐标在顶点着色器中随机化,这样云层就能呈现出不同的图案。
1 | vec2 distortUV(vec2 uv,vec2 offset){ |
注:这里为了凸显出云层本身,临时把柱子和背景云隐藏掉了。
光束
材质也用到了遮罩纹理。
直接采样的话会感觉光的边缘太直了,用smoothstep
生成遮罩来虚化它吧。
1 | float mask1=1.; |
应用遮罩后就变成了这样:
星星
用到了three.js
的粒子系统THREE.Points
。每一个粒子的位置数据都是随机生成的。
它的遮罩纹理包含了 3 种图案,在Shader
中也要随机化UV
,这样星星就能显示出不同的形状。
1 | float seed=random(vRandom); |
雾气
在Shader
中,可以用噪声来生成雾的图案。
1 | float getNoise(vec3 p){ |
渲染优化
众神归位!目前场景是这样子的:
可以看到后面的柱子太明显了,用three.js
自带的雾THREE.Fog来虚化它们。
添加后期处理效果,用到了postprocessing这个库。加上 2 个滤镜:辉光滤镜BloomEffect
和色调映射滤镜ToneMappingEffect
(色调映射为ACES_FILMIC
)。
光这样还是不够。我们需要修改柱子的Shader
,用three.js
材质的onBeforeCompile
方法可以基于材质本身的Shader
来进行修改。
将原本的光照改成Toon
风格的光照。
1 | float dotNL_toon=smoothstep(.25,.27,dotNL); |
Nice!这个渲染可以说是很还原了。
其他
至于路的动画、门的交互等,由于本身比较复杂,请自行查看项目源码,里面有对应的注释。