使用OpenGL (JOGL) 实现好的 3D 爆炸和粒子效果?

10

我一直想写这个项目...作为大学的一个项目,我和朋友一起编写了一个需要好的爆炸和粒子效果的游戏。我们遇到了一些问题,但我们解决得相当优雅(我认为),我想分享这些知识。

好的,我们找到了这个教程:制作粒子爆炸效果,使用Java和JOGL实现似乎很容易。在回答我们如何实现这个教程之前,我将解释渲染是如何完成的:

相机:只是一个包含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向量是“上”向量,位置就是...位置。

那么(如果用问题方式表达),如何实现良好的粒子效果?

P.S:所有代码示例和游戏内截图(包括答案和问题)都来自于托管在此处的游戏:Astroid Shooter


这个是否应该转换为社区维基? - TheAmateurProgrammer
@DownVoters - 如果您能解释一下为什么要给我点踩,我会非常感激。 - gilad hoch
2
你有可能被投反对票是因为你看起来在一条非常微妙的线上徘徊,既有有用的问题又像广告垃圾,而社区通常不喜欢这样做。很高兴你决定撰写一个有帮助的解决方案,但是通常我们不希望每个帖子都以“......你可以在我的iOS应用商店的新应用中看到这个快速排序实现!<链接>”结束。 - Tim
@Tim 谢谢,虽然我不认为这是垃圾邮件。我费尽心思地研究了这个问题很长时间,无论在这里还是其他地方都找不到解决办法。我有的想法,在我看来,是一个像顿悟一样特别的解决方案,并且我想分享它。我相信这个想法是独特、有用和原创的。发帖是出于回报这个美好社区的愿望,这个社区曾经给我提供了许多帮助解决我过去遇到的问题。绝对不是垃圾邮件... - gilad hoch
1个回答

23

好的,那么让我们来看一下如何首先实现粒子:我们有一个抽象类 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方法中,我们首先绘制所有不透明的对象,然后将所有Sprite进行排序(根据相机到精灵的平方距离),然后绘制粒子,使得离相机更远的先绘制。但是我们真正需要深入了解的是getQuadBillboard方法。我们可以理解每个粒子必须“坐”在一个垂直于相机位置的平面上,就像这样: perpendicular to camera sprites 计算这样一个垂直平面的方法并不难:
  1. 从相机位置中减去粒子位置来得到一个垂直于平面的向量,并将其标准化,以便它可以用作平面的法线。现在,通过法线和位置可以紧密地定义一个平面,而这些我们都已经有了(粒子位置是平面穿过的点)。

  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}

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

好的,这是一个不错的尝试,但出现了一个意外的问题。这里有一张截图说明了问题: enter image description here 正如您所看到的,精灵相互交叉,因此如果我们查看相交的2个精灵,则第1个精灵的一部分在第2个精灵的后面,另一部分在第2个精灵的前面,从而导致一些奇怪的渲染,其中交叉的线条可见。请注意,即使我们在呈现粒子时禁用了glDepthMask,结果仍将显示交叉线条,因为每个精灵中发生了不同的混合。因此,我们必须想办法使精灵不相交。我们的想法真的很酷。

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

enter image description here

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

enter image description here

基本上,我们将所有精灵放置在平行平面上,这样就不会发生相交。它并没有影响可见数据,因为它保持不变。从任何其他角度看,它看起来都很扭曲,但从摄像机的视角来看,它仍然很棒。因此,对于实现:

当获取代表四边形广告牌的4个向量和粒子位置时,我们需要输出一组新的4个向量来表示原始四边形广告牌。如何做到这一点的想法在这里得到了很好的解释:平面和直线的交点。我们有“直线”,它由相机位置和每个4个向量定义。我们有平面,因为我们可以使用相机Z向量作为法线,并使用粒子位置。此外,对于排序精灵的比较函数,应该使用齐次矩阵,该矩阵由我们的相机正交基定义,实际上,计算非常容易:cam.getZ_Vector().getX()*pos.getX() + cam.getZ_Vector().getY()*pos.getY() + cam.getZ_Vector().getZ()*pos.getZ();。我们还应该注意到的一件事是,如果一个粒子在相机的视角之外,即在相机后面,我们不想看到它,特别是我们不想计算它的投影(可能会导致一些非常奇怪和迷幻的效果…)。现在只需要展示最终的Sprite

结果非常好:

enter image description here

希望这能有所帮助,很想听取您对这篇“文章”(或者游戏:})的评论。您可以随意探索、复制并使用它,不需要解释,但请保留HTML标签。

你获取广告牌基向量的方法太过复杂了。只需要利用模型视图矩阵左上角包含视图旋转的事实,以及纯旋转矩阵是正交的,也就是说转置矩阵就是逆矩阵。因此,广告牌的基向量只是模型视图矩阵的前三行的前三个元素。 - datenwolf
1
你能详细说明一下是如何完成的吗?我不确定你所指的矩阵是什么... - gilad hoch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接