freeCode@IT

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

2019-08-09  本文已影响30人  杨强AT南京

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


OpenGL中3D对象控制

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

  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的对象控制模型


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

            // 世界坐标
            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状态控制编程例子

  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. 运行效果
    • 世界坐标系,照相机坐标系,透视坐标系默认处理效果

照相机

照相机说明

照相机的数学模型

照相机的解释

照相机的LookAt矩阵的表示

方向向量

右向量

上向量

使用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矩阵

照相机的代码实现


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

透视图

透视图模型:

经典的透视图示意图

构建透视矩阵

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

透视图使用例子

    // 相机与透视图只需要设置一次。
    // 相机坐标
    // 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));

使用世界坐标系

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

深度测试

深度相关函数

深度测试方式

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

深度测试代码

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

上一篇下一篇

猜你喜欢

热点阅读