【OpenGL_03】纹理、变换、坐标系统、摄像机

LearnOpenGL网站学习,记录一下学习笔记

纹理

纹理

本章节细节与原理在博客:纹理映射
个人理解的纹理映射:纹理贴图的大小在[0,1],[0,1]之间的正方形中间,将这个坐标分别映射到物体(已经经过标准化),比如说也是个正方形表面,得到它的标准化设备坐标,分别将标准化的四个顶点与纹理贴图的四个顶点映射,也就是将坐标的RGB(A)值赋值给标准化坐标。中间的坐标通过插值赋予RGB(A)值。

纹理环绕方式

纹理坐标的范围通常是从(0, 0)到(1, 1),如果我们把纹理坐标设置在范围之外,OpenGL默认的行为是重复这个纹理图像(我们基本上忽略浮点纹理坐标的整数部分),但OpenGL提供了更多的选择:

纹理过滤

纹理像素: Texture Pixel也叫Texel,你可以想象你打开一张.jpg格式图片,不断放大你会发现它是由无数像素点组成的,这个点就是纹理像素
纹理坐标: 纹理坐标是你给模型顶点设置的那个数组,OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色。

在对纹理进行映射的过程中,可能会遇见,一个纹理贴图较小,而映射的部分面积很大,那么这个贴图需要放大。OpenGL有以下两种常见的处理方式:
GL_NEAREST:临近过滤

GL_LINEAR:线性过滤

多级渐变纹理

在纹理缩小过程中,使用原图(也就是高分辨率贴图)可能会造成不真实和内存浪费的问题,可以使用分辨率较小的贴图解决问题,也就是Mipmap贴图
注意只能在原纹理缩小过程中使用

生成纹理过程

glGenTexture( ):创建纹理、
glBindTexture( ): 绑定纹理
glTexParameteri( ): 设置纹理过滤和纹理环绕

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

加载纹理

加载多张纹理时,需要在FragmentShader文本中采用多个采样器,将多个纹理进行混合,OpenGL有多个采样器,至少16个,编号分别为数字GL_TEXTURE0,GL_TEXTURE1,…

#version 330 core
...

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

在主程序中,需要激活相应的纹理采样器

ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

变换

变换相关知识在博客【Notes_3】现代图形学入门——基础变换、MVP变换模型
四元数相关知识在博客【OpenGL_02】欧拉角、万向锁、四元数

坐标系统

坐标相关知识在博客【Unity Shader入门精要】——坐标空间、坐标空间的变换

摄像机

位置、方向、右轴、上轴

确定一个射线机的位置需要以下一个属性:摄像机的世界坐标系位置、观察方向、一个指向它右测的向量以及一个指向它上方的向量

1.摄像机位置:获取摄像机位置很简单。摄像机位置简单来说就是世界空间中一个指向摄像机位置的向量

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

2.摄像机方向:用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量:

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

3.右轴:我们需要的另一个向量是一个右向量(Right Vector),它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量(如果我们交换两个向量叉乘的顺序就会得到相反的指向x轴负方向的向量):

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

4.上轴:现在我们已经有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把方向向量和右向量进行叉乘:

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

LootAt

LookAt矩阵本质上就是一个矩阵空间变换矩阵
其中子空间是世界坐标系、父空间是摄像机坐标系。
注意,位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向。

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),    //position
           glm::vec3(0.0f, 0.0f, 0.0f),            //目标位置
           glm::vec3(0.0f, 1.0f, 0.0f));           //世界坐标上向量

自由移动

定义摄像机变量

glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);

LookAt函数变成了:

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上我们刚刚定义的方向向量。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。让我们摆弄一下这些向量,在按下某些按钮时更新cameraPos向量。

计算LookAt矩阵时,需要用到摄像机方向,摄像机方向的求法是用CameraPosition-CameraTarget。在glm::lookat( )函数中,定义了CameraTarget = CameraPosition+CameraFront。所以最后的LookAt函数始终指向目标函数,也就是cameraFront

void processInput(GLFWwindow *window)
{
    ...
    float cameraSpeed = 0.05f; // adjust accordingly
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        cameraPos += cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        cameraPos -= cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

视角移动

欧拉角相关知识在博客【OpenGL_02】欧拉角、万向锁、四元数

基于上图,相机的旋转向量direction的求法如下图:

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));

鼠标输入

在主程序中,告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。捕捉光标表示的是,如果焦点在你的程序上(译注:即表示你正在操作这个程序,Windows中拥有焦点的程序标题栏通常是有颜色的那个,而失去焦点的程序标题栏则是灰色的),光标应该停留在窗口中(除非程序失去焦点或者退出)。我们可以用一个简单地配置调用来完成:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

为了计算俯仰角和偏航角,我们需要让GLFW监听鼠标移动事件。(和键盘输入相似)我们会用一个回调函数来完成,函数的原型如下:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if(firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;  // 注意这里是相反的,因为OpenGL的y坐标是从底部往顶部依次增大的,屏幕坐标系的原点是左上角
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.05;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw   += xoffset;
    pitch += yoffset;

    if(pitch > 89.0f)
        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);
}

缩放


glfwSetScrollCallback(window, scroll_callback);//注册鼠标滚动回调事件

projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
  if(fov >= 1.0f && fov <= 45.0f)
    fov -= yoffset;
  if(fov <= 1.0f)
    fov = 1.0f;
  if(fov >= 45.0f)
    fov = 45.0f;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值