This article we want to achieve a burning flame effect, which will contain the relevant algorithm to produce the burning effect, if you do not understand, you can adjust the relevant parameters to further understand, first take a look at the effect.

Step 1: Build the foundation

In order to achieve the above effect, we will first build the Canvas DOM and the related shader code.

The steps here are the same as in the first step of this article, you can check them out here, or I’ll attach the entire code at the end (don’t forget to introduce the relevant JS files).

Note that in order for the flame position to be in the middle, I set the width of my canvas to 375 and the height to 667. These two values are related to the parameters in the later slice shader. If you find the flame position is not correct when you implement the effect, you can adjust them accordingly.

Step 2: A proper noise algorithm

To achieve the flame burning effect, we need to use a noise algorithm, WebGL has gradient noise, cell noise and other algorithms, but the simulated flame burning effect is not very vivid, I use another noise algorithm here:

float noise(vec3 p){
    vec3 i = floor(p);
    vec4 a = dot(i, vec3(1..57..21.)) + vec4(0..57..21..78.);
    vec3 f = cos((p-i)*acos(1.)) * (-. 5) +. 5;
    a = mix(sin(cos(a)*a),sin(cos(1.+a)*(1.+a)), f.x);
    a.xy = mix(a.xz, a.yw, f.y);
    return mix(a.x, a.y, f.z);
}
Copy the code

In the algorithm we first use the floor function to return the smallest integer part of P, and then use the dot function to take positive values of I and another vec3.

Then define a f calculated using the cosine and acOS functions, and then mix the sine and cosine functions of A, and finally make a linear mixture of Mix.

In order to facilitate understanding there is a a = mix (sine (cosine) (a) a), sin (cos (1 + a) (1 + a)), f.x); Function picture for reference.

If you still don’t understand it you can graph each step of the function.

Through the above we get a noise algorithm, the next is to apply the noise algorithm, and use the algorithm to outline the flame.

Step 3: Modify the slice shader

In the first step we initialized a simple vertex shader and a fragment shader. Now we need to modify the fragment shader.

void main() {
    vec2 v = 1.5 + 3. * v_TexCoord;
    
    vec3 org = vec3(0..- 2..4.); 
    vec3 dir = normalize(vec3(v.x*1.6, -v.y, 1.5));
    
    vec4 p = raymarch(org, dir);
    float glow = p.w;
    
    vec4 col = mix(vec4(1... 5.1..1.), vec4(0.1.. 5.1..1.), p.y*. 02+4.);
    
    gl_FragColor = mix(vec4(0.), col, pow(glow*2..4.));       
}
Copy the code

V_TexCoord is the texture coordinate we passed in, multiplied and added. Remember the flame position in step 1? These two parameters are used for adjustment.

Next we define a variable of VEC3 and a normalized variable dir.

We then define a variable p, which is processed using the custom function Raymarch, which will be described later, a linear mix of positions, and assignment.

Next comes the RayMarch function and the custom functions it uses.

float sphere(vec3 p, vec4 spr){
    return length(spr.xyz-p) - spr.w;
}

float flame(vec3 p){
    float d = sphere(p*vec3(1... 5.1.), vec4(. 0.1... 0.1.));
    return d + (noise(p+vec3(. 0,time*2... 0)) + noise(p*3.) *. 5) *25.*(p.y) ;
}

float scene(vec3 p){
    return min(100.-length(p) , abs(flame(p)) );
}

vec4 raymarch(vec3 org, vec3 dir){
    float d = 0.0, glow = 0.0, eps = 0.02;
    vec3  p = org;
    bool glowed = false;
    
    for(int i=0; i<64; i++)
    {
        d = scene(p) + eps;
        p += d * dir;
        if( d>eps )
        {
            if(flame(p) < . 0)
                glowed=true;
            if(glowed)
                glow = float(i)/64.; }}return vec4(p,glow);
}
Copy the code

The main purpose of the Raymarch function is to use noise to outline the overall appearance of the flame, including size, color value, and effect position.

To outline the core of the flame is to render the specified color at the location specified by the shard shader, and then connect the shards linearly to create the flame, for example, the outer flame is rendered linearly yellow and the inner flame is rendered linearly blue.

In the raymarch function above, we used 64 in the for loop and then added 64 to it. If you are interested, adjust the two values and you will see that the brightness of the flame changes.

In the code above, we used the flame function to define the speed of the flame and the height of the flame. We used the scene function to define the overall effect. Try changing 100 to 10 and you will see the color of the flame change completely.

All the code

Above we have implemented all the effects step by step. It should be noted that I set the canvas background to black for the flame effect instead of the fragment shader. Below is all the code, you can try it locally and understand the interesting content of the algorithm.

<template> <div> <canvas id="glcanvas" ref="webgl" width="375" height="667"></canvas> </div> </template> <script> /* eslint-disable */ import testImg from './static/img/img1.jpeg' export default { props: { msg: String }, mounted() { let VSHADER_SOURCE = ` attribute vec4 a_Position; attribute vec2 a_TexCoord; varying vec2 v_TexCoord; void main() { gl_Position = a_Position; v_TexCoord = a_TexCoord; }` let FSHADER_SOURCE = ` precision mediump float; uniform sampler2D u_Sampler; uniform float time; varying vec2 v_TexCoord; float noise(vec3 p){ vec3 i = floor(p); vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.); vec3 f = cos((p-i)*acos(-1.))*(-.5)+.5; a = mix(sin(cos(a)*a),sin(cos(1.+a)*(1.+a)), f.x); a.xy = mix(a.xz, a.yw, f.y); return mix(a.x, a.y, f.z); } float sphere(vec3 p, vec4 spr){ return length(spr.xyz-p) - spr.w; } float flame (vec3 p) {float d = sphere (vec3 p * (1),) 5, 1.), vec4 (. 0, 1,,, 0, 1))); return d + (noise(p+vec3(.0,time*2.,.0)) + noise(p*3.)*.5)*.25*(p.y) ; } float scene(vec3 p){ return min(100.-length(p) , abs(flame(p)) ); } vec4 raymarch(vec3 org, vec3 dir){float d = 0.0, glow = 0.0, eps = 0.02; vec3 p = org; bool glowed = false; for(int i=0; i<64; i++) { d = scene(p) + eps; p += d * dir; if( d>eps ) { if(flame(p) < .0) glowed=true; if(glowed) glow = float(i)/64.; } } return vec4(p,glow); } void main() {vec2 v = -1.5 + 3. * v_TexCoord; vec3 org = vec3(0., -2., 4.); Vec3 dir = normalize(v.x*1.6, -v.y, -1.5); vec4 p = raymarch(org, dir); float glow = p.w; Vec4 col = mix (vec4 (. 1, 5, 1, 1)), vec4 (0.1) 5, 1), (1)), p.y *. 02 +. 4); gl_FragColor = mix(vec4(0.), col, pow(glow*2.,4.)); }` let canvas = this.$refs.webgl let gl = getWebGLContext(canvas); initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE); let n = this.initVertexBuffers(gl); this.inirTextures(gl, n); let u_time = gl.getUniformLocation(gl.program, "time"); Let newTime = 0.1; Let draw = function(){newTime = newTime + 0.05; gl.uniform1f(u_time, newTime); Gl. ClearColor (0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); requestAnimationFrame(draw); } draw() }, methods: {initVertexBuffers(gl){var verticesTexCoords = new Float32Array([-1.0, 1.0, 0.0, 1.0, -1.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 0.0,]); var n = 4; var vertexCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW); var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT; var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0); gl.enableVertexAttribArray(a_Position); var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord'); gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2); gl.enableVertexAttribArray(a_TexCoord) return n; }, inirTextures(gl, n){ var texture = gl.createTexture(); var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler'); var image = new Image(); image.onload = ()=>{this.loadTexture(gl, n, texture, u_Sampler, image); }; image.crossOrigin = "anonymous"; image.src = testImg return true; }, loadTexture(gl, n, texture, u_Sampler, image){ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.uniform1i(u_Sampler, 0); gl.drawArrays(gl.TRIANGLE_STRIP, 0, n); } } } </script> <style lang="scss"> #glcanvas{ background-color: #000; } </style>Copy the code