Three.js реализует искровые эффекты

WebGL three.js
Three.js реализует искровые эффекты

Эта статья участвовала в приказе о созыве Haowen, нажмите, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!

предисловие

Всем привет, это волшебник CSS и WebGL - alphardex.

Я только что получил «Искрового рыцаря» Кели в Геншине на прошлых выходных, поэтому по прихоти я хотел использовать three.js для достижения особого эффекта огня, не взрыва бомбы, а искр, оставшихся на траве после взрыва бомбы. эффект взрыва

RBvmVJ.jpg

Эффект в игре относительно мультяшный, а эффект в этой статье будет ближе к реальности

Давайте начнем!

Готов к работе

Прежде чем начать этот проект, вы должны сначала понять концепцию лучевого марша.Статьи о начале работыили черезэта статьяВы также можете приступить к работе.После освоения основных понятий вы можете начать

Этот проект требует:

авторышаблон three.js: Нажмите вилку в правом нижнем углу, чтобы скопировать копию.

Модульность шейдера:glslify

Пакет шейдера npm:glsl-noise,glsl-sdf-primitives,glsl-sdf-ops

текст

Построение сцены

По предыдущей практике строим сцену, ставим плоскость, закрывающую экран, и задаем необходимые параметры (скорость и цвет искр)

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();
  }
  // 创建材质
  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;
  }
  // 创建平面
  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;
    }
  }
}

Далее начинаем писать фрагментный шейдер

Создайте светящийся градиентный эллипс

Если вы внимательно посмотрите на форму искры, вы обнаружите, что ее общая форма похожа на эллипс, и это также светящийся градиентный эллипс, поэтому мы должны найти способ создать эту форму. Кратко опишите идею: значение, полученное при марш-броске лучей, меняется на позицию луча pos и силу прогресса движения луча, ось Y положения луча будет использоваться для установки цвета искры; сила прогресса лучевой марш используется для задания формы искры (здесь эллипс)

#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)

R0RM7Q.png

Генерировать искры с шумом

Далее применяем шум к этому эллипсу (здесь выбран традиционный шум, другие шумы тоже можно выбрать для лучшего вида)

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;
}

R0fRFH.gif

Необъяснимо похоже на черное пламя сестры Фрайд из Dark Souls 3, и хотя оно крутое, мы покрасили его, чтобы оно больше походило на настоящую искру.

добавить цвета искрам

Смешайте цвета с помощью функции смешивания (интенсивность — это ось Y положения источника света) и умножьте на предыдущий цвет.

void main(){
    ...
    
    float fireBody=result.y/64.;
    vec3 mixColor=mix(uColor1,uColor2,fireBody);
    color*=mixColor;
    
    gl_FragColor=vec4(color,1.);
}

адрес проекта

Ray Marching Fire