一、環境配置與運行
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() {
if (stop) return;
double FPS = CalFrequency();
printf("FPS = %f\n", FPS);
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
有任何問題以及評論或建議歡迎留言!