OpenGL 立方体和金字塔

Posted

技术标签:

【中文标题】OpenGL 立方体和金字塔【英文标题】:OpenGL cube and pyramid 【发布时间】:2018-10-04 01:42:38 【问题描述】:

我有这个 OpenGL 代码来绘制一个立方体和金字塔。但是,该程序将金字塔和立方体一起旋转。我的任务是只让立方体本身移动,而不是同时移动两个对象。我知道要发生这种情况,我必须为两者实现着色器。我不确定如何同时实现两个着色器。有小费吗?

/*
This program demonstrates simple lighting.
A pyramid is lighted by a point light and can be rotated by mouse.

Ying Zhu
Georgia State University

October 2016
*/

// GLEW header
#include <GL/glew.h> // This must appear before freeglut.h

// Freeglut header
#include <GL/freeglut.h>

// GLM header files
#include <glm/glm.hpp> 

#include <glm/gtc/matrix_transform.hpp> 
// #include <glm/gtx/transform2.hpp>
#include <glm/gtc/matrix_access.hpp>
// #include <glm/gtx/projection.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp> 

// C++ header files
#include <iostream>

using namespace std;
using namespace glm;

#define BUFFER_OFFSET(offset) ((GLvoid *) offset)

// VBO buffer IDs
GLuint vertexArrayBufferID = 0;
GLuint normalArrayBufferID = 0;
GLuint cubePosition = 0;
GLuint cubeElements = 0;

GLuint program; // shader program ID

// Shader variable IDs
GLint vPos; // vertex attribute: position
GLint normalID; // vertex attribute: normal

GLint mvpMatrixID; // uniform variable: model, view, projection matrix
GLint modelMatrixID; // uniform variable: model, view matrix
GLint normalMatrixID; // uniform variable: normal matrix for transforming normals
GLint lightSourcePositionID; // uniform variable: for lighting calculation
GLint diffuseLightProductID; // uniform variable: for lighting calculation
GLint ambientID;
GLint attenuationAID;
GLint attenuationBID;
GLint attenuationCID;

// Transformation matrices
mat4 projMatrix;
mat4 mvpMatrix;
mat4 modelMatrix;
mat4 viewMatrix;
mat3 normalMatrix;  // Normal matrix for transforming normals

// Light parameters
vec4 lightSourcePosition = vec4(0.0f, 4.0f, 0.0f, 1.0f);
vec4 diffuseMaterial = vec4(0.5f, 0.5f, 0.0f, 1.0f);
vec4 diffuseLightIntensity = vec4(1.0f, 1.0f, 1.0f, 1.0f);
vec4 ambient = vec4(0.2f, 0.2f, 0.2f, 1.0f);
float attenuationA = 1.0f;
float attenuationB = 0.2f;
float attenuationC = 0.0f;

vec4 diffuseLightProduct;

// Camera parameters
vec3 eyePosition = vec3(0.0f, 0.0f, 4.0f);
vec3 lookAtCenter = vec3(0.0f, 0.0f, 0.0f);
vec3 upVector = vec3(0.0f, 1.0f, 0.0f);
float fieldOfView = 30.0f;
float nearPlane = 0.1f;
float farPlane = 1000.0f;

// Mouse controlled rotation angles
float rotateX = 0;
float rotateY = 0;

struct VertexData 
    GLfloat vertex[3];

    VertexData(GLfloat x, GLfloat y, GLfloat z) 
        vertex[0] = x; vertex[1] = y; vertex[2] = z;
    
;

//---------------------------------------------------------------
// Initialize vertex arrays and VBOs
void prepareVBOs() 
    // Define a 3D pyramid. 
    GLfloat vertices[][4] = 
        1.0f, -1.0f, 1.0f, 1.0f, // face 1
        -1.0f, -1.0f, -1.0f, 1.0f,
        1.0f, -1.0f, -1.0f, 1.0f,
         1.0f, -1.0f, -1.0f, 1.0f , // face 2
        0.0f, 1.0f, 0.0f, 1.0f,
         1.0f, -1.0f, 1.0f, 1.0f ,
         1.0f, -1.0f, 1.0f, 1.0f , // face 3
         0.0f, 1.0f, 0.0f, 1.0f ,
        -1.0f, -1.0f, 1.0f, 1.0f,
         -1.0f, -1.0f, 1.0f, 1.0f , // face 4
         0.0f, 1.0f, 0.0f, 1.0f ,
         -1.0f, -1.0f, -1.0f, 1.0f ,
         0.0f, 1.0f, 0.0f, 1.0f , // face 5
         1.0f, -1.0f, -1.0f, 1.0f ,
         -1.0f, -1.0f, -1.0f, 1.0f ,
         1.0f, -1.0f, 1.0f, 1.0f , // face 6
         -1.0f, -1.0f, 1.0f, 1.0f ,
         -1.0f, -1.0f, -1.0f, 1.0f 
    ;

    GLfloat normals[][4] = 
        0.0f, -1.0f, 0.0f, 1.0f, // normal 1
        0.0f, -1.0f, 0.0f, 1.0f ,
        0.0f, -1.0f, 0.0f, 1.0f ,
        0.8944f, 0.4472f, 0.0f, 1.0f, // normal 2
         0.8944f, 0.4472f, 0.0f, 1.0f ,
         0.8944f, 0.4472f, 0.0f, 1.0f ,
        -0.0f, 0.4472f, 0.8944f, 1.0f, // normal 3
         -0.0f, 0.4472f, 0.8944f, 1.0f ,
         -0.0f, 0.4472f, 0.8944f, 1.0f ,
        -0.8944f, 0.4472f, 0.0f, 1.0f, // normal 4
         -0.8944f, 0.4472f, 0.0f, 1.0f ,
         -0.8944f, 0.4472f, 0.0f, 1.0f ,
        0.0f, 0.4472f, -0.8944f, 1.0f, // normal 5
         0.0f, 0.4472f, -0.8944f, 1.0f ,
         0.0f, 0.4472f, -0.8944f, 1.0f ,
         0.0f, -1.0f, 0.0f, 1.0f , // normal 6
         0.0f, -1.0f, 0.0f, 1.0f ,
         0.0f, -1.0f, 0.0f, 1.0f 
    ;

    // Cube positioins 
    VertexData vertexData[] = 
        VertexData(0.0, 0.0, 0.0), /* Index 0 */
        VertexData(0.0, 0.0, 1.0), /* Index 1 */
        VertexData(0.0, 1.0, 0.0), /* Index 2 */
        VertexData(0.0, 1.0, 1.0), /* Index 3 */
        VertexData(1.0, 0.0, 0.0), /* Index 4 */
        VertexData(1.0, 0.0, 1.0), /* Index 5 */
        VertexData(1.0, 1.0, 0.0), /* Index 6 */
        VertexData(1.0, 1.0, 1.0), /* Index 7 */
    ;

    // Cube elements
    GLubyte indices[] = 
        4, 5, 7, // +X face
        4, 7, 6,
        0, 2, 3, // ‐X face
        0, 3, 1,
        2, 6, 7, // +Y face
        2, 7, 3,
        0, 1, 5, // ‐Y face
        0, 5, 4,
        0, 4, 6, // +Z face
        0, 6, 2,
        1, 3, 7, // ‐Z face
        1, 7, 5
    ;
    // Get an unused buffer object name. Required after OpenGL 3.1. 
    glGenBuffers(1, &vertexArrayBufferID);

    // If it's the first time the buffer object name is used, create that buffer. 
    glBindBuffer(GL_ARRAY_BUFFER, vertexArrayBufferID);

    // Allocate memory for the active buffer object. 
    // 1. Allocate memory on the graphics card for the amount specified by the 2nd parameter.
    // 2. Copy the data referenced by the third parameter (a pointer) from the main memory to the 
    //    memory on the graphics card. 
    // 3. If you want to dynamically load the data, then set the third parameter to be NULL. 
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &normalArrayBufferID);
    glBindBuffer(GL_ARRAY_BUFFER, normalArrayBufferID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(normals), normals, GL_STATIC_DRAW);

    glGenBuffers(1, &cubePosition);
    glBindBuffer(GL_ARRAY_BUFFER, cubePosition);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData),
        vertexData, GL_STATIC_DRAW);

    glGenBuffers(1, &cubeElements);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeElements);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices),
        indices, GL_STATIC_DRAW);


//---------------------------------------------------------------
// Print out the output of the shader compiler
void printLog(GLuint obj)

    int infologLength = 0;
    char infoLog[1024];

    if (glIsShader(obj)) 
        glGetShaderInfoLog(obj, 1024, &infologLength, infoLog);
    
    else 
        glGetProgramInfoLog(obj, 1024, &infologLength, infoLog);
    

    if (infologLength > 0) 
        cout << infoLog;
    


//-------------------------------------------------------------------
void prepareShaders() 
    // Vertex shader source code
    // A point light source is implemented. 
    // For simplicity, only the ambient and diffuse components are implemented. 
    // The lighting is calculated in world space, not in camera space. 
    const char* vSource = 
        "#version 330\n"
        "in vec4 vPos;"
        "in vec4 normal;"

        "uniform mat4x4 mvpMatrix;"
        "uniform mat4x4 modelMatrix;"
        "uniform mat3x3 normalMatrix;"
        "uniform vec4 lightSourcePosition;"
        "uniform vec4 diffuseLightProduct;"
        "uniform vec4 ambient;"
        "uniform float attenuationA;"
        "uniform float attenuationB;"
        "uniform float attenuationC;"
        "out vec4 color;"

        "void main() "
        "   gl_Position = mvpMatrix * vPos;"
        // Transform the vertex position to the world space. 
        "   vec4 transformedVertex = modelMatrix * vPos;"
        // Transform the normal vector to the world space. 
        "   vec3 transformedNormal = normalize(normalMatrix * normal.xyz);"
        // Light direction
        "   vec3 lightVector = normalize(transformedVertex.xyz - lightSourcePosition.xyz);"
        // Distance between the light source and vertex
        "   float dist = distance(lightSourcePosition.xyz, transformedVertex.xyz);"
        // Attenuation factor
        "   float attenuation = 1.0f / (attenuationA + (attenuationB * dist) + (attenuationC * dist * dist));"
        // Calculate the diffuse component of the lighting equation.
        "   vec4 diffuse = attenuation * (max(dot(transformedNormal, lightVector), 0.0) * diffuseLightProduct);"
        // Combine the ambient component and diffuse component. 
        "   color = ambient + diffuse;"
        ""
    ;

    // Fragment shader source code
    const char* fSource = 
        "#version 330\n"
        "in vec4 color;"
        "out vec4 fragColor;"
        "void main() "
        "   fragColor = color;"
        ""
    ;

    // Declare shader IDs
    GLuint vShader, fShader;

    // Create empty shader objects
    vShader = glCreateShader(GL_VERTEX_SHADER);
    fShader = glCreateShader(GL_FRAGMENT_SHADER);

    // Attach shader source code the shader objects
    glShaderSource(vShader, 1, &vSource, NULL);
    glShaderSource(fShader, 1, &fSource, NULL);

    // Compile shader objects
    glCompileShader(vShader);
    printLog(vShader);

    glCompileShader(fShader);
    printLog(fShader);

    // Create an empty shader program object
    program = glCreateProgram();

    // Attach vertex and fragment shaders to the shader program
    glAttachShader(program, vShader);
    glAttachShader(program, fShader);

    // Link the shader program
    glLinkProgram(program);
    printLog(program);


//---------------------------------------------------------------
// Retrieve the IDs of the shader variables. Later we will
// use these IDs to pass data to the shaders. 
void getShaderVariableLocations(GLuint shaderProgram) 

    // Retrieve the ID of a vertex attribute, i.e. position
    vPos = glGetAttribLocation(shaderProgram, "vPos");
    normalID = glGetAttribLocation(shaderProgram, "normal");

    mvpMatrixID = glGetUniformLocation(shaderProgram, "mvpMatrix");

    modelMatrixID = glGetUniformLocation(shaderProgram, "modelMatrix");
    normalMatrixID = glGetUniformLocation(shaderProgram, "normalMatrix");

    lightSourcePositionID = glGetUniformLocation(shaderProgram, "lightSourcePosition");
    diffuseLightProductID = glGetUniformLocation(shaderProgram, "diffuseLightProduct");
    ambientID = glGetUniformLocation(shaderProgram, "ambient");

    attenuationAID = glGetUniformLocation(shaderProgram, "attenuationA");
    attenuationBID = glGetUniformLocation(shaderProgram, "attenuationB");
    attenuationCID = glGetUniformLocation(shaderProgram, "attenuationC");


//---------------------------------------------------------------
void setShaderVariables() 
    // value_ptr is a glm function
    glUniformMatrix4fv(mvpMatrixID, 1, GL_FALSE, value_ptr(mvpMatrix));
    glUniformMatrix4fv(modelMatrixID, 1, GL_FALSE, value_ptr(modelMatrix));
    glUniformMatrix3fv(normalMatrixID, 1, GL_FALSE, value_ptr(normalMatrix));

    glUniform4fv(lightSourcePositionID, 1, value_ptr(lightSourcePosition));
    glUniform4fv(diffuseLightProductID, 1, value_ptr(diffuseLightProduct));
    glUniform4fv(ambientID, 1, value_ptr(ambient));
    glUniform1f(attenuationAID, attenuationA);
    glUniform1f(attenuationBID, attenuationB);
    glUniform1f(attenuationCID, attenuationC);


//---------------------------------------------------------------
// Set lighting related parameters
void setLightingParam() 
    diffuseLightProduct = diffuseMaterial * diffuseLightIntensity;


//---------------------------------------------------------------
// Build the model matrix. This matrix will transform the 3D object to the proper place. 
mat4 buildModelMatrix() 

    mat4 rotationXMatrix = rotate(mat4(1.0f), radians(rotateX), vec3(1.0f, 0.0f, 0.0f));
    mat4 rotationYMatrix = rotate(mat4(1.0f), radians(rotateY), vec3(0.0f, 1.0f, 0.0f));

    mat4 matrix = rotationYMatrix * rotationXMatrix;

    return matrix;


//---------------------------------------------------------------
void buildMatrices() 
    modelMatrix = buildModelMatrix();

    mvpMatrix = projMatrix * viewMatrix * modelMatrix;

    normalMatrix = column(normalMatrix, 0, vec3(modelMatrix[0][0], modelMatrix[0][1], modelMatrix[0][2]));
    normalMatrix = column(normalMatrix, 1, vec3(modelMatrix[1][0], modelMatrix[1][1], modelMatrix[1][2]));
    normalMatrix = column(normalMatrix, 2, vec3(modelMatrix[2][0], modelMatrix[2][1], modelMatrix[2][2]));

    // Use glm::inverseTranspose() to create a normal matrix, which is used to transform normal vectors. 
    normalMatrix = inverseTranspose(normalMatrix);


//---------------------------------------------------------------
// Handles the display event
void display()

    // Clear the window with the background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    buildMatrices();

    setShaderVariables();

    // Activate the shader program
    glUseProgram(program);

    // If the buffer object already exists, make that buffer the current active one. 
    // If the buffer object name is 0, disable buffer objects. 
    glBindBuffer(GL_ARRAY_BUFFER, vertexArrayBufferID);

    // Associate the vertex array in the buffer object with the vertex attribute: "position"
    glVertexAttribPointer(vPos, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    // Enable the vertex attribute: "position"
    glEnableVertexAttribArray(vPos);

    glBindBuffer(GL_ARRAY_BUFFER, normalArrayBufferID);
    glVertexAttribPointer(normalID, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
    glEnableVertexAttribArray(normalID);

    // Start the shader program. Draw the object. The third parameter is the number of triangles. 
    glDrawArrays(GL_TRIANGLES, 0, 18);

    glBindBuffer(GL_ARRAY_BUFFER, cubePosition);
    glVertexAttribPointer(vPos, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    glEnableVertexAttribArray(vPos);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeElements);
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));

    // Refresh the window
    glutSwapBuffers();


//---------------------------------------------------------------
// Handles the reshape event
void reshape(int width, int height)

    // Specify the width and height of the picture within the window
    glViewport(0, 0, width, height);

    projMatrix = perspective(fieldOfView, (float)width / (float)height, nearPlane, farPlane);

    viewMatrix = lookAt(eyePosition, lookAtCenter, upVector);


//---------------------------------------------------------------
// Read mouse motion data and convert them to rotation angles. 
void passiveMotion(int x, int y) 

    rotateY = (float)x * -0.8f;
    rotateX = (float)y * -0.8f;

    // Generate a dislay event to force refreshing the window. 
    glutPostRedisplay();


//-----------------------------------------------------------------
void init() 
    prepareVBOs();

    prepareShaders();

    getShaderVariableLocations(program);

    setLightingParam();

    // Specify the background color
    glClearColor(1, 1, 1, 1);

    glEnable(GL_DEPTH_TEST);

    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);


//---------------------------------------------------------------
void main(int argc, char *argv[])

    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);

    glutCreateWindow("Lighting Demo");

    glutReshapeWindow(800, 800);

    glewInit();

    init();

    // Register the display callback function
    glutDisplayFunc(display);

    // Register the reshape callback function
    glutReshapeFunc(reshape);

    // Register the passive mouse motion call back function
    // This function is called when the mouse moves within the window
    // while no mouse buttons are pressed. 
    glutPassiveMotionFunc(passiveMotion);

    // Start the event loop
    glutMainLoop();

【问题讨论】:

你必须使用不同的模型矩阵。在绘制调用之前更改模型矩阵 (glUniformMatrix4fv(modelMatrixID, 1, GL_FALSE, value_ptr(modelMatrix));)。 所以你是说我应该将 value_ptr() 的参数更改为我将为立方体本身创建的单个 modelMatrix? 对象(网格)的位置和方向由模型矩阵定义。如果要绘制具有不同位置和/或方向的不同对象,则必须使用不同的模型矩阵。您可以使用相同的着色器,但必须在绘制对象之前更改模型矩阵 (glUniformMatrix4fv)。 【参考方案1】:

好吧,这里最明显的罪魁祸首是为两者设置一个 ModelMatrix - 我看不到您的代码中有任何逻辑可以为您正在渲染的每个对象独立设置它们。

由于每个对象都有不同的旋转(大概,除非您打算在另一个之上绘制一个不同的平移),您可能希望为每个绘制调用生成/加载不同的模型矩阵。

【讨论】:

【参考方案2】:

你不需要使用不同的着色器,你只需要使用不同的模型矩阵。假设您的场景中有两个对象,如下所示:

while (!myWindow(shouldClose))

    myShader.use();

    glBindVertexArray(myVao1);
    glDrawArrays(GL_TRIANGLES, 0, x); // Draw pyramid

    glBindVertaxArray(myVao2);
    glDrawArrays(GL_TRIANGLES, 0, x); // Draw cube

假设您只希望第二个模型在 y 轴上旋转,您可以这样做:

float rotationDegree = 0;
while (!myWindow(shouldClose))

    myShader.use();
    myShader.setMat4(glm::mat4(1.0f)) // Make sure to set it to normal matrix for the pyrmamid
    glBindVertexArray(myVao1);
    glDrawArrays(GL_TRIANGLES, 0, x); // Draw pyramid

    glBindVertaxArray(myVao2);
    glm::mat4 model = glm::mat4(1.0f);
    glm::rotate(model, glm::radians(rotationDegree), glm::vec3(0.0f, 1.0f, 0.0f));
    rotateionDegree += 0.01;
    myShader.setMat4("model", model); // Set you model matrix in your shader.
    glDrawArrays(GL_TRIANGLES, 0, x); // Draw cube

【讨论】:

以上是关于OpenGL 立方体和金字塔的主要内容,如果未能解决你的问题,请参考以下文章

使用 C++ 在 OpenGL 中使用键盘移动 3d 形状

使用 C++ 在 OpenGL 中移动自动旋转的 3d 多边形

为 3D Cup 创建圆柱体基础(现代 OpenGL、GLM)

在使用 C++ 在 OpenGL 中移动形状时按时间自动旋转形状

同时旋转多个立方体的问题

立方体旋转 - OpenGL