• 一般情况下实现第一人称视角游戏有两种方法,一是移动场景,二是移动眼坐标.移动场景方法比较简单,使用glTranslatef与glRotatef配合即可,但一般只在简单场景和单角色的情况下使用,而且角色的各种计算(如实时坐标、碰撞)不好实现,所以不推荐使用;移动眼坐标的方法就非常灵活,它对场景和角色的状态未做任何操作,一般只要设置成跟随主角色移动旋转即可实现第一人称视角视觉效果,在OpenGL中主要用gluLookAt函数来实现,其函数原形如下。
    用途:定义视图变换
    包含文件:<glu.h>
    描述:根据眼睛的位置,场景中心的位置和从观察者的角度往上指的矢量,定义一个视图变换.
    句法:void gluLookAt(Gldouble eyex, Gldouble eyey, Gldouble eyez, Gldouble aimx, Gldouble aimy, Gldouble aimz, Gldouble upx, Gldouble upy, Gldouble upz);
    参数: eyex, eyey, eyez:眼睛的三维坐标
    aimx, aimy, aimz:被观察的场景中心的三维坐标
    upx, upy, upz:指定向上矢量的三维坐标.
    除了空战游戏,要实现角色在三维场景中漫游,只需要改变眼睛的三维坐标(eyex, eyey, eyez)和被观察的场景中心的三维坐标(aimx, aimy, aimz)就可以了,指定向上矢量的三维坐标(upx, upy, upz)保持不变,其中又以眼睛的三维坐标为主, 被观察的场景中心的三维坐标为次,本人制作了一个类,粗略实现了漫游功能.首先将三维坐标数据统一成一个结构,如下:

    //三维点结构。
    struct Point{
    float x;
    float y;
    float z;
    Point(){
    x=y=z=0.0f;
    }
    Point(float InputX,float InputY,float InputZ){
    x=InputX;y=InputY;z=InputZ;
    }
    ~Point(){
    }
    };


    //角色类如下
    class Dat_Roll {
    public:
    Dat_Roll();
    ~Dat_Roll();
    void TurnLeft(const float InputAngle){//角色左旋
    m_fViewAngle-=InputAngle*PI/180;
    RefreshAimPos();
    };
    void TurnRight(const float InputAngle){//角色右旋
    m_fViewAngle+=InputAngle*PI/180;
    RefreshAimPos();
    };
    void TurnUp(const float InputAngle){//向上看
    if(m_fViewAngleVertical>PI/2) //防止翻转 m_fViewAngleVertical=PI/2;
    if(m_fViewAngleVertical<-PI/2) m_fViewAngleVertical=-PI/2;
    m_fViewAngleVertical-=InputAngle*PI/180;
    RefreshAimPos();
    };
    void TurnDown(const float InputAngle){//向下看
    if(m_fViewAngleVertical>PI/2) //防止翻转 m_fViewAngleVertical=PI/2;
    if(m_fViewAngleVertical<-PI/2) m_fViewAngleVertical=-PI/2;
    m_fViewAngleVertical+=InputAngle*PI/180;
    RefreshAimPos();
    };
    void MoveForward(){//向前移动
    m_NextEyePos.x+=cos(m_fViewAngle)*m_fMoveSpeed;
    m_NextEyePos.z+=sin(m_fViewAngle)*m_fMoveSpeed;
    RefreshAimPos();
    };
    void MoveBackward(){//向后移动
    m_NextEyePos.x-=cos(m_fViewAngle)*m_fMoveSpeed;
    m_NextEyePos.z-=sin(m_fViewAngle)*m_fMoveSpeed;
    RefreshAimPos();
    };
    void MoveLeft(){ //向左移动
    m_NextEyePos.x+=sin(m_fViewAngle)*m_fMoveSpeed;
    m_NextEyePos.z+=cos(m_fViewAngle)*m_fMoveSpeed;
    RefreshAimPos();
    };
    void MoveRight(){ //向右移动
    m_NextEyePos.x-=sin(m_fViewAngle)*m_fMoveSpeed;
    m_NextEyePos.z-=cos(m_fViewAngle)*m_fMoveSpeed;
    RefreshAimPos();
    };
    void RefreshAimPos(){//刷新目标点,重新计算目标点的三维坐标
    m_AimPos.x=m_EyePos.x+m_fEyeAimDis*cos(m_fViewAngle)*cos(m_fViewAngleVertical);
    m_AimPos.y=m_EyePos.y+m_fEyeAimDis*sin(m_fViewAngleVertical);
    m_AimPos.z=m_EyePos.z+m_fEyeAimDis*sin(m_fViewAngle)*cos(m_fViewAngleVertical);
    };
    Point GetEyePos(){
    return m_EyePos;
    };
    Point GetAimPos(){
    return m_AimPos;
    };
    Point GetHoverPos(){
    return m_HoverPos;
    };
    private:
    Point m_EyePos;//眼睛的三维坐标
    Point m_NextEyePos;//眼睛下一位置的三维坐标,用于碰撞检测
    Point m_AimPos;//被观察的场景中心的三维坐标
    Point m_HoverPos;//指定向上矢量的三维坐标.
    float m_fViewAngle;//水平角度
    float m_fViewAngleVertical;//垂直角度
    float m_fMoveSpeed;//移动速度
    float m_fEyeAimDis;//眼睛到中心的距离
    };

    Dat_Roll::Dat_Roll(){
    m_EyePos=Point(5.0f,4.0f,5.0f);
    m_AimPos=Point(0.0f,0.0f,0.0f);
    m_HoverPos=Point(0.0f,1.0f,0.0f);
    m_fViewAngle=-90.0f*PI/180.0f;
    m_fViewAngleVertical=0.0f;
    m_fMoveSpeed=10.0f;
    m_fEyeAimDis=10;
    m_NextEyePos=Point(0,0,0);
    }

    Dat_Roll::~Dat_Roll(){
    }

    此文属本人原创,转载请著名作者和出处.
  • 大家都知道可以用下面的方法将一个物体摆放在opengl三维场景中的某个位置:
    glPushMatrix();
    glScalef(fZoomValue); //缩放
    glTranslatef(xTrans,yTrans,zTrans); //平移
    glRotatef(xRot,1,0,0 ); //绕x轴旋转
    glRotatef(yRot,0,1,0 ); //绕y轴旋转
    glRotatef(zRot,0,0,1 ); //绕z轴旋转
    DrawSomeObj(**); //画物体
    glPopMatrix();
    如果是简单场景,即我们知道其绕三轴的夹角,那此方法是可行的,但是,如果要求在点(1,2,3)和点(7,8,9)之间绘制一个柱面,其轴线就是两点的连线,请问上面的办法如何处理。
    有人说进行三角函数,计算出夹角,在结果上可以,但函数复杂,速度慢,还有一个变换一次后角度改变的问题,很容易就出错。建议大家不要用此方法。我推荐用点积和叉乘来一次性解决问题。
    首先看glRotatef的定义:
    glRotatef(Angle,vx,vy,vz);
    Angle是一个标量,是变换前后的角度差;
    vx,vy,vz组成一个矢量,表示旋转的轴;
    只要可以计算出他们的值,就可以一次性将物体朝向旋转到位,而不需要进行开始时的三次旋转。

    其次我们来看看点积和叉乘的数学定义:
    点积: (x1 , y1 , z1 ) .( x2 , y2 , z2 ) = x1x2 + y1y2 + z1z2
    叉乘: ( x1 , y1 , z1 ) X ( x2 , y2 , z2 ) =( y1z2 - z1y2 , z1x2 - x1z2 , x1y2 - y1x2 )
    点积可以来计算两矢量的夹角,公式如下:
    cos (V ^ W) =V.W / | V | | W |
    叉乘可以计算两矢量的垂直矢量,叉乘后的新矢量就是垂直于前两矢量的矢量.
    这样,Angle,vx,vy,vz就都可以计算出来了,下面请看一个例子:
    我要在x1,y1,z1--开始点,x2,y2,z2--结束点之间画一个柱面,画住的原始函数如下:
    void DrawPrism(float SideLen,int SideNum,float Height){
    if(SideNum<3) return;
    if(SideLen<0.000001 || Height<0.000001) return;
    int i=0;

    glPushMatrix();
    for(i=0;i<SideNum;i++)
    {
    glBegin(GL_TRIANGLES);
    glVertex3f(0,0,Height);
    glVertex3f(SideLen*cos(i*2*PI/SideNum),SideLen*sin(i*2*PI/SideNum),Height);
    glVertex3f(SideLen*cos((i+1)*2*PI/SideNum),SideLen*sin((i+1)*2*PI/SideNum),Height);
    glEnd();
    }
    for(i=0;i<SideNum;i++)
    {
    glBegin(GL_TRIANGLES);
    glVertex3f(0,0,0);
    glVertex3f(SideLen*cos((i+1)*2*PI/SideNum),SideLen*sin((i+1)*2*PI/SideNum),0);
    glVertex3f(SideLen*cos(i*2*PI/SideNum),SideLen*sin(i*2*PI/SideNum),0);
    glEnd();
    }
    for(i=0;i<SideNum;i++)
    {
    glBegin(GL_QUADS);
    glVertex3f(SideLen*cos((i+1)*2*PI/SideNum),SideLen*sin((i+1)*2*PI/SideNum),0);
    glVertex3f(SideLen*cos((i+1)*2*PI/SideNum),SideLen*sin((i+1)*2*PI/SideNum),Height);
    glVertex3f(SideLen*cos(i*2*PI/SideNum),SideLen*sin(i*2*PI/SideNum),Height);
    glVertex3f(SideLen*cos(i*2*PI/SideNum),SideLen*sin(i*2*PI/SideNum),0);
    glEnd();
    }
    glPopMatrix();
    }
    其默认是以(0,0,0)为起点,轴向为z正向,(0,0,Height)为终点,要把它"搬"到Point1(x1,y1,z1),Point2(x2,y2,z2)之间,用点积和叉乘就容易办到了,代码如下:
    double a,b,c,d,a1,b1,c1,a2,b2,c2,d1,d2;//定义矢量
    double alfa;//定义旋转角度

    //这三步求得Point1和Point2的矢量1
    a1 = x2 - x1;//
    b1 = y2 - y1;//
    c1 = z2 - z1;

    //这是旋转前轴向代表的矢量2
    a2 = 0;
    b2 = 0;
    c2 = 1;

    d1 = sqrt(a1*a1 + b1*b1 + c1*c1);//求矢量1的长度
    d2 = sqrt(a2*a2 + b2*b2 + c2*c2);//求矢量2的长度,其实就是1,这里为了说明没有简化
    d = d1;//获得矢量1的长度

    alfa = (GLdouble)( acos( abs(a1*a2 + b1*b2 + c1*c2)/d1/d2 ) * 180/PI );//由点积计算旋转角度

    //由叉乘计算旋转轴
    a = b1*c2 - b2*c1;
    b = a2*c1 - a1*c2;
    c = a1*b2 - a2*b1;

    alfa = -alfa;//修正角度(也可以修改下面的glrotatef函数)

    if(c1<0)
    {
    alfa = 180 - alfa;
    }
    glPushMatrix();
    //glScalef(fZoomValue); //这里不用考虑缩放,所以不用
    glTranslatef(x1,y1,z1); //平移
    glRotatef(alfa,a,b,c ); //旋转
    DrawPrism(SideLen,4,d); //画柱,函数在上面
    glPopMatrix();

    自此函数完成,一般把上述过程优化后写入一个函数里,以方便使用.

    如果是已知3轴旋转角,如何简化成一个glRotatef函数呢,用同样的办法就可以办到,复杂的一步是要通过这3轴旋转角计算出新矢量,这个计算过程在许多图形学的书中都有,公式是:
    绕X轴旋转角q的矩阵
    | 1 0 0 0 |
    | 0 cos(q) sin(q) 0 |
    | 0 -sin(q) cos(q) 0 |
    | 0 0 0 1 |

    绕Y轴旋转角q的矩阵:
    | cos(q) 0 -sin(q) 0 |
    | 0 1 0 0 |
    | sin(q) 0 cos(q) 0 |
    | 0 0 0 1 |

    绕Z轴旋转角q的矩阵:
    | cos(q) sin(q) 0 0 |
    |-sin(q) cos(q) 0 0 |
    | 0 0 1 0 |
    | 0 0 0 1 |

    计算即可.

    此文属本人原创,转载请著名作者和出处.