<五>初探opengl,编写我们的镜头

  现在我们尝试编写一个镜头类,有了这个类,我们能上下左右前后移动,感觉在玩fps游戏,很不错,下面开始看看怎么写。

 

  初次接触镜头类是我在魔兽地图编辑中,当时创建一个镜头的步骤就是放到某个位置,调节角度,分别有3个角度可以调节,一个是类似高度一样的东西,一个是环绕着某个点的旋转角度,还有就是镜头的旋转。opengl镜头其实跟这个是差不多的。

  1.首先我们需要定一个摄像机位置 ,也就是把摄像机放到什么位置去

  2.然后我们要定一个目标位置,这个决定我们摄像机观察的方向

  3.然后就是一个上向量,这个一般为(0,1,0)

  从这几个变量,我们可以获得摄像机的右轴,我们用摄像机方向叉乘上向量,就能得到一条垂直于摄像机方向与上向量构成的一个平面的法向量。有了这条轴,我们可以左右平移摄像机。还能得到计算机的上轴 ,就方向和右轴叉乘下就可以得到了,用来上下移动。

  接下来我们可以使用函数lookAt来建构这个观察模型

glm::mat4 view;
view = glm::lookAt(vec3Pos, vec3Target, vec3Up); //提供摄像机位置,摄像机目标,上向量,他会根据上面的方法得出其他东西,构建矩阵

这样作为视图转换中的观察矩阵穿进去就好。貌似忘了记录这个:

转换过程如上,模型变换->视图变换->投射变换->裁剪。

 

首先我们写一段代码,创建一个立方体

float vertices[] = {
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,   // 右上
        0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,   // 右下
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,   // 左下
        -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,    // 左上

        0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,   // 右上
        0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,   // 右下
        -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,   // 左下
        -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f    // 左上
    };


    unsigned int indices[] = {
        3, 2, 1, 0, 1, 3,
        7, 6, 5, 4, 5, 7,
        0, 1, 4, 1, 4, 5,
        0, 3, 4, 3, 4, 7,
        2, 3, 6, 3, 6, 7,
        1, 2, 6, 1, 5, 6,
    };

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    //颜色链接
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    glEnable(GL_DEPTH_TEST);

我们定义了立方体的8个顶点和具体面的索引,其他按照之前做法来就可以了,最后是开启深度测试,不然面都画到前面来,没有了层次就不像立方体了。

绘图代码:

glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

现在应该立方体画出来了,这样还是看不出他是立方体,我们需要转换一下观察的位置。

     glm::mat4 model = glm::mat4(1.0f);
        model = glm::rotate(model, glm::radians(0.0f), glm::vec3(0.5f, 1.0f, 0.0f)); //模型矩阵,可以实现模型基本的操作,例如缩放,位移等
        model = glm::translate(model, cubePositions[i]);

        glm::mat4 view = glm::mat4(1.0f);
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); //观察矩阵,用来调整观察的视野

        glm::mat4 projection = glm::mat4(1.0f);  //投影矩阵,分为正射投影和透视投影。2d一般正射,3d一般透视投影,这里使用了透视投影,营造3d效果
        projection = glm::perspective(glm::radians(fov), (float)800 / 600, 0.1f, 100.0f); //透视投影矩阵生成,fov为镜头的开口角度,
     //创建位置,目标,上向量
        glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); 
        glm::vec3 target = glm::vec3(0, 0, 0);
        glm::vec3 cameraDir = glm::normalize(cameraPos - target);

        glm::vec3 up = glm::vec3(0, 1, 0);
        glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDir));

        glm::vec3 cameraUp = glm::cross(cameraDir, cameraRight);

        float radius = 10.0f;
        float camX = sin(glfwGetTime())*radius;
        float camZ = cos(glfwGetTime())*radius;
        //view = glm::lookAt(glm::vec3(camX, 5, camZ), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
        //view = glm::lookAt(cameraPos, target, up);
        view = glm::lookAt(pos, pos + front, up); //创建观察矩阵,传入了位置,目标,和上向量,这里的目标恒定为摄像机前面的一点,由front控制
       //给着色器传入几个矩阵,供给他们计算
        unsigned int transformLoc = glGetUniformLocation(shader->ID, "model");
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(model));
        unsigned int transformLoc2 = glGetUniformLocation(shader->ID, "view");
        glUniformMatrix4fv(transformLoc2, 1, GL_FALSE, glm::value_ptr(view));
        unsigned int transformLoc3 = glGetUniformLocation(shader->ID, "projection");
        glUniformMatrix4fv(transformLoc3, 1, GL_FALSE, glm::value_ptr(projection));

     glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

因为懒惰,我们没有写这些进阶过来的过程,具体可以看一下opengl教程 坐标系统

几个矩阵分别控制的东西我们已经知道了,接下来就是顶点着色器的编写。

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;

out vec4 vertexColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view*model* vec4(aPos, 1.0);
    vertexColor = vec4(aColor, 1);
}

可以看到,我们从代码里已经传递进来的3个矩阵,通过一定的顺序来相乘来进行转换

我们能看到一个类似的效果,立方体的观察,随着你的调整会从不同角度的观察。

 

  此时,我们要加入点更加游戏的元素,就是使用键盘鼠标来操作镜头移动。

  我们在主循环里加入一个键盘操作处理函数的调用,然后编写我们的方法

while (!glfwWindowShouldClose(window))
{
    handleInput(window);
    draw();
    glfwSwapBuffers(window);
    glfwPollEvents();
}
void Camera::handleInput(GLFWwindow* window)
{
    float speed = 0.005f;
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        pos += speed * front;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        pos -= speed * front;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        pos -= glm::normalize(glm::cross(front, up)) * speed;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        pos += glm::normalize(glm::cross(front, up)) * speed;
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

pos是我们摄像机的位置,front是表示一个方向,让我们镜头保持看向某一个方向,当我们前后移动,我们和front处理,当我们需要左右移动,我们求出右轴后左右变换即可。

然后我们需要修改我们lookAt参数,使其适配移动

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); //cameraPos+cameraFront让镜头目标一致处理front方向上

现在,应该可以进行前后左右的移动了。此刻,我们还要加上鼠标的操作,就完美了。

 

  首先我们得了解一个叫欧拉角的东西,这个是可以表示3d空间中任何旋转的3个值,分别是俯视角(pitch),偏航角(yaw)和滚转角(roll)

用这三个角,我们就能控制我们的镜头各种移动了,我们先看看着3个角怎么实现操作

俯视角pitch是镜头方向和xz平面构成的夹角,假设我们距离原点为1,那y的高度就是sin pitch,x和z是cos pitch。

偏航角yaw是方向偏离x轴的角度,可见z为sin yaw, x为是cos yaw

所以根据上面所看,当我们知道pitch和yaw角度后,即可计算出一个方向向量

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

这几个角度,我们可以通过鼠标的操作来获取。

首先我们做一些设置

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);

设置glfwSetInputMode,鼠标就会锁定在窗口里,鼠标也看不到了,这时候关闭窗口就很麻烦,我在键盘操作里加个了关闭热键。然后添加鼠标操作的回调

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if(firstMouse) //为上次鼠标点设置默认值
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX; //x方向的偏移
    float yoffset = lastY - ypos; //y方向偏移,因为是鼠标向下移动,鼠标位置越大
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.05; //灵敏度
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw   += xoffset;
    pitch += yoffset;

    if(pitch > 89.0f) //这里俯视角不能高于89度,否则视角会发生逆转
        pitch = 89.0f;
    if(pitch < -89.0f)
        pitch = -89.0f;

    glm::vec3 front;
    front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(front);
}

这里就能计算出摄像机新的朝向角度,配合上wads的移动,我们就能实现看起来像3d游戏里的自由移动了。

 

那么入门级别的教程笔记就写到这里了。

posted on 2018-07-13 11:52  usp10  阅读(337)  评论(0编辑  收藏  举报

导航