Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

OpenGL: Частицы






Частицы и системы частиц - распространённая вещь, которую вы можете увидеть в любой игре.

Обычно частицы создаются двумя способами. Первый - текстурированный квадрат, всегда
обращенный к камере. Второй - использование точечных спрайтов, которые обычно являются
массивами треугольников или точечными примитивами. В этом уроке мы будем использовать
первый метод.

Итак, что составляет частицу? У вас может быть много разных переменных, но вот те, что мы
будем использовать:

Position - содержит мировые координаты центра частицы.
Velocity - содержит направление и скорость, с которой частица двигается.
Color - 32-битный цвет частицы.
Size - ширина и высота квадрата частицы
Life - длительность времени в секундах, пока частица активна (видна)
Angle - угол вращения UV-координат частицы
Texture - класс, сохраняющий текстуру


Также на все частицы влияет гравитация. Одна и та же на все частицы.

Чтобы продемонстрировать работу частиц, я создал эффект пламени. Оранжевый цвет,
используемый для эффекта: R=215; G=115; B=40.

Ещё не всё понятно? Ну, тогда я могу только посоветовать попробовать разобратся
в процессе написания кода...


Исходный код взят из урока "Загрузка текстур". Добавлены файлы vector.h и vector.cpp,
а так же новые файлы particle.h и particle.cpp - класс частиц.

Файл vector.h - заголовочный файл класса CVector:

#ifndef VECTOR_H
#define VECTOR_H

#include <math.h>

class CVector
{
    public:

        // Конструкторы
        CVector() : x(0.0f), y(0.0f), z(0.0f) {}
        CVector(float xxx, float yyy, float zzz) : x(xxx), y(yyy), z(zzz) {}

        // Копирующий конструктор
        CVector(const CVector &vec) : x(vec.x), y(vec.y), z(vec.z) {}

        // Перегружаем операторы
        CVector& operator =(const CVector &vec);
        CVector operator +(const CVector &vec) const;
        CVector operator -(const CVector &vec) const;
        CVector operator -() const;
        void operator +=(const CVector &vec);
        void operator -=(const CVector &vec);
        bool operator ==(const CVector &vec) const;
        bool operator !=(const CVector &vec) const;

        float operator *(const CVector &vec) const;

        // Устанавливает вектор в переданные переменные
        void set(float xxx, float yyy, float zzz);
        void set(const CVector &vec);

        void negate(); // Вычитает вектор
        void normalize(); // Нормализует вектор
        void scale(float amt); // Масштабирует вектор на переданные значения

        float magnitude() const; // Возвращает величину вектора

        // Cross
        void crossProduct(const CVector &vec, CVector &result) const;
        CVector crossProduct(const CVector &vec) const;

        // Public data
        float x;
        float y;
        float z;
};

// Typedef
typedef CVector CPos;

#endif



Файл vector.cpp - реализация класса вектор:

#include <stdlib.h>
#include <assert.h>
#include "vector.h"

inline bool TEqual(float a, float b, float t)
{
    return ((a > b - t) && (a < b + t));
}

// Оператор присваивания
CVector& CVector::operator =(const CVector &vec)
{
    x = vec.x;
    y = vec.y;
    z = vec.z;
        return *this;
}

// Оператор сложения
CVector CVector::operator +(const CVector &vec) const
{
    return CVector(x + vec.x, y + vec.y, z + vec.z);
}

// Оператор вычитания
CVector CVector::operator -(const CVector &vec) const
{
    return CVector(x - vec.x, y - vec.y, z - vec.z);
}

// Противоположное значение
CVector CVector::operator -() const
{
    return CVector(-x, -y, -z);
}

// +=оператор
void CVector::operator +=(const CVector &vec)
{
    x += vec.x;
    y += vec.y;
    z += vec.z;
}

// -= оператор
void CVector::operator -=(const CVector &vec)
{
    x -= vec.x;
    y -= vec.y;
    z -= vec.z;
}

// Оператор сравнения
bool CVector::operator ==(const CVector &vec) const
{
    return (TEqual(x, vec.x, .001f) &&
            TEqual(y, vec.y, .001f) &&
            TEqual(z, vec.z, .001f));
}

// Оператор !=
bool CVector::operator !=(const CVector &vec) const
{
    return !(*this == vec);
}

// Умножение
float CVector::operator *(const CVector &vec) const
{
    return (x * vec.x + y * vec.y + z * vec.z);
}

// Установить вектор в переданные позиции x,y,z
void CVector::set(float xxx, float yyy, float zzz)
{
    x = xxx;
    y = yyy;
    z = zzz;
}

// Устанавливает вектор в позиции переданного CVector
void CVector::set(const CVector &vec)
{
    x = vec.x;
    y = vec.y;
    z = vec.z;
}

// делает вектор отрицательным
void CVector::negate()
{
    x = -x;
    y = -y;
    z = -z;
}

// Нормализует вектор
void CVector::normalize()
{
    assert(!TEqual(magnitude(), 0.0f, .001f)); // убедимся, что длинна не 0

    float oneOverLen = 1.0f / magnitude();

    x *= oneOverLen;
    y *= oneOverLen;
    z *= oneOverLen;
}

// Масштабирует вектор на переданное значение
void CVector::scale(float amt)
{
    x *= amt;
    y *= amt;
    z *= amt;
}

// возвращает величину
float CVector::magnitude() const
{
    return sqrtf((x * x) + (y * y) + (z * z));
}

// находит  cross
void CVector::crossProduct(const CVector &vec, CVector &result) const
{
    result.x = (y * vec.z) - (vec.y * z);
    result.y = (z * vec.x) - (vec.z * x);
    result.z = (x * vec.y) - (vec.x * y);
}

// тоже находит cross
CVector CVector::crossProduct(const CVector &vec) const
{
    return CVector((y * vec.z) - (vec.y * z),
                   (z * vec.x) - (vec.z * x),
                   (x * vec.y) - (vec.x * y));
}





Теперь создадим новый класс, класс частиц. Опишем его в файлах
particle.h и particle.cpp
Хидер-файл класса частиц, particle.h:
#ifndef PARTICLE_H
#define PARTICLE_H
 
#include <stdio.h>
#include "main.h"
#include "vector.h"
 
// Создаёт ARGB-цвет:
#define ARGB(A, R, G, B) ( (int)((A & 0xFF) << 24 |
                        (R & 0xFF) << 16 |
                        (G & 0xFF) << 8 |
                        (B & 0xFF)) )
 
// Функции получают значения A,R,G и B из ARGB-цвета:
#define GET_A(c) ((c >> 24) & 0xFF)
#define GET_R(c) ((c >> 16) & 0xFF)
#define GET_G(c) ((c >> 8) & 0xFF)
#define GET_B(c) (c & 0xFF)
 
// Возвращает рандомный процент между 0 и 1:
#define RAND_PERCENT() ((rand() & 0x7FFF) / ((float)0x7FFF))
 
// Возвращает рандомное значение между (и включая) "min" и "max"
// Естественно, "min" < "max"
#define RAND(min, max) (min + (max - min) * RAND_PERCENT())
 
// Гравитация, воздействующая на каждую частицу. Значение
// получено методом "как лучше выглядит" %)
const float kParticleGravity = -9.81f / 10.0f;
 
// Частица
class CParticle
{
    public:
 
        CParticle(); // Конструктор по умолчанию
 
        // Функция инициализирует частицы:
        bool init(const CPos &pos, const CVector &vel, float lifeSpan, float size,
            float angle = 0.0f, int color = 0xFFFFFFFF, GLuint TID=-1);
 
        void process(float dt); // Двигает частицы каждый кадр
        void render(); // Выводит частицы на экран
 
        // Если значение "жизнь" частицы больше 0, она активна, иначе - нет
        bool isAlive() { return (life > 0.0f); }
 
    private:
 
        CPos pos; // Позиция в мире
        CVector vel; // Скорость
 
        int color; // ARGB цвет частицы
        float size; // Размеры частицы
        float life; // Время жизни частицы в секундах
        float angle; // Угол в градусах для вращения текстурных коорд-т каждую секунду
        float angleNow; // Текущий угол вращения текстуры
 
        GLuint textureID;   // Текстура частицы
};
 
#endif
 


Вот и весь класс частиц. Что он делает? Создает частицы, накладывает на них текстуры,
запускает жизненный цикл, выводит на экран, подчищает умершие, и т.д.

Теперь напишем реализацию класса.
Файл particle.cpp:

#include "particle.h"
 
// Конструктор по умолчанию - обнуляет всё
CParticle::CParticle()
{
    color = ARGB(255, 255, 255, 255);
    size = 0.0f;
    life = 0.0f;
    angle = 0.0f;
 
}
 
// Инициализация частиц:
bool CParticle::init(const CPos &p, const CVector &v, float lifeSpan, float s, float a, int c,
                     GLuint TID)
{
 
    pos = p; // Установим позицию
    vel = v; // Установим скорость
 
    // Не допустим инициализацию "мертвой" частицы
    if(lifeSpan <= 0.0f)
        return false;
 
    life = lifeSpan; // Установим время жизни частицы в секундах
 
    // Размер должен быть положительным
    if(s <= 0.0f)
        return false;
 
    size = s; // Установим размер
    angle = a; // Установим угол вращения UV координат
    color = c; // Установим цвет

    this->textureID = TID;
    return true;
}
 
void CParticle::process(float dt)
{
    // Если частица мертва, сбросим её позицию
    if(isAlive() == false)
    {
        life = RAND(1.0f, 2.0f); // Сделаем её снова живой
        pos = CPos(0,0,0);
            return;
    }
 
    // Применим скорость
    pos.x += vel.x * dt;
    pos.y += vel.y * dt;
    pos.z += vel.z * dt;
 
    // Применим гравитацию
    pos.y += kParticleGravity * dt;
 
    life -= dt; // Уменьшим оставшееся время жизни
 
    // Применим вращение на "angle" в секунду, если он > 0
/*
    if(angle != 0.0f)
        texture.setRotation(texture.getRotAngle() + (angle * dt));
*/

    this->angleNow = this->angleNow + (angle * dt);
}
 
void CParticle::render()
{
    // Если частица не "жива", рендерить нечего
    if(isAlive() == false)
        return;
 
    // Не могу рендерить с отсутствующей текстурой
    if(this->textureID < 0)
        return;
 
    // Эта функция OpenGL делает Z-буфер доступным только для чтения. Это значит, что
    // OpenGL использует текущие значения Z-буфера для определения, должна ли быть частица
    // отрендерена или нет, НО, если частица не отрисовывается, никакие значения Z-буфера
    // не будут установлены.
    // Почему мы так делаем: Мы хотим, частицы были прозрачными, и не перекрывали друг
    // друга. Теперь мы может рендерить их с той же Z-глубиной, и они будут прозрачными,
    // но в то же время они не будут отрисовыватся в чем-то, что меньше их Z-глубины.   
    glDepthMask(false);
 
    // Установим цвет и текстуры
    glColor4ub(GET_R(color), GET_G(color), GET_B(color), GET_A(color));

    // Применим вращение на текстуру и забиндим её:
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();

    glTranslatef(0.5f, 0.5f, 0.0f); // Центр текстуры - центр новой системы координат
    glRotatef(this->angleNow, 0.0f, 0.0f, 1.0f); // Вращаем по оси Z

    glTranslatef(-0.5f, -0.5f, 0.0f); // Перемещаем назад

    glBindTexture(GL_TEXTURE_2D, this->textureID); // Привязываем текстуру
    glMatrixMode(GL_MODELVIEW);

    glPushMatrix();
 
        // Переместим частицы в указанные координаты
        glTranslatef(pos.x, pos.y, pos.z);
 
        float halfSize = size * 0.5f;
 
        // Отрисуем частицы
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 1.0f);
            glVertex3f(-halfSize, halfSize, 0.0f); // Верхняя левая вершина
 
            glTexCoord2f(0.0f, 0.0f);
            glVertex3f(-halfSize, -halfSize, 0.0f); // Нижняя левая вершина
 
            glTexCoord2f(1.0f, 0.0f);
            glVertex3f(halfSize, -halfSize, 0.0f); // Нижняя правая вершина
 
            glTexCoord2f(1.0f, 1.0f);
            glVertex3f(halfSize, halfSize, 0.0f); // Верхняя правая вершина
        glEnd();
 
    glPopMatrix();
 
    glDepthMask(true); // Переключим Z-буфер в нормальное состояние read-write
}
 






Ну и наконец используем все вышенакоденное %)
Переходим к файлу main.cpp:

// В инклуды добавим новый файл:
#include "particle.h"

// После обьявим макс. количество частиц и добавим новые переменные:
#define MAX_PARTICLES 256

CParticle gParticles[MAX_PARTICLES];      // Массив частиц
float gRGB[3] = {0};                      // Мировой RGB-цвет

// Добавим прототипы новых функций:
void UpdateBkgrndColor();       // Изменяет мировой цвет


// Теперь добавим две новых функции:
///////////////////////////////////////////////////////////////
//
//          Функция изменяет бекграунд-цвет сцены
//
///////////////////////////////////////////////////////////////
void UpdateBkgrndColor()
{
    // Насколько увеличивать R,G и B компоненты цвета
    static float inc[3] = { 0.005f, 0.005f, 0.005f };

    int which = rand()%3; // Рандомно выберем компонент для увеличения

    for(int i = 0; i < 3; ++i)
    {
        if(i == which)
        {
            gRGB[i] += inc[i];

            // Удерживаем значения 0<>1
            if(gRGB[i] > 1.0f)
            {
                gRGB[i] = 1.0f;
                inc[i] = -inc[i];
            }
            else if(gRGB[i] < 0.0f)
            {
                gRGB[i] = 0.0f;
                inc[i] = -inc[i];
            }
        }
    }
}
 

///////////////////////////////////////////////////////////////////////////////
//
//  Функция возвращает true, если число FPS менее 60 (или указанного),
//      и false если более.
//
///////////////////////////////////////////////////////////////////////////////
bool LockFrameRate(int frame_rate = 60)
{
    static float lastTime = 0.0f;

    // Текущее время в секундах:
    float currentTime = GetTickCount() * 0.001f;

    // Получаем прошедшее с предыдущего кадра время. Если прошло достаточно, возвращаем true.
    if((currentTime - lastTime) > (1.0f / frame_rate))
    {
        // Сбросим последнее время
        lastTime = currentTime;
            return true;
    }

    return false;
}


////////////////////////////////////////////////////////////////////////////////////////
//
//  Инициализируем всё необходимое в функции Init():
//
////////////////////////////////////////////////////////////////////////////////////////
void Init(HWND hWnd)
{
    g_hWnd = hWnd;
    GetClientRect(g_hWnd, &g_rRect);
    InitializeOpenGL(g_rRect.right, g_rRect.bottom);

    // Инициализируем класс текстур
    Texture = new CTexture();
    // Загружаем нужную нам текстуру:
    Texture->LoadTexture(IL_BMP, "particle.bmp", &textures[0]);

    // Инициализируем генератор случайных чисел
    srand(GetTickCount());

    // Инициализируем все частицы:
    for(int i=0; i<MAX_PARTICLES; i++)
    {
        // Если не получилось инициализировать какую-нибуть частицу - выходим
        if(!gParticles[i].init(CPos(0,0,0), CVector(RAND(-0.25f, 0.25f),
                    RAND(0.5f,1.5f), 0.0f),
                    RAND(1.0f, 2.0f), 0.75f, 30.0f,
                    ARGB(255, 215, 115, 40), textures[0].texID))
        {
        MessageBox(NULL, "Не могу инициализировать частицы.", "ERROR",
                    MB_OK | MB_ICONERROR);
            exit(1);
        }
    }

    // Включим прозрачность для того, чтобы текстуры частиц были прозрачными:
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_COLOR, GL_ONE);
}


// И, наконец, главная функция программы:

///////////////////////////////////////////////////////////////
//
//          Функция вызывается каждый кадр и рендерит сцену
//
///////////////////////////////////////////////////////////////
 
void RenderScene()
{
 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(0,0,5, 0,0,0, 0,1,0);

    // Переменные для нашего таймера
    static float beginTime = GetTickCount() * .001f;
    static float endTime = beginTime;
    static float dt = 0.0f; // Delta Time - время, прошедшее с прошлого кадра

    if(LockFrameRate())
    {
        beginTime = GetTickCount() * 0.001f;    // Получаем время начала кадра
        dt = beginTime - endTime;   // Получаем dt
       
        // Установим бекграунд-цвет сцены в "gRGB":
        glClearColor(gRGB[0], gRGB[1], gRGB[2], 1.0f);

        // Обработаем и отрисуем все частицы:
        for(int i = 0; i < MAX_PARTICLES; ++i)
        {
            gParticles[i].render();
            gParticles[i].process(dt);
        }

        UpdateBkgrndColor();

        // Получим время, прошедшее с конца кадра
        endTime = GetTickCount() * 0.001f;
    }

    SwapBuffers(g_hDC);
}
 



Надеюсь, этот первый опыт с частицами вам пригодится.






Исходные коды к уроку




Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.