原神,启动!three.js 复刻原神登录界面技术浅析

之前看到有人在浏览器端复刻了原神的登录界面,效果非常还原。

本文就让我们一起来看看这种效果是如何实现的,主要分析Shader相关的部分。

怀着学习的目的,我自己也写了一版,观看地址:genshin-replica.miaohezi.com/

背景

背景是一个渐变色,由 3 种颜色组成,可以用Shadersmoothstep函数配合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...

相关推荐
格瑞@_@2 天前
11.Three.js使用indexeddb前端缓存模型优化前端加载效率
前端·javascript·缓存·three.js·indexeddb缓存
谢小飞2 天前
我做了三把椅子原来纹理这样加载切换
前端·three.js
小白菜学前端3 天前
ThreeJS创建一个3D物体的基本流程
3d·three.js
茶老翁4 天前
1-初识Three.js
前端·three.js
莫石5 天前
搓绳子(直)
前端·数学·three.js
小白菜学前端6 天前
3d 添加辅助坐标器和轨道控制器
3d·three.js
孙_华鹏8 天前
threejs——实战中材质的应用
前端·three.js·数据可视化
天涯学馆11 天前
Three.js灯光阴影与动画交互
前端·unity3d·three.js
格瑞@_@15 天前
6.Three.js贴图与uv映射(uv坐标)理解和实践
javascript·three.js·贴图·uv
入秋丶23 天前
threejs - 包围盒和包围球
three.js