简单记录一下在OSG中使用OpenGL函数(以及glad库)绘制自定义图形。
2023年10月18日补充:
OSG使用没有在GLExtensions中声明的OpenGL函数接口
源码:
#include <osgViewer/viewer>
#include <osg/Geode>
#include "CustomDrawable.h"
int main()
{
osg::ref_ptr<osg::Geode> root = new osg::Geode;
root->addDrawable(new CustomDrawable);
osgViewer::Viewer viewer;
viewer.setSceneData(root);
viewer.realize();
//OSG使用纯OpenGL绘制似乎可以不用调用这三个函数,这三个函数的作用参见前文<OSG使用顶点着色器和片元着色器>
viewer.getCamera()->getGraphicsContext()->getState()->resetVertexAttributeAlias(false);
viewer.getCamera()->getGraphicsContext()->getState()->setUseModelViewAndProjectionUniforms(true);
viewer.getCamera()->getGraphicsContext()->getState()->setUseVertexAttributeAliasing(true);
return viewer.run();
}
#include <osg/Drawable>
class CustomDrawable:public osg::Drawable
{
public:
CustomDrawable();
~CustomDrawable();
protected:
osg::BoundingBox computeBoundingBox() const override;
void drawImplementation(osg::RenderInfo& renderInfo) const override;
private:
GLfloat* vertices = nullptr;
GLuint* indices = nullptr;
mutable unsigned int vao;
mutable unsigned int vbo;
mutable unsigned int ebo;
mutable bool doOnce = true;
};
#include "CustomDrawable.h"
CustomDrawable::CustomDrawable()
{
this->setUseDisplayList(false);
this->setUseVertexBufferObjects(true);
vertices = new GLfloat[2 * 2 * 3];
vertices[0] = 1.0;
vertices[1] = 1.0;
vertices[2] = 0.0;
vertices[3] = 1.0;
vertices[4] = -1.0;
vertices[5] = 0.0;
vertices[6] = -1.0;
vertices[7] = -1.0;
vertices[8] = 0.0;
vertices[9] = -1.0;
vertices[10] = 1.0;
vertices[11] = 0.0;
indices = new GLuint[6];
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 2;
indices[4] = 3;
indices[5] = 0;
}
CustomDrawable::~CustomDrawable()
{
}
osg::BoundingBox CustomDrawable::computeBoundingBox() const
{
return osg::BoundingBox(osg::Vec3f(-1.0, -1.0, -1.0), osg::Vec3f(1.0, 1.0, 1.0));
}
void CustomDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
auto ext = renderInfo.getState()->get<osg::GLExtensions>();
if (doOnce)
{
ext->glGenVertexArrays(1, &vao);
ext->glBindVertexArray(vao);
ext->glGenBuffers(1, &vbo);
ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, vbo);
ext->glBufferData(GL_ARRAY_BUFFER_ARB, 2 * 2 * 3 * 4, vertices, GL_STATIC_DRAW);
ext->glGenBuffers(1, &ebo);
ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, ebo);
ext->glBufferData(GL_ELEMENT_ARRAY_BUFFER_ARB, 6 * 4, indices, GL_STATIC_DRAW);
ext->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
ext->glEnableVertexAttribArray(0);
doOnce = false;
}
ext->glBindVertexArray(vao);
ext->glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0, 1);
//务必要在Draw后将Buffer绑定置空,不然有可能影响到其他常规绘制对象,例如osgText::Text
ext->glBindVertexArray(NULL);
ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, NULL);
ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, NULL);
}
运行效果:
--------------------------------------------------- 12月7日更新 ---------------------------------------------------
使用着色器:
主要是drawImplementation函数内的改动。
需要注意的是似乎这样直接调用OpenGL函数绘制,并使用着色器的话,内置的osg_ModelViewProjectionMatrix变量就失效了,需要自行获取ModelViewMatrix和ProjectionMatrix输入着色器。
void CustomDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
auto ext = renderInfo.getState()->get<osg::GLExtensions>();
//获取ModelViewMatrix和ProjectionMatrix
auto mvMatrix = renderInfo.getState()->getModelViewMatrix();
auto proMatrix = renderInfo.getState()->getProjectionMatrix();
auto mvValue = mvMatrix.ptr();
auto proValue = proMatrix.ptr();
float _mvMatrix[16];
float _proMatrix[16];
for (int i = 0; i < 16; i++)
{
_mvMatrix[i] = (float)mvValue[i];
_proMatrix[i] = (float)proValue[i];
}
if (doOnce)
{
ext->glGenVertexArrays(1, &vao);
ext->glBindVertexArray(vao);
ext->glGenBuffers(1, &vbo);
ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, vbo);
ext->glBufferData(GL_ARRAY_BUFFER_ARB, N * N * 3 * 4, vertices, GL_STATIC_DRAW);
ext->glGenBuffers(1, &ebo);
ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, ebo);
ext->glBufferData(GL_ELEMENT_ARRAY_BUFFER_ARB, (N - 1) * (N - 1) * 6 * 4, indices, GL_STATIC_DRAW);
ext->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
ext->glEnableVertexAttribArray(0);
//Shader为自己简单封装的一个类,内容跟OpenGL中使用Shader一致
vfShader = new Shader(ext, "./Test.vert", "./Test.frag");
doOnce = false;
}
vfShader->Bind();
vfShader->SetMat4("modelViewMatrix", _mvMatrix);
vfShader->SetMat4("projectionMatrix", _proMatrix);
ext->glBindVertexArray(vao);
ext->glDrawElementsInstanced(GL_TRIANGLES, (N - 1) * (N - 1) * 6, GL_UNSIGNED_INT, (void*)0, 1);
//务必要在Draw后将着色器绑定置空,不然如果场景中有常规加载的模型,则会受到当前绑定的着色器影响
vfShader->unBind();
//务必要在Draw后将Buffer绑定置空,不然有可能影响到其他常规绘制对象,例如osgText::Text
ext->glBindVertexArray(NULL);
ext->glBindBuffer(GL_ARRAY_BUFFER_ARB, NULL);
ext->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, NULL);
}
顶点着色器:
#version 430
layout(location = 0) in vec3 vPos;
//uniform mat4 osg_ModelViewProjectionMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
void main()
{
gl_Position = projectionMatrix*modelViewMatrix*vec4(vPos,1.0);
}
片元着色器:
#version 430
out vec4 FragColor;
void main()
{
FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
另外computeBoundingBox函数中设置的包围盒大小一定要大于等于图形的大小,不然会产生裁切问题,如下图,包围盒顶点的值是±1.0,图形顶点的值是±256.0,垂直在一定角度范围内是正常的,但是再继续倾斜就会发生裁切。另外,包围盒的大小也会影响程序刚运行时初始视角离图形的远近,例如包围盒设置±512.0就会比设置±256.0的初始视角要远。
嵌入glad库:(还在尝试)
glad+glfw库是很多OpenGL资料中非常常见的一种搭配,因为在查询OSG使用OpenGL绘制自定义图形的资料时,找到了在OSG中用glut库的glutSolidTeapot函数绘制teapot,所以想既然glut都能嵌入使用,那glad是不是也可以呢?因为很多OpenGL资料都是用的glad,如果能够找到办法嵌入使用,那么就可以直接挪到OSG中而不用想办法转换实现。
先说结论:理论上似乎是可以,但是glGetString不知道为什么获取不到,并且文件包含比较麻烦。 可以。
1、首先glad头文件的包含问题
因为OSG底层包含了系统的gl.h文件,其中当没有定义__gl_h_这个宏的时候会定义这个宏,所以当这个宏已经被定义了的时候,glad会报错:
然后就需要把glad.h的包含放在整个代码的最前面,像下面这样
在CustomDrawable.h中放在最前面
然后把CustomDrawable.h的包含放在main.cpp最前面
这样就不会报这个错了。
2、glad的初始化
glad库使用时先需要对glad进行初始化,在glad+glfw库的使用中,glad的初始化是由gladLoadGLLoader函数来完成的。
由glfwGetProcAddress的名称、注释以及接口参数可以得知,这是一个通过传入名称来获取函数地址的函数。
通过gladLoadGLLoader的源码可以得知,glad通过传入的glfwGetProcAddress函数来初始化自身的函数指针接口。
所以我需要在OSG中找到一个能够通过名称来获取函数地址的函数。
因为OSG的OpenGL相关函数主要都在GLExtensions类中,所以就先在它的头文件中寻找,然后发现有一个在类外的函数叫做getGLExtensionFuncPtr正好是我需要的,在doOnce中加入如下代码:
结果发现并不能初始化成功,单步跟入gladLoadGLLoader函数内,发现是在第一步获取glGetString时返回为空。
然后尝试单独调用getGLExtensionFuncPtr,发现确实无法获取到glGetString,但是像glGenBuffers什么的都是可以获取到的。
查看getGLExtensionFuncPtr的源码,发现它是通过调用wingdi.h中的wglGetProcAddress来获取函数地址,目前暂不清楚为什么wglGetProcAddress获取不到glGetString的函数地址…
--------------------------------------------------- 12月8日更新 ---------------------------------------------------
查找资料发现wglGetProcAddress这个函数只能获取OpenGL 1.1+的拓展,而glGetString是OpenGL 1.0的,所以这个函数无法获取到glGetString的函数地址。
接着在StackOverFlow上找到了关于wgl+glad的提问,里面提到了用gladLoadGL()进行初始化,这是glad的另一个初始化函数。
这里需要注意如果调用gladLoadGL()返回false有可能是因为没有添加opengl32.lib,需要在附加依赖项中添加这个lib,原因查看glad源码可知。
主要是drawImplementation函数内的改动。
void CustomDrawable::drawImplementation(osg::RenderInfo& renderInfo) const
{
//获取ModelViewMatrix和ProjectionMatrix
auto mvMatrix = renderInfo.getState()->getModelViewMatrix();
auto proMatrix = renderInfo.getState()->getProjectionMatrix();
auto mvValue = mvMatrix.ptr();
auto proValue = proMatrix.ptr();
float _mvMatrix[16];
float _proMatrix[16];
for (int i = 0; i < 16; i++)
{
_mvMatrix[i] = (float)mvValue[i];
_proMatrix[i] = (float)proValue[i];
}
if (doOnce)
{
if (!gladLoadGL())
{
std::cout << "Failed to initialize GLAD" << std::endl;
}
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, N * N * 3 * 4, vertices, GL_STATIC_DRAW);
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (N - 1) * (N - 1) * 6 * 4, indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
vfShader = new Shader("./Test.vert", "./Test.frag");
doOnce = false;
}
glUseProgram(vfShader->id);
auto modelViewLoc = glGetUniformLocation(vfShader->id, "modelViewMatrix");
auto projectionLoc = glGetUniformLocation(vfShader->id, "projectionMatrix");
glUniformMatrix4fv(modelViewLoc, 1, GL_FALSE, _mvMatrix);
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, _proMatrix);
glBindVertexArray(vao);
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glDrawElements(GL_TRIANGLES, (N - 1) * (N - 1) * 6, GL_UNSIGNED_INT, (void*)0);
//务必要在Draw后将着色器绑定置空,不然如果场景中有常规加载的模型,则会受到当前绑定的着色器影响
glUseProgram(NULL);
//务必要在Draw后将Buffer绑定置空,不然有可能影响到其他常规绘制对象,例如osgText::Text
glBindVertexArray(NULL);
glBindBuffer(GL_ARRAY_BUFFER_ARB, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, NULL);
}
运行效果: