10

我一直想写它有一段时间了……作为大学的一个项目,我(和朋友)写了一个需要良好爆炸和粒子效果的游戏。我们遇到了一些问题,我们非常优雅地解决了(我认为),我想分享知识。

好的,所以我们找到了这个教程:Make a Particle Explosion Effect,它看起来很容易使用 Java 和 JOGL 来实现。在我回答我们如何实现本教程之前,我将解释渲染是如何完成的:

Camera:只是一个标准正交基,这基本上意味着它包含 3 个归一化正交向量和一个表示相机位置的第 4 个向量。渲染是使用gluLookAt

glu.gluLookAt(cam.getPosition().getX(), cam.getPosition().getY(), cam.getPosition().getZ(), 
              cam.getZ_Vector().getX(), cam.getZ_Vector().getY(), cam.getZ_Vector().getZ(), 
              cam.getY_Vector().getX(), cam.getY_Vector().getY(), cam.getY_Vector().getZ());

这样相机的z向量实际上是目标,y向量是“向上”向量,而位置是,嗯……位置。

那么(如果用问题风格来表示),如何实现一个好的粒子效果呢?

PS:所有代码示例和游戏截图(包括答案​​和问题)均取自游戏,托管在此处:Astroid Shooter

4

1 回答 1

22

好吧,让我们看看我们如何首先处理粒子的实现:我们有一个Sprite代表单个粒子的抽象类:

protected void draw(GLAutoDrawable gLDrawable) {
    // each sprite has a different blending function.
    changeBlendingFunc(gLDrawable);

    // getting the quad as an array of length 4, containing vectors
    Vector[] bb = getQuadBillboard();
    GL gl = gLDrawable.getGL();

    // getting the texture
    getTexture().bind();

    // getting the colors
    float[] rgba = getRGBA();
    gl.glColor4f(rgba[0],rgba[1],rgba[2],rgba[3]);

    //draw the sprite on the computed quad
    gl.glBegin(GL.GL_QUADS);
    gl.glTexCoord2f(0.0f, 0.0f); gl.glVertex3d(bb[0].x, bb[0].y, bb[0].z);
    gl.glTexCoord2f(1.0f, 0.0f); gl.glVertex3d(bb[1].x, bb[1].y, bb[1].z);
    gl.glTexCoord2f(1.0f, 1.0f); gl.glVertex3d(bb[2].x, bb[2].y, bb[2].z);
    gl.glTexCoord2f(0.0f, 1.0f); gl.glVertex3d(bb[3].x, bb[3].y, bb[3].z);
    gl.glEnd();
}

我们这里的大多数方法调用都很容易理解,这并不奇怪。渲染非常简单。在该display方法上,我们首先绘制所有不透明的对象,然后,我们将所有的Sprites 并排序(与相机的平方距离),然后绘制粒子,以便首先绘制离相机较远的粒子。但我们必须在这里更深入地研究的是方法getQuadBillboard。我们可以理解,每个粒子都必须“坐在”一个垂直于相机位置的平面上,就像这里: 垂直于相机精灵 计算这样一个垂直平面的方法并不难:

  1. substruct 粒子位置从相机位置得到一个垂直于平面的向量,并对其进行归一化,因此可以将其用作平面的法线。现在一个平面由我们现在拥有的法线和位置紧密定义(粒子位置是平面经过的点)

  2. Y通过标准化相机矢量在平面上的投影来计算四边形的“高度” 。您可以通过计算得到投影向量:H = cam.Y - normal * (cam.Y dot normal)

  3. 通过计算创建四边形的“宽度”W = H cross normal

  4. 返回 4 个点/向量:{position+H+W,position+H-W,position-H-W,position-H+W}

但并非所有精灵都这样,有些不是垂直的。例如,冲击波环精灵,或飞行的火花/烟雾轨迹: 在此处输入图像描述 所以每个精灵都必须给它自己独特的“广告牌”。顺便说一句,烟雾轨迹和飞行精灵火花的计算也是一个挑战。我们创建了另一个抽象类,我们称之为:LineSprite. 我将跳过这里的解释,您可以在这里查看代码:LineSprite.

嗯,第一次尝试很好,但是出现了一个意想不到的问题。这是一个说明问题的屏幕截图: 在此处输入图像描述 如您所见,精灵彼此相交,所以如果我们查看相交的 2 个精灵,第一个精灵的一部分在第二个精灵的后面,而另一部分在前面第二个精灵,这导致了一些奇怪的渲染,交叉点的线是可见的。请注意,即使我们禁用glDepthMask了 ,在渲染粒子时,结果仍然会显示相交线,因为每个精灵中发生了不同的混合。所以我们必须以某种方式使精灵不相交。我们的想法真的很酷。

你知道所有这些非常酷的3D 街头艺术吗?这是一张强调这个想法的图片:

在此处输入图像描述

我们认为这个想法可以在我们的游戏中实现,所以精灵不会相互交叉。这是一张图片来说明这个想法:

在此处输入图像描述

基本上,我们让所有的精灵都在平行平面上,所以不会发生交叉。并且它没有影响可见数据,因为它保持不变。从其他角度看,它会显得有些拉长,但从相机的角度来看,它仍然看起来很棒。所以对于实施:

当得到代表一个四边形广告牌的4个向量,以及粒子的位置时,我们需要输出一组新的4个向量,代表原始的四边形广告牌。如何做到这一点的想法在这里得到了很好的解释:Intersection of a plane and a line。我们有由相机位置和 4 个向量中的每一个定义的“线”。我们有平面,因为我们可以使用我们的相机Z矢量作为法线,以及粒子的位置。此外,用于对精灵进行排序的比较函数会有一个小的变化。它现在应该使用由我们的相机正交基定义的齐次矩阵,实际上,计算就像计算一样简单:cam.getZ_Vector().getX()*pos.getX() + cam.getZ_Vector().getY()*pos.getY() + cam.getZ_Vector().getZ()*pos.getZ();. 我们应该注意的另一件事是,如果一个粒子超出了相机的视角,即在相机后面,我们不想看到它,尤其是,我们不想计算它的投影(可以导致一些非常奇怪和迷幻的效果......)。剩下的就是展示最后一Sprite堂课

结果非常好:

在此处输入图像描述

希望对您有所帮助,希望得到您对这篇“文章”(或游戏:} 的评论,您可以随意探索、分叉和使用...)

于 2012-09-16T12:24:43.163 回答