关闭

【一步步学OpenGL 14】 -《相机控制1(键盘事件)》

标签: openglGLSLC++
2891人阅读 评论(2) 收藏 举报
分类:

教程14

相机控制1(键盘事件)

http://ogldev.atspace.co.uk/
原文:http://ogldev.atspace.co.uk/www/tutorial14/tutorial14.html
CSDN完整版专栏: http://blog.csdn.net/column/details/13062.html


背景

在之前的教程中我们学习了如何将相机至于3d世界的任意一个位置,下一步就要实现让用户来控制它。移动应该是不受限制的:用户可以在任何方向上移动。相机的控制通过两种输入设备来实现:使用键盘控制位置的移动,使用鼠标来改变目标视角,这个和第一人称射击角色类似。这篇教程介绍键盘的控制,鼠标的控制放在下一个教程中。

我们要实现传统的上下左右四键控制。注意我们相机的变换是通过当前位置position、target向量(前方视角)和上方头顶up向量定义的,当我们使用键盘控制移动的时候我们只是改变我们的位置,我们不能倾斜相机不能将相机的视角移动到目标物体方向(不会改变target向量和up向量)。
为了使用键盘控制我们要用到另外一个GLUT接口函数:glutSpecialFunc()。这个函数注册了一个事件回调,当“专用功能按键”按下时触发。专用按键主要包括:功能键、方向键,以及PAGE-UP/PAGE-DOWN/HOME/END/INSERT这些按键。如果想获取常规按键(字母键和数字键)的触发事件,需要使用这个接口函数:glutKeyboardFunc()。

源代码详解

相机的所有功能都封装在相机类中。相机类中存储着相机所有的属性并且会根据它收到的运动事件改变属性的的值,这些属性可以被创建变换矩阵的管线类获取。

(Camera.h)

class Camera
{
public:
    Camera();
    Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
    bool OnKeyboard(int Key);
    const Vector3f& GetPos() const
    const Vector3f& GetTarget() const
    const Vector3f& GetUp() const

private:
    Vector3f m_pos;
    Vector3f m_target;
    Vector3f m_up;
};

这个是相机类的定义,存储了三个定义相机的属性:位置(position),target向量和up向量。定义了两个构造函数,默认的构造函数将相机至于原点,朝向Z轴正向,up向量指向天空(0,1,0),另一个构造函数同时使用参数初始化三个成员属性。OnKeyboard()函数用于实现相机类的键盘事件,并返回一个布尔值来表示事件是否被类接收成功,如果收到的键值是和我们定义的相关的(方向键其中之一)就返回真值,否则返回false。这样你就可以构建一系列的客户端来接收键盘事件,并在当有一个客户端首先成功接收到某个相关的事件之后终止事件监测。

(Camera.cpp:42)

bool Camera::OnKeyboard(int Key)
{
    bool Ret = false;

    switch (Key) {

    case GLUT_KEY_UP:
    {
        m_pos += (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_DOWN:
    {
        m_pos -= (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_LEFT:
    {
        Vector3f Left = m_target.Cross(m_up);
        Left.Normalize();
        Left *= StepSize;
        m_pos += Left;
        Ret = true;
    }
    break;

    case GLUT_KEY_RIGHT:
    {
        Vector3f Right = m_up.Cross(m_target);
        Right.Normalize();
        Right *= StepSize;
        m_pos += Right;
        Ret = true;
    }
    break;
    }

    return Ret;
}

这个函数实现根据键盘按键事件移动相机。GLUT根据方向键定义了几个宏变量,这样switch语句就可以根据这几个宏变量判断是哪个按键了,这几个宏变量其实是简单的int整型变量,不是枚举。

前后移动是最简单的,因为这俩种移动方向和tartget向量在一条线上,我们只需要从起始位置加上或者减去一定数量的tartget向量即可实现前后移动,target向量本身不会变化。注意,在加减之前我们是使用一个常量‘步长’值和target向量相乘的,不管哪个方向的移动都会乘上这个步长,其实就是改变移动速度,这个步长常量之后我们会放到类里也作为一个变量。为了保证步长稳定不变,我们确保让它每次都和单位向量相乘(比如之前我们都确保target向量和up向量是单位长度的)。

左右两边的移动就稍微复杂一些了,左右的移动需要一个和tartget向量与up向量所在平面垂直的一个移动向量,这个平面将3d空间分成两部分,每个部分都有一个向量和平面垂直并且方向相反(可以叫他们left向量和right向量)。这两个向量可以用tartget向量和up向量作差积求得,两种组合:target * up 和 up * tartget(差积不满足交换律,改变左右顺序结果不是同一个向量)。得到left或者right向量之后对其进行单位化,然后乘以步长,最后加到position位置向量上实现左右方向上的移动,同时target和up向量都没有变化。

注意这个函数里面的操作用到了向量新的操作符比如‘+=’和‘-=’,这些都定义在Vector3f类中了。

(tutorial14.cpp:73)

static void SpecialKeyboardCB(int Key, int x, int y)
{
    GameCamera.OnKeyboard(Key);
}


static void InitializeGlutCallbacks()
{
    glutDisplayFunc(RenderSceneCB);
    glutIdleFunc(RenderSceneCB);
    glutSpecialFunc(SpecialKeyboardCB);
}

这里我们新的事件回调来处理专用键盘事件。这个回调在按键按下时接收键盘键值或者鼠标的位置。我们这里忽略鼠标的具体位置信息,只把点击事件传给一个定义为全局的相机类的实例对象。

(tutorial14.cpp:55)

p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());

之前我们使用固定写好的向量在管线类中初始化相机的参数,现在把那些固定的向量丢弃,相机的那几个向量属性值都直接从相机类中获取。

示例Demo

#include <stdio.h>
#include <string.h>

#include <math.h>
#include <GL/glew.h>
#include <GL/freeglut.h>

#include "ogldev_util.h"
#include "ogldev_glut_backend.h"
#include "ogldev_pipeline.h"
#include "ogldev_camera.h"

#define WINDOW_WIDTH 1024
#define WINDOW_HEIGHT 768

GLuint VBO;
GLuint IBO;
GLuint gWVPLocation;

Camera* pGameCamera = NULL;
PersProjInfo gPersProjInfo;

const char* pVSFileName = "shader.vs";
const char* pFSFileName = "shader.fs";

static void RenderSceneCB()
{
    glClear(GL_COLOR_BUFFER_BIT);

    static float Scale = 0.0f;

    Scale += 0.1f;

    Pipeline p;
    p.Rotate(0.0f, Scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 3.0f);
    p.SetCamera(*pGameCamera);
    p.SetPerspectiveProj(gPersProjInfo);

    glUniformMatrix4fv(gWVPLocation, 1, GL_TRUE, (const GLfloat*)p.GetWVPTrans());

    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

    glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

    glDisableVertexAttribArray(0);

    glutSwapBuffers();
}

// 传递键盘事件
static void SpecialKeyboardCB(int Key, int x, int y)
{
    OGLDEV_KEY OgldevKey = GLUTKeyToOGLDEVKey(Key);
    pGameCamera->OnKeyboard(OgldevKey);
}


static void InitializeGlutCallbacks()
{
    glutDisplayFunc(RenderSceneCB);
    glutIdleFunc(RenderSceneCB);
    // 注册键盘事件
    glutSpecialFunc(SpecialKeyboardCB);
}

static void CreateVertexBuffer()
{
    Vector3f Vertices[4];
    Vertices[0] = Vector3f(-1.0f, -1.0f, 0.5773f);
    Vertices[1] = Vector3f(0.0f, -1.0f, -1.15475f);
    Vertices[2] = Vector3f(1.0f, -1.0f, 0.5773f);
    Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);

    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
}

static void CreateIndexBuffer()
{
    unsigned int Indices[] = { 0, 3, 1,
                               1, 3, 2,
                               2, 3, 0,
                               0, 1, 2 };

    glGenBuffers(1, &IBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}

static void AddShader(GLuint ShaderProgram, const char* pShaderText, GLenum ShaderType)
{
    GLuint ShaderObj = glCreateShader(ShaderType);

    if (ShaderObj == 0) {
        fprintf(stderr, "Error creating shader type %d\n", ShaderType);
        exit(1);
    }

    const GLchar* p[1];
    p[0] = pShaderText;
    GLint Lengths[1];
    Lengths[0]= strlen(pShaderText);
    glShaderSource(ShaderObj, 1, p, Lengths);
    glCompileShader(ShaderObj);
    GLint success;
    glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
    if (!success) {
        GLchar InfoLog[1024];
        glGetShaderInfoLog(ShaderObj, 1024, NULL, InfoLog);
        fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
        exit(1);
    }

    glAttachShader(ShaderProgram, ShaderObj);
}

static void CompileShaders()
{
    GLuint ShaderProgram = glCreateProgram();

    if (ShaderProgram == 0) {
        fprintf(stderr, "Error creating shader program\n");
        exit(1);
    }

    string vs, fs;

    if (!ReadFile(pVSFileName, vs)) {
        exit(1);
    };

    if (!ReadFile(pFSFileName, fs)) {
        exit(1);
    };

    AddShader(ShaderProgram, vs.c_str(), GL_VERTEX_SHADER);
    AddShader(ShaderProgram, fs.c_str(), GL_FRAGMENT_SHADER);

    GLint Success = 0;
    GLchar ErrorLog[1024] = { 0 };

    glLinkProgram(ShaderProgram);
    glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
    if (Success == 0) {
        glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
        fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
        exit(1);
    }

    glValidateProgram(ShaderProgram);
    glGetProgramiv(ShaderProgram, GL_VALIDATE_STATUS, &Success);
    if (!Success) {
        glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
        fprintf(stderr, "Invalid shader program: '%s'\n", ErrorLog);
        exit(1);
    }

    glUseProgram(ShaderProgram);

    gWVPLocation = glGetUniformLocation(ShaderProgram, "gWVP");
    assert(gWVPLocation != 0xFFFFFFFF);
}

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Tutorial 14");

    InitializeGlutCallbacks();

    pGameCamera = new Camera(WINDOW_WIDTH, WINDOW_HEIGHT);

    // Must be done after glut is initialized!
    GLenum res = glewInit();
    if (res != GLEW_OK) {
      fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
      return 1;
    }

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    CreateVertexBuffer();
    CreateIndexBuffer();

    CompileShaders();

    gPersProjInfo.FOV = 60.0f;
    gPersProjInfo.Height = WINDOW_HEIGHT;
    gPersProjInfo.Width = WINDOW_WIDTH;
    gPersProjInfo.zNear = 1.0f;
    gPersProjInfo.zFar = 100.0f;

    glutMainLoop();

    return 0;
}

着色器shader.vs和shader.fs不变。

4
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

【一步步学OpenGL 13】 -《相机空间》

教程13相机空间背景在之前的几个教程我们已经学习了两种变换。第一种是基本变换,用来改变物体的位置(平移变换)、方向(旋转变换)、尺寸(缩放变换),可以将物体至于3d世界任意位置;第二种变换是透视投影变换用于将3d世界的一个顶点位置投射到2d世界(比如:一个平面),一旦坐标置于2d那么就很容易将他们映...
  • cordova
  • cordova
  • 2016-09-25 16:24
  • 3829

【Modern OpenGL】摄像机系统 Camera

在前面的教程中,我们讨论了视口矩阵和我们可以怎样利用视口矩阵让绘制的场景移动(我们在上次教程中成功将那个二维平面稍稍向后移动了一点)。OpenGL本身对摄像机这个概念并不熟悉,但是我们可以通过移动场景中所有的对象(就好像反方向移动一个摄像机一样)来模拟一个。 在本次教程中,我们将会讨论我们怎样在Op...
  • aganlengzi
  • aganlengzi
  • 2016-01-02 15:23
  • 2221

OpenGL--摄像机漫游

理论基础在3D游戏中,我们通常可以通过鼠标或键盘操纵角色英雄在场景中移动,从不同的角度观察物体,这其实就是本章要介绍的摄像机漫游。 关于摄像机漫游其实就是围绕一个函数实现的(通过改变视点以及观察方向来实现),具体的函数为OpenGL中辅助函数库中的gluLookat(),通过设置相应的参数实现场景...
  • u010223072
  • u010223072
  • 2016-11-28 17:07
  • 3114

【一步步学OpenGL 4】-《着色器》

教程4:着色器原文:http://ogldev.atspace.co.uk/www/tutorial04/tutorial04.html背景:从这篇教程开始,我们将使用shader着色器来实现每一个效果和技术点。着色器是目前做3D图形最流行的方式。在某种程度上我们可以说这是一个“退步”吧,或者说技术...
  • cordova
  • cordova
  • 2016-09-11 00:18
  • 7164

【一步步学OpenGL 2】-《你好顶点》

你好顶点原文:http://ogldev.atspace.co.uk/www/tutorial02/tutorial02.html背景这里要第一次开始使用GLEW(the OpenGL Extension Wrangler Library)库。GLEW可以帮助我们解决一些伴随OpenGL扩展库管理出...
  • cordova
  • cordova
  • 2016-09-10 00:10
  • 10487

【一步步学OpenGL 5】-《一致变量Uniform Variables》

教程5:一致变量原文:http://ogldev.atspace.co.uk/www/tutorial05/tutorial05.html背景在这个教程中我们将遇到一个新的shader变量:一致变量(Uniform Variables)。一致变量和普通属性的区别:普通变量所包含的数据是顶点具体化的,...
  • cordova
  • cordova
  • 2016-09-13 16:23
  • 4097

【一步步学OpenGL 15】 -《相机控制2(鼠标事件)》

教程15相机控制2(鼠标事件)原文: http://ogldev.atspace.co.uk/www/tutorial15/tutorial15.html* CSDN完整版专栏: * http://blog.csdn.net/column/details/13062.html背景在这个教程我们将实现...
  • cordova
  • cordova
  • 2016-10-09 19:30
  • 3116

【一步步学OpenGL 13】 -《相机空间》

教程13相机空间背景在之前的几个教程我们已经学习了两种变换。第一种是基本变换,用来改变物体的位置(平移变换)、方向(旋转变换)、尺寸(缩放变换),可以将物体至于3d世界任意位置;第二种变换是透视投影变换用于将3d世界的一个顶点位置投射到2d世界(比如:一个平面),一旦坐标置于2d那么就很容易将他们映...
  • cordova
  • cordova
  • 2016-09-25 16:24
  • 3829

OpenGL学习:欧拉角实现第一人称相机(FPS camera with Euler angle)

上一节视变换(view transformation) ,介绍了相机的设置参数,并建立了圆形坐标系和球形坐标系下的相机位置随着时间改变的绘制立方体程序。程序中用户无法通过键盘和鼠标来和场景中物体交互,本节实现一个第一人称相机来更好地与场景中物体交互。本节代码可地址:https://gith...
  • arag2009
  • arag2009
  • 2017-09-05 17:02
  • 219

OpenGL照相机

我们这节会创建一个游戏世界,里面有一些游戏模型,可以通过键盘的上下左右键,在游戏世界里面行走。 如下图: 决定一个照相机的因素有三个,照相机的位置,照相机的朝向,照相机的向上的正方向 我们可以通过Opgl的一个类GLFrame来获取一个默认的摄像机的数据结构 GLFrame ...
  • jk823394954
  • jk823394954
  • 2015-08-27 20:57
  • 380
    个人资料
    • 访问:454213次
    • 积分:4955
    • 等级:
    • 排名:第6671名
    • 原创:91篇
    • 转载:17篇
    • 译文:30篇
    • 评论:229条
    关于我
    人生苦短,道阻且艰;修行不易,且行且努力。

    【专业兴趣】:
    游戏开发,图形学,图像处理与计算机视觉,iOS平台

    【专业技能】:
    iOS,游戏开发

    【个人主页】:信厚的独立博客
    【个人项目】:个人项目
    【GitHub】:jiangxh1992
    【邮箱】:jiangxinhou艾特outlook点com
    我的微博
    博客专栏
    世界在看我>_<
    把广告压下去