three.js 實現(xiàn)火花特效
↓推薦關(guān)注↓
前言
大家好,這里是 CSS兼WebGL 魔法使——alphardex。
上周末剛在原神里抽到了“火花騎士”可莉,于是就心血來潮,想用three.js來實現(xiàn)一種火系的特效,不是炸彈的爆炸,而是炸彈爆炸后在草上留下的火花效果
[2]
游戲里的效果相對比較卡通化,而本文的效果將更加逼近現(xiàn)實一點

讓我們開始吧!
準備工作
在開始本項目之前,你首先要了解ray marching這個概念,掌握了基礎(chǔ)概念后就可以開始了
本項目需要用到:
筆者的three.js模板:https://codepen.io/alphardex/pen/yLaQdOq
著色器模塊化:glslify?https://github.com/glslify/glslify
著色器npm包:glsl-noise,glsl-sdf-primitives,glsl-sdf-ops
正文
場景搭建
按之前的慣例,搭建一個場景,放一個鋪滿屏幕的平面,設(shè)定一些必要的參數(shù)(火花的速度與顏色)
class?RayMarchingFire?extends?Base?{
??constructor(sel:?string,?debug:?boolean)?{
????super(sel,?debug);
????this.clock?=?new?THREE.Clock();
????this.cameraPosition?=?new?THREE.Vector3(0,?0,?1);
????this.params?=?{
??????velocity:?2,
????};
????this.colorParams?=?{
??????color1:?"#ff801a",
??????color2:?"#ff5718",
????};
??}
??//?初始化
??init()?{
????this.createScene();
????this.createOrthographicCamera();
????this.createRenderer();
????this.createRayMarchingFireMaterial();
????this.createPlane();
????this.createLight();
????this.trackMousePos();
????this.addListeners();
????this.setLoop();
??}
??//?創(chuàng)建材質(zhì)
??createRayMarchingFireMaterial()?{
????const?rayMarchingFireMaterial?=?new?THREE.ShaderMaterial({
??????vertexShader:?rayMarchingFireVertexShader,
??????fragmentShader:?rayMarchingFireFragmentShader,
??????side:?THREE.DoubleSide,
??????uniforms:?{
????????uTime:?{
??????????value:?0,
????????},
????????uMouse:?{
??????????value:?new?THREE.Vector2(0,?0),
????????},
????????uResolution:?{
??????????value:?new?THREE.Vector2(window.innerWidth,?window.innerHeight),
????????},
????????uVelocity:?{
??????????value:?3,
????????},
????????uColor1:?{
??????????value:?new?THREE.Color(this.colorParams.color1),
????????},
????????uColor2:?{
??????????value:?new?THREE.Color(this.colorParams.color2),
????????},
??????},
????});
????this.rayMarchingFireMaterial?=?rayMarchingFireMaterial;
????this.shaderMaterial?=?rayMarchingFireMaterial;
??}
??//?創(chuàng)建平面
??createPlane()?{
????const?geometry?=?new?THREE.PlaneBufferGeometry(2,?2,?100,?100);
????const?material?=?this.rayMarchingFireMaterial;
????this.createMesh({
??????geometry,
??????material,
????});
??}
??//?動畫
??update()?{
????const?elapsedTime?=?this.clock.getElapsedTime();
????const?mousePos?=?this.mousePos;
????if?(this.rayMarchingFireMaterial)?{
??????this.rayMarchingFireMaterial.uniforms.uTime.value?=?elapsedTime;
??????this.rayMarchingFireMaterial.uniforms.uMouse.value?=?mousePos;
????}
??}
}
接下來開始編寫片元著色器
創(chuàng)建發(fā)光漸變橢圓
仔細觀察火花的形狀你會發(fā)現(xiàn)其實它的大致形狀像一個橢圓,而且還是發(fā)光的漸變橢圓,于是我們就要想辦法來創(chuàng)建這種形狀。簡要說下思路:ray marching獲取的值改成光線位置pos和光線移動的進度strength,光線位置的y軸將用于設(shè)定火花的顏色;光線移動的進度strength用于設(shè)定火花的形狀(這里就是橢圓)
#pragma glslify:centerUv=require(../modules/centerUv)
#pragma glslify:getRayDirection=require(../modules/getRayDirection)
#pragma glslify:sdSphere=require(glsl-sdf-primitives/sdSphere)
#pragma glslify:opU=require(glsl-sdf-ops/union)
#pragma glslify:cnoise=require(glsl-noise/classic/3d)
uniform float uTime;
uniform vec2 uMouse;
uniform vec2 uResolution;
uniform float uVelocity;
uniform vec3 uColor1;
uniform vec3 uColor2;
varying vec2 vUv;
varying vec3 vPosition;
float fire(vec3 p){
vec3 p2=p*vec3(1.,.5,1.)+vec3(0.,1.,0.);
float geo=sdSphere(p2,1.);
float result=geo;
return result;
}
vec2 sdf(vec3 p){
float result=opU(abs(fire(p)),-(length(p)-100.));
float objType=1.;
return vec2(result,objType);
}
vec4 rayMarch(vec3 eye,vec3 ray){
float depth=0.;
float strength=0.;
float eps=.02;
vec3 pos=eye;
for(int i=0;i<64;i++){
pos+=depth*ray;
float dist=sdf(pos).x;
depth=dist+eps;
if(dist>0.){
strength=float(i)/64.;
}
}
return vec4(pos,strength);
}
void main(){
vec2 p=centerUv(vUv,uResolution);
p=p*vec2(1.6,-1);
vec3 ro=vec3(0.,-2.,4.);
vec3 ta=vec3(0.,-2.5,-1.5);
float fl=1.25;
vec3 rd=getRayDirection(p,ro,ta,fl);
vec3 color=vec3(0.);
vec4 result=rayMarch(ro,rd);
float strength=pow(result.w*2.,4.);
vec3 ellipse=vec3(strength);
color=ellipse;
gl_FragColor=vec4(color,1.);
}
centerUv.glsl
vec2 centerUv(vec2 uv,vec2 resolution){
uv=2.*uv-1.;
float aspect=resolution.x/resolution.y;
uv.x*=aspect;
return uv;
}
#pragma glslify:export(centerUv);
getRayDirection.glsl
#pragma glslify:setCamera=require(./setCamera)
vec3 getRayDirection(vec2 p,vec3 ro,vec3 ta,float fl){
mat3 ca=setCamera(ro,ta,0.);
vec3 rd=ca*normalize(vec3(p,fl));
return rd;
}
#pragma glslify:export(getRayDirection)
setCamera.glsl
mat3 setCamera(in vec3 ro,in vec3 ta,float cr)
{
vec3 cw=normalize(ta-ro);
vec3 cp=vec3(sin(cr),cos(cr),0.);
vec3 cu=normalize(cross(cw,cp));
vec3 cv=(cross(cu,cw));
return mat3(cu,cv,cw);
}
#pragma glslify:export(setCamera)

用噪聲生成火花
接下來就對這個橢圓應(yīng)用上噪聲(這里選了傳統(tǒng)噪聲,為了更好看的外觀,也可以選擇其他的噪聲)
float fire(vec3 p){
vec3 p2=p*vec3(1.,.5,1.)+vec3(0.,1.,0.);
float geo=sdSphere(p2,1.);
// float result=geo;
float displacement=uTime*uVelocity;
vec3 displacementY=vec3(.0,displacement,.0);
float noise=(cnoise(p+displacementY))*p.y*.4;
float result=geo+noise;
return result;
}

莫名感覺像黑魂3里的芙莉德修女的黑焰,盡管這樣也很cool,我們還是給它加上顏色,讓它更像現(xiàn)實中的火花
給火花加上顏色
將顏色通過mix函數(shù)混合起來(強度是光線位置的y軸),和之前的顏色相乘即可
void main(){
...
float fireBody=result.y/64.;
vec3 mixColor=mix(uColor1,uColor2,fireBody);
color*=mixColor;
gl_FragColor=vec4(color,1.);
}

項目地址
Ray Marching Fire:https://codepen.io/alphardex/pen/OJmPpeJ
作者:alphardex
https://juejin.cn/post/6979744391074316319
