自己是这方面的小白,希望大神可以对有问题以及可以优化的地方提出来。也欢迎指出不足和吐槽。希望帮到小白。
opengl 太阳、地球、月亮 酷炫实例(一):
https://blog.csdn.net/qq_40515692/article/details/100778870
opengl 太阳、地球、月亮 酷炫实例(二):
https://blog.csdn.net/qq_40515692/article/details/100802534
自己参考得比较多的网站是这个:
http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html
以及:
https://blog.csdn.net/xie_zi/article/details/1911406
所有代码都可以去github免费下载:
https://github.com/Iamttp/OpenGLTest
好先上这一节的效果图。这一节是上一节的继续,在上一节的代码基础上添加了粒子系统。
是不是有点震撼呢?我也可以吗?答案是肯定的。而且下面介绍的这个粒子系统通用性较好,很容易修改,弄出属于自己的特效。
现在直接给出主函数代码,可以看到和上一节的代码几乎没有区别,就只是引入了头文件,定义了Waterfall w;,然后在flag为1时调用了w.Update();,在myDisplay里面持续渲染(调用了函数w.Render();)
#include <gl/glut.h>
#include <stdio.h>
#include <time.h>
#include <cmath>
#include "Waterfall.h"
static float angle = 0.0, ratio; // angle绕y轴的旋转角,ratio窗口高宽比
static float x = 0.0f, y = 0.0f, z = 3.0f; //相机位置
static float lx = 0.0f, ly = 0.0f, lz = -1.0f; //视线方向,初始设为沿着Z轴负方向
static int my_angle = 0; // 表示旋转的角度
Waterfall w;
int flag = 0;
/**
* 定义观察方式
*/
void changeSize(int w, int h) {
//除以0的情况
if (h == 0) h = 1;
ratio = 1.0f * w / h;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//设置视口为整个窗口大小
glViewport(0, 0, w, h);
//设置可视空间
gluPerspective(45, ratio, 1, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
/**
* 视野漫游函数
*/
void orientMe(float ang) {
lx = sin(ang);
lz = -cos(ang);
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
void moveMeFlat(int direction) {
x = x + direction * (lx) * 0.1;
z = z + direction * (lz) * 0.1;
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
/**
* 加入按键控制
*/
void processSpecialKeys(int key, int x, int y) {
switch (key) {
case GLUT_KEY_F1:
flag = 1;
break;
case GLUT_KEY_LEFT:
angle -= 0.01f;
orientMe(angle);
break;
case GLUT_KEY_RIGHT:
angle += 0.01f;
orientMe(angle);
break;
case GLUT_KEY_UP:
moveMeFlat(1);
break;
case GLUT_KEY_DOWN:
moveMeFlat(-1);
break;
}
}
void myDisplay(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 太阳
glPushMatrix();
glColor3f(1.0, 1.0, 0.0);
glutSolidSphere(0.15, 200, 200);
glPopMatrix();
// 地球
glPushMatrix();
glColor3f(0.0, 0.0, 1.0);
glRotated(my_angle, 1.0, 1.0, 1.0); //公转
glTranslatef(0.5, 0.5, -0.5); //平移
glutSolidSphere(0.1, 200, 200);
glPopMatrix();
// 月亮
glPushMatrix();
glColor3f(1.0, 1.0, 1.0);
glRotated(my_angle, 1.0, 1.0, 1.0); //然后移动到地球旁边旋转
glTranslatef(0.5, 0.5, -0.5); //平移
glRotated(my_angle, 1.0, 1.0, 1.0); //先假设原点为地球旋转
glTranslatef(-0.15, -0.15, 0.15); //平移
glutSolidSphere(0.05, 200, 200); //绘制月亮
glPopMatrix();
if (flag == 1) {
flag = 0;
w.Update();
}
glPushMatrix();
w.Render();
glPopMatrix();
glutSwapBuffers();
}
/**
* 计时增加角度
*/
void myIdle(void) {
static int mm = 0;
mm++;
if (mm % 300000 == 0) {
++my_angle;
if (my_angle >= 360) my_angle = 0;
myDisplay();
}
}
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(1000, 1000);
glutCreateWindow("太阳,地球和月亮"); // 改了窗口标题
glutDisplayFunc(&myDisplay);
glutIdleFunc(&myIdle); // 表示在CPU空闲的时间调用某一函数
glutSpecialFunc(&processSpecialKeys); // 按键
glutReshapeFunc(&changeSize);
// 在OpenGL中,默认是没有开启深度检测的,后绘制的物体覆盖先绘制的物体。
// GL_DEPTH_TEST 用来开启更新深度缓冲区的功能
glEnable(GL_DEPTH_TEST);
glutMainLoop();
return 0;
}
那么这个Waterfall怎么实现的呢?
第一步,我们需要定义一个结构体,这个结构体描述了每个粒子的属性,比如:这个粒子的生命(还可以存在多久)、这个粒子的RGB值,这个粒子的位置,这个粒子的速度等等。
typedef struct {
float life; // last time
float r, g, b; // color
float x, y, z; // the position
float xi, yi, zi; // what direction to move
} WaterfallParticle;
第二步,考虑类提供给外界的接口,实现自然是定义时调用的构造函数了,定义一些我们全局(相对于上面提到的个体的参数)可能要用到的参数,比如有多少粒子、每个粒子存在时间等等。
然后是Update函数,它主要是初始化创建一些粒子(一般就创建mParticleNumber个)。
然后是Render函数负责渲染粒子,RenderParticle函数负责渲染单个粒子。
那每个粒子存在哪里呢?我们这里就使用C++ STL里面的list来作为存储粒子的容器。那为什么不用数组呢?因为粒子数目没有固定。那为什么不用vector?这个比list常用多了,但是因为我们会有大量的删除插入操作,所以使用list,具体原因如下:
vector与数组类似,拥有一段连续的内存空间,并且起始地址不变。便于随机访问,时间复杂度为O(1),但因为内存空间是连续的,所以在进入插入和删除操作时,会造成内存块的拷贝,时间复杂度为O(n)。
list底层是由双向链表实现的,因此内存空间不是连续的。根据链表的实现原理,List查询效率较低,时间复杂度为O(n),但插入和删除效率较高。只需要在插入的地方更改指针的指向即可,不用移动数据。
#define DEFAULT_PARTICLE_NUMBER 5000
#define DEFAULT_PARTICLE_LIFESPAN 30
class Waterfall {
public:
Waterfall(GLuint particleNumber = DEFAULT_PARTICLE_NUMBER,
GLuint particleLifespan = DEFAULT_PARTICLE_LIFESPAN)
: mParticleNumber(particleNumber),
mParticleLifespan(particleLifespan) {}
void Update();
//渲染,普通的渲染函数就是渲染每一粒存在的粒子
virtual void Render();
int getSize() { return ls.size(); }
private:
GLuint mParticleNumber;
GLuint mParticleLifespan;
std::list<WaterfallParticle> ls;
void RenderParticle(const WaterfallParticle& p);
};
第三步,就是实现这些函数了,我们首先实现Update吧,这里初始化mParticleNumber个粒子,颜色、速度、生命值很简单直接赋值为随机数就可以了,初始位置怎么赋值呢?三维中的圆可以自行百度它的方程,我这里已经计算好了,效果如文章开头(数学渣表示不敢确定正不正确 / w \)。当然也可以直接二维的圆,使用注释的代码。
void Waterfall::Update() {
//新粒子的创建
WaterfallParticle particle;
int newParticleNumber = mParticleNumber;
for (int i = 0; i < newParticleNumber; ++i) {
auto rate = randFloat(-0, 1);
double sqrt2 = sqrt(2);
double sqrt6 = sqrt(6);
particle.x = cos(2 * PI * rate) / sqrt2 + sin(2 * PI * rate) / sqrt6;
particle.y = -1.0 * cos(2 * PI * rate) / sqrt2 + sin(2 * PI * rate) / sqrt6;
particle.z = -2.0 * sin(2 * PI * rate) / sqrt6;
//
/*
particle.x = cos(2 * PI * rate) * 1;
particle.y = sin(2 * PI * rate) * 1;
particle.z = 0;
*/
//
particle.r = randFloat(0, 1);
particle.g = randFloat(0, 1);
particle.b = randFloat(0, 1);
particle.xi = randFloat(-0.1f, 0.1f);
particle.yi = randFloat(-0.1f, 0.1f);
particle.zi = randFloat(-0.1f, 0.1f);
particle.life = mParticleLifespan;
ls.push_back(particle);
}
}
然后是Render,这个函数就是更新位置和及时清除life为0的粒子。然后就是对还活着的粒子进行单个渲染,用了一些C++11的写法,可以自行百度哟。
void Waterfall::Render() {
auto p = ls.begin();
while (p != ls.end()) {
p->x += p->xi * 0.1;
p->y += p->yi * 0.1;
p->z += p->zi * 0.1;
p->life--;
if (p->life == 0) {
ls.erase(p++);
}
else {
p++;
}
}
glPointSize(2);//设置点的大小为2
glBegin(GL_POINTS);
for (auto& item : ls) RenderParticle(item);
glEnd();
}
最后就是单个粒子的渲染,这里就非常简单了。
void Waterfall::RenderParticle(const WaterfallParticle& p) {
glColor4f(p.r, p.g, p.b, p.life);
glVertex3f(p.x, p.y, p.z);
}
最后附上Waterfall.h的所有代码,主函数代码(运行后按F1出现粒子光环)已经在文章开头给出了。
#ifndef WATERFALL_H
#define WATERFALL_H
#include <gl/glut.h>
#include <list>
const double PI = acos(-1.0);
typedef struct {
float life; // last time
float r, g, b; // color
float x, y, z; // the position
float xi, yi, zi; // what direction to move
} WaterfallParticle;
#define DEFAULT_PARTICLE_NUMBER 5000
#define DEFAULT_PARTICLE_LIFESPAN 30
float randFloat01() { return 1.0 * rand() / RAND_MAX; }
float randFloat(float from, float to) {
return from + (to - from) * randFloat01();
}
int randInt(int from, int to) { return from + rand() % (to - from); }
class Waterfall {
public:
Waterfall(GLuint particleNumber = DEFAULT_PARTICLE_NUMBER,
GLuint particleLifespan = DEFAULT_PARTICLE_LIFESPAN)
: mParticleNumber(particleNumber),
mParticleLifespan(particleLifespan) {}
void Update();
//渲染,普通的渲染函数就是渲染每一粒存在的粒子
virtual void Render();
int getSize() { return ls.size(); }
private:
GLuint mParticleNumber;
GLuint mParticleLifespan;
std::list<WaterfallParticle> ls;
void RenderParticle(const WaterfallParticle& p);
};
//粒子的状态更新,可以尽情发挥自己的创意编写代码
void Waterfall::Render() {
auto p = ls.begin();
while (p != ls.end()) {
p->x += p->xi * 0.1;
p->y += p->yi * 0.1;
p->z += p->zi * 0.1;
p->life--;
if (p->life == 0) {
ls.erase(p++);
}
else {
p++;
}
}
glPointSize(2);//设置点的大小为10
glBegin(GL_POINTS);
for (auto& item : ls) RenderParticle(item);
glEnd();
}
void Waterfall::Update() {
//新粒子的创建
WaterfallParticle particle;
int newParticleNumber = mParticleNumber;
for (int i = 0; i < newParticleNumber; ++i) {
auto rate = randFloat(-0, 1);
double sqrt2 = sqrt(2);
double sqrt6 = sqrt(6);
particle.x = cos(2 * PI * rate) / sqrt2 + sin(2 * PI * rate) / sqrt6;
particle.y = -1.0 * cos(2 * PI * rate) / sqrt2 + sin(2 * PI * rate) / sqrt6;
particle.z = -2.0 * sin(2 * PI * rate) / sqrt6;
/*
particle.x = cos(2 * PI * rate) * 1;
particle.y = sin(2 * PI * rate) * 1;
particle.z = 0;
*/
particle.r = randFloat(0, 1);
particle.g = randFloat(0, 1);
particle.b = randFloat(0, 1);
particle.xi = randFloat(-0.1f, 0.1f);
particle.yi = randFloat(-0.1f, 0.1f);
particle.zi = randFloat(-0.1f, 0.1f);
particle.life = mParticleLifespan;
ls.push_back(particle);
}
}
void Waterfall::RenderParticle(const WaterfallParticle& p) {
glColor4f(p.r, p.g, p.b, p.life);
glVertex3f(p.x, p.y, p.z);
}
#endif
OpenGL专栏: https://blog.csdn.net/qq_40515692/article/details/103938499