好的,那么让我们来看一下如何首先实现粒子:我们有一个抽象类 Sprite
,代表一个单独的粒子:
protected void draw(GLAutoDrawable gLDrawable) {
changeBlendingFunc(gLDrawable);
Vector[] bb = getQuadBillboard();
GL gl = gLDrawable.getGL();
getTexture().bind();
float[] rgba = getRGBA();
gl.glColor4f(rgba[0],rgba[1],rgba[2],rgba[3]);
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](https://istack.dev59.com/VGMp6.webp)
计算这样一个垂直平面的方法并不难:
从相机位置中减去粒子位置来得到一个垂直于平面的向量,并将其标准化,以便它可以用作平面的法线。现在,通过法线和位置可以紧密地定义一个平面,而这些我们都已经有了(粒子位置是平面穿过的点)。
通过将相机的Y
向量投影到平面上并对其进行归一化,计算出四边形的“高度”。您可以通过计算获得投影向量:H = cam.Y - normal * (cam.Y dot normal)
通过计算W = H cross normal
来创建四边形的“宽度”。
返回4个点/向量:{position+H+W,position+H-W,position-H-W,position-H+W}
但并非所有的精灵都像这样,有些不是垂直的。例如,冲击波环形精灵或飞行火花/烟雾轨迹:
因此,每个精灵都必须提供自己独特的“广告牌”。顺便说一下,烟雾轨迹和飞行火花的计算也是一个挑战。我们创建了另一个抽象类,称之为:LineSprite
。我将跳过这里的解释,你可以在这里查看代码:LineSprite
。
好的,这是一个不错的尝试,但出现了一个意外的问题。这里有一张截图说明了问题:
正如您所看到的,精灵相互交叉,因此如果我们查看相交的2个精灵,则第1个精灵的一部分在第2个精灵的后面,另一部分在第2个精灵的前面,从而导致一些奇怪的渲染,其中交叉的线条可见。请注意,即使我们在呈现粒子时禁用了glDepthMask
,结果仍将显示交叉线条,因为每个精灵中发生了不同的混合。因此,我们必须想办法使精灵不相交。我们的想法真的很酷。
你知道那些非常酷的
3D街头艺术吗?这里有一张图强调了这个想法:
![enter image description here](https://istack.dev59.com/ZzFYp.webp)
我们认为这个想法可以在我们的游戏中实现,这样精灵就不会互相交叉。下面是一张图来说明这个想法:
![enter image description here](https://istack.dev59.com/wtLcu.webp)
基本上,我们将所有精灵放置在平行平面上,这样就不会发生相交。它并没有影响可见数据,因为它保持不变。从任何其他角度看,它看起来都很扭曲,但从摄像机的视角来看,它仍然很棒。因此,对于实现:
当获取代表四边形广告牌的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](https://istack.dev59.com/BE3Hh.webp)
希望这能有所帮助,很想听取您对这篇“文章”(或者游戏:})的评论。您可以随意探索、复制并使用它,不需要解释,但请保留HTML标签。