原神,启动!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...

相关推荐
刘皇叔code13 小时前
如何给Three.js中ExtrudeGeometry的不同面设置不同材质
webgl·three.js
vivo互联网技术20 小时前
拥抱新一代 Web 3D 引擎,Three.js 项目快速升级 Galacean 指南
前端·three.js
你真的可爱呀5 天前
5.Three.js 学习(基础+实践)
学习·three.js
战场小包7 天前
弟弟想看恐龙,用文心快码3.5S快速打造恐龙乐园,让弟弟看个够
前端·three.js·文心快码
入秋7 天前
Three.js后期处理实战:镜头颜色、色差、点阵与颜色管道的深度解析
前端·three.js
Becauseofyou1378 天前
如果你刚入门Three.js,这几个开源项目值得你去学习
前端·three.js
Jedi Hongbin10 天前
Three.js shader内置矩阵注入
前端·javascript·three.js
接着奏乐接着舞。10 天前
3D地球可视化教程 - 第1篇:基础地球渲染系统
前端·javascript·vue.js·3d·three.js