52

webgl实现火焰效果

 5 years ago
source link: http://dopro.io/webgl实现火焰效果.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

此篇文章我们要实现一个燃烧的火焰效果,其中会包含产生燃烧效果的相关算法,如果不是很理解的话,可以自己动手调节相关参数来进一步理解,先看一下实现的效果。

QfiMziq.gif

第一步: 构建基础

为了实现上面的效果我们先来构建出canvas的DOM,以及相关的shader代码。

这里的步骤和这篇文章中第一步是相同的,你可以移步[https://juejin.im/post/5dcba9aaf265da4d556d0164]这里查看,或者在最后我也会附上全部的代码(不要忘记引入相关的js文件)。

需要注意的是为了让火焰的位置在中间我这边canvas的宽度设置为了375,高度设置为了667,这两个值与后面片元着色器中的参数有关,如果你实现效果时发现火焰位置不对可以适当调整。

第二步: 一个合适的噪音算法

要实现火焰的外焰燃烧效果,我们需要使用一个噪音算法,webgl有梯度噪音,细胞噪音等等的算法,但是模拟出来的外焰燃烧效果个人感觉不是很生动,在此我这边使用了另一种噪音算法:

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

}

在算法中我们首先使用floor函数返回p的最小整数部分,然后使用dot函数对i和另一个vec3数值取正。  

然后定义一个使用cos和acos函数计算出来的f,再对a做混合的sin与cos函数操作,最后进行mix的线性混合。

为了便于理解这里有一个a = mix(sin(cos(a) a),sin(cos(1.+a) (1.+a)), f.x);函数图片,可供参考。

vqeYzuM.jpg!web

如果还是不理解的话可以将函数中的每一步做出图像来理解。

通过上面我们就得到了一个噪音的算法,接下来就是将噪音算法应用起来,并且使用算法勾勒出火焰了。

第三步: 修改片元着色器

在第一步中我们初始化了一个简单的顶点着色器和片元着色器,下面我们就要对片元着色器进行修改了。

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.));

}

v_TexCoord是我们传入的纹理坐标值,对其进行了乘与加处理,还记得第一步中说过的火焰位置吗?这两个参数就是用来调节的。

后面我们定义了一个vec3的变量,同时定义了一个归一化后的变量dir。

接着我们定义了一个变量p,对其使用了自定义函数raymarch进行处理,后面介绍这个函数,最后就是对位置的一个线性混合了,最后是赋值。

接下来就是raymarch函数以及其使用到的自定义函数了。

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

}

raymarch函数的主要作用就是配合noise勾勒出火焰整体的外观,包括大小,颜色值,以及效果的位置等等。

勾勒出火焰的核心就是让片元着色器指定的位置渲染出指定的颜色,然后将这些片元线性连接起来就是火焰了,例如外焰要渲染成线性的黄色,内焰渲染成蓝色。  

在上面的raymarch函数中我们在for循环中使用了64,并且在后面除了64,如果你感兴趣的话,可以去调节这两个数值,你会发现火焰的明亮发生了变化。

上面的代码中我们使用flame函数定义了火焰燃烧的速度以及外焰燃烧的高度等信息,使用scene函数定义了整体的效果,可以试着将其中的100修改为10,你会发现火焰的颜色完全变了。

全部的代码

上面我们一步步的实现了全部的效果,需要注意的是我这边为了火焰的效果将canvas背景设置为了黑色,而不是片元着色器中导致的,下面就是所有的代码,你可以放在本地去试一下,同时理解一下算法有趣的内容。

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

 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.)); 
 }`

 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>

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK