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.hphả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.libtrê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
computeBoundingBoxtrả 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.