【OpenGL】分形Julia集 OpenGL C++实现(五)
一、环境配置与运行
opengl配置
作者用的是VS2019,首先是新建项目
然后是下载glut包,它帮助你使用opengl
复制文末的代码,Ctrl+F5 运行。这个时候应该会比较卡。
修改到release,会优化代码,加快速度。
如果你的电脑是单核CPU,可以通过减少N的值加快速度(但是会降低部分精度)。
OpenMP配置
这样就可以比较愉快的运行了。
操作指南:
- 鼠标右键 —— 暂停/继续
- 方向键 —— 移动
- PGUP —— 放大
- PGDN —— 缩小
- 鼠标左键 —— 移动三维空间
二、代码讲解
不了解代码的就可以就到这里了。
代码的核心就是使用特定方法得到每一个像素点应该的RGB值(先不管如何通过像素点的坐标如何对应转化到RGB值的),然后通过OpenGL的顶点数组功能将每一个点的位置和对应的颜色传入显卡进行渲染得到结果。
这里的特定方法就是Julia集,这个的相关解释可以百度。简单的说就是:
- 我们先假设要绘制的平面大小为1000*1000。
- 然后每个点像素坐标为(x,y),通过坐标(x,y)得到初始复数为X0。比如坐标(100,50)的点的X0为(0.1,0.05)
- 我们先初始化一个复数C。
- 通过一定次数的循环计算Xn = Xn-12+C
- 如果计算得到的Xn大小大于一个给定的值M,则表示当前点发散,并记录n(也即循环次数),通过n来分配一个RGB值。
- 而如果在循环给定的值N次后,还是没有大于给定的值M,那么表示当前点收敛,分配一个RGB值。
最终就得到了一张分形图了。
然后就是如果让分形图动起来。这里的关系就是:
- 不同的复数C值会有不同的分形图,而相近的C值分形图也相近。
- 不同的像素坐标映射比例(即比如上面的例子`坐标(100,50)的点的X0为(0.1,0.05)* 0.1),会决定分形图的大小(缩放)。
- 不同的像素坐标映射加上偏移量(即比如上面的例子坐标(100,50)的点的X0为(0.1,0.05)+(0.1,0.1)),会决定分形图的偏移(上下左右移动)。
上面3条的合理运用就可以让分形图动起来!
OpenGL参考资料: http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html
然后是顶点数组:
减少函数的调用次数,是提高运行效率的方法之一。于是我们想到了显示列表。把绘制立方体的代码装到一个显示列表中,以后只要调用这个显示列表即可。
这样看起来很不错,但是显示列表有一个缺点,那就是一旦建立后不可再改。如果我们要绘制的不是立方体,而是一个能够走动的人物,因为人物走动时,四肢的位置不断变化,几乎没有办法把所有的内容装到一个显示列表中。必须每种动作都使用单独的显示列表,这样会导致大量的显示列表管理困难。
顶点数组是解决这个问题的一个方法。使用顶点数组的时候,也是像前面的方法一样,用一个数组保存所有的顶点,用一个数组保存顶点的序号。但最后绘制的时候,不是编写循环语句逐个的指定顶点了,而是通知OpenGL,“保存顶点的数组”和“保存顶点序号的数组”所在的位置,由OpenGL自动的找到顶点,并进行绘制。
这里使用顶点数组管理个人认为是很好的方法。核心就是:
struct col {
GLushort r, g, b;
} colKTable[N];
struct pos {
double x, y, z;
} posArr[HEIGHT * WIDTH];
col colArr[HEIGHT * WIDTH];
int index_list[HEIGHT * WIDTH];
- 将要绘制的点的索引保存在数组
index_list
中。index_list[i * WIDTH + j] = i * WIDTH + j;
,就是直接的0,1,2… - 将要绘制的点的位置保存在数组
posArr
中
posArr[i * WIDTH + j].x = -0.5 + (double)i / (HEIGHT); // (-1,1)
posArr[i * WIDTH + j].y = -0.5 + (double)j / (WIDTH); // (1,-1)
posArr[i * WIDTH + j].z = 0.1;
- 将上面说的n(也即循环次数)到颜色的映射保存到
colKTable
中 - 将要绘制的点的颜色保存在数组
colArr
中
有了上面的这些准备工作,剩下的就是逻辑实现了。
三、最终代码
#include <GL/glut.h>
#include <cmath>
#include <cstdio>
#include <ctime>
static double myratio; // angle绕y轴的旋转角,ratio窗口高宽比
static double x = 0.0f, y = 0.0f, z = 1.3f; //相机位置
static double lx = 0.0f, ly = 0.0f, lz = -1.0f; //视线方向,初始设为沿着Z轴负方向
const int WIDTH = 1000;
const int HEIGHT = 1000;
bool mouseDown = false;
double xrot = 0.0f, yrot = 0.0f;
double xdiff = 0.0f, ydiff = 0.0f;
/**
* 定义观察方式
*/
void changeSize(int w, int h) {
//除以0的情况
if (h == 0) h = 1;
myratio = 1.0f * w / h;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//设置视口为整个窗口大小
glViewport(0, 0, w, h);
//设置可视空间
gluPerspective(45, myratio, 1, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
/**
* 视野漫游函数
*/
void orientMe(double directionx, double directiony) {
x += directionx * 0.1;
y += directiony * 0.1;
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
/**
* 视野漫游函数
*/
void moveMeFlat(int direction) {
z += direction * (lz) * 0.1;
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
// 鼠标右键stop
bool stop = false;
/**
* 鼠标事件
*/
void mouse(int button, int state, int x, int y) {
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) {
stop = !stop;
}
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
mouseDown = true;
xdiff = x - yrot;
ydiff = -y + xrot;
}
else
mouseDown = false;
}
/**
* 鼠标移动事件
*/
void mouseMotion(int x, int y) {
if (mouseDown) {
yrot = x - xdiff;
xrot = y + ydiff;
glutPostRedisplay();
}
}
/**
* 加入按键控制
*/
double rateZoom = 1.5f;
const int N = 110, M = 1000;
struct col {
GLushort r, g, b;
} colKTable[N];
struct pos {
double x, y, z;
} posArr[HEIGHT * WIDTH];
col colArr[HEIGHT * WIDTH];
int index_list[HEIGHT * WIDTH];
struct complex {
double i, j;
inline complex mul(complex& b) {
double tempI = i * b.i - j * b.j;
double tempJ = i * b.j + j * b.i;
i = tempI, j = tempJ;
return *this;
}
inline complex add(complex& b) {
i += b.i, j += b.j;
return *this;
}
};
complex alpha{ 0, 0 };
void myDisplay() {
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
// 实现鼠标旋转的核心
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_DOUBLE, sizeof(pos), posArr);
glColorPointer(3, GL_UNSIGNED_SHORT, sizeof(col), colArr);
glDrawElements(GL_POINTS, HEIGHT * WIDTH, GL_UNSIGNED_INT, index_list);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glFlush();
glutSwapBuffers();
}
double CalFrequency() {
static int count;
static double save;
static clock_t last, current;
double timegap;
++count;
if (count <= 50)
return save;
count = 0;
last = current;
current = clock();
timegap = (current - last) / (double)CLK_TCK;
save = 50.0 / timegap;
return save;
}
bool isGo = true;
void myIdle() {
double FPS = CalFrequency();
printf("FPS = %f\n", FPS);
if (!stop) {
double rate = 0.03;
static double ii = -1, jj = -1;
if (isGo) {
static bool flag = true;
if (flag) ii += rate;
else ii -= rate;
if (ii > 1) {
jj += rate;
flag = false;
}
if (ii < -1) {
jj += rate;
flag = true;
}
if (jj == 0) {
ii += rate;
}
}
complex C{ ii, jj };
double d = rateZoom * 2 / HEIGHT;
#pragma omp parallel for
for (int i = 0; i < HEIGHT; i++)
for (int j = 0; j < WIDTH; j++) {
auto& item = colArr[i * WIDTH + j];
item.r = item.g = item.b = 0;
complex X{ -rateZoom + i * d, -rateZoom + j * d }; // (-1.5,1.5)
X = X.add(alpha);
for (auto& k : colKTable) {
X = X.mul(X).add(C);
if (X.i * X.i + X.j * X.j > M) {
item.r = k.r;
item.g = k.g;
item.b = k.b;
break;
}
}
}
}
myDisplay();
}
void init() {
for (int k = 0; k < N; k++) {
int tempK = k * k;
tempK = tempK * tempK;
colKTable[k].r = tempK + tempK;
colKTable[k].g = exp(k);
colKTable[k].b = tempK * k;
}
for (int i = 0; i < HEIGHT; i++)
for (int j = 0; j < WIDTH; j++) {
posArr[i * WIDTH + j].x = -0.5 + (double)i / (HEIGHT); // (-1,1)
posArr[i * WIDTH + j].y = -0.5 + (double)j / (WIDTH); // (1,-1)
posArr[i * WIDTH + j].z = 0.1;
index_list[i * WIDTH + j] = i * WIDTH + j;
}
glClearColor(0.93f, 0.93f, 0.93f, 0.0f);
}
void processSpecialKeys(int key, int x, int y) {
bool temp;
temp = stop;
stop = false;
isGo = false;
// 越近加的越慢
double addRate = 0.1 * std::abs(std::exp(rateZoom) - 1);
switch (key) {
case GLUT_KEY_UP:
// orientMe(0, 1);
alpha.j += addRate;
break;
case GLUT_KEY_DOWN:
// orientMe(0, -1);
alpha.j -= addRate;
break;
case GLUT_KEY_LEFT:
// orientMe(-1, 0);
alpha.i -= addRate;
break;
case GLUT_KEY_RIGHT:
// orientMe(1, 0);
alpha.i += addRate;
break;
case GLUT_KEY_PAGE_DOWN:
// moveMeFlat(-1);
rateZoom = (rateZoom + addRate) > 2 ? 2 : rateZoom + addRate;
break;
case GLUT_KEY_PAGE_UP:
// moveMeFlat(1);
rateZoom -= addRate;
break;
case GLUT_KEY_END:
moveMeFlat(-1);
break;
case GLUT_KEY_HOME:
moveMeFlat(1);
break;
default:
break;
}
myIdle();
stop = temp;
isGo = true;
}
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE);
glutInitWindowPosition(300, 0);
glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow("Demo"); // 改了窗口标题
glutDisplayFunc(myDisplay);
glutIdleFunc(myIdle); // 表示在CPU空闲的时间调用某一函数
glutSpecialFunc(processSpecialKeys); // 按键
glutReshapeFunc(changeSize);
glutMouseFunc(mouse);
glutMotionFunc(mouseMotion);
init();
glutMainLoop();
return 0;
}
四、效果演示
https://www.bilibili.com/video/BV1vz4y1Q7mN?p=1
有任何问题以及评论或建议欢迎留言!