
2633
.pdfперемещаются частицы. Чем больше ее значение, тем медленнее они двигаются. Переменные xspeed и yspeed позволяют контролировать направление хвоста потока частиц. Переменная xspeed будет добавляться к текущей скорости частицы по оси X. Если у xspeed имеет положительное значение, то частица будет смещаться направо. Если у xspeed – отрицательное значение, то частица будет смещаться налево. Чем выше значение, тем больше это смещение в соответствующем направлении. yspeed работает также, но по оси Y. Последняя переменная zoom предназначена для панорамирования внутрь и вне сцены. В машине моделирования частиц, это позволяет увеличить размер просмотра, или резко его сократить:
float slowdown=2.0f; |
// Торможение частиц |
||
float xspeed; |
// Основная скорость по X (с клавиатуры изменяется |
||
направление хвоста) |
|
|
|
float yspeed; |
// Основная скорость по Y (с клавиатуры изменяется |
||
направление хвоста) |
|
|
|
float zoom=-40.0f; |
// Масштаб пучка частиц |
||
Далее задается переменная loop, которая используется для задания |
|||
частиц и вывода частиц на экран: |
|||
GLuint loop; |
// Переменная цикла |
||
GLuint col; |
|
// Текущий выбранный цвет |
|
GLuint delay; |
|
// Задержка для эффекта радуги |
|
GLuint texture[1]; |
// Память для текстуры |
||
Переменная col |
используется для сохранения цвета, с которым |
созданы частицы. delay будет использоваться, чтобы циклически повторять цвета в режиме радуги.
Булевая переменная active работает следующим образом: если эта переменная истинна, то частица ''жива'' и летит. Если она равно ложь – частица выключается
Переменные life и fade управляют тем, как долго частица будет отображаться, и насколько яркой она будет, пока ''жива''. Переменная life постепенно уменьшается на значение fade. В этой программе некоторые частицы будут гореть дольше, чем другие:
typedef struct |
// Структура частицы |
{ |
|
bool active; |
// Активность (Да/нет) |
float life; |
// Жизнь |
float fade; |
// Скорость угасания |
Переменные r, g и b задают красную, зеленую и синюю яркости частицы. Чем ближе r к 1.0f, тем более красной будет частица. Если все три переменных равны 1.0f, то это создаст белую частицу:
153
float r; |
// Красное значение |
float g; |
// Зеленое значение |
float b; |
// Синее значение |
Переменные x, y и z задают, где частица будет отображена на экране. x задает положение нашей частицы по оси X. y задает положение нашей частицы по оси Y, и, наконец, z задает положение нашей частицы по оси Z:
float x; |
// X позиция |
float y; |
// Y позиция |
float z; |
// Z позиция |
Следующие три переменные управляют тем, как быстро частица |
|
перемещается по заданной оси: |
|
float xi; |
// X направление |
float yi; |
// Y направление |
float zi; |
// Z направление |
Если xi имеет отрицательное значение, то частица будет двигаться влево. Если положительное, то вправо. Если yi имеет отрицательное значение, то частица будет двигаться вниз. Если положительное, то вверх. Если zi имеет отрицательное значение, то частица будет двигаться вглубь экрана, и, если положительное, то вперед к зрителю.
Если xg имеет положительное значение, то частицу будет ''притягивать'' вправо, если отрицательное – влево. Значение yg ''притягивает'' вверх или вниз, и zg ''притягивает'' вперед или назад от
зрителя: |
|
|
float xg; // X Гравитация |
|
|
float yg; |
// Y Гравитация |
|
float zg; |
// Z Гравитация |
|
Введем particles – название структуры: |
||
} |
|
|
particles; |
// Структура Частиц |
|
Затем создается массив называемый particle, который имеет размер |
||
MAX_PARTICLES. Это зарезервированная память будет хранить |
||
информацию о каждой индивидуальной частице: |
||
particles particle[MAX_PARTICLES]; |
// Массив частиц (Место для |
|
информации о частицах) |
|
|
Для |
создания эффекта радуги |
необходимо обеспечить |
запоминание двенадцати разных цветов, которые постепенно изменяются от красного до фиолетового в массиве цвета. Для каждого цвета от 0 до 11 запоминается красная, зеленая и синяя яркость.
static GLfloat colors[12][3]= // Цветовая радуга
{
{1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
154
{0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
{0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
}
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Объявление WndProc
Далее, в следующей части кода загружается картинка и конвертирует его в текстуру:
AUX_RGBImageRec *LoadBMP(char *Filename) // Загрузка
картинки |
|
|
{ |
|
|
FILE *File=NULL; |
// Индекс файла |
|
if (!Filename) |
// Проверка имени файла |
|
{ |
|
|
return NULL; |
// Если нет вернем NULL |
|
} |
|
|
File=fopen(Filename,"r"); |
// Проверка существует ли файл |
|
if (File) |
// Файл существует? |
|
{ |
|
|
fclose(File); |
// Закрыть файл |
|
return auxDIBImageLoad(Filename); // Загрузка картинки и |
||
возврат на нее указателя |
|
|
} |
|
|
return NULL; |
// Если загрузка не удалась вернем NULL |
|
} |
|
|
Status используется для того, чтобы отследить, действительно ли |
||
текстура была загружена и создана: |
||
int LoadGLTextures() |
// Загрузка картинки и конвертирование в |
|
текстуру |
|
|
{ |
|
|
int Status=FALSE; |
// Индикатор состояния |
|
AUX_RGBImageRec *TextureImage[1]; // Создать место для |
||
текстуры |
|
|
memset(TextureImage,0,sizeof(void *)*1); // Установить указатель в |
||
NULL |
|
|
Текстура загружается кодом, который будет загружать картинку |
||
частицы и конвертировать ее в текстуру с линейным фильтром: |
||
if (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // Загрузка |
||
текстуры частицы |
|
|
{ |
|
|
Status=TRUE; // Задать статус в TRUE |
||
glGenTextures(1, &texture[0]); |
// Создать одну текстуру |
glBindTexture(GL_TEXTURE_2D, texture[0]);
155
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_L INEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LI NEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
} |
|
if (TextureImage[0]) |
// Если текстура существует |
{ |
|
if (TextureImage[0]->data) |
// Если изображение текстуры |
существует |
|
{
free(TextureImage[0]->data); // Освобождение памяти изображения текстуры
}
free(TextureImage[0]);// Освобождение памяти под структуру
}
return Status; // Возвращает статус
}
Можно увеличить область просмотра, например: вместо 100.0f, можно рассматривать частицы на 200.0f единиц в глубине экрана:
// Изменение размеров и инициализация окна GL GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{
if (height==0) // Предотвращение деления на ноль, если окно слишком мало
{
height=1; // Сделать высоту равной единице
}
//Сброс текущей области вывода и перспективных преобразований glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION); // Выбор матрицы проекций glLoadIdentity(); // Сброс матрицы проекции
//Вычисление соотношения геометрических размеров для окна gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,200.0f);
//(Модифицировали))
glMatrixMode(GL_MODELVIEW); // Выбор матрицы просмотра модели glLoadIdentity(); // Сброс матрицы просмотра модели
}
Далее для загрузки текстуры и включения смешивания для частиц добавим код:
156
int InitGL(GLvoid) // Все начальные настройки OpenGL здесь
{
if (!LoadGLTextures())// Переход на процедуру загрузки текстуры
{
return FALSE; // Если текстура не загружена возвращает FALSE
}
Делее решим проблемы разрешения плавного затенения, очищения фона черным цветом, запрещения теста глубины, разрешения смешивания и наложения текстуры (После разрешения наложения текстуры выбирается текстура частицы):
glShadeModel(GL_SMOOTH); |
// Разрешить плавное затенение |
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
// Черный фон |
glClearDepth(1.0f); |
// Установка буфера глубины |
glDisable(GL_DEPTH_TEST); |
// Запрещение теста глубины |
glEnable(GL_BLEND); |
// Разрешает смешивание |
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Тип смешивания
// Улучшенные вычисления перспективы glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
glHint(GL_POINT_SMOOTH_HINT,GL_NICEST); |
// Улучшенные |
|||
точечное смешение |
|
|
|
|
glEnable(GL_TEXTURE_2D); |
// Разрешение наложения |
|||
текстуры |
|
|
|
|
glBindTexture(GL_TEXTURE_2D,texture[0]); |
// Выбор текстуры |
|||
В коде ниже инициализируется каждая из частиц: |
||||
for (loop=0;loop<MAX_PARTICLES;loop++)// Инициализация всех |
||||
частиц |
|
|
|
|
{ |
|
|
|
|
particle[loop].active=true; |
// Сделать все частицы активными |
|||
particle[loop].life=1.0f; |
// Сделать все частицы с полной жизнью |
//Случайная скорость угасания particle[loop].fade=float(rand()%100)/1000.0f+0.003f;
Теперь, когда частица активна, задается ее цвет: particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0];
//Выбор красного цвета радуги particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1];
//Выбор зеленного цвета радуги particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2];
//Выбор синего цвета радуги
Далее задается направление, в котором каждая частица двигается, наряду со скоростью:
particle[loop].xi=float((rand()%50)-26.0f)*10.0f;
157
//Случайная скорость по оси X particle[loop].yi=float((rand()%50)-25.0f)*10.0f;
//Случайная скорость по оси Y particle[loop].zi=float((rand()%50)-25.0f)*10.0f;
//Случайная скорость по оси Z
Далее установим величину гравитации, которая воздействует на каждую частицу:
particle[loop].xg=0.0f; // Задает горизонтальное притяжение в ноль particle[loop].yg=-0.8f; // Задает вертикальное притяжение вниз particle[loop].zg=0.0f; // Задает притяжение по оси Z в ноль
}
return TRUE; // Инициализация завершена OK
}
Используя команду glVertex3f(), частицы позиционируются – вместо использования их перемещения, так как при таком способе вывода частиц не изменяется матрица просмотра модели при выводе частиц:
int DrawGLScene(GLvoid) // Прорисовка
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Очистка экрана и буфера глубины
glLoadIdentity();// Сброс матрицы просмотра модели Начинается вывод с цикла, который обновит каждую из частиц: for (loop=0;loop<MAX_PARTICLES;loop++) // Цикл по всем
частицам
{
Далее происходит проверка активности частицы: if (particle[loop].active) // Если частицы активны
{
Следующие три переменные x, y и z – временные переменные, которые используются, чтобы запомнить позицию частицы по x, y и z:
float x=particle[loop].x; |
// Захватим позицию X частицы |
|
float y=particle[loop].y; |
// Захватим позицию Н нашей частицы |
|
float z=particle[loop].z+zoom; |
// Позиция частицы по Z + Zoom |
Закрасим частицы: particle[loop].r – красная яркость частицы, particle[loop].g – зеленая яркость, и particle[loop].b – синяя яркость:
// Вывод частицы, используя наши RGB значения, угасание частицы согласно её жизни glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop ].life);
158
Вместо использования текстурированного четырехугольника, используется текстурированная полоска из треугольников, чтобы ускорить работу программы:
glBegin(GL_TRIANGLE_STRIP); // Построение четырехугольника из треугольной полоски Надо помнить, что число треугольников, которые видно на экране,
будет равно числу вершин минус два:
glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z);// Верхняя правая glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // Верхняя левая glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // Нижняя правая glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z); // Нижняя левая glEnd(); // Завершение построения полоски треугольников Перемещение частицы.
//Передвижение по оси X на скорость по X particle[loop].x+=particle[loop].xi/(slowdown*1000);
//Передвижение по оси Y на скорость по Y particle[loop].y+=particle[loop].yi/(slowdown*1000);
//Передвижение по оси Z на скорость по Z particle[loop].z+=particle[loop].zi/(slowdown*1000);
В первой строке ниже добавляется сопротивление (xg) к скорости
перемещения (xi): particle[loop].xi+=particle[loop].xg;
//Притяжение по X для этой записи particle[loop].yi+=particle[loop].yg;
//Притяжение по Y для этой записи particle[loop].zi+=particle[loop].zg;
//Притяжение по Z для этой записи
Сопротивление применяется к скорости перемещения по y и z, так же, как и по x.
В следующей строке забирается ''часть жизни'' от частицы, а именно: каждая частица имеет свое значение угасания, поэтому они будут гореть с различными скоростями:
particle[loop].life-=particle[loop].fade; // Уменьшить жизнь частицы на ''угасание''
Далее проверим – ''жива '' ли частица, после изменения ее жизни: if (particle[loop].life<0.0f) // Если частица погасла
{
Если частица сгорела, то ей необходимо ''дать новую жизнь '' и сбросить позицию частицы в центр экрана.
particle[loop].life=1.0f; // Дать новую жизнь
// Случайное значение угасания particle[loop].fade=float(rand()%100)/1000.0f+0.003f;
159
particle[loop].x=0.0f; |
// На центр оси X |
particle[loop].y=0.0f; |
// На центр оси Y |
particle[loop].z=0.0f; |
// На центр оси Z |
После того, как частица была сброшена в центр экрана, ей задается
новая |
скорость |
перемещения/направления: |
||
particle[loop].xi=xspeed+float((rand()%60)-32.0f); |
//Скорость |
и |
||
направление по оси X |
|
|
|
|
particle[loop].yi=yspeed+float((rand()%60)-30.0f); |
//Скорость и |
|
||
направление по оси Y |
|
|
|
|
particle[loop].zi=float((rand()%60)-30.0f); |
//Скорость и |
|
||
направление по оси Z |
|
|
|
|
Далее необходимо назначить новый цвет частице: |
|
|||
particle[loop].r=colors[col][0]; |
// Выбор красного из таблицы |
|
||
цветов |
|
|
|
|
particle[loop].g=colors[col][1]; |
// Выбор зеленого из таблицы |
|
||
цветов |
|
|
|
|
particle[loop].b=colors[col][2]; |
// Выбор синего из таблицы |
|
||
цветов |
|
|
|
|
} |
|
|
|
|
Строка ниже контролирует, насколько гравитация будет притягивать вверх:
//Если клавиша 8 на цифровой клавиатуре нажата и гравитация меньше чем 1.5, тогда увеличение притяжения вверх
if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;
Далее строка создает точно противоположный эффект. При помощи нажатия '2' на цифровой клавиатуре уменьшается yg, создается более сильное притяжение вниз:
//Если клавиша 2 на цифровой клавиатуре нажата и гравитация больше чем -1.5, тогда увеличение притяжения вниз
if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;
Притяжение вправо и влево осуществляется с клавиш:
//Если клавиша 6 на цифровой клавиатуре нажата и гравитация
меньше чем 1.5, тогда увеличение притяжения вправо
if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;
// Если клавиша 4 на цифровой клавиатуре нажата и гравитация больше чем -1.5, тогда увеличение притяжения влево
if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;
Далее попробуем реализовать эффект взрыва:
160
if (keys[VK_TAB]) // Клавиша табуляции вызывает взрыв
{ |
|
particle[loop].x=0.0f; |
// Центр по оси X |
particle[loop].y=0.0f; |
// Центр по оси Y |
particle[loop].z=0.0f; |
// Центр по оси Z |
particle[loop].xi=float((rand()%50)-26.0f)*10.0f; |
// Случайная |
|
скорость по оси X |
|
|
particle[loop].yi=float((rand()%50)-25.0f)*10.0f; |
// Случайная |
|
скорость по оси Y |
|
|
particle[loop].zi=float((rand()%50)-25.0f)*10.0f; |
// Случайная |
|
скорость по оси Z |
|
|
} |
|
|
} |
|
|
} |
|
|
return TRUE; |
// Все правильно |
|
} |
|
|
При помощи нажатия клавиши табуляции все частицы будут отброшены назад к центру экрана, при этом скорость перемещения частиц будет в 10, раз больше, создавая большой взрыв частиц. После того, как частицы взрыва постепенно исчезнут, появиться предыдущий
столб частиц: |
|
if (keys[VK_TAB]) |
// Клавиша табуляции вызывает взрыв |
{ |
|
particle[loop].x=0.0f; |
// Центр по оси X |
particle[loop].y=0.0f; |
// Центр по оси Y |
particle[loop].z=0.0f; |
// Центр по оси Z |
particle[loop].xi=float((rand()%50)-26.0f)*10.0f; |
// Случайная |
||
скорость по оси |
|
|
|
particle[loop].yi=float((rand()%50)-25.0f)*10.0f; |
// Случайная |
||
скорость по оси Y |
|
|
|
particle[loop].zi=float((rand()%50)-25.0f)*10.0f; |
// Случайная |
||
скорость по оси Z |
|
|
|
} |
|
|
|
} |
|
|
|
return TRUE; |
// Все правильно |
|
|
} |
|
|
|
И далее: код в KillGLWindow(), CreateGLWindow() и WndProc() не |
|||
изменился, изменения следует внести в WinMain(): |
|
||
int WINAPI WinMain( |
|
|
|
HINSTANCE hInstance, |
// Экземпляр |
|
|
HINSTANCE hPrevInstance, // Предыдущий экземпляр |
|||
LPSTR lpCmdLine, |
|
// Параметры командной строки |
161
int nCmdShow) |
// Показать состояние окна |
{ |
|
MSG msg; |
// Структура сообщения окна |
BOOL done=FALSE; |
// Булевская переменная выхода |
из цикла |
|
// Запросим у пользователя какой режим отображения он
предпочитает |
|
if (MessageBox(NULL) |
// Полноэкранный режим |
"Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO) |
|
{ |
|
fullscreen=FALSE; |
// Оконный режим |
} |
|
// Создадим окно OpenGL
if (!CreateGLWindow (640,480,16,fullscreen))
{ |
|
return 0; |
// Если окно не было создано |
} |
|
if (fullscreen) // Полноэкранный режим (Добавлена строка)
{
slowdown=1.0f; // Скорость частиц (для 3dfx) (Добавлена строка)
}
while (!done)// Цикл, который продолжается пока done=FALSE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Есть ожидаемое сообщение?
{
if (msg.message==WM_QUIT) // Есть сообщение о выходе?
{ |
|
done=TRUE; |
// Если так done=TRUE |
} |
|
else // Если нет, продолжает работать с сообщениями окна
{ |
|
TranslateMessage(&msg); |
// Переводит сообщение |
DispatchMessage(&msg); |
// Отсылает сообщение |
} |
|
} |
|
else |
// Если сообщений нет |
{ |
|
//Рисует сцену. Ожидает нажатия кнопки ESC и сообщения о выходе от DrawGLScene()
//Если активно? Было получено сообщение о выходе?
if ((active && !DrawGLScene()) || keys[VK_ESCAPE])
162