Android OpenGL粒子特效

在本篇,我们将开启一个新的项目,探索粒子的世界。粒子是一种基本的图形元素,它们通常被表示为一组点。通过巧妙地组合一些基础的物理效果,我们能够创造出许多令人惊叹的视觉效果。想象一下,我们可以模拟一个水滴从喷泉中喷出,然后优雅地落回地面的场景。同样,我们也能模拟出逼真的下雨效果,或者制作出爆炸和烟花的动画。粒子系统的数学原理相对简单,这使得它们很容易被集成到任何三维场景中。

在本章,我们将逐步构建一个粒子系统。首先,我们会介绍创建粒子系统所需的基本要素。接下来,我们将添加喷泉效果,让粒子喷射到空中。此外,我们还会探讨如何通过技术手段,如混合和点精灵,来提升粒子的视觉效果,使它们看起来更加好看。

创建着色器

作为开始,我们将构建一个基础的粒子系统,模拟一个喷泉。这个喷泉可以想象成在灯光下喷发的泉水,或者像烟花表演中的喷泉一样。为了实现这个效果,我们需要处理一些技术细节。

首先,我们需要找到一种方式来在内存中表示所有的粒子。虽然可以使用Java对象数组,但在运行时创建和删除大量对象可能会导致资源消耗过大,并且没有简单的方法将数据传递给OpenGL。因此,我们选择将所有粒子数据嵌入到一个固定大小的数组中。添加粒子时,我们只需增加粒子计数,将数据写入粒子数组,并把变化的内容复制到本地缓冲区。当空间不足时,可以通过在数组开头重新开始来回收空间。

接下来,我们需要一种方法来绘制每个粒子。我们将每个粒子表示为一个顶点,并绘制一组点,每个点都有其独特的位置和颜色。

最后,我们需要一种方法来更新这些粒子。将这些逻辑放入着色器程序中,可以让GPU分担一部分更新工作。对于每个粒子,我们需要存储一个方向向量和一个创建时间。利用创建时间,我们可以计算出粒子自创建以来经过的时间,然后使用这个时间、方向向量和位置来推算出粒子当前的位置。我们将使用一个浮点数来存储时间,并以0.0表示粒子系统开始运行的时间。

有了这些基本需求,我们可以为着色器程序制定一套初始规范。首先,我们需要定义一个uniform变量用于投影矩阵,以及一个用于当前时间的uniform变量,以便着色器计算出每个粒子自创建以来经过的时间。我们还需要定义四个与粒子属性相对应的属性:位置、颜色、方向向量和创建时间。

1.编写顶点着色器

让我们开始给着色器添加代码。继续并在“/res/raw/”文件夹内创建一个名为“particle_vertex_shader.glsl”的新顶点着色器。首先以如下定义作为开始:

uniform mat4 u_Matrix;
uniform float u_Time;

attribute vec3 a_Position;
attribute vec3 a_Color;
attribute vec3 a_DirectionVector;
attribute float a_ParticleStartTime;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main(){
    v_Color = a_Color;
    v_ElapsedTime = u_Time - a_ParticleStartTime;
    vec3 currentPosition = a_Position + (a_DirectionVector * v_ElapsedTime);
    gl_Position = u_Matrix * vec4(currentPosition,1.0);
    gl_PointSize = 10.0;
}

这些定义满足了我们的粒子着色器的需求。我们在片段着色器中也需要使用颜色和运行时间,因此我们也为这两个变量创建了两个varying。

在main函数中,我们首先把颜色发送给片段着色器,接着计算粒子从被创建之后运行了多少时间,并且把那个时间也发送给片段着色器。为了计算粒子的当前位置,我们把方向向量与运行时间相乘,并与a_Position相加。运行时间越长,粒子走得越远。

要完成这个着色器代码,我们把粒子用那个矩阵进行投影,而且,因为我们把粒子渲染为一个点,所以把点的大小设成了10个像素。

当我们做数学运算时,要确保不会意外地把w分量弄混乱,这是很重要的。因此,我们将用3分量向量表示位置和方向,只有需要把它与u_Matrix相乘时,才把它转换为完全的4分量向量。这确保上面的数学运算只影响x、y和z分量。

2.编写片段着色器

现在我们可以继续并添加片段着色器了。在与顶点着色器相同的地方创建一个被称为“particle_fragment_shader.glsl”的新文件,并加入如下代码:

precision mediump float;
varying vec3 v_Color;
varying float v_ElapsedTime;

void main(){
    gl_FragColor = vec4(v_Color / v_ElapsedTime,1.0);
}

通过把颜色除以运行时间,这个着色器会使年轻的粒子明亮,而使年老的粒子暗淡。如果发生除以0的情况怎么办?根据规范,这会导致一个不明确的结果,但不会导致着色器程序终止。要一个更加可预测的结果,你可以总是给分母加上一个很小的数。

3.用一个类封装着色器

着色器代码完成了,我们现在可以用一个类封装着色器,它使用我们在本书第一部分所使用的模式。让我们首先给 ShaderProgram加入一些新的常量:

    protected val U_TIME = "u_Time"

    protected val A_DIRECTION_VECTOR = "a_DirectionVector"
    protected val A_PARTICLE_START_TIME = "a_ParticleStartTime"

这些新的常量被定义后,我们可以继续加入一个名为“ParticleShaderProgram”的新类,它继承自ShaderProgram,并以如下代码作为这个类的开始:

    private var uMatrixLocation = 0
    private var uTimeLocation = 0

    var aPositionLocation = 0
    var aColorLocation = 0
    var aDirectionVectorLocation = 0
    var aParticleStartTimeLocation = 0

接着继续完成这个类的定义:

    constructor(context: Context):super(context, R.raw.particle_vertex_shader,R.raw.particle_fragment_shader1){
        uMatrixLocation = findUniformLocationByName(U_MATRIX)
        uTimeLocation = findUniformLocationByName(U_TIME)

        aPositionLocation = findAttribLocationByName(A_POSITION)
        aColorLocation = findAttribLocationByName(A_COLOR)
        aDirectionVectorLocation = findAttribLocationByName(A_DIRECTION_VECTOR)
        aParticleStartTimeLocation = findAttribLocationByName(A_PARTICLE_START_TIME)

        uTextureUnitLocation = findUniformLocationByName(U_TEXTURE_UNIT)
    }

    fun setUniforms(matrix:FloatArray,elapsedTime:Float){
        GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,matrix,0)
        GLES20.glUniform1f(uTimeLocation,elapsedTime)
    }

封装模式与前面篇章的一致,这里就不再过多赘述。

添加粒子系统

我们现在可以开始创建粒子系统了。让我们创建一个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值