Хороший эффект 3D-взрыва и частиц с использованием OpenGL (JOGL)?

Я хотел бы написать это в течение некоторого времени... как проект для университета, я написал (с другом) игру, в которой нужны хорошие взрывы и эффекты частиц. мы столкнулись с некоторыми проблемами, которые мы решили довольно элегантно (я думаю), и я хотел бы поделиться знаниями.

Итак, мы нашли этот учебник: Произвести эффект взрыва частицы, который казался достаточно простым для реализации с использованием 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 является вектором "вверх", а положение - это... позиция.

так (если поставить его в стиле вопроса), как реализовать хороший эффект частиц?

PS: все образцы кода и скриншоты в игре (как в ответе, так и в вопросе) берутся из игры, которая размещается здесь: Astroid Shooter

Ответ 1

Итак, давайте посмотрим, как мы в первый раз приближаемся к реализации частиц: у нас был абстрактный класс 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 способ вычислить такую ​​перпендикулярную плоскость не сложно:

  • подставить положение частицы из положения камеры, чтобы получить вектор, перпендикулярный плоскости, и нормализовать его, чтобы он мог использоваться как нормальный для плоскости. теперь плоскость определяется плотно нормалью и положением, которое мы теперь имеем (положение частицы - это точка, через которую проходит плоскость)

  • вычислить "высоту" квада, нормализуя проекцию вектора камеры 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}

но не все спрайты действуют так, некоторые не перпендикулярны. например, спрайты с ударной волной или летающие искры/дымовые дорожки: enter image description here поэтому каждый спрайт должен был дать ему собственный уникальный "рекламный щит".BTW, вычисление дымовых дорожек и искры искры sprites тоже было проблемой. мы создали еще один абстрактный класс, мы его назвали: LineSprite. я пропущу объяснения здесь, вы можете увидеть здесь код: LineSprite.

Хорошо, эта первая попытка была приятной, но возникла неожиданная проблема. вот скриншот, который иллюстрирует проблему: enter image description here как вы можете видеть, спрайты пересекаются друг с другом, поэтому, если мы посмотрим на 2 спрайта, который пересекается, часть 1-го спрайта находится за вторым спрайтом, а другая его часть находится напротив 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

надеюсь, что это поможет, хотелось бы получить ваши комментарии к этой "статье" (или к игре:}, которую вы можете исследовать, использовать и использовать, но вы хотите...)