美文网首页
LearnOpenGL 坐标系统(1)

LearnOpenGL 坐标系统(1)

作者: li_礼光 | 来源:发表于2020-09-25 10:04 被阅读0次

五个坐标系统

  • 局部空间(物体空间)
  • 世界空间
  • 观察空间
  • 裁剪空间
  • 屏幕空间

五个坐标系统的概念 : 坐标系统

为了将坐标, 从一个坐标系转换到另一个坐标系. 我们需要用到的几个矩阵. 最重要的几个分别是 :

  • 模型(Model)
  • 视图(View)
  • 投影(Projection)

顶点坐标 始于 局部空间 ==> 世界坐标 ==> 观察坐标 ==> 裁剪坐标 ==> 屏幕坐标


投射投影

由投影矩阵创建的观察区域. 参照以下的图.
也就是从 NEAR PLANE 到 FAR PLANE之间的这段, 也叫 平截头体

平截头体

正射投影

一个类似立方体的平截头体. 参照以下的图
也就是NEAR PLANE的面积 跟FAR PLANE面积是一样的,

image.png

把它们都组合到一起

为每一个步骤都创建了一个转换矩阵:模型矩阵观察矩阵投影矩阵。一个顶点的坐标将会根据以下过程被转换到裁剪坐标:

Vclip = MprojectionMviewMmodelVlocal

注意每个矩阵被运算的顺序是相反的(记住我们需要从右往左乘上每个矩阵)。
最后的顶点应该被赋予顶点着色器中的gl_Position且OpenGL将会自动进行透视划分和裁剪。

Important

顶点着色器的输出需要所有的顶点都在裁剪空间内,而这是我们的转换矩阵所做的。OpenGL然后在裁剪空间中执行透视划分从而将它们转换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(在我们的例子中屏幕是800 *600)。这个过程称为视口转换。




实践

坐标系统先大概知道有这些东西, 这一章的主题可能会比较难理解 , 因为从这几个矩阵里面, 还不清楚该怎么用, 或则怎么理解.

 Vclip          = Mprojection   ⋅   Mview   ⋅   Mmodel  ⋅   Vlocal
 对应 :
 gl_Position    = projection    *   view    *   model   *   vec4(aPos, 1.0);
 
 注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。
 最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。

Shader

#define STRINGIZE(x) #x
#define SHADER(shader) STRINGIZE(shader)

/// 着色器程序之间的数据传递
static char *myCoordinateVertexShaderStr = SHADER(
    \#version 330 core\n
    layout (location = 0) in vec3 position; //顶点数据源输入
    layout (location = 1) in vec3 color; //颜色数据源输入
    out vec4 vertexColor;//把片元着色器的颜色从这里输出
                                                 
    uniform mat4 myProjection;//投影矩阵
    uniform mat4 myView;//观察矩阵
    uniform mat4 myModel;//模型矩阵
                               
    void main()
    {
        gl_Position = myProjection * myView * myModel * vec4(position, 1.0f);
        vertexColor = vec4(color, 1.0f); //输出给片元着色器
    }
);

//片元着色器程序
static char *myCoordinateFragmentShaderSrc = SHADER(
    \#version 330 core\n
    in vec4 vertexColor;//从顶点着色器中拿color的值
    out vec4 color;
    void main()
    {
      color = vertexColor;//获取值
    }
);

顶点数据

#include "glad.h"
#include <GLFW/glfw3.h>

//正方形
static GLfloat myCoordinateVertices[] = {
    -0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  //左下     红
     0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  //右下     绿
     0.5f,  0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  //右上     绿
    -0.5f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  //左上     红
};
  

//索引
static GLint myCoordinateVerticesIndices[] = { // 注意索引从0开始!
    0, 1, 2, // 第一个三角形
    0, 3, 2  // 第二个三角形
};

程序

#include <iostream>

#include "MyCoordinate.hpp"
#include "MyProgram.hpp"
#include "MyCoordinateShader.hpp"
#include "MyCoordinateVertices.hpp"
#include "glm.hpp"
#include "matrix_transform.hpp"
#include "type_ptr.hpp"

int runMyCoordinate() {
    int result = glfwInit();
    if (result == GL_FALSE) {
        printf("glfwInit 初始化失败");
        return -1;
    }
    
    //这里的宏不好提示出来, 根据LearnOpenGL的文档提示, 用这三个
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //Mac平台需要加入
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);

    //创建一个Window
    GLFWwindow *window = glfwCreateWindow(600, 400, "My Opengl Window", NULL, NULL);
    if(!window) {
        printf("window 创建失败");
    }
    
    //opengl运行模式 -- 单线程, 理解为跟当前的Window做一次绑定操作.
    glfwMakeContextCurrent(window);
    
    //任何的OpenGL接口调用都必须在初始化GLAD库后才可以正常访问。如果成功的话,该接口将返回GL_TRUE,否则就会返回GL_FALSE。
    gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
    
     //----------------------------------------------------------------------
    //先创建我们的Program对象, 加载顶点着色器程序和片元着色器程序
    MyProgram myProgram = MyProgram(myCoordinateVertexShaderStr, myCoordinateFragmentShaderSrc);
    
    GLuint VBO , VAO , EBO;
    unsigned int squareIndicesCount = 0;
    //创建VBO
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(myCoordinateVertices), myCoordinateVertices, GL_STATIC_DRAW);

    //创建VAO
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    
    //创建EBO, 这里的EBO相当于索引的作用
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(myCoordinateVerticesIndices), myCoordinateVerticesIndices, GL_STATIC_DRAW);

    //解绑VAO
    glBindVertexArray(0);
    //计算索引个数
    squareIndicesCount = sizeof(myCoordinateVerticesIndices)/sizeof(myCoordinateVerticesIndices[0]);
   
    glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

    
    //进行绘制
    while(!glfwWindowShouldClose(window)){
       //检查事件
        glfwPollEvents();
        
        //渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(myProgram.program);
        
        
        ///变换处理
        
        GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
        GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
        GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
        
        //单位矩阵
        glm::mat4 trans = glm::mat4(1.0f);

        //矩阵赋值
        glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(trans));
        glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(trans));
        glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(trans));

        
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, squareIndicesCount, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
        
        //交换缓冲
        glfwSwapBuffers(window);
    }
    
    //程序销毁
    glfwTerminate();
    
    return 1;
}

先运行一遍看看效果

没有做任何变换

前面有说 :
为每一个步骤都创建了一个转换矩阵:模型矩阵观察矩阵投影矩阵。一个顶点的坐标将会根据以下过程被转换到裁剪坐标:

Vclip = MprojectionMviewMmodelVlocal

注意由右往左的顺序看这几个矩阵

所以在Shader中定义三个对应的Uniform.

uniform mat4 myProjection;//投影矩阵
uniform mat4 myView;//观察矩阵
uniform mat4 myModel;//模型矩阵
gl_Position = myProjection * myView * myModel * vec4(position, 1.0f);

其实跟这个公式是一样的.

然后我们在程序中.

GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
        
//单位矩阵
glm::mat4 trans = glm::mat4(1.0f);

//矩阵赋值
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(trans));
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(trans));

这里做了一个和单位矩阵转换的操作. 其实这里的意思就是对应的顶点数据没有发生任何的改变.



投影矩阵变换

我们的顶点坐标, 通过模型, 观察和投影矩阵来转换, 最终得到的对象应该是:

  • 往后想地板倾斜
  • 离我们有点距离
  • 透视展示(顶点越远, 变得越小)

整体效果类似于看火车轨道, 越远越窄,越小.

代码

///变换处理
GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");

//单位矩阵
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
        
//矩阵变换
model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(1.0f,0.0f,0.0f));//以x轴旋转45度
view = glm::translate(view, glm::vec3(0.0f,0.0f, -3.0f)); // 向Z轴的负方向移动
projection = glm::perspective(glm::radians(60.0f), 1.0f, 0.01f, 100.f);//投影矩阵
     
//矩阵值处理  
glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
效果图

注意不要混淆, 前面的平移, 旋转, 缩放的矩阵变换. 跟这里的效果不一样, 这里是相当于整个对象倾斜了.

PS : 在坐标系统源代码中.直接定义了矩阵, 没有赋值单位矩阵, 有可能会导致你显示不出来.

glm::mat4 model;
glm::mat4 view;
glm::mat4 projection;

这里最好初始化一个矩阵都是单位矩阵. 比如

glm::mat4 model = glm::mat4(1.0f);



3D

自制顶点数据

//立方体 : 8个点连成一个立方体
static GLfloat myCoordinateCubeVertices[] = {
    //左侧
    -0.5f, -0.5f, -0.5f, 1.0f,0.0f,0.0f, //左下后 红 0
    -0.5f, -0.5f,  0.5f, 1.0f,0.0f,0.0f, //左下前 红 1
    -0.5f,  0.5f,  0.5f, 1.0f,1.0f,0.0f, //左上前 蓝 2
    -0.5f,  0.5f, -0.5f, 1.0f,1.0f,0.0f, //左上后 蓝 3
    //右侧
     0.5f, -0.5f, -0.5f, 0.0f,0.0f,0.0f, //右下后 白 4
     0.5f, -0.5f,  0.5f, 0.0f,0.0f,0.0f, //右下前 白 5
     0.5f,  0.5f,  0.5f, 0.0f,1.0f,1.0f, //右上前 蓝 6
     0.5f,  0.5f, -0.5f, 0.0f,1.0f,1.0f, //右上后 绿 7
};


//立方体索引
static GLint myCoordinateCubeVerticesIndices[] = { // 注意索引从0开始!
    
    //第1面 : 左 0123
    0, 1, 2,
    0, 2, 3,
    
    //第2面 : 右 4567
    4, 5, 6,
    4, 6, 7,

    //第3面 : 上 2367
    2, 3, 6,
    3, 6, 7,

    //第4面 : 下 0145
    0, 1, 4,
    1, 4, 5,

    //第5面 : 前 1256
    1, 2, 5,
    2, 5, 6,

    //第6面 : 后 0347
    0, 3, 4,
    3, 4, 7,
};

加入深度测试 GL_DEPTH_TEST

  while(!glfwWindowShouldClose(window)){
       //检查事件
        glfwPollEvents();
        
        //渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUseProgram(myProgram.program);
         
        glEnable(GL_DEPTH_TEST);

        ///变换处理
        GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
        GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
        GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");

        //单位矩阵
        glm::mat4 model = glm::mat4(1.0f);
        glm::mat4 view = glm::mat4(1.0f);
        glm::mat4 projection = glm::mat4(1.0f);
        
        model = glm::rotate(model,(GLfloat)glfwGetTime() * 1.0f,  glm::vec3(1.0f,1.0f,0.0f));//以x,y轴旋转
        view = glm::translate(view, glm::vec3(0.0f,0.0f, -3.0f)); // 向Z轴的负方向移动
        projection = glm::perspective(glm::radians(60.0f), 1.0f, 0.01f, 100.f);//投影矩阵
        
        glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

        
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, squareIndicesCount, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
        
        //交换缓冲
        glfwSwapBuffers(window);
    }
QQ20200924-020827-HD.gif



纹理Cube

目前以颜色的方式生成一个旋转的立方体. 现在给这个立方体加上纹理贴图.

修改一下Shader

/// 顶点着色器 CubeTexture
static char *myCoordinateCubeTextureVertexShaderStr = SHADER(
    \#version 330 core\n
    layout (location = 0) in vec3 position; //顶点数据源输入
    layout (location = 1) in vec3 color; //颜色数据源输入
    layout (location = 2) in vec2 texCoords; //纹理数据源输入(2D)

    out vec2 vertexTexCoords;//把片元着色器的纹理从这里输出
                                                             
    uniform mat4 myProjection;//投影
    uniform mat4 myView;//观察
    uniform mat4 myModel;//模型
                                 
    void main()
    {
        gl_Position = myProjection * myView * myModel * vec4(position, 1.0f);
        vertexTexCoords = texCoords;
    }
);

/// 片元着色器 CubeTexture
static char *myCoordinateCubeTextureFragmentShaderSrc = SHADER(
    \#version 330 core\n
    in vec2 vertexTexCoords;//从顶点着色器中拿纹理的值
                        
    uniform sampler2D myTexture;
                                                               
    out vec4 FragColor;
                                                               
    void main()
    {
      FragColor = texture(myTexture, vertexTexCoords);
    }
);

程序修改

设置纹理坐标

//纹理坐标, 纹理坐标用的三角形坐标一致
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)0);
glEnableVertexAttribArray(2);

生成纹理

unsigned int texture;
unsigned char *data;
int width, height, nrChannels;
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);
data = stbi_load( "/Users/liliguang/Desktop/LearnOpengl/LearnOpenGl/LearnOpenGl/Demo/Common/ImgSources/dizhuan.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);
  glBindTexture(GL_TEXTURE_2D, 0);
}
else
{
  std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

加载纹理

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(myProgram.program, "myTexture"), 0);

效果

纹理Cube.gif


多个自旋转纹理Cube

glm::vec3 cubePositions[10];
for (int i = 0; i<10; i++) {
    float xRandomNum = arc4random_uniform(10.0) - 5.0f;
    float yRandomNum = arc4random_uniform(10.0) - 5.0f;
    cubePositions[i] =glm::vec3( xRandomNum,  yRandomNum,  0.0f);
}

//进行绘制
while(!glfwWindowShouldClose(window)){
   //检查事件
    glfwPollEvents();
    
    //渲染指令
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(myProgram.program);
     
    //加载纹理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(glGetUniformLocation(myProgram.program, "myTexture"), 0);
    
    //单位矩阵
    glm::mat4 model = glm::mat4(1.0f);
    glm::mat4 view = glm::mat4(1.0f);
    glm::mat4 projection = glm::mat4(1.0f);
    
    ///变换处理
    GLint myModelLoc = glGetUniformLocation(myProgram.program,"myModel");
    GLint myViewLoc = glGetUniformLocation(myProgram.program,"myView");
    GLint myProjectionLoc = glGetUniformLocation(myProgram.program,"myProjection");
          
    projection = glm::perspective(glm::radians(120.0f), 1.0f, 0.01f, 100.f);//投影矩阵
    glUniformMatrix4fv(myProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
    
    glBindVertexArray(VAO);
    for(unsigned int i = 0; i < 10; I++)
    {
        //改变模型数据的位置
        model = glm::mat4(1.0f);
        model = glm::translate(model,glm::vec3(cubePositions[i].x,cubePositions[i].y,0.0f));//x,y平移
        glUniformMatrix4fv(myModelLoc, 1, GL_FALSE, glm::value_ptr(model));

        view = glm::mat4(1.0f);
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -5.0f)); // 向Z轴的负方向移动
        view = glm::rotate(view,(GLfloat)glfwGetTime() * 1.0f,  glm::vec3(cubePositions[i].x,cubePositions[i].y,0.0f));//以x,y轴旋转
        glUniformMatrix4fv(myViewLoc, 1, GL_FALSE, glm::value_ptr(view));

        glDrawElements(GL_TRIANGLES, squareIndicesCount, GL_UNSIGNED_INT, 0);
    }

    //交换缓冲
    glfwSwapBuffers(window);
}
glBindVertexArray(0);

//程序销毁
glfwTerminate();
多个Cube

PS : 如果需要自旋,这里需要注意translate和rotate 对应的关系,
比如translate 矩阵x轴移动2, y轴移动2. 要以xy轴旋转, 那么对应的是rotate的x也是2,y也是2. 等比例对应.



最后留一个问题:就是前后两面纹理正常,其他四面不正常,怎么修改

相关文章

网友评论

      本文标题:LearnOpenGL 坐标系统(1)

      本文链接:https://www.haomeiwen.com/subject/qvfwyktx.html