- 分享
3D柏林噪声地形
- @ 2026-4-18 17:45:07
编译参数:-std=c++14 -O2 -lopengl32 -lgdi32 -lglu32
WASD空格控制,ESC释放/捕获鼠标
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cmath>
using namespace std;
class PerlinNoise3D {
private:
BYTE p[512];
float fade(float t) { return t*t*t*(t*(t*6-15)+10); }
float lerp(float t,float a,float b){return a+t*(b-a);}
float grad(int hash,float x,float y,float z){
int h=hash&15;
float u = h<8 ? x : y;
float v = h<4 ? y : (h==12||h==14 ? x : z);
return ((h&1)?-u:u)+((h&2)?-v:v);
}
public:
PerlinNoise3D(){
static const int perm[]={151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180};
for(int i=0;i<256;i++)p[i]=perm[i];
for(int i=0;i<256;i++)p[256+i]=p[i];
}
float noise(float x, float y, float z) {
int X = (int)floor(x) & 255, Y = (int)floor(y) & 255, Z = (int)floor(z) & 255;
x -= floor(x); y -= floor(y); z -= floor(z);
float u = fade(x), v = fade(y), w = fade(z);
int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z;
int B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
float l1 = lerp(u, grad(p[AA], x,y,z), grad(p[BA], x-1,y,z));
float l2 = lerp(u, grad(p[AB], x,y-1,z), grad(p[BB], x-1,y-1,z));
float l3 = lerp(u, grad(p[AA+1], x,y,z-1), grad(p[BA+1], x-1,y,z-1));
float l4 = lerp(u, grad(p[AB+1], x,y-1,z-1), grad(p[BB+1], x-1,y-1,z-1));
return lerp(w, lerp(v, l1, l2), lerp(v, l3, l4));
}
float octave(float x,float z){
float h=noise(x*0.1f,z*0.1f,0)*0.5f
+noise(x*0.2f,z*0.2f,0)*0.25f
+noise(x*0.4f,z*0.4f,0)*0.125f
+noise(x*0.8f,z*0.8f,0)*0.0625f;
return (h+1)*0.5f*10;
}
};
const int W=1024, H=768;
PerlinNoise3D pn;
float px=0, pz=0, yaw=0, pitch=0, velY=0;
bool third=false, menu=false;
HWND hwnd;
HDC dc;
HGLRC rc;
float height(float x,float z){ return pn.octave(x,z); }
void DrawSkybox() {
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glPushMatrix();
glRotatef(-pitch*180/3.14159f, 1,0,0);
glRotatef(-yaw*180/3.14159f, 0,1,0);
glBegin(GL_QUADS);
glColor3f(0.2f, 0.6f, 1.0f);
glVertex3f(-200, 150, -200);
glVertex3f( 200, 150, -200);
glVertex3f( 200, 150, 200);
glVertex3f(-200, 150, 200);
glColor3f(0.8f, 0.9f, 1.0f);
glVertex3f(-200,-20,-200);
glVertex3f( 200,-20,-200);
glVertex3f( 200,-20, 200);
glVertex3f(-200,-20, 200);
glColor3f(0.3f,0.7f,1.0f); glVertex3f(-200,150,-200); glVertex3f(-200,150,200); glColor3f(0.8f,0.9f,1.0f); glVertex3f(-200,-20,200); glVertex3f(-200,-20,-200);
glColor3f(0.3f,0.7f,1.0f); glVertex3f( 200,150,-200); glVertex3f( 200,150,200); glColor3f(0.8f,0.9f,1.0f); glVertex3f( 200,-20,200); glVertex3f( 200,-20,-200);
glColor3f(0.3f,0.7f,1.0f); glVertex3f(-200,150, 200); glVertex3f( 200,150,200); glColor3f(0.8f,0.9f,1.0f); glVertex3f( 200,-20,200); glVertex3f(-200,-20, 200);
glColor3f(0.3f,0.7f,1.0f); glVertex3f(-200,150,-200); glVertex3f( 200,150,-200); glColor3f(0.8f,0.9f,1.0f); glVertex3f( 200,-20,-200); glVertex3f(-200,-20,-200);
glEnd();
glPopMatrix();
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
}
void CenterMouse() {
POINT pt;
pt.x = W/2;
pt.y = H/2;
ClientToScreen(hwnd, &pt);
SetCursorPos(pt.x, pt.y);
}
void LockMouse() {
ShowCursor(FALSE);
RECT r;
GetClientRect(hwnd, &r);
ClipCursor(&r);
}
void UnlockMouse() {
ClipCursor(NULL);
ShowCursor(TRUE);
}
void init() {
PIXELFORMATDESCRIPTOR pfd = {
sizeof(pfd),1,
PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,32,0,0,0,0,0,0,0,0,0,0,0,0,0,
16,0,0,PFD_MAIN_PLANE,0,0,0,0
};
dc=GetDC(hwnd);
int pf=ChoosePixelFormat(dc,&pfd);
SetPixelFormat(dc,pf,&pfd);
rc=wglCreateContext(dc);
wglMakeCurrent(dc,rc);
glEnable(GL_DEPTH_TEST);
glDisable(GL_FOG);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
GLfloat sun_pos[] = {500,1000,500,0};
GLfloat sun_amb[] = {0.8f,0.8f,0.75f,1};
GLfloat sun_dif[] = {1.0f,1.0f,0.9f,1};
glLightfv(GL_LIGHT0,GL_POSITION,sun_pos);
glLightfv(GL_LIGHT0,GL_AMBIENT,sun_amb);
glLightfv(GL_LIGHT0,GL_DIFFUSE,sun_dif);
glEnable(GL_COLOR_MATERIAL);
glViewport(0,0,W,H);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90, (float)W/H, 0.1f, 300);
glMatrixMode(GL_MODELVIEW);
}
void draw() {
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
float h = height(px,pz);
float cy = h + 1.8f + velY;
float lx = px + cosf(yaw);
float ly = cy + sinf(pitch);
float lz = pz + sinf(yaw);
gluLookAt(px,cy,pz, lx,ly,lz, 0,1,0);
DrawSkybox();
float cx = floorf(px/48)*48;
float cz = floorf(pz/48)*48;
glBegin(GL_QUADS);
for(int dz=-48;dz<=48;dz++){
for(int dx=-48;dx<=48;dx++){
float fx = cx+dx, fz=cz+dz;
float h0=height(fx,fz);
float h1=height(fx+1,fz);
float h2=height(fx+1,fz+1);
float h3=height(fx,fz+1);
float c=60+h0*6; if(c>255)c=255;
glColor3f(c/255.0f,(c+30)/255.0f,(c/2+20)/255.0f);
glVertex3f(fx,h0,fz);
glVertex3f(fx+1,h1,fz);
glVertex3f(fx+1,h2,fz+1);
glVertex3f(fx,h3,fz+1);
}
}
glEnd();
SwapBuffers(dc);
}
LRESULT CALLBACK WndProc(HWND h,UINT m,WPARAM w,LPARAM l) {
switch(m) {
case WM_MOUSEMOVE: {
if(menu) return 0;
POINT curr;
GetCursorPos(&curr);
ScreenToClient(h,&curr);
int dx = curr.x - W/2;
int dy = curr.y - H/2;
yaw += dx * 0.003f;
pitch -= dy * 0.003f;
CenterMouse();
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(h,m,w,l);
}
int WINAPI WinMain(HINSTANCE i,HINSTANCE,LPSTR,int s) {
WNDCLASS wc={0};
wc.lpfnWndProc=WndProc;
wc.hInstance=i;
wc.lpszClassName="GLClass";
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
RegisterClass(&wc);
hwnd=CreateWindowEx(0,"GLClass","3D Terrain",WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,W,H,0,0,i,0);
ShowWindow(hwnd,s);
UpdateWindow(hwnd);
init();
LockMouse();
CenterMouse();
MSG msg;
while(1) {
if(PeekMessage(&msg,0,0,0,PM_REMOVE)) {
if(msg.message==WM_QUIT) break;
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
static bool lesc=0,lf5=0;
bool esc = GetAsyncKeyState(VK_ESCAPE)&0x8000;
bool f5 = GetAsyncKeyState(VK_F5)&0x8000;
if(esc&&!lesc){menu=!menu;menu?UnlockMouse():LockMouse();}
lesc=esc;
if(f5&&!lf5)third=!third;
lf5=f5;
if(!menu) {
float spd=0.15f;
float sy=sinf(yaw),cy=cosf(yaw);
if(GetAsyncKeyState('W')){px+=cy*spd;pz+=sy*spd;}
if(GetAsyncKeyState('S')){px-=cy*spd;pz-=sy*spd;}
if(GetAsyncKeyState('A')){px-=sy*spd;pz+=cy*spd;}
if(GetAsyncKeyState('D')){px+=sy*spd;pz-=cy*spd;}
float h=height(px,pz);
static bool ground=1;
if(GetAsyncKeyState(VK_SPACE)&&ground) {
velY=2.5f; ground=0;
}
const float gravity=0.25f;
velY-=gravity;
float y=h+1.8f+velY;
if(y<=h+1.8f){velY=0;ground=1;}
}
draw();
}
}
wglMakeCurrent(NULL,NULL);
wglDeleteContext(rc);
ReleaseDC(hwnd,dc);
return 0;
}
1 comments
-
Myobot @ 2026-4-18 17:45:29
同学你好,我看到你分享了一个使用3D柏林噪声生成地形并实现第一人称漫游的OpenGL程序。这是一个非常棒的图形学实践项目,涉及了噪声生成、地形渲染和交互控制等多个方面。
从代码结构来看,你已经掌握了柏林噪声的核心算法实现,并且能够将其应用于地形高度场生成。不过,作为竞赛教练,我更关注的是算法原理和优化思路。让我从几个角度分析这个项目:
算法分析
-
柏林噪声算法:你的
PerlinNoise3D类实现了经典的3D柏林噪声,包括:- 梯度函数
grad():根据哈希值计算方向向量 - 插值函数
lerp()和缓动函数fade():确保平滑过渡 - 八度噪声叠加
octave():通过多频率叠加产生更自然的地形
- 梯度函数
-
地形生成优化:
- 当前代码每次绘制都重新计算地形高度,对于1024个顶点(48×48网格)来说计算量较大
- 考虑是否可以预计算或缓存部分结果?
启发式提问
-
性能优化:如果地形范围扩大到256×256,帧率会明显下降。你考虑过哪些优化策略?
- 提示:观察
draw()函数中的双重循环,思考如何减少重复计算
- 提示:观察
-
算法扩展:当前的八度噪声只使用了4个频率。如果要生成更复杂的地形(如山脉、峡谷),你会如何调整参数?
- 提示:考虑频率权重、振幅衰减等参数的影响
-
碰撞检测:当前的跳跃物理很简单。如果要实现更真实的物理效果(如斜坡滑动、障碍物碰撞),你会如何设计算法?
改进建议
-
代码结构:
- 考虑将地形生成和渲染分离成不同类
- 使用顶点缓冲对象(VBO)替代立即模式渲染(
glBegin/glEnd)
-
算法优化:
// 可以考虑的优化方向 float getHeightCached(float x, float z) { // 实现简单的缓存机制,避免重复计算相同坐标的高度 } -
功能扩展:
- 添加LOD(层次细节)系统,根据距离调整地形细节
- 实现水面、植被等环境元素
竞赛思维训练
虽然这不是传统算法题,但其中蕴含的优化思想与竞赛相通:
- 空间换时间:缓存计算结果
- 分治思想:将大地形分块处理
- 近似算法:远处使用简化计算
思考题:如果要求实时生成无限大的地形(如Minecraft),你会如何设计数据结构和算法?
请分享你目前遇到的任何具体问题,或者你对上述哪个方向最感兴趣,我们可以深入讨论。记住,好的图形程序不仅是功能实现,更是算法效率和代码设计的体现。
-
- 1