openGL实现第一人称视角

    xiaoxiao2023-11-23  174

    最近做的一个题目要求用openGL实现一个漫游功能,虽然不知道这个漫游是不是指第一人称(其实我觉得第三人称俯视的那种也算),不过都差不多

    主要使用openGL的gluLookAt函数,通过计算球面坐标来实现


    目录

    gluLookAt()

    实现过程

    demo

    最终效果

     

    gluLookAt()

    void gluLookAt( GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);

    可以看到gluLookAt函数中有三组三维坐标

    第一组eye xyz是摄像机的位置,第二组center xyz是摄像机看向的位置,第三组up xyz是向上向量

    注意第一组和第二组是坐标,而第三组是向量

    可以这么理解

    第一组:脑袋/眼睛的位置

    第二组:眼睛看向的位置

    第三组:头顶的方向(躺着和坐着看一个物体,看到的景象是不同的)

    一开始有人跟我这么解释“up为头顶方向”的时候,我并不理解,觉得如果抬头看一个物体的话,那么头顶的位置不也得向后倾斜?emmm,其实头顶方向这个说法并不严谨,应该说你正视前方时头顶的方向,或者你理解成看东西的时候眼珠子动脑袋不动

     

    就比如上面这两个小人,两人眼睛的位置是相同的(你可以认为这个人是独眼怪),眼睛看向的位置是相同的,但头顶的方向是不同的,看到的图像也是不同的

     

    实现的思路是根据鼠标相对于屏幕中心点的距离来控制转头的幅度

    glutPassiveMotionFunc(yourFunc0);//不按鼠标移动 glutMotionFunc(yourFunc1);//按鼠标移动

    实现过程

    openGL两个鼠标事件返回的鼠标坐标都以左上角为原点,如下图,所以会导致最后实现的第一人称视角y轴反转,这个很容易解决,计算出的角度加一个负号或者处理一下屏幕坐标将原点变为左下角。

    //不按鼠标移动 void onMouseMovePassive(int screen_x,int screen_y) { //相对于屏幕中心点的偏移量 float offsetx = screen_x - centerpoint_x; float offsety = screen_y - centerpoint_y; //偏移量转化为角度 screenrate_x = offsetx / centerpoint_x *PI;//水平方向 screenrate_y = offsety / centerpoint_y *PI/2;//竖直方向 //水平方向 look_x_temp = r * sin(screenrate_x); look_z_temp = r * cos(screenrate_x);//最后使用时要和相机坐标相加/减 //竖直方向 look_y = r * sin(-screenrate_y); float r_castlenght = abs(r * cos(screenrate_y));//投影在xz面的长度 //根据长度计算真正的look_x,look_z look_x = r_castlenght * look_x_temp / r; look_z = r_castlenght * look_z_temp / r; }

    因为在水平方向最大要能转180度,而竖直方向最大转90度,所以鼠标相对中心点移动比例转换成角度时,水平方向要乘以180度,竖直方向乘以90度

    (这个第一人称仍然有它的问题,比如鼠标移动到边框后不可继续移动。以往我们在FPS游戏中的视角都是只要鼠标在往右拖动就可以一直转向,但由于我们使用的是鼠标相对中心点的偏移,导致最大只能转向180度,将水平方向偏移角度的PI增大几倍可以一定程度缓解这个问题,但同时也会使得“灵敏度”过高,目前还没有想到一个好的解决办法)

     

    因为gluLookAt参数的第一组和第二组点都是坐标,第一人称摄像机是可以移动的,我们要计算第二组摄像机看向的点,就要计算摄像机看向的点相对于摄像机的位置

    计算球面坐标

    先看水平方向,a为scrrenrate_x,通过屏幕坐标在水平方向上的偏移量计算出的角度,先从y轴俯视xoz平面

    摄像机朝向为Z轴负方向

     

    可以得到水平方向上相机看向的点的xz坐标为( r*sin(a),r*cos(a) ),r随便设一个数字即可

    将Y竖直方向考虑进来,b即是screenrate_y,根据屏幕坐标在水平方向上偏移量计算出的角度,

    y = r*sin(b)

    半径r在xoz平面的投影长度r_castlength为abs(r*cos(b)),即原点到(x,o,z)点的距离,结合之前在水平方向上求出的点(x',0,z')

    x' =  r*sin(a),z' = r*cos(a) ,相似三角形就能求出x,z的值

    得到球面坐标(x,y,z)是相对于相机的坐标

    在gluLookAt函数中将相对于摄像机的坐标变为世界坐标系中的坐标

    gluLookAt(cpos_x, cpos_y, cpos_z, cpos_x + look_x, look_y+look_y, cpos_z - look_z, 0, 1, 0);

    其中cpos_xyz是摄像机的坐标,因为我的相机因为其他功能需求的原因,初始是面朝Z轴负方向,所以第6个参数是cpos_z - look_z。如果没有特殊需求,为了方便理解初始面还是面朝Z正方向比较不容易出错

     

    相机移动就很简单了,glutKeyboardFunc设置键盘函数,根据上面计算xoz面的坐标来控制WASD往对应方向移动

    因为Z轴反过来了所以想起来有些绕,为了保险还是在纸上自己画一画对照着做。

    float step = 0.01; void keyboardFunc(unsigned char key,int x,int y) { if (key == 'w') { //朝镜头方向前进look_x,look_z cpos_x += look_x_temp * step; cpos_z -= look_z_temp * step; } if (key == 's') { //后退 cpos_x -= look_x_temp * step; cpos_z += look_z_temp * step; } if (key == 'a') { //向左移动 (x,y)对应(y,-x) //则(look_x,look_z)对应的为(-look_z,look_x) //Z轴取反 cpos_x += -look_z_temp * step; cpos_z -= look_x_temp * step; } if (key == 'd') { //向右移动 (x,y)对应(-y,x) //则(look_x,look_z)对应的为(look_z,-look_x) //Z轴取反 cpos_x += look_z_temp * step; cpos_z -= -look_x_temp * step; } }

    因为使用的是只考虑平面的look_x_temp,look_z_temp,而不是根据球面坐标计算的look_x,look_z,所以不会有抬头低头速度不一样、斜着走比正着走快(因为根本没法斜着走hhhhh)等问题

     

    demo

    #define _CRT_SECURE_NO_WARNINGS #define WindowWidth 400 #define WindowHeight 400 #define WindowTitle "第一人称视角漫游demo" #include <glut.h> #include <stdio.h> #include <stdlib.h> #include <math.h> #include<iostream> using namespace std; float PI = 3.14; //视角控制 float look_x = 0, look_y = 0, look_z = 0, look_x_temp = 0, look_z_temp = 0; float screenrate_x, screenrate_y;//鼠标屏幕坐标相对于中心点移动的比例 float r = 10; //相机位置 float cpos_x = 0, cpos_y = 2, cpos_z = 10; int centerpoint_x = WindowWidth / 2, centerpoint_y = WindowHeight / 2; void display(void) { // 清除屏幕 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 设置视角 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(75, 1, 1, 30); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(cpos_x, cpos_y, cpos_z, cpos_x + look_x, cpos_y + look_y, cpos_z - look_z, 0, 1, 0);//注意因为相机是面朝z负方向,所以cpos_z - lookz //gluLookAt(cpos_x, cpos_y, cpos_z, 0, 0, 0, 0, 1, 0);//原固定视角 //蓝色地面 glColor3f(0.0, 0.0, 1.0); glBegin(GL_QUADS); glVertex3f(-8.0f, 0.0f, -8.0f); glVertex3f(-8.0f, 0.0f, 8.0f); glVertex3f(8.0f, 0.0f, 8.0f); glVertex3f(8.0f, 0.0f, -8.0f); glEnd(); //红色墙面 glColor3f(1.0, 0.0, 0.0); glBegin(GL_QUADS); glVertex3f(8.0f, 0.0f, -8.0f); glVertex3f(8.0f, 8.0f, -8.0f); glVertex3f(-8.0f, 8.0f, -8.0f); glVertex3f(-8.0f, 0.0f, -8.0f); glEnd(); glutSwapBuffers(); } void myIdle(void) { display(); } //不按鼠标移动事件 void onMouseMovePassive(int screen_x, int screen_y) { float offsetx = screen_x - centerpoint_x; float offsety = screen_y - centerpoint_y; screenrate_x = offsetx / centerpoint_x * PI;//用于摄像机水平移动 screenrate_y = offsety / centerpoint_y * PI / 2;//用于摄像机上下移动 //水平方向 look_x_temp = r * sin(screenrate_x); look_z_temp = r * cos(screenrate_x);//最后使用时要和相机坐标相加/减 //竖直方向 look_y = r * sin(-screenrate_y); float r_castlenght = abs(r * cos(screenrate_y));//投影在xz面的长度 //根据长度计算真正的look_x,look_z look_x = r_castlenght * look_x_temp / r; look_z = r_castlenght * look_z_temp / r; } //按下鼠标移动事件 void onMouseDownAndMove(int x, int y) { } float step = 0.01; void keyboardFunc(unsigned char key, int x, int y) { if (key == 'w') { //朝镜头方向前进look_x,look_z cpos_x += look_x_temp * step; cpos_z -= look_z_temp * step; } if (key == 's') { cpos_x -= look_x_temp * step; cpos_z += look_z_temp * step; } if (key == 'a') { //左 (x,y)对应(y,-x) //则(look_x,look_z)对应的为(-look_z,look_x) //Z轴取反 cpos_x += -look_z_temp * step; cpos_z -= look_x_temp * step; } if (key == 'd') { //右 (x,y)对应(-y,x) //则(look_x,look_z)对应的为(look_z,-look_x) //Z轴取反 cpos_x += look_z_temp * step; cpos_z -= -look_x_temp * step; } } int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(100, 100); glutInitWindowSize(WindowWidth, WindowHeight); glutCreateWindow(WindowTitle); glutPassiveMotionFunc(onMouseMovePassive); glutMotionFunc(onMouseDownAndMove); glutKeyboardFunc(keyboardFunc); glutDisplayFunc(&display); glutIdleFunc(&myIdle); glutMainLoop(); return 0; }

     

    最终效果

     

    最新回复(0)