最近在整理之前做过的东西,之前的制作了一个简易版的《Minecraft》的游戏,当时是看一个学长的博客,感觉很有趣。那时第一次了解到柏林噪声(Perlin Noise),下面介绍一下我对这个游戏的总结。
先附上我的游戏截图:
1. 地形的生成
采用的柏林噪声生成地形,关于柏林噪声的相关概念下面的博客已经说得很清楚了
[数学]柏林噪声 :http://www.cnblogs.com/Memo/archive/2008/09/08/1286963.html
柏林噪声不但可以生成随机的地形也可以生成一些自然界中的纹理,火焰云彩等。一个噪声函数基本上是一个种子随机发生器。它需要一个整数作为参数,然后返回根据这个参数返回一个随机数。如果你两次都传同一个参数进来,它就会产生两次相同的数。这条规律非常重要,否则柏林函数只是生成一堆垃圾。也就是说,柏林噪声是一个有规律的随机值。
柏林噪声主要的流程是:首先获得随机值,平滑噪声(对噪声插值)形成一个连续的函数。最后叠加上面的那些带有不同频率和振幅的函数形成一个柏林噪声函数。
下面说一下我的理解,柏林噪声里面有两个主要的参数:persistence(持续度)和Octaves(倍频),图中更直观的说明两个参数的作用。
持续度:持续度越大,振幅越大,对于图像而言图像更加混乱,对比更加突出,能看出更多细节。
倍频:每个你所叠加的噪声函数就是一个倍频,每一个噪声函数是上一个噪声函数的两倍频率。我认为叠加每次的不同的频率是为了让图像在原有的噪声函数的基础上增加更多的细节信息。
接下来只要选取合适的参数生成随机地形即可。我们可以使用2D的噪声函数,不同的灰度值代表高度,从而生成3D的地图。
柏林噪声的代码在最后会给出。2. 实现挖掘和建造功能(核心功能)
在这个程序中我没有使用opengl中自带的拾取操作,我是根据一种检测的方法,对于挖掘是,判断和视线相交,并且选择距离人物位置最近的功能块,建造也是类似。
首先要定义人物类的几个参数:
vec3 toward;//人物当前朝向
vec3 location;//人物当前位置
如果我们用遍历的方法去搜索,当地图很大的时候,效率是很低的而且游戏体验感不好。那么我们就需要考虑使用数据结构和算法提升效率,我这里使用的是四叉树组织地形,最近我也试过一个数据结构Kd-tree,它的最近邻搜索我感觉很适合这个问题。这样的话就能很快的提升程序的运行效率。
这个游戏里我使用了大概十几个不同的块来进行创建不同的建筑。
3. 游戏循环
对于一个游戏,我们有很多事件,每一帧我们应该是先获取玩家的输入,然后更改游戏内的状态,最后渲染画面。
void realPerFps()
{
Input(); // 输入
update(); // 修改状态
render(); // 渲染画面
}
这样的好处是我们不用每一次改变状态就刷新画面,因为这样做很多时候的刷新是无效的,比如我们的鼠标拖动事件和按钮事件,每次触发就更新画面这样是很不好的。那么解决的方法就是,我们把事件都当成一个状态,我们每次的操作只是改变了这些状态,具体的实际操作都是在一个realPerFps()函数里实现的。这样我们就可以具体的固定的帧率来刷新我们的画面。
柏林噪声代码
以下代码可以直接使用
#ifndef NOISE_H
#define NOISE_H
#include <cmath>
class Perlin{
public:
float persistence;
int Number_Of_Octaves;
Perlin(){
persistence = 0.50;
Number_Of_Octaves = 4;
}
double Noise(int x,int y) // 根据(x,y)获取一个初步噪声值
{
int n = x + y * 57;
n = (n<<13) ^ n;
return ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
}
double SmoothedNoise(int x, int y) //光滑噪声
{
double corners = ( Noise(x-1, y-1)+Noise(x+1, y-1)+Noise(x-1, y+1)+Noise(x+1, y+1) ) / 16;
double sides = ( Noise(x-1, y) +Noise(x+1, y) +Noise(x, y-1) +Noise(x, y+1) ) / 8;
double center = Noise(x, y) / 4;
return corners + sides + center;
}
double Cosine_Interpolate(double a,double b, double x) // 余弦插值
{
double ft = x * 3.1415927;
double f = (1 - cos(ft)) * 0.5;
return a*(1-f) + b*f;
}
double Linear_Interpolate(double a, double b, double x) //线性插值
{
return a*(1-x) + b*x;
}
double InterpolatedNoise(float x,float y) // 获取插值噪声
{
int integer_X = int(x);
float fractional_X = x - integer_X;
int integer_Y = int(y);
float fractional_Y = y - integer_Y;
double v1 = SmoothedNoise(integer_X, integer_Y);
double v2 = SmoothedNoise(integer_X + 1, integer_Y);
double v3 = SmoothedNoise(integer_X, integer_Y + 1);
double v4 = SmoothedNoise(integer_X + 1, integer_Y + 1);
double i1 = Cosine_Interpolate(v1, v2, fractional_X);
double i2 = Cosine_Interpolate(v3, v4, fractional_X);
return Cosine_Interpolate(i1, i2, fractional_Y);
}
double PerlinNoise(float x,float y) // 最终调用:根据(x,y)获得其对应的PerlinNoise值
{
double noise = 0;
double p = persistence;
int n = Number_Of_Octaves;
for(int i=0; i<n; i++)
{
double frequency = pow(2,i);
double amplitude = pow(p,i);
noise = noise + InterpolatedNoise(x * frequency, y * frequency) * amplitude;
}
return noise;
}
};
#endif // NOISE_H