欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[OpenGL] 随机分形地形生成

程序员文章站 2022-03-26 10:36:35
...

reference : 《随机分形地形生成及其浏览》

* 本项目属于游戏程序设计5000行计划


        算法实现起来很简单,分形也很有意思,我们假设我们越靠近一个物体,它的细节就会显示出来,那么对于具有分形特性的物体而言,我们看到的细节和整体并没有什么区别。正如我们不断zoom in观察这个 Koch 雪花,每个突出的角又能长出三个角,如此递归,永无止境……

       [OpenGL] 随机分形地形生成

        地形也有这样的特性,对于两张不同尺度上的地形俯拍图,我们看到的细节也是类似的,也就是说,对于下图中的蓝色区域的地面,我们截取两个不同大小的橙色框所得到的图片,在人眼看来是差不多的。

        [OpenGL] 随机分形地形生成

        利用这样的特性,我们就可以用分形的思想来生成随机地形。

        具体的算法在开头给出的论文已经说的非常明白了,虽然这个论文并不是该算法(diamand - square algorithm)的最早版本,但中文看起来比英文要好理解一些。在这里只给出一些概述。


        一维分形地形


       类似于二分法,取线段中点,使y值为线段两段的平均值加上一个随机数,得到两条线段,再分别取两个线段的中点,重复前面的过程。


       二维分形地形

       [OpenGL] 随机分形地形生成

        由一维扩展而成,利用已知的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 算法


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

       [OpenGL] 随机分形地形生成

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

         先计算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)

         然后再对新的四个正方形做同样的操作。


[OpenGL] 随机分形地形生成[OpenGL] 随机分形地形生成

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;
}