This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!

preface

Hi, this is ALphardex, the CSS and WebGL magic wizard.

Last weekend, I drew The “spark knight” Curi in the original god, so I had a whim to use three.js to achieve a special effect of fire, not the explosion of the bomb, but the spark effect left on the grass after the bomb explosion

While the effects in the game are relatively cartoonish, the effects in this article will be more realistic

Let’s get started!

The preparatory work

Before you start this project, it’s important to understand the concept of Ray Marching, and if you don’t, it doesn’t matter; I’ve written an introduction to it before, or you can get started with this one, once you’ve mastered the basics

This project requires:

The author’s three.js template: click fork in the lower right corner to copy it

Shader modularity: GLSLIFy

Shader NPM package: GLSL-Noise, GLSL-SDF-Primitives, GLSL-SDF-OPS

The body of the

Scenario building

As usual, set up a scene, put a flat surface on the screen, and set the necessary parameters (the speed and color of the spark)

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"}; }/ / initialization
  init() {
    this.createScene();
    this.createOrthographicCamera();
    this.createRenderer();
    this.createRayMarchingFireMaterial();
    this.createPlane();
    this.createLight();
    this.trackMousePos();
    this.addListeners();
    this.setLoop();
  }
  // Create material
  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;
  }
  // Create the plane
  createPlane() {
    const geometry = new THREE.PlaneBufferGeometry(2.2.100.100);
    const material = this.rayMarchingFireMaterial;
    this.createMesh({
      geometry,
      material,
    });
  }
  / / animation
  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; }}}Copy the code

Next, start writing the chip shader

Create a glow gradient ellipse

If you look closely at the shape of the spark, you will see that it is actually roughly shaped like an ellipse, and it is a glowing gradient ellipse, so we need to find a way to create this shape. The value obtained by Ray Marching is changed to pos of light position and progress strength of light movement. The y axis of light position will be used to set the color of the spark. The progress of light movement Strength is used to set the shape of the spark (in this case, the ellipse)

#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.);
}
Copy the code

centerUv.glsl

vec2 centerUv(vec2 uv,vec2 resolution){
    uv=2.*uv1.;
    float aspect=resolution.x/resolution.y;
    uv.x*=aspect;
    return uv;
}

#pragma glslify:export(centerUv);
Copy the code

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)
Copy the code

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)
Copy the code

Create sparks with noise

The next step is to apply noise to the ellipse (traditional noise is chosen here, other noises can be selected for better appearance).

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;
}
Copy the code

Somehow it feels like sister Fred’s black flame from Black Soul 3. Although it is cool, we have to color it to make it more like a real spark

Add color to the spark

Mix the colors using the MIX function (the intensity is the Y-axis of the light position) and multiply them by the previous colors

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

The project address

Ray Marching Fire