reference : 《随机分形地形生成及其浏览》
* 本项目属于游戏程序设计5000行计划
算法实现起来很简单,分形也很有意思,我们假设我们越靠近一个物体,它的细节就会显示出来,那么对于具有分形特性的物体而言,我们看到的细节和整体并没有什么区别。正如我们不断zoom in观察这个 Koch 雪花,每个突出的角又能长出三个角,如此递归,永无止境……
地形也有这样的特性,对于两张不同尺度上的地形俯拍图,我们看到的细节也是类似的,也就是说,对于下图中的蓝色区域的地面,我们截取两个不同大小的橙色框所得到的图片,在人眼看来是差不多的。
利用这样的特性,我们就可以用分形的思想来生成随机地形。
具体的算法在开头给出的论文已经说的非常明白了,虽然这个论文并不是该算法(diamand - square algorithm)的最早版本,但中文看起来比英文要好理解一些。在这里只给出一些概述。
类似于二分法,取线段中点,使y值为线段两段的平均值加上一个随机数,得到两条线段,再分别取两个线段的中点,重复前面的过程。
由一维扩展而成,利用已知的a,b,c,d高度值,计算e,h,f,g, 设r为随机数(r随着迭代的次数,取值范围不断缩小)其中
h(e) = (h(a) + h(b)) / 2 + r(e)
h(f) = (h(b) + h(c)) / 2 + r(f)
h(g) = (h(c) + h(d)) / 2 + r(g)
h(h) = (h(a) + h(d)) / 2 + r(h)
然后利用e,f,g,h,计算m
h(m) = (h(e) + h(f) + h(g) + h(h))/4 + r(m)
如此得到一次递归流程,然后再对四个正方形进行同样的操作……
同样适用于二维,但是对上述算法的改进。
上图反映的是两次递归过程,以下只描述一次递归中做的事情。
先计算h(E) = (h(A) + h(B) + h(C) + h(D)) / 4 + r(E)
再计算:
h(F) = (h(A) + h(B) + h(E) + h(E)) / 4 + r(F)
h(I) = (h(A) + h(C) + h(E) + h(E)) / 4 + r(I)
h(S) = (h(B) + h(D) + h(E) + h(E)) / 4 + r(S)
h(H) = (h(C) + h(D) + h(E) + h(E)) / 4 + r(H)
然后再对新的四个正方形做同样的操作。
fractal.h
#pragma once class fractal { private: enum { MAX = 10000 }; float roughness; int times; int height; float minHeight; float maxHeight; int n; int D; int indiceNum; float** data; float* vertex; int* indice; float step; void genIndice(); void chooseColor(float height); public: void draw(); void calculate(); fractal(int _height, int _times, float _step, int D,int seed); }; fractal.cpp #include <time.h> #include <math.h> #include <stdlib.h> #include <iostream> #include "fractal.h" #include "gl/glut.h" using namespace std; // height : 值越大,地形起伏越大 // times : 迭代次数,次数越多,tire越多 // step : 绘制时一个网格的程度 // D : 随机数衰减因子,越大随机数随迭代的取值越小 // seed : 随机数种子 fractal::fractal(int _height, int _times, float _step, int _D,int seed) { srand(seed); step = _step; times = _times; height = _height; D = _D; n = pow(2, times) + 1; indiceNum = 6 * (n - 1)*(n - 1); vertex = new float[3 * n*n]; indice = new int[indiceNum]; data = new float*[n]; for (int i = 0; i < n; i++) { data[i] = new float[n]; for (int j = 0; j < n; j++) { data[i][j] = 0; } } } // 生成顶点索引数据 void fractal::genIndice() { for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1; j++) { indice[3 * ((n - 1) * i + j)] = (n * i + j); indice[3 * ((n - 1) * i + j) + 1] = (n * i + j + 1); indice[3 * ((n - 1) * i + j) + 2] = (n * (i + 1) + j + 1); } } cout << endl; int off = 3 * (n - 1)*(n - 1); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1; j++) { indice[off + 3 * ((n - 1) * i + j)] = (n * i + j); indice[off + 3 * ((n - 1) * i + j) + 1] = (n * (i + 1) + j); indice[off + 3 * ((n - 1)* i + j) + 2] = (n * (i + 1) + j + 1); } } } // 生成[-num,num]之间的随机数 static float randnum(float num) { float max = num; float min = -num; int r; float x; r = rand(); x = (float)(r & 0x7fff) / (float)0x7fff; return (x * (max - min) + min); } // 计算顶点高度 void fractal::calculate() { int size = n - 1; int number = 1; int ratio = pow(2, D); roughness = height; //times为迭代次数 for (int t = 0; t < times; t++) { // diamand阶段 for (int i = 0; i < number; i++) { for (int j = 0; j < number; j++) { float r = randnum(.5) * roughness; int center_x = (size >> 1) + size * i; int center_y = (size >> 1) + size * j; data[center_x][center_y] = (data[size * i][size * j] + data[size*i + size][size * j] + data[size * i][size * j + size] + data[size * i + size][size * j + size]) / 4 + r; } } // square阶段 int pointNum = ((t + 1) << 1) + 1; int pointStep = (n - 1) / (pointNum - 1); for (int i = 0; i < pointNum; i++) { for (int j = 0; j < pointNum; j++) { if ((i + j) % 2 == 1){ float r = randnum(.5) * roughness; if (i == 0){ data[i*pointStep][j*pointStep] = (data[n - pointStep][j*pointStep] + data[(i + 1)*pointStep][j*pointStep] + data[i*pointStep][(j + 1)*pointStep] + data[i*pointStep][(j-1)*pointStep]) / 4 + r; } else if (j == 0){ data[i*pointStep][j*pointStep] = (data[(i-1)*pointStep][j*pointStep] + data[(i + 1)*pointStep][j*pointStep] + data[i*pointStep][(j + 1)*pointStep] + data[i*pointStep][n - pointStep]) / 4 + r; } else if (i == pointNum - 1){ data[i*pointStep][j*pointStep] = (data[pointStep][j*pointStep] + data[(i - 1)*pointStep][j*pointStep] + data[i*pointStep][(j + 1)*pointStep] + data[i*pointStep][(j - 1)*pointStep]) / 4 + r; } else if (j == pointNum - 1){ data[i*pointStep][j*pointStep] = (data[(i-1)*pointStep][j*pointStep] + data[(i + 1)*pointStep][j*pointStep] + data[i*pointStep][pointStep] + data[i*pointStep][(j - 1)*pointStep]) / 4 + r; } else { data[i*pointStep][j*pointStep] = (data[(i - 1)*pointStep][j*pointStep] + data[(i + 1)*pointStep][j*pointStep] + data[i*pointStep][(j + 1)*pointStep] + data[i*pointStep][(j - 1)*pointStep]) / 4 + r; } } } } roughness = roughness / ratio; size >>= 1; number <<= 1; } // 把data映射到vertex数据上,并删除data,同时计算出最大高度和最小高度 minHeight = 10000; maxHeight = -10000; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { vertex[3 * (i*n + j)] = i* step - n*step / 2; vertex[3 * (i*n + j) + 1] = data[i][j]; vertex[3 * (i*n + j) + 2] = j*step - n*step / 2; if (maxHeight < data[i][j]) { maxHeight = data[i][j]; } if (minHeight > data[i][j]) { minHeight = data[i][j]; } } delete[] data[i]; } delete[] data; // 生成索引 genIndice(); } // 根据高度选择颜色 void fractal::chooseColor(float height) { const GLfloat blue[] = { 1.0 * 65 / 255, 1.0 * 127 / 255, 1.0 * 219 / 255 }; const GLfloat green[] = { 1.0 * 73 / 255, 1.0 * 161 / 255, 1.0 * 101 / 255 }; const GLfloat yellow[] = { 1.0 * 172 / 255, 1.0 * 189 / 255, 1.0 * 117 / 255 }; const GLfloat brown[] = { 1.0 * 153 / 255, 1.0 * 123 / 255, 1.0 * 46 / 255 }; float interval = maxHeight - minHeight; if (height < minHeight + interval / 4){ glColor3fv(blue); } else if (height < minHeight + interval / 2){ glColor3fv(green); } else if (height < minHeight + 3 * interval / 4){ glColor3fv(yellow); } else if (height < maxHeight) { glColor3fv(brown); } } void fractal::draw() { glPushMatrix(); glBegin(GL_TRIANGLES); for (int i = 0; i < indiceNum / 3; i++) { chooseColor(vertex[3 * indice[3 * i] + 1]); glVertex3f(vertex[3 * indice[3 * i]], vertex[3 * indice[3 * i] + 1], vertex[3 * indice[3 * i] + 2]); chooseColor(vertex[3 * indice[3 * i + 1] + 1]); glVertex3f(vertex[3 * indice[3 * i + 1]], vertex[3 * indice[3 * i + 1] + 1], vertex[3 * indice[3 * i + 1] + 2]); chooseColor(vertex[3 * indice[3 * i + 2] + 1]); glVertex3f(vertex[3 * indice[3 * i + 2]], vertex[3 * indice[3 * i + 2] + 1], vertex[3 * indice[3 * i + 2] + 2]); } glColor3f(1,1,1); glEnd(); glPopMatrix(); } main.cpp #define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include<time.h> #include"fractal.h" #include"texture.h" fractal* f; float center[] = { 0, 0, 0 }; float eye[] = { 0, 0, 20 }; float tx, ty = 10, ax, ay=10, mx, my, zoom = 0; bool isLine = false; bool isDown = false; void reshape(int width, int height) { if (height == 0) { height = 1; } glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); float whRatio = (GLfloat)width / (GLfloat)height; gluPerspective(45, whRatio, 1, 500); glMatrixMode(GL_MODELVIEW); } void idle() { glutPostRedisplay(); } void init(void) { glClearColor(1.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glColor4f(1.0, 1.0, 1.0, 1.0f); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); // height : 值越大,地形起伏越大 // times : 迭代次数,次数越多,tire越多 // step : 绘制时一个网格的程度 // D : 随机数衰减因子,越大随机数随迭代的取值越小(大于1) // seed : 随机数种子 f = new fractal(5, 3, 2, 2, 23); f->calculate(); } void redraw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClearColor(0, 0, 0, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0, 1, 0); if(isLine)glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glTranslatef(tx,ty,zoom); glRotatef(ax, 1.0f, 0.0f, 0.0f); glRotatef(ay, 0.0f, 1.0f, 0.0f); glPushMatrix(); glTranslatef(0, -roomSizeY / 2,0); f->draw(); glPopMatrix(); glutSwapBuffers(); } void myMouse(int button, int state, int x, int y) { if (button == GLUT_LEFT_BUTTON) { if (state == GLUT_DOWN) { isDown = true; mx = x; my = y; } else if (state == GLUT_UP) { isDown = false; } } glutPostRedisplay(); } void mouseMotion(int x, int y) { if (isDown) { ax += 1.0f*(y - my) / 10.0f; ay += 1.0f*(x - mx) / 10.0f; mx = x; my = y; } glutPostRedisplay(); } void myKeyboard(unsigned char key, int x, int y) { switch (key) { case 'a': { //左移 tx -= 1; break; } case 'd': { //右移 tx += 1; break; } case 'w': { //上移 ty += 1; break; } case 's': { //下移 ty -= 1; break; } case 'z': { //后移 zoom += 1; break; } case 'c': { //前移 zoom -= 1; break; } case 'p': { // 切换绘制模式 if (isLine) { isLine = false; } else isLine = true; } } glutPostRedisplay(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE); glutInitWindowSize(800, 600); int windowHandle = glutCreateWindow("Simple GLUT App"); glutDisplayFunc(redraw); glutReshapeFunc(reshape); glutMouseFunc(myMouse); glutMotionFunc(mouseMotion); glutKeyboardFunc(myKeyboard); glutIdleFunc(idle); init(); glutMainLoop(); system("pause"); return 0; }