OSG之遮挡裁剪节点

221 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天

1 遮挡裁剪节点

遮挡裁剪节点(osg::OccluderNode)继承自 osg::Group 节点。osg::OccluderNode 节点的主要作用是裁剪掉被遮挡的物体,也就是场景中被其他物体所遮挡的物体。最优的遮挡裁剪算法只选择其中可见的物体。

从某种意义上来说,Z缓冲器只选择并绘制那些可见的物体,但是必须将这些物体都送入管线的大部分阶段。高效的遮挡算法背后的思想是:提前执行一些简单的测试,从而避免将所有的数据送入管线的大部分阶段。

目前遮挡裁剪算法主要有两种,分别是基于点的遮挡裁剪和基于单元的遮挡裁剪。

下图将可以清晰地表达出这两种算法的区别。

2.png

基于点的可见性通常用于绘制,也就是从单个视点位置看到所有的物体。基于单元的可见性判断主要针对一个方体或者圆体单元进行可见性判断,利用这种方法判断出的不可见物体在单元内部的所有点看上去是不可见的,它的优点是:一旦计算出一个单元的可见性,只要视点在这个单元中,通常就可以将可见性判断结果用于很多真实画面。

但这种算法有一个缺陷,就是所需计算的开销要比基于视点的可见性大得多。因此,通常将其作为一个预处理步骤。可以用这样一个形象的比喻来说明这两种算法,那就是基于点的可见性和基于单元的可见性可以认为分别是一个点光源和区域光源叫。

osg::OccluderNode 主要采用的是基于点的遮挡算法,但节点本身不具备遮挡能力,因此,在判断时需要指定一个遮挡面,可以调用下面的函数:

void setOccluder(ConvexPlanarOccluder* occluder)

指定平面时需要注意,该平面应该为一个凸多边形。

osg::OccluderNode的继承关系图如下图所示。

3.png

2 遮挡裁剪节点示例

遮挡裁剪节点(osg::OccluderNode)示例的代码如程序清单所示。

#include <osgViewer/Viewer>

#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/OccluderNode>
#include <osg/StateSet>
#include <osg/ConvexPlanarOccluder>
#include <osg/BoundingBox>
#include <osg/BoundingSphere>

#include <osgDB/ReadFile>
#include <osgDB/WriteFile>

#include <osgUtil/Optimizer>

#include "utility.h"

#include <iostream>

// 创建遮挡节点
osg::ref_ptr<osg::Node> createOccluder(const osg::Vec3& v1, const osg::Vec3& v2, const osg::Vec3& v3, const osg::Vec3& v4)
{
	// 创建遮挡节点对象
	osg::ref_ptr<osg::OccluderNode> occluderNode = new osg::OccluderNode;

	// 创建遮挡平面
	osg::ref_ptr<osg::ConvexPlanarOccluder> cpo = new osg::ConvexPlanarOccluder;

	// 关联遮挡板平面
	occluderNode->setOccluder(cpo.get());
	occluderNode->setName("occluder");

	// 初始化一个遮挡平面
	osg::ConvexPlanarPolygon& occluder = cpo->getOccluder();
	occluder.add(v1);
	occluder.add(v2);
	occluder.add(v3);
	occluder.add(v4);

	// 为遮挡面画一个四边形
	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
	osg::ref_ptr<osg::Vec3Array> coords = new osg::Vec3Array(occluder.getVertexList().begin(), occluder.getVertexList().end());
	//osg::ref_ptr<osg::Vec3Array> coords = new osg::Vec3Array();
	//coords->push_back(v1);
	//coords->push_back(v2);
	//coords->push_back(v3);
	//coords->push_back(v4);
	//std::cout << "size: " << coords->size() << std::endl;
	geom->setVertexArray(coords);

	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(1);
	(*colors)[0].set(1.0f, 1.0f, 1.0f, 0.5f);
	geom->setColorArray(colors.get());
	geom->setColorBinding(osg::Geometry::BIND_OVERALL);
	geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));

	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(geom.get());

	osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
	// 关闭光照
	stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
	// 使用混合,保证alpha纹理正确
	stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
	// 设置透明渲染单元
	stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);

	geom->setStateSet(stateset.get());

	// 添加四边形作为遮挡节点,遮挡节点本身不具备遮挡能力
	occluderNode->addChild(geode.get());

	return occluderNode.get();
}

// 创建绕模型的遮挡场景
osg::ref_ptr<osg::Group> createOccludersAroundModel(osg::ref_ptr<osg::Node> model)
{
	// 创建场景组节点
	osg::ref_ptr<osg::Group> scene = new osg::Group();
	scene->setName("OccluderScen");

	// 添加子节点
	scene->addChild(model.get());
	model->setName("cow.osg");

	// 计算模型的包围盒
	const osg::BoundingSphere bs = model->getBound();

	// 根据包围盒创建前、后、左、右4个遮挡面
	osg::BoundingBox bb;
	bb.expandBy(bs);

	// 前遮挡面
	scene->addChild(createOccluder(bb.corner(0), bb.corner(1), bb.corner(5), bb.corner(4)));

	// 右遮挡面
	scene->addChild(createOccluder(bb.corner(1), bb.corner(3), bb.corner(7), bb.corner(5)));

	// 左遮挡面
	scene->addChild(createOccluder(bb.corner(2), bb.corner(0), bb.corner(4), bb.corner(6)));

	// 后遮挡面
	scene->addChild(createOccluder(bb.corner(3), bb.corner(2), bb.corner(6), bb.corner(7)));

	return scene.get();
}

int main()
{
	// 创建Viewer对象,场景浏览器
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();

	// 创建场景组根节点
	osg::ref_ptr<osg::Group> root = new osg::Group();

	// 创建一个节点,读取牛模型
	osg::ref_ptr<osg::Node> node = osgDB::readNodeFile(GetCurrentPath() + "\\Data\\cow.osg");

	// 加载遮挡场景
	root->addChild(createOccludersAroundModel(node.get()));

	// 优化场景数据
	osgUtil::Optimizer optimizer;
	optimizer.optimize(root.get());

	// 设置窗口大小
	setWindowSize(viewer.get(), 600, 400, "OccluderNode");

	// 设置场景数据
	viewer->setSceneData(root.get());
	// 初始化并创建窗口
	viewer->realize();
	// 开始渲染
	viewer->run();

	return 0;
}

效果图

4.PNG

动态效果图

1.gif

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天