Sử dụng OpenGL thuần túy và GLAD để vẽ đồ họa tùy chỉnh trong OSG

Hướng dẫn này trình bày cách tích hợp các lệnh OpenGL gốc, bao gồm cả việc sử dụng thư viện GLAD, để vẽ các đối tượng đồ họa tùy chỉnh trong OpenSceneGraph (OSG).

Thiết lập Cơ bản

Để bắt đầu, chúng ta cần một cấu trúc OSG cơ bản với một Geode để chứa Drawable tùy chỉnh của chúng ta. Mã sau đây minh họa điều này:


#include <osgViewer/Viewer>
#include <osg/Geode>
#include "CustomDrawable.h" // Tệp tiêu đề cho đối tượng tùy chỉnh của chúng ta

int main()
{
    // Tạo nút gốc Geode
    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    // Thêm đối tượng Drawable tùy chỉnh vào Geode
    root->addDrawable(new CustomDrawable);

    // Thiết lập trình xem OSG
    osgViewer::Viewer viewer;
    viewer.setSceneData(root);
    viewer.realize();

    // Các cài đặt liên quan đến OpenGL để tương thích tốt hơn
    viewer.getCamera()->getGraphicsContext()->getState()->resetVertexAttributeAlias(false);
    viewer.getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true);
    viewer.getCamera()->getGraphicsContext()->getState()->setUseVertexAttributeAliasing(true);

    // Chạy trình xem
    return viewer.run();
}
  

Lớp Drawable Tùy chỉnh

Chúng ta sẽ định nghĩa một lớp kế thừa từ osg::Drawable để xử lý việc vẽ tùy chỉnh. Lớp này sẽ quản lý dữ liệu đỉnh, chỉ số và trạng thái OpenGL cần thiết.


#include <osg/Drawable>
#include <osg/State>
#include <osg/GLExtensions>

class CustomDrawable: public osg::Drawable
{
public:
    CustomDrawable();
    ~CustomDrawable();

protected:
    // Ghi đè để tính toán hộp giới hạn
    osg::BoundingBox computeBoundingBox() const override;
    // Ghi đè để thực hiện logic vẽ OpenGL
    void drawImplementation(osg::RenderInfo& renderInfo) const override;

private:
    // Dữ liệu đỉnh và chỉ số
    GLfloat* m_vertices = nullptr;
    GLuint* m_indices = nullptr;

    // Đối tượng OpenGL (VAO, VBO, EBO)
    mutable unsigned int m_vao = 0;
    mutable unsigned int m_vbo = 0;
    mutable unsigned int m_ebo = 0;

    // Cờ để đảm bảo khởi tạo OpenGL chỉ diễn ra một lần
    mutable bool m_needsInitialization = true;

    // Khai báo biến cho shader (nếu sử dụng)
    // Shader* m_shader = nullptr;
};
  

Triển khai Drawable Tùy chỉnh

Phần này bao gồm việc triển khai các hàm thành viên của lớp CustomDrawable. Chúng ta sẽ khởi tạo dữ liệu hình học và xử lý việc vẽ OpenGL trong drawImplementation.


#include "CustomDrawable.h"
#include <iostream> // Để gỡ lỗi

// Định nghĩa kích thước lưới tùy chỉnh nếu cần
// const int GRID_SIZE = 100; // Ví dụ

CustomDrawable::CustomDrawable()
{
    // Vô hiệu hóa DisplayList và bật VBO cho hiệu suất
    this->setUseDisplayList(false);
    this->setUseVertexBufferObjects(true);

    // Khởi tạo dữ liệu đỉnh cho một hình tam giác đơn giản (ví dụ)
    // Kích thước: 4 đỉnh, mỗi đỉnh 3 thành phần (x, y, z)
    m_vertices = new GLfloat[4 * 3];
    m_vertices[0] =  1.0f; m_vertices[1] =  1.0f; m_vertices[2] = 0.0f; // Đỉnh trên phải
    m_vertices[3] =  1.0f; m_vertices[4] = -1.0f; m_vertices[5] = 0.0f; // Đỉnh dưới phải
    m_vertices[6] = -1.0f; m_vertices[7] = -1.0f; m_vertices[8] = 0.0f; // Đỉnh dưới trái
    m_vertices[9] = -1.0f; m_vertices[10] =  1.0f; m_vertices[11] = 0.0f; // Đỉnh trên trái

    // Khởi tạo dữ liệu chỉ số để tạo thành hai tam giác
    // Kích thước: 6 chỉ số cho 2 tam giác
    m_indices = new GLuint[6];
    m_indices[0] = 0; m_indices[1] = 1; m_indices[2] = 2; // Tam giác 1
    m_indices[3] = 2; m_indices[4] = 3; m_indices[5] = 0; // Tam giác 2
}

CustomDrawable::~CustomDrawable()
{
    // Giải phóng bộ nhớ động
    delete[] m_vertices;
    delete[] m_indices;

    // Lưu ý: Việc giải phóng tài nguyên OpenGL (VAO, VBO, EBO) thường
    // được xử lý bởi OSG thông qua cơ chế quản lý trạng thái của nó.
    // Tuy nhiên, nếu bạn tự quản lý chúng một cách rõ ràng,
    // bạn có thể thêm lệnh xóa ở đây nếu cần, nhưng hãy cẩn thận
    // với vòng đời và phạm vi của các đối tượng OpenGL này trong OSG.
}

osg::BoundingBox CustomDrawable::computeBoundingBox() const
{
    // Định nghĩa hộp giới hạn xung quanh hình học
    // Đảm bảo kích thước hộp giới hạn đủ lớn để chứa toàn bộ hình học
    return osg::BoundingBox(osg::Vec3f(-1.0f, -1.0f, -1.0f), osg::Vec3f(1.0f, 1.0f, 1.0f));
}

void CustomDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
    // Truy cập các tiện ích OpenGL thông qua RenderInfo
    osg::State* state = renderInfo.getState();
    osg::GLExtensions* ext = state->get<osg::GLExtensions>();

    // Khởi tạo các đối tượng OpenGL chỉ một lần
    if (m_needsInitialization)
    {
        // Tạo và ràng buộc Vertex Array Object (VAO)
        ext->glGenVertexArrays(1, &m_vao);
        ext->glBindVertexArray(m_vao);

        // Tạo và ràng buộc Vertex Buffer Object (VBO) cho dữ liệu đỉnh
        ext->glGenBuffers(1, &m_vbo);
        ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, m_vbo);
        // Sao chép dữ liệu đỉnh vào VBO
        ext->glBufferData(GL_ARRAY_BUFFER_ARB, 4 * 3 * sizeof(GLfloat), m_vertices, GL_STATIC_DRAW);

        // Tạo và ràng buộc Element Buffer Object (EBO) cho dữ liệu chỉ số
        ext->glGenBuffers(1, &m_ebo);
        ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, m_ebo);
        // Sao chép dữ liệu chỉ số vào EBO
        ext->glBufferData(GL_ELEMENT_ARRAY_BUFFER_ARB, 6 * sizeof(GLuint), m_indices, GL_STATIC_DRAW);

        // Cấu hình thuộc tính đỉnh (vị trí)
        // GL_ARRAY_BUFFER_ARB là định danh cũ hơn cho GL_ARRAY_BUFFER
        ext->glVertexAttribPointer(0, // Index của thuộc tính đỉnh (layout=0 trong shader)
                                   3, // Số lượng thành phần (x, y, z)
                                   GL_FLOAT, // Kiểu dữ liệu
                                   GL_FALSE, // Có chuẩn hóa không
                                   3 * sizeof(float), // Bước nhảy giữa các đỉnh liên tiếp
                                   (void*)0); // Offset của dữ liệu trong VBO
        // Kích hoạt mảng thuộc tính đỉnh
        ext->glEnableVertexAttribArray(0);

        // Hủy ràng buộc các đối tượng để tránh ghi đè không mong muốn
        ext->glBindVertexArray(0);
        ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, 0);
        ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);

        m_needsInitialization = false; // Đánh dấu là đã khởi tạo xong
    }

    // Ràng buộc VAO đã tạo
    ext->glBindVertexArray(m_vao);

    // Vẽ các phần tử sử dụng EBO
    // GL_TRIANGLES: Chế độ vẽ
    // 6: Số lượng chỉ số để vẽ
    // GL_UNSIGNED_INT: Kiểu dữ liệu của chỉ số
    // (void*)0: Offset của dữ liệu chỉ số trong EBO (bắt đầu từ đầu)
    // 1: Số lượng lần vẽ (cho GL_DRAW_ELEMENTS_INSTANCED)
    // ext->glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0, 1);
    // Sử dụng glDrawElements thông thường nếu không cần instancing
    ext->glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);


    // Quan trọng: Hủy ràng buộc VAO và các bộ đệm sau khi vẽ
    // Điều này ngăn chặn các đối tượng vẽ khác bị ảnh hưởng bởi trạng thái này.
    ext->glBindVertexArray(0);
    ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, 0);
    ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}
  

Sử dụng Shaders

Để có khả năng tùy chỉnh cao hơn, bạn có thể sử dụng shaders. Điều này yêu cầu sửa đổi hàm drawImplementation để biên dịch, liên kết và sử dụng các shader, đồng thời truyền ma trận biến đổi.


// ... (Trong lớp CustomDrawable) ...

// Khai báo biến shader nếu sử dụng
// Shader* m_vfShader = nullptr; // Cần định nghĩa lớp Shader hoặc sử dụng thư viện của OSG

void CustomDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
    osg::State* state = renderInfo.getState();
    osg::GLExtensions* ext = state->get<osg::GLExtensions>();

    // Lấy ma trận ModelView và Projection từ OSG
    auto mvMatrix = state->getModelViewMatrix();
    auto proMatrix = state->getProjectionMatrix();
    // Chuyển đổi sang mảng float để sử dụng trong shader
    float mvArray[16];
    float proArray[16];
    for (int i = 0; i < 16; ++i) {
        mvArray[i] = static_cast<float>(mvMatrix.ptr()[i]);
        proArray[i] = static_cast<float>(proMatrix.ptr()[i]);
    }

    if (m_needsInitialization)
    {
        // --- Khởi tạo OpenGL (VAO, VBO, EBO) như trước ---
        ext->glGenVertexArrays(1, &m_vao);
        ext->glBindVertexArray(m_vao);
        ext->glGenBuffers(1, &m_vbo);
        ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, m_vbo);
        ext->glBufferData(GL_ARRAY_BUFFER_ARB, 4 * 3 * sizeof(GLfloat), m_vertices, GL_STATIC_DRAW);

        ext->glGenBuffers(1, &m_ebo);
        ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, m_ebo);
        ext->glBufferData(GL_ELEMENT_ARRAY_BUFFER_ARB, 6 * sizeof(GLuint), m_indices, GL_STATIC_DRAW);

        ext->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
        ext->glEnableVertexAttribArray(0);

        // --- Khởi tạo Shader ---
        // Cần có một lớp Shader được định nghĩa trước hoặc sử dụng API của OSG
        // Ví dụ: m_vfShader = new Shader(ext, "./Test.vert", "./Test.frag");
        // Nếu không có lớp Shader tùy chỉnh, bạn có thể cần các hàm OpenGL để tải và biên dịch shader.

        // Hủy ràng buộc
        ext->glBindVertexArray(0);
        ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, 0);
        ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);

        m_needsInitialization = false;
    }

    // --- Sử dụng Shader ---
    // ext->glUseProgram(m_vfShader->ID); // Thay thế bằng cách sử dụng shader của bạn
    // Lấy vị trí của các uniform
    // GLuint modelViewLoc = glGetUniformLocation(m_vfShader->ID, "modelViewMatrix");
    // GLuint projectionLoc = glGetUniformLocation(m_vfShader->ID, "projectionMatrix");
    // Truyền ma trận vào shader
    // glUniformMatrix4fv(modelViewLoc, 1, GL_FALSE, mvArray);
    // glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, proArray);

    // --- Vẽ ---
    ext->glBindVertexArray(m_vao);
    // ext->glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0, 1);
    ext->glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);


    // --- Hủy ràng buộc ---
    // ext->glUseProgram(0); // Hủy ràng buộc shader
    ext->glBindVertexArray(0);
    ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, 0);
    ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}
  

Ví dụ về Vertex Shader


#version 330 core
layout (location = 0) in vec3 aPos; // Vị trí đỉnh

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;

void main()
{
    gl_Position = projectionMatrix * modelViewMatrix * vec4(aPos, 1.0);
}
  

Ví dụ về Fragment Shader


#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f); // Màu đỏ
}
  

Tích hợp Thư viện GLAD

Việc tích hợp GLAD vào OSG có thể phức tạp do OSG tự quản lý các khai báo OpenGL. Dưới đây là các bước và lưu ý quan trọng:

1. Bao gồm Tệp Tiêu đề GLAD

Đảm bảo tệp tiêu đề glad.h được bao gồm ở vị trí sớm nhất có thể trong mã nguồn của bạn, trước bất kỳ tệp tiêu đề OpenGL nào khác, để tránh xung đột định nghĩa.

Trong tệp CustomDrawable.h:


#pragma once
// Bao gồm glad.h SỚM NHẤT CÓ THỂ
#include <glad/glad.h>
#include <osg/Drawable>
// ... các bao gồm khác
  

Trong tệp main.cpp, hãy bao gồm tệp tiêu đề của bạn sau khi bao gồm glad.h.

2. Khởi tạo GLAD

GLAD cần được khởi tạo để tải các con trỏ hàm OpenGL. Trong môi trường OSG, bạn không sử dụng glfwGetProcAddress. Thay vào đó, bạn cần sử dụng hàm lấy con trỏ hàm của OSG.

Trong hàm drawImplementation, khi m_needsInitialization là true:


void CustomDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
    // ... Lấy các tiện ích OSG ...
    osg::State* state = renderInfo.getState();
    // Hàm lấy con trỏ hàm của OSG (thay thế cho glfwGetProcAddress)
    // Cần tìm hàm tương đương hoặc sử dụng phương thức getProcAddress của OSG nếu có.
    // Nếu sử dụng WGL, có thể là WGL tương đương với getProcAddress.
    // Dưới đây là cách tiếp cận ban đầu dựa trên ví dụ của bạn, nhưng cần điều chỉnh.

    // Ví dụ tiếp cận: Sử dụng OSG's getProcAddress hoặc tương đương WGL/GLX
    // (Cần kiểm tra lại API chính xác của OSG)
    // auto getProcAddr = reinterpret_cast<GLAD_API_CALL*(*)(const char*)>(state->getProcAddress("glGetString")); // Ví dụ

    // Cách tiếp cận được đề xuất là sử dụng gladLoadGL() sau khi WGL/GLX đã được thiết lập bởi OSG.
    if (m_needsInitialization)
    {
        // Kiểm tra xem OSG đã thiết lập ngữ cảnh OpenGL chưa
        // GLAD có thể cần một con trỏ hàm để lấy địa chỉ hàm.
        // Tùy thuộc vào backend đồ họa của OSG (WGL, GLX, CGL, EGL)
        // bạn sẽ cần một hàm phù hợp để lấy con trỏ hàm.

        // Phương pháp phổ biến và đơn giản hơn là sử dụng gladLoadGL()
        // nếu OSG đã thiết lập ngữ cảnh GL.
        // Đảm bảo bạn đã thêm opengl32.lib vào linker dependencies (trên Windows).
        if (!gladLoadGL()) // Thử khởi tạo với hàm mặc định
        {
            std::cerr << "Failed to initialize GLAD!" << std::endl;
            // Xử lý lỗi
        } else {
            std::cout << "GLAD initialized successfully." << std::endl;
        }

        // Tiếp tục với việc tạo VAO, VBO, EBO và cấu hình thuộc tính đỉnh
        // Bây giờ bạn có thể sử dụng trực tiếp các hàm OpenGL (ví dụ: glGenVertexArrays)
        // thay vì thông qua osg::GLExtensions vì GLAD đã tải chúng.

        glGenVertexArrays(1, &m_vao);
        glBindVertexArray(m_vao);

        glGenBuffers(1, &m_vbo);
        glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
        glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(GLfloat), m_vertices, GL_STATIC_DRAW);

        glGenBuffers(1, &m_ebo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(GLuint), m_indices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(0);

        // Hủy ràng buộc
        glBindVertexArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        m_needsInitialization = false;
    }

    // --- Sử dụng các lệnh OpenGL trực tiếp ---
    glBindVertexArray(m_vao);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);

    // --- Hủy ràng buộc ---
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
  

Lưu ý Quan trọng:

  • Thứ tự bao gồm: glad.h phải được bao gồm trước tiên.
  • Khởi tạo: Sử dụng gladLoadGL() sau khi OSG đã thiết lập ngữ cảnh đồ họa. Đảm bảo liên kết với thư viện OpenGL thích hợp (ví dụ: opengl32.lib trên Windows).
  • Xóa Trạng thái: Luôn hủy ràng buộc các đối tượng VAO, VBO, EBO và shader sau khi sử dụng để tránh xung đột với các lệnh vẽ khác trong OSG.
  • Hộp giới hạn: Đảm bảo computeBoundingBox trả về một hộp đủ lớn để chứa hình học của bạn, nếu không, các vấn đề cắt xén có thể xảy ra. Kích thước của hộp giới hạn cũng ảnh hưởng đến góc nhìn ban đầu.

Thẻ: osg OpenGL glad vẽ tùy chỉnh đồ họa 3d

Đăng vào ngày 3 tháng 7 lúc 00:09