[OpenGL] 随机分形地形生成

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)

        如此得到一次递归流程,然后再对四个正方形进行同样的操作……


        Diamand - Square 算法


       同样适用于二维,但是对上述算法的改进。

       

        上图反映的是两次递归过程,以下只描述一次递归中做的事情。

         先计算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;
}



  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值