美文网首页freeCode@IT
GL01-10:OpenGL中3D对象控制

GL01-10:OpenGL中3D对象控制

作者: 杨强AT南京 | 来源:发表于2019-08-09 19:10 被阅读30次

    本文主题是3D对象的状态控制(位置,大小,方向),在OpenGL中3D对象的状态控制用矩阵变换实现,但是为了更加精细与方便控制,整个变换细分成多个阶段。程序员能控制的主要包含:
      1. 世界坐标系(Model)
      2. 照相机坐标系(View)
      3. 透视坐标系(Perspective)
    本文主要内容就是这三个方面。同时还介绍了深度测试的处理,这是让3D显得更加真实与自然的处理技术,OpenGL通过深度缓冲自动实现。


    OpenGL中3D对象控制

    OpenGL中对象状态与行为的控制

    • OpenGL最终呈现给视觉的效果是经过很多变换的,这些变换最终合并成一个变换,为了更好的控制3D对象的状态与行为,这些变换拆分成比较细的模型,通过对不同模型的控制,达到最终不同的视觉效果。
      • 上述其实就是一种屏幕世界--实际世界两者之间的数学建模过程。
        • 实际世界 -> 单纯的数学模型- > 复杂的数学模型 -> 虚拟的世界模型->屏幕坐标。
      • 下面就来说明这种控制的过程,以及负责的控制功能。
    1. OpenGL中3D对象本身的变换只是控制3D对象位置,方向,大小很简单的一部分,实际上3D对象如果孤立的考虑,其位置永远在圆点,大小永远假设为1.0,其实就是标准的坐标系。

      • 局部坐标系:构成局部空间;
    2. 如果多个物体放在一起,则位置、大小、方向需要采用统一的坐标系,原来的3D对象就会根据需要变换成统一的坐标系。

      • 世界坐标系:构成世界空间;
    3. 如果一第一视角观察,整个世界空间的对象,又会呈现不同的方向,大小,这个时候,需要把世界空间中的所有3D对象变换成第一视角坐标系中坐标。

      • 照相机坐标系:照相机空间;
    4. 由于3D对象,在照相机空间中,存在远近,为了体现这种远近关系,使用透视变换实现远近效果的实现,透视变换中,增加了矩阵中的w值,用来控制物体大小。(x/w, y/w, z/w)

      • 透视坐标系:透视空间
    5. 后要显示在屏幕上,形成屏幕坐标系,屏幕坐标系是2D的,其中3D对象转换为2D显示;

      • 屏幕坐标系:屏幕空间;

    OpenGL的对象控制模型

    • 上面5个坐标系的控制,最后一个是标准化的过程,程序员不用干预与关注(想了解原理与实现的,可以回顾下《计算机图形学》这本书),在这里我们主要干预三个环节:

      1. 局部空间 -> 世界空间:Model(就是我们在3D变换中说明的内容)
      2. 世界空间 -> 照相机空间:View(控制视角)
      3. 照相机空间 -> 透视空间(虚拟3D空间):Perspective(透视效果)
    • 这样3D对象的坐标变换成虚拟空间的3D坐标变换关系为

      • new\_point =Perspective \times View \times Model \times point
    • Shader脚本中的代码

    
        uniform mat4 model;
        uniform mat4 view;
        uniform mat4 projection;
        void main(){
            gl_Position = projection * view * model * vec4(aPos.x, aPos.y, aPos.z, 1.0);
        }
    
    
    • 为了更好控制3D,需要在代码中传递透视矩阵projection,照相机矩阵View与3D对象变换矩阵Model。
      • 如果不需要控制,可以使用标准单位矩阵(无变换)。
                // 世界坐标
                glm::mat4 model = glm::mat4(1.0f);
                unsigned int modelLoc = glGetUniformLocation(programmID, "model"); // 设置变换矩阵
                glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model);
    
                // 相机坐标
                glm::mat4 view = glm::mat4(1.0f);
                unsigned int viewLoc = glGetUniformLocation(programmID, "view"); // 设置变换矩阵
                glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0].x);
    
                // 裁剪坐标
                glm::mat4 projection = glm::mat4(1.0f);
                unsigned int projectionLoc = glGetUniformLocation(programmID, "projection"); // 设置变换矩阵
                glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, &projection[0].x);
    

    标准的3D状态控制编程例子

    • 因为手工写坐标变换矩阵太麻烦,所以这里直接使用GLM库完成各种花式矩阵运算。
    1. common.h文件
    #ifndef COMMON_H
    
    #define COMMON_H
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <math.h>
    #include <GL/glew.h>
    #include <GLFW/glfw3.h>
    
    
    GLFWwindow* initContext();  // 上下文初始化
    void destroyConext();
    GLboolean initOpenGL();     // OpenGL初始化与加载
    GLuint yqData();        // 数据准备
    GLuint yqIndecies();    // 索引
    GLuint yqShader();          // GLSL
    GLuint yqTexture();     // 纹理
    ////////////////////////////////
    #endif
    
    
    1. common.cpp
    #include "common.h"
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"
    // 上下文初始化
    GLFWwindow* initContext(){
        if(!glfwInit()){
            printf("GLFW初始化失败!\n");
            return NULL;
        }
        // 设置提示
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
        GLFWwindow* win = glfwCreateWindow(600,600, "OpenGL透视与照相机", NULL, NULL);
        if(! win){
            printf("创建窗体失败!\n");
            return NULL;
        }
        // 设置当前调用线程的上下文为win;
        glfwMakeContextCurrent(win);
        return win;
    }
    void destroyConext(){
        glfwTerminate();
    }
    // OpenGL初始化与加载
    GLboolean initOpenGL(){
        if(glewInit() != GLEW_OK){   // GLEW_OK:#define GLEW_OK 0
            printf("OpenGL加载失败!\n");
            return GL_FALSE;
        }
        return GL_TRUE;
    }
    GLuint yqShader(){    
        // 纹理坐标的传递,纹理坐标的使用
        const char *vertexShaderSource = ""
            "#version 410 core\n" 
            "layout (location = 0) in vec3 aPos;\n"         // 顶点坐标
            "layout (location = 1) in vec2 aTexture;\n"     // 纹理坐标
            "layout (location = 2) in vec4 aColor;\n"     // 颜色坐标
            "out vec2 vTexture;\n"         // 传递纹理坐标到片着色器
            "out vec4 vColor;\n"         // 传递颜色坐标到片着色器
            "uniform mat4 model;\n"
            "uniform mat4 view;\n"
            "uniform mat4 projection;\n"
            "void main(){\n" 
            "   gl_Position = projection * view * model * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
            "   vTexture = aTexture;\n"     // 输出到下一个着色器
            "   vColor = aColor;\n"     // 输出到下一个着色器
            "}\0";   // 空字符
    
        const char *fragmentShaderSource = ""
            "#version 410 core\n"
            "out vec4 FragColor;\n" 
            "in vec2 vTexture;\n"               // 上面顶点着色器传递过来的纹理坐标,用来生成采样
            "in vec4 vColor;\n"               // 上面顶点着色器传递过来的纹理坐标,用来生成采样
            "uniform sampler2D sTexture;\n"
            "void main(){\n"
            "   FragColor =  texture(sTexture, vTexture) * vColor;\n"   // 采样
            "}\n\0";
        unsigned int vertexShader;
        vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
        glCompileShader(vertexShader);  
    
        unsigned int fragmentShader;
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
        glCompileShader(fragmentShader);
    
        unsigned int shaderProgram;
        shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);
    
        // glUseProgram(shaderProgram);
    
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        return shaderProgram;
    }
    GLuint yqData(){
        // 顶点属性数组
        GLuint arrayID;
        glGenVertexArrays(1, &arrayID); 
        glBindVertexArray(arrayID);  
        
        GLfloat r = 0.5f;
        GLfloat p = r * cos(45.0f * M_PI / 180.0f);
        // 数据(顶点坐标 + 纹理坐标)
        GLfloat  vertices[] = {    // 纹理坐标按照(0,0) - (1,1)之间的4个点确定
            0.0f,  0.0f,  0.3f,     0.0f, 0.0f,   1.0f, 0.0f, 0.0f, 1.0f,
               r,  0.0f,  0.0f,     1.0f, 0.0f,   0.0f, 1.0f, 1.0f, 1.0f,
               p,     p,  0.0f,     0.0f, 1.0f,   0.0f, 0.0f, 1.0f, 1.0f,
            0.0f,     r,  0.0f,     1.0f, 0.0f,   0.0f, 1.0f, 0.0f, 1.0f, 
              -p,     p,  0.0f,     0.0f, 1.0f,   0.0f, 0.0f, 1.0f, 1.0f,
              -r,  0.0f,  0.0f,     1.0f, 0.0f,   0.0f, 1.0f, 0.0f, 1.0f,
              -p,    -p,  0.0f,     0.0f, 1.0f,   0.0f, 0.0f, 1.0f, 1.0f,
            0.0f,    -r,  0.0f,     1.0f, 0.0f,   0.0f, 1.0f, 0.0f, 1.0f,
               p,    -p,  0.0f,     0.0f, 1.0f,   0.0f, 0.0f, 1.0f, 1.0f,
               r,  0.0f,  0.0f,     1.0f, 0.0f,   0.0f, 1.0f, 0.0f, 1.0f
        };
        // 数据缓冲
        GLuint bufferID;
        glGenBuffers(1, &bufferID);
        glBindBuffer(GL_ARRAY_BUFFER, bufferID);
        glBufferData(GL_ARRAY_BUFFER,sizeof(vertices), vertices, GL_STATIC_DRAW);
    
        // 顶点属性
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), NULL);  
        glEnableVertexAttribArray(0);  
        // 文理属性
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (const void *)(3 * sizeof(GLfloat)));  // 顶点属性(输入):注意location=3,对应的顶点索引也是3
        glEnableVertexAttribArray(1); 
        // 颜色索引
        glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (const void *)(5 * sizeof(GLfloat)));  // 顶点属性(输入):注意location=3,对应的顶点索引也是3
        glEnableVertexAttribArray(2); 
    
        // 关闭顶点分组的操作
        glBindVertexArray(0); // 要使用再切换回来
        return arrayID;
    }
    GLuint yqIndecies(){
        unsigned int indices[] = { // 注意绘制的顺序
            0, 1, 2, 3, 4, 5, 6, 7, 8, 1
        };
        GLuint indexID;
        glGenBuffers(1, &indexID);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexID);  // 指定索引缓冲的类型
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);   // 拷贝索引数据
        return  indexID;
    }
    GLuint yqTexture(){
        GLuint textureID;
        // 创建一个纹理ID
        glGenTextures(1, &textureID);
        // 绑定纹理ID到纹理的管理数据
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, textureID);   //指定内存的纹理类型
        
        // 使用第三方库加载图像
        int width, height, depth;
        unsigned char *data = stbi_load("bird.png", &width, &height, &depth, 0);
        // 设置纹理使用的图像数据
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);  // 生成类型为2D的纹理映射
        // 释放图像
        stbi_image_free(data);
        return textureID;
    }
    
    
    
    1. 结构文件gl01_graphics_umbrella.cpp
    #include "common.h"
    
    #include <glm/glm.hpp>   //glm::vec3, glm::vec4, glm::mat4
    #include <glm/ext.hpp>  //glm::translate, glm::rotate, glm::scale, glm::perspective, glm::pi
    #include <glm/gtc/type_ptr.hpp>    // 把glm结构体转换为指针
    
    int main(int argc, char const *argv[]){
        GLFWwindow *win = initContext(); 
        if(!win){
            return -1;
        }
        if(!initOpenGL()){
            destroyConext();
            return -1;
        }
        GLuint arrayID = yqData();
        glBindVertexArray(arrayID);     // 必须开启顶点分组
        GLuint indeciesID = yqIndecies();
        glBindVertexArray(0);           // 关闭顶点分组
        GLuint programmID = yqShader();
        GLuint textureID = yqTexture(); // 加载纹理
        glUseProgram(programmID);       // 已经激活,就无需再激活
        GLint location = glGetUniformLocation(programmID, "sTexture");   // 设获取shader的uniform变量
        glUniform1i(location, 0);       // 设置纹理对象为0位置的纹理数据,
    
        glUseProgram(0); // 关闭Shader程序
    
        GLdouble oldTime = glfwGetTime();
        while(!glfwWindowShouldClose(win)){
            if(glfwGetTime() - oldTime > 0.1){
                glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);
                glBindVertexArray(arrayID);     // 绑定顶点分组
                glUseProgram(programmID);       // 使用Shader
    
                // 世界坐标
                glm::mat4 model = glm::mat4(1.0f);
                unsigned int modelLoc = glGetUniformLocation(programmID, "model"); // 设置变换矩阵
                glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0].x);
    
                // 相机坐标
                glm::mat4 view = glm::mat4(1.0f);
                unsigned int viewLoc = glGetUniformLocation(programmID, "view"); // 设置变换矩阵
                glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0].x);
    
                // 裁剪坐标
                glm::mat4 projection = glm::mat4(1.0f);
                unsigned int projectionLoc = glGetUniformLocation(programmID, "projection"); // 设置变换矩阵
                glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, &projection[0].x);
    
                glPointSize(5.0f);
                // glLineWidth(20.0f);
                // glEnable(GL_DEPTH_TEST);
                glEnable(GL_CULL_FACE);
                glCullFace(GL_BACK);   // 提出背面
                // glFrontFace(GL_CCW);   // 顺序控制(顺时针/逆时针)
                
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);   // 多边形绘制模式(GL_POINT,GL_LINE, GL_FILL)默认GL_FILL
                
                glDrawElements(GL_TRIANGLE_FAN, 10, GL_UNSIGNED_INT, 0);
                
                glUseProgram(0);                // 解除使用Shader
                glBindVertexArray(0);           // 接触顶点分组
                glfwSwapBuffers(win);
                oldTime = glfwGetTime();
            }
            // glfwWaitEvents();
            glfwPollEvents();
        }
        destroyConext();
        return 0;
    }
    
    // g++ -o main gl01_graphics_umbrella.cpp common.cpp -l glfw -l glew -framework opengl
    
    
    1. 运行效果
      • 世界坐标系,照相机坐标系,透视坐标系默认处理效果

    照相机

    照相机说明

    • 到目前为止,所有的3D对象都是在标准坐标系中完成绘制。可以在标准坐标系中做各种变换来改变3D对象的大小、位置、旋转等。

      • 3D对象在自身坐标系中的变换,是其自身的变换动作。
    • 照相机是以人为第一视角的方式处理3D对象,就算3D对象没有变换,第一视角变化,3D对象也会变化。

      • 照相机是另外一套坐标系统,需要把3D对象所在坐标系的坐标转换为照相机的坐标系统。
      • 照相机的本质实际上是两个坐标系的转换,就是一个矩阵运算。

    照相机的数学模型

    照相机的解释

    • 照相机的数学模型就是一个坐标系的建立,体现在代数中就是一个矩阵的建立。
      • 矩阵的行向量(列向量)表示第一视角的坐标系的三个坐标轴(由三个正交的向量构成)。
        1. 方向向量(相机的方向控制)
        2. 右向量(相机的平衡控制)
        3. 上向量(使用方向向量与右向量的叉乘(向量积)得到)
      • 三个向量 + 第一视角的位置 形成一个朝向矩阵。

    照相机的LookAt矩阵的表示

    方向向量

    • 方向向量使用向量的差运算得到

      • 方向向量 = 照相机的位置向量 - 3D对象的位置向量
        • 3D对象的位置向量一般可以去3D对象坐标系的原点。
    • 假设照相机位置坐标是camerPos = (P_x, P_y, P_x),3D对象坐标系原点自然是targetPos= (0,0,0)

      • 则方向向量directionVec = camerPos- targetPos= (x_0, y_0, z_0)-(0, 0, 0)
    • glm提供向量的运算符-实现向量的差运算。

    右向量

    • 因为假设3D对象是平衡的!可以取一个向上的向量up = (0, 1, 0)
      • 则右向量为rightVec = up \times directionVec
      • 注意:
        • 为什么up在前面?因为OpenGL中采用的是右手系(D3D使用左手系),只要使用右手比划下,就知道计算右向量的两个叉乘向量的先后。


          右手坐标系示意图
    • glm提供cross函数实现两个向量的叉乘运算。

    • 补充:向量的叉乘运算定义

      • R \times U = \begin{bmatrix} \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} \\ \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} \end{bmatrix} = (\color{red}{R_y}\color{green}{U_z} - \color{red}{R_z}\color{green}{U_y}, \color{red}{R_z}\color{green}{U_x} - \color{red}{R_x}\color{green}{U_z}, \color{red}{R_x}\color{green}{U_y} - \color{red}{R_y}\color{green}{U_x})
    • 注意上面的计算规律。

    上向量

    • 上向量就是方向向量与右向量的叉乘。
      • 则上向量为upVec = directVext \times rightVec

    使用glm库得计算结果

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #include <glm/glm.hpp>   //glm::vec3, glm::vec4, glm::mat4
    #include <glm/ext.hpp>  //glm::translate, glm::rotate, glm::scale, glm::perspective, glm::pi
    #include <glm/gtc/type_ptr.hpp>    // 把glm结构体转换为指针
    
    using namespace glm;
    int main(int argc, const char** argv) {
        // 计算照相机的方向
        vec3 cameraPos = vec3(0.0f, 0.0f, 10.0f);
        vec3 targetPos = vec3(0.0f, 0.0f,  0.0f);
    
        vec3 directVec = cameraPos - targetPos;
        directVec = normalize(directVec);       // 单位化,正则化,标准化
        printf("%f,%f,%f\n", directVec.x, directVec.y, directVec.z);
    
    
        // 计算照相机的右向量
        vec3 up = vec3(0.0f, 1.0f, 0.0f);
        vec3 rightVec = cross(up, directVec);      // 叉乘不满足交换律(满足反交换律,所以注意方向)
        rightVec = normalize(rightVec); 
        printf("%f,%f,%f\n", rightVec.x, rightVec.y, rightVec.z);
    
        // 计算相机最后一个坐标向量(上向量)
    
        vec3 upVec = cross(directVec, rightVec);     // 向量叉乘的时候,谁前谁后,使用右手坐标系比划下就可以确定。
        upVec = normalize(upVec); 
        printf("%f,%f,%f\n", upVec.x, upVec.y, upVec.z);
    
        return 0;
    }
    
    // g++   -omain  gl02_matrix_vector.cpp
    
    
    • 输出结果
    yangqiangdeMacBook-Pro:glew05_camera_coord yangqiang$ g++    -omain  gl02_matrix_vector.cpp
    yangqiangdeMacBook-Pro:glew05_camera_coord yangqiang$ ./main
    0.000000,0.000000,1.000000
    1.000000,0.000000,0.000000
    0.000000,1.000000,0.000000
    

    LookAt矩阵

    • 使用上面构造的新的坐标系,做一个平移即可。构造的新的矩阵就是LookAt矩阵:

      • LookAt = \begin{bmatrix} \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} & 0 \\ \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} & 0 \\ \color{blue}{D_x} & \color{blue}{D_y} & \color{blue}{D_z} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 1 & 0 & 0 & -\color{purple}{P_x} \\ 0 & 1 & 0 & -\color{purple}{P_y} \\ 0 & 0 & 1 & -\color{purple}{P_z} \\ 0 & 0 & 0 & 1 \end{bmatrix}
    • 记号说明:

      • (\color{purple}{P_x} , \color{purple}{P_y} , \color{purple}{P_z} )表示照相机的位置;
      • (\color{red}{R_x} , \color{red}{R_y} , \color{red}{R_z} )表示照相机的右向量(X-坐标轴);
      • (\color{green}{U_x} , \color{green}{U_y} , \color{green}{U_z} )表示照相机的位置(Y-坐标轴);
      • (\color{blue}{D_x} , \color{blue}{D_y} , \color{blue}{D_z} )表示照相机的位置(Z-坐标轴);
    • glm中提供一个lookAt函数可以直接搞定这个矩阵的计算过程。

    照相机的代码实现

    
        glm::mat4 view = glm::lookAt(
            glm::vec3(0.0f, 0.0f, -0.8f),     // 相机位置
            glm::vec3(0.0f, 0.0f,  0.0f),     // 目标(3D对象,一般是原坐标系原点)
            glm::vec3(0.0f, 1.0f,  0.0f));    // up向量
        // view  = glm::rotate(view, glm::radians(45.0f), glm::vec3(0.0f, 0.0f,1.0f));
        unsigned int viewLoc = glGetUniformLocation(programmID, "view"); // 设置变换矩阵
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0].x);
    
    
    • 说明:

      1. 上面相机的位置值,因为没有透视矩阵处理,所以值只能在[-1.0 , 1.0]之间。超过这个范围就不会被处理。如果加上透视图,只要在透视图指定Far平面内,都可见。
      2. 上面相机的Z坐标的正负,可以说明相机的方向是Z正半轴,还是负半轴。
    • 下面是z=- 0.8与0.8的效果(前面是-0.8),注意伞形的顶点z坐标是0.3。

      • 照相机的使用
    • 上面第一个图的洞产生的原理示意图如下:

      • 照相机的绘制范围示意图

    透视图

    透视图模型:

    经典的透视图示意图
    • 透视图的原理与数学计算过程比较麻烦,但是几个关键参数必须明确:

      • Fov:视觉角度(上下视觉范围)
      • Near Plane - Far Plane:可视区域
      • Aspect:纵横比(屏幕的纵横比)
    • 透视图的数学原理可以参考如下文章:

      • http://www.songho.ca/opengl/gl_projectionmatrix.html

    构建透视矩阵

    glm::perspective函数介绍

    1. 函数定义:
    GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> perspective(T fovy, T aspect, T zNear, T zFar)
        {
            GLM_IF_CONSTEXPR(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_LH_ZO)
                return perspectiveLH_ZO(fovy, aspect, zNear, zFar);
            else GLM_IF_CONSTEXPR(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_LH_NO)
                return perspectiveLH_NO(fovy, aspect, zNear, zFar);
            else GLM_IF_CONSTEXPR(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_ZO)
                return perspectiveRH_ZO(fovy, aspect, zNear, zFar);
            else GLM_IF_CONSTEXPR(GLM_CONFIG_CLIP_CONTROL == GLM_CLIP_CONTROL_RH_NO)
                return perspectiveRH_NO(fovy, aspect, zNear, zFar);
        }
    
    1. 参数说明:
      • fovy:视角(y-范围):格式为弧度表示
      • aspect:纵横比(x-范围)
      • zNear与zFar:近平面与原平面(z-范围)
    • 透视矩阵使用glm库就变得非常简单了。
        // 透视矩阵
        // glm::mat4 projection = glm::mat4(1.0f);
        glm::mat4 projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.0f, 100.0f);
        unsigned int projectionLoc = glGetUniformLocation(programmID, "projection"); // 设置变换矩阵
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
    

    透视图使用例子

    • 使用透视图以后,照相机的位置就不在局限于[ -1.0, 1.0 ]之间了。下面是相机与透视图的设置代码。
        // 相机与透视图只需要设置一次。
        // 相机坐标
        // glm::mat4 view = glm::mat4(1.0f);
        // view  = glm::translate(view, glm::vec3(0.0f, 0.0f, -1.0f));
        glm::mat4 view = glm::lookAt(
            glm::vec3(0.0f, 0.0f, -5.0f),     // 相机位置
            glm::vec3(0.0f, 0.0f,  0.0f),     // 目标(3D对象,一般是原坐标系原点)
            glm::vec3(0.0f, 1.0f,  0.0f));    // up向量
        // view  = glm::rotate(view, glm::radians(45.0f), glm::vec3(0.0f, 0.0f,1.0f));
        unsigned int viewLoc = glGetUniformLocation(programmID, "view"); // 设置变换矩阵
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0].x);
    
        // 透视矩阵
        // glm::mat4 projection = glm::mat4(1.0f);
        glm::mat4 projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.0f, 100.0f);
        unsigned int projectionLoc = glGetUniformLocation(programmID, "projection"); // 设置变换矩阵
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
    
    • 上面代码的运行效果
      • 透视图使用

    使用世界坐标系

    • 使用世界坐标系来构建多样化的3D世界。

      • 每个3D对象都统一变换到世界坐标(就是每个3D对象都有一个变换矩阵)
      • 注:世界坐标的变换已经在前面另外一个主题中介绍过,这里直接使用。
    • 实现代码如下:

        glm::vec3 npos[] = {    // 再构建4把伞
            glm::vec3( 2.0f,  2.0f, -15.0f), 
            glm::vec3(-1.5f, -2.2f, -2.5f),  
            glm::vec3(-3.8f, -2.0f, -12.3f),  
            glm::vec3( 2.4f, -0.4f, -3.5f)
        };
        GLdouble oldTime = glfwGetTime();
        while(!glfwWindowShouldClose(win)){
            if(glfwGetTime() - oldTime > 0.1){
                glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);
                
                glUseProgram(programmID);       // 使用Shader
    
                glBindVertexArray(arrayID);     // 绑定顶点分组
                // 世界坐标(每个对象绘制,都可以设置一次,来控制在世界坐标系中的位置与大小)
                glm::mat4 model = glm::mat4(1.0f);
                unsigned int modelLoc = glGetUniformLocation(programmID, "model"); // 设置变换矩阵
                glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0].x);
    
                glDrawElements(GL_TRIANGLE_FAN, 10, GL_UNSIGNED_INT, 0);
                // 按照上面定义的位置,循环绘制4个3D对象。
                for (int i =0 ; i < sizeof(npos)/sizeof(glm::vec3); i++){
                    model = glm::mat4(1.0f);
                    model = glm::translate(model,npos[I]);
                    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, &model[0].x);
                    glDrawElements(GL_TRIANGLE_FAN, 10, GL_UNSIGNED_INT, 0);
                }
                
                glUseProgram(0);                // 解除使用Shader
                glBindVertexArray(0);           // 接触顶点分组
                glfwSwapBuffers(win);
                oldTime = glfwGetTime();
            }
            // glfwWaitEvents();
            glfwPollEvents();
        }
        destroyConext();
    
    • 效果如下:
      • 使用世界坐标系构建3D世界

    深度测试

    • 深度测试主要是OpenGL提供了一种z坐标比较测试,每个绘制的像素的深度会保存,如果每个需要绘制的像素,在绘制前需要与缓冲中的z-值比较,满足的才会绘制。

    深度相关函数

    • 深度测试相关函数4个:

      • glEnable:开启深度测试;
        • 设置位位:GL_DEPTH_TEST
      • glDepthFunc:深度测试判别方式(大于还是小于),这个不对就会出现问题
      • glClearDepth:设置深度缓冲清空值;
      • glClear:清空深度缓冲;
      • glDepthMask:深度是否写入缓冲。
    • 注意:

      • 默认深度测试是关闭的,所以启动深度测试是必须的。
      • 某些时候glDepthFunc设置深度测试条件是必须的,比如GL_LEQUAL(小于等于在默认情况下,就不会正常显示)

    深度测试方式

    GL_NEVER:都不绘制
    GL_LESS:绘制像素深度小于缓冲深度才绘制
    GL_EQUAL:等于
    GL_LEQUAL:小于等于
    GL_GREATER:大于
    GL_NOTEQUAL:不等于
    GL_GEQUAL:大于等于
    GL_ALWAYS:总是绘制
    
    • 默认的是:GL_LESS

    深度测试代码

    1. 开启
        glEnable(GL_DEPTH_TEST);
        // glEnable(GL_DEPTH_CLAMP);
        glDepthFunc(GL_GEQUAL);
    
    1. 清除深度缓冲
                glClearDepth (0.0f);
                // glClear (GL_DEPTH_BUFFER_BIT);
                // glClear(GL_COLOR_BUFFER_BIT);
                glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    
    • 在Mac上,OpenGL4.1版本,测试方式为GL_LESS无法完成绘制。

    相关文章

      网友评论

        本文标题:GL01-10:OpenGL中3D对象控制

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