<五>初探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游戏里的自由移动了。

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

原文地址:https://www.cnblogs.com/usp10/p/9304181.html

时间: 2024-10-14 07:51:06

<五>初探opengl,编写我们的镜头的相关文章

&lt;五&gt;初探opengl,变换我们的图形

这节主要是对我们的纹理矩形进行一下变换,例如缩放,旋转,中间需要运用到一些线性代数的东西这里就不再阐述,因为我自己也不怎么会...我们直接介绍代码怎么写吧. 矩阵的相乘是从右往左读取的,这点提醒一下自己. GLM 代码中大部分的矩阵处理信息都是通过glm库来处理的,全称OPENGL MATHEMATICS ,去这里下载,把glm文件夹放到工程的include下即可.代码i引入文件库 #include <glm/glm.hpp> #include <glm/gtc/matrix_trans

基于OpenGL编写一个简易的2D渲染框架-05 渲染文本

阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/ 简要步骤: 获取要绘制的字符的 Unicode 码,使用 FreeType 库获取对应的位图数据,添加到字符表中(后面同样的字符可以再表中直接索引),将字符表上的字符填充到一张纹理上.计算每个字符的纹理坐标,使用渲染器绘制 注意的问题: 对于中英文混合的字符串,使用 char 存储时,英文字符占 1 个字节,而中

基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒子系统和其他纹理时会得不到想要的结果,渲染器还存在许多的不足: 1.当渲染许多透明图形时,没有对其进行排序,使得本应透明的图形没有透明. 2.不能对不同的顶点使用不同的状态进行渲染. 渲染器要做的东西很简单,就是 1.传递数据到 GPU 2.设置 OpenGL 状态信息(Alpha测试.模板测试.深

基于OpenGL编写一个简易的2D渲染框架01——创建窗口

最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的是 vs2013,使用C++语言编写项目.这个小项目叫Simple2D,意味着简易的2D框架.最终的目的是可以渲染几何图形和图片,最后尝试加上一个2D粒子系统和Box2D物理引擎,并编译一个简单的游戏. 第一步,就是创建一个Win32项目. 接下来,生成一个窗口.编写一个RenderWindow类,

基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader

Shader 只是进行一些简单的封装,主要功能: 1.编译着色程序 2.绑定 Uniform 数据 3.根据着色程序的顶点属性传递顶点数据到 GPU 着色程序的编译 GLuint Shader::createShaderProgram(const char* vsname, const char* psname) { std::string vShaderSource, fShaderSource; std::ifstream vShaderFile, fShaderFile; vShaderF

【Unity 3D】学习笔记三十五:游戏实例——摄像机切换镜头

摄像机切换镜头 在游戏中常常会切换摄像机来观察某一个游戏对象,能够说.在3D游戏开发中,摄像头的切换是不可或缺的. 这次我们学习总结下摄像机怎么切换镜头. 代码: private var Camera0: GameObject; private var Camera1: GameObject; private var Camera2: GameObject; private var Camera: GameObject; function Start() { //获取摄像机对象 Camera =

基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer

假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 position; Vec4 color; }; 然后填充矩形四个顶点是数据信息: Vertex* data = ( Vertex* ) malloc(sizeof( Vertex ) * 4); data[0].position.set(0, 0, 0); data[1].position.set(

基于OpenGL编写一个简易的2D渲染框架-04 绘制图片

阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeImage 库加载图像数据,再创建 OpenGL 纹理,通过 Canvas2D 画布绘制,最后又 Renderer 渲染器渲染 本来想用 soil 库加载图像数据的,虽然方便,但是加载有些格式的图像文件时会出现一些问题.最后,改用 FreeImage 库来加载图像了. 添加 FreeImage 库到工

基于OpenGL编写一个简易的2D渲染框架02——搭建OpenGL环境

由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20Creating%20a%20window/ 以下,我摘取了一点片段 Windows上的OpenGL库 如果你是Windows平台,opengl32.lib已经包含在Microsoft SDK里了,它在Visual Studio安装的时候就默认安装了.由于这篇教程用的是VS编译器,并且是在Windo