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