编译参数:-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

  • @ 2026-4-18 17:45:29

    同学你好,我看到你分享了一个使用3D柏林噪声生成地形并实现第一人称漫游的OpenGL程序。这是一个非常棒的图形学实践项目,涉及了噪声生成、地形渲染和交互控制等多个方面。

    从代码结构来看,你已经掌握了柏林噪声的核心算法实现,并且能够将其应用于地形高度场生成。不过,作为竞赛教练,我更关注的是算法原理和优化思路。让我从几个角度分析这个项目:

    算法分析

    1. 柏林噪声算法:你的PerlinNoise3D类实现了经典的3D柏林噪声,包括:

      • 梯度函数grad():根据哈希值计算方向向量
      • 插值函数lerp()和缓动函数fade():确保平滑过渡
      • 八度噪声叠加octave():通过多频率叠加产生更自然的地形
    2. 地形生成优化

      • 当前代码每次绘制都重新计算地形高度,对于1024个顶点(48×48网格)来说计算量较大
      • 考虑是否可以预计算或缓存部分结果?

    启发式提问

    1. 性能优化:如果地形范围扩大到256×256,帧率会明显下降。你考虑过哪些优化策略?

      • 提示:观察draw()函数中的双重循环,思考如何减少重复计算
    2. 算法扩展:当前的八度噪声只使用了4个频率。如果要生成更复杂的地形(如山脉、峡谷),你会如何调整参数?

      • 提示:考虑频率权重、振幅衰减等参数的影响
    3. 碰撞检测:当前的跳跃物理很简单。如果要实现更真实的物理效果(如斜坡滑动、障碍物碰撞),你会如何设计算法?

    改进建议

    1. 代码结构

      • 考虑将地形生成和渲染分离成不同类
      • 使用顶点缓冲对象(VBO)替代立即模式渲染(glBegin/glEnd)
    2. 算法优化

      // 可以考虑的优化方向
      float getHeightCached(float x, float z) {
          // 实现简单的缓存机制,避免重复计算相同坐标的高度
      }
      
    3. 功能扩展

      • 添加LOD(层次细节)系统,根据距离调整地形细节
      • 实现水面、植被等环境元素

    竞赛思维训练

    虽然这不是传统算法题,但其中蕴含的优化思想与竞赛相通:

    • 空间换时间:缓存计算结果
    • 分治思想:将大地形分块处理
    • 近似算法:远处使用简化计算

    思考题:如果要求实时生成无限大的地形(如Minecraft),你会如何设计数据结构和算法?

    请分享你目前遇到的任何具体问题,或者你对上述哪个方向最感兴趣,我们可以深入讨论。记住,好的图形程序不仅是功能实现,更是算法效率和代码设计的体现。

    • 1