实例化的两种方式
当实例数量较少时选择通过uniform数组 + gl_InstanceID方式实现;当数据较大时超过uniform限定时选择通过顶点数组的方式实现,需要用到的相关opengl函数glVertexAttribDivisor(int a, int b)。
方式二:vbo + glVertexAttribDivisor
{
//----------------instance test-------------------
root->removeChild(0, root->getNumChildren());
osg::ref_ptr<osg::Geometry> drawInstance = new osg::Geometry;
osg::Vec3 vtxCoords[] = {
osg::Vec3(-1.0, 2.0, -1.0),
osg::Vec3(1.0, 2.0, -1.0),
osg::Vec3(-1.0, 2.0, 1.0)
};
osg::Vec3 offsets[] = {
osg::Vec3(-2.0, 0.0, 0.0),
osg::Vec3( 2.0, 0.0, 0.0),
osg::Vec3(0.0, 0.0, 2.0)
};
osg::Vec3 colors[] = {
osg::Vec3(1.0, 0.0, 0.0),
osg::Vec3(0.0, 1.0, 0.0),
osg::Vec3(0.0, 0.0, 1.0)
};
int vCount = sizeof(vtxCoords) / sizeof(osg::Vec3);
drawInstance->setVertexAttribArray(0, new osg::Vec3Array(vCount, vtxCoords), osg::Array::BIND_PER_VERTEX);
drawInstance->setVertexAttribArray(1, new osg::Vec3Array(3, offsets), osg::Array::BIND_PER_VERTEX);
drawInstance->setVertexAttribArray(2, new osg::Vec3Array(vCount, colors), osg::Array::BIND_PER_VERTEX);
drawInstance->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3, 3));
{
drawInstance->setUseDisplayList(false);
drawInstance->setUseVertexBufferObjects(true);
osg::BoundingBox bb(-3., -3., -3., 3., 3., 3.);
drawInstance->setInitialBound(bb);
osg::StateSet* pStateSet = drawInstance->getOrCreateStateSet();
pStateSet->setAttribute(new osg::VertexAttribDivisor(1, 1));
const std::string vs = {
"#version 330 compatibility \n"
"layout(location = 0) in vec3 aPos; \n"
"layout(location = 1) in vec3 aOffset; \n"
"layout(location = 2) in vec3 aColor; \n"
" \n"
"out vec4 fColor; \n"
" \n"
"void main() \n"
"{ \n"
" gl_Position = gl_ModelViewProjectionMatrix * vec4(aPos + aOffset, 1.0); \n"
" fColor = vec4(aColor, 1.0); \n"
"} \n"
" \n"
};
const std::string fs = {
"#version 330 compatibility \n"
" \n"
"in vec4 fColor; \n"
" \n"
"void main() \n"
"{ \n"
" gl_FragColor = fColor; \n"
"} \n"
" \n"
};
osg::ref_ptr<osg::Program> program = new osg::Program;
program->addShader(new osg::Shader(osg::Shader::VERTEX, vs));
program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fs));
pStateSet->setAttributeAndModes(program, osg::StateAttribute::ON);
}
root->addChild(drawInstance);
}
效果截图:
注意事项:
1、禁用显示列表并启用顶点缓存对象。
drawInstance->setUseDisplayList(false);
drawInstance->setUseVertexBufferObjects(true);
2、glVertexAttribDivisor函数的设置:这个函数告诉了OpenGL该什么时候更新顶点属性的内容至新一组数据。它的第一个参数是需要的顶点属性,第二个参数是属性除数(Attribute Divisor)。默认情况下,属性除数是0,告诉OpenGL我们需要在顶点着色器的每次迭代时更新顶点属性。将它设置为1时,我们告诉OpenGL我们希望在渲染一个新实例的时候更新顶点属性。而设置为2时,我们希望每2个实例更新一次属性,以此类推。我们将属性除数设置为1,是在告诉OpenGL,处于位置值2的顶点属性是一个实例化数组。
osg::StateSet* pStateSet = drawInstance->getOrCreateStateSet();
pStateSet->setAttribute(new osg::VertexAttribDivisor(1, 1));
3、顶点属性的绑定;主要用于与glVertexAttribDivisor第一个参数匹配
drawInstance->setVertexAttribArray(0, new osg::Vec3Array(3, vtxCoords), osg::Array::BIND_PER_VERTEX);
drawInstance->setVertexAttribArray(1, new osg::Vec3Array(3, offsets), osg::Array::BIND_PER_VERTEX);
drawInstance->setVertexAttribArray(2, new osg::Vec3Array(3, colors), osg::Array::BIND_PER_VERTEX);
4、注意初始化包围盒,否则可能因为系统效率优化直接将shader生成几何体实例剔除掉,导致看不见
osg::BoundingBox bb(-3., -3., -3., 3., 3., 3.);
drawInstance->setInitialBound(bb);
5、动态修改数组时,需要调用函数dirty以通知gpu,实现数据更新
osg::ref_ptr<osg::Vec3Array> offsets = new osg::Vec3Array;
...修改
offsets->dirty()
6、多个实例化渲染时,模型不能共享!!!如下:
osg::ref_pt<osgDB::Options> _options = new osgDB::Options("roads_designer");
_options->setObjectCacheHint(osgDB::Options::CACHE_ALL);
auto group1 = new InstanceGroup(10, 11);
group1->addChild(osgDB::readRefNodeFile("roads/qiao/qiaodun.ive", _options));
auto group2 = new InstanceGroup(10, 11);
group2->addChild(osgDB::readRefNodeFile("roads/qiao/qiaodun.ive", _options));
// XXX,以上情况会导致,group1失效,只有group2有效渲染。原因是_options使得group1 和group2之间共享了相同的模型
7、通常更新方式
type one:
offsets->assign(waveKeyPoints->begin(), waveKeyPoints->end());
offsets->dirty();
//intial->eg: instance = new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4, offsets->size());
instance->setNumInstances(offsets->size());
type two:
//eg: drawInstance = new osg::Geometry;
drawInstance->removePrimitiveSet(0, drawInstance->getNumPrimitiveSets());
drawInstance->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4, offsets->size()));
type three
//初始化时创建足够多的实例,然后only更新数组
for (int i = 0; i < waveKeyPoints->size(); ++i) {
osg::Vec3 p = waveKeyPoints->at(i);
offsets->at(i) = p;
}
offsets->dirty();