You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 代码省略resize(){constgl=this.gl;constemptyPixels=newUint8Array(gl.canvas.width*gl.canvas.height*4);// screen textures to hold the drawn screen for the previous and the current framethis.backgroundTexture=util.createTexture(gl,gl.NEAREST,emptyPixels,gl.canvas.width,gl.canvas.height);this.screenTexture=util.createTexture(gl,gl.NEAREST,emptyPixels,gl.canvas.width,gl.canvas.height);}// 代码省略
constscreenFrag=` precision mediump float; uniform sampler2D u_screen; uniform float u_opacity; varying vec2 v_tex_pos; void main() { vec4 color = texture2D(u_screen, 1.0 - v_tex_pos); // a hack to guarantee opacity fade out even with a value close to 1.0 gl_FragColor = vec4(floor(255.0 * color * u_opacity) / 255.0); }`;this.fadeOpacity=0.996;// 代码省略drawTexture(texture,opacity){// 代码省略gl.uniform1i(program.u_screen,2);gl.uniform1f(program.u_opacity,opacity);gl.drawArrays(gl.TRIANGLES,0,6);}
draw(){// 代码省略this.drawScreen();this.updateParticles();}drawScreen(){constgl=this.gl;// draw the screen into a temporary framebuffer to retain it as the background on the next frameutil.bindFramebuffer(gl,this.framebuffer,this.screenTexture);gl.viewport(0,0,gl.canvas.width,gl.canvas.height);this.drawTexture(this.backgroundTexture,this.fadeOpacity);this.drawParticles();util.bindFramebuffer(gl,null);// enable blending to support drawing on top of an existing background (e.g. a map)gl.enable(gl.BLEND);gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);this.drawTexture(this.screenTexture,1.0);gl.disable(gl.BLEND);// save the current screen as the background for the next frameconsttemp=this.backgroundTexture;this.backgroundTexture=this.screenTexture;this.screenTexture=temp;}drawTexture(texture,opacity){constgl=this.gl;constprogram=this.screenProgram;gl.useProgram(program.program);// 代码省略gl.drawArrays(gl.TRIANGLES,0,6);}drawParticles(){constgl=this.gl;constprogram=this.drawProgram;gl.useProgram(program.program);// 代码省略gl.drawArrays(gl.POINTS,0,this._numParticles);}updateParticles(){constgl=this.gl;util.bindFramebuffer(gl,this.framebuffer,this.particleStateTexture1);gl.viewport(0,0,this.particleStateResolution,this.particleStateResolution);constprogram=this.updateProgram;gl.useProgram(program.program);// 代码省略gl.drawArrays(gl.TRIANGLES,0,6);// swap the particle state textures so the new one becomes the current oneconsttemp=this.particleStateTexture0;this.particleStateTexture0=this.particleStateTexture1;this.particleStateTexture1=temp;}
目录
引子
了解绘制粒子之后,接着去看如何绘制粒子轨迹。
绘制轨迹
在原文中提到绘制轨迹的方法是将粒子绘制到纹理中,然后在下一帧上使用该纹理作为背景(稍微变暗),并每一帧交换输入/目标纹理。这里涉及两个重点使用的 WebGL 功能点:
基于绘制粒子的基础上,增加逻辑的主要思路:
不包含随机生成的粒子轨迹效果见示例,下面看看具体的实现。
纹理
新增纹理相关逻辑:
初始化的背景纹理和屏幕纹理都是以 Canvas 的宽高作为标准,同样是以每个像素 4 个分量存储。
屏幕着色器程序
新增屏幕着色器程序对象,最终显示可见的内容就是这个对象负责绘制:
顶点数据
顶点相关逻辑:
这里可以看出以顶点数据按照二维解析,总共 6 个点,绘制的是一个矩形,为什坐标都是 0 和 1 ,接着看下面的着色器。
顶点着色器
新增顶点着色器和对应绑定的变量:
从这些分散的逻辑中,找到着色器中的变量对应的实际值:
a_pos
:quadBuffer
中每个顶点二维数据。v_tex_pos
: 跟a_pos
的值一样,会在对应的片元着色器中使用。这里
gl_Position
的计算方式,结合前面说到的顶点坐标都是 0 和 1 ,发现计算结果的范围是 [-1.0, +1.0] ,在裁减空间范围内,就可以显示出来。片元着色器
片元着色器和对应绑定的变量:
从这些分散的逻辑中,找到着色器中的变量对应的实际值:
u_screen
: 动态变化的纹理,需根据上下文判断 。u_opacity
: 透明度,需根据上下文判断。v_tex_pos
: 从顶点着色器传递过来,也就是quadBuffer
中的数据。1.0 - v_tex_pos
的范围是 [0, 1] ,正好包含了整个纹理的范围。最终颜色乘以动态u_opacity
的效果就是原文中所说“稍微变暗”的目的。更新着色器程序
新增更新着色器程序对象,是让粒子产生移动轨迹的关键:
顶点数据
与屏幕着色器程序的顶点数据公用一套。
顶点着色器
与屏幕着色器程序的顶点着色器公用一套。
片元着色器
针对更新的片元着色器和对应绑定的变量:
从这些分散的逻辑中,找到着色器中的变量对应的实际值:
u_wind
:风场图片生成的纹理windTexture
。u_particles
:所有粒子颜色信息的纹理particleStateTexture0
。u_wind_res
: 生成图片的宽高。u_wind_min
: 风场数据分量最小值。u_wind_max
: 风场数据分量最大值。根据
quadBuffer
的顶点数据从纹理particleStateTexture0
中获取对应位置的像素信息,用像素信息解码出粒子位置,通过lookup_wind
方法获取相邻 4 个像素的平滑插值,之后基于风场最大值和最小值得出偏移量offset
,最后得到新的位置转为颜色输出。在这个过程中发现下面几个重点:怎么获取相邻 4 个像素?
看主要方法:
px
;vc
作为第 1 个参考点,移动基本单位单个分量px.x
得到第 2 个参考点;px.y
得到第 3 个参考点,移动基本单位px
得到第 4 个参考点。二维地图中,两极和赤道粒子如何区别?
就像原文中:
对应的处理逻辑:
radians
方法将角度转换为弧度值,pos.y * 180.0 - 90.0
猜测是风数据转为角度的规则。cos
余弦值在 [0,π] 之间逐渐变小,对应offset
的第一个分量就会逐渐变大,效果看起来速度变快了。第二个分量加上了符号-
,推测是要跟图片纹理一致,图片纹理默认在 Y 轴上是反的。绘制
绘制这块变化很大:
screenTexture
,注意从这里开始绘制的结果是不可见的,接着绘制了整个背景纹理backgroundTexture
和基于纹理particleStateTexture0
的所有单个粒子,然后解除帧缓冲区绑定。这部分绘制结果会存储在纹理screenTexture
中。blendFunc
设置的两个参数效果是重叠的部分后绘制会覆盖先绘制。然后绘制了整个纹理screenTexture
,也就是说帧缓冲区的绘制结果都显示到了画布上。backgroundTexture
变成了现在呈现的纹理内容,作为下一帧的背景。particleStateTexture1
,注意从这里开始绘制的结果是不可见的,基于纹理particleStateTexture0
绘制产生偏移后的状态,整个绘制结果会储存在纹理particleStateTexture1
中。particleStateTexture0
变成了移动后的纹理内容,作为下一帧粒子呈现的依据。这样连续的帧绘制,看起来就是动态的效果。疑惑
感觉好像是那么回事,但有的还是不太明白。
偏移为什么要用 lookup_wind 里面的计算方式 ?
原文解释说找平滑插值,但这里面的数学原理是什么?找到之后为什么又要
mix
一次?个人也没找到比较好的解释。参考资料
🗑️
最近看了电影《酷爱电影的庞波小姐 》,看了内容后感觉这名称跟作品主体不是很贴切。
这个类型和《白箱》有些类似,电影里面后续感觉有些太顺畅,可能是限于篇幅不能讲的过多。
里面剪辑影片时候展现的效果很不错,让我想起来《黑客帝国》第一部里面选择武器装备时候的效果。
The text was updated successfully, but these errors were encountered: