osgEarth使用addDrawable绘制的矢量要素没有纹理或者说没有立体感问题

本文讲述了作者在使用osgEarth库时遇到的立体感缺失问题,通过调整光照设置、使用OpenGL核心模式以及自定义顶点和片段着色器解决了这个问题,展示了如何为osg地球节点编写自己的着色器以实现定制化渲染。
摘要由CSDN通过智能技术生成

项目场景:

编译的一个osgEarth八叉树的例子,我使用的osg版本是3.6.5,osgearth版本为3.5。


问题描述

加载出来的效果总是没有立体感,直接复制的博主的代码。

我的结果
原文结果

原因分析:

刚开始以为是光照,法线,或者深度测试等问题所以将创建Geode部分修改为如下:

 得到的结果还是一样没有纹理和立体感。最终在微信群王锐和Event提示下,想起当初使用的是vcpkg编译的osgEarth相关依赖库,并且在x64-windows.cmake里添加了osg_OPENGL_PROFILE=GL3的参数,具体如下图:

指定该参数后,osg使用的是opengl的core模式,不再支持固定渲染管线。 


解决方案:

由于osg不支持固定渲染管线,所以需要自己写shader:

 修改后的代码如下:

osg::Node* OctreeBuilder::createElement(const std::string& id, const osg::Vec3& center, float radius)
{
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(new osg::ShapeDrawable(new osg::Sphere(center, radius)));
	geode->setName(id);

	// 创建顶点着色器
	osg::ref_ptr<osg::Shader> vertexShader = new osg::Shader(osg::Shader::VERTEX, R"(
        varying vec3 normal;
        void main() {
            gl_Position = ftransform();
            normal = normalize(gl_NormalMatrix * gl_Normal);
        }
    )");

	// 创建片段着色器
	osg::ref_ptr<osg::Shader> fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, R"(
        varying vec3 normal;
        void main() {
            vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
            float diff = max(dot(normal, lightDir), 0.0);
            vec4 diffuseColor = vec4(0.7, 0.7, 0.7, 1.0);
            gl_FragColor = diffuseColor * diff;
        }
    )");

	// 创建光源
	osg::ref_ptr<osg::Light> light = new osg::Light;
	light->setLightNum(0);
	light->setPosition(osg::Vec4(0.0, 0.0, 1000.0, 1.0));

	// 创建光照模型
	osg::ref_ptr<osg::LightModel> lightModel = new osg::LightModel;
	lightModel->setTwoSided(true);

	// 创建着色器程序
	osg::ref_ptr<osg::Program> program = new osg::Program;
	program->addShader(vertexShader.get());
	program->addShader(fragmentShader.get());

	// 设置着色器程序到 StateSet
	osg::StateSet* stateSet = geode->getOrCreateStateSet();
	stateSet->setAttributeAndModes(program.get(), osg::StateAttribute::ON);

	return geode.release();
}

运行结果:

由于自己写了shader,可以在片段着色器里修改漫反射光的颜色,比如修改为红色,如下:

运行结果:

完整代码如下:

OctreeBuilder.h

#ifndef H_COOKBOOK_CH8_OCTREEBUILDER
#define H_COOKBOOK_CH8_OCTREEBUILDER

#include <osg/Geode>
#include <osg/LOD>

class OctreeBuilder
{
public:
	OctreeBuilder() : _maxChildNumber(16), _maxTreeDepth(8), _maxLevel(0) {}
	int getMaxLevel() const { return _maxLevel; }

	void setMaxChildNumber(int max) { _maxChildNumber = max; }
	int getMaxChildNumber() const { return _maxChildNumber; }

	void setMaxTreeDepth(int max) { _maxTreeDepth = max; }
	int getMaxTreeDepth() const { return _maxTreeDepth; }

	typedef std::pair<std::string, osg::BoundingBox> ElementInfo;
	osg::Group* build(int depth, const osg::BoundingBox& total,
		std::vector<ElementInfo>& elements);

	osg::Node* createElement(const std::string& id, const osg::Vec3& center, float radius);
protected:
	osg::LOD* createNewLevel(int level, const osg::Vec3& center, float radius);
	
	osg::Geode* createBoxForDebug(const osg::Vec3& max, const osg::Vec3& min);

	int _maxChildNumber;
	int _maxTreeDepth;
	int _maxLevel;
};

#endif

 OctreeBuilder.cpp

#include <windows.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <osg/LightModel>
#include <osgDB/ReadFile>
#include <osgEarth/Registry>

using namespace std;

#include <osg/ShapeDrawable>
#include <osg/Geometry>
#include <osg/PolygonMode>
#include <osg/Material>
#include <osg/Texture2D>
#include "OctreeBuilder.h"

osg::Group* OctreeBuilder::build(int depth, const osg::BoundingBox& total, std::vector<ElementInfo>& elements)
{
	int s[3];  // axis sides (0 or 1)

	//存放当前包围盒的最大、中间、最小点,以为划分八叉树做准备
	osg::Vec3 extentSet[3] = {
		total._min,
		(total._max + total._min) * 0.5f,
		total._max
	};

	std::vector<ElementInfo> childData;
	//遍历父结点的所有孩子,让包含在当前盒子里的,不完全包含但是中点在盒子里的,都压入到当前盒子的子结点
	for (unsigned int i = 0; i < elements.size(); ++i)
	{
		const ElementInfo& obj = elements[i];
		if (total.contains(obj.second._min) && total.contains(obj.second._max))
			childData.push_back(obj);
		else if (total.intersects(obj.second))
		{
			osg::Vec3 center = (obj.second._max + obj.second._min) * 0.5f;
			if (total.contains(center))
			{
				childData.push_back(obj);
			}
		}
	}

	//如果当前结点的孩子数量已经达标,或者层数已经达标,则认为是叶结点
	bool isLeafNode = false;
	if ((int)childData.size() <= _maxChildNumber || depth > _maxTreeDepth)
	{
		isLeafNode = true;
	}

	//当前八叉树根
	osg::ref_ptr<osg::Group> group = new osg::Group;

	//如果不是叶结点,继续分,把空间一分为八
	if (!isLeafNode)
	{
		osg::ref_ptr<osg::Group> childNodes[8];

		//空间一分为八2*2*2
		for (s[0] = 0; s[0] < 2; ++s[0]) //x
		{
			for (s[1] = 0; s[1] < 2; ++s[1]) //y
			{
				for (s[2] = 0; s[2] < 2; ++s[2]) //z
				{
					// Calculate the child extent
					//extentSet 0是最小,1是中间,2是最大
					//下面这个小算法有点磨人,分别求出min和max的x, y, z自己好好推几个试试
					osg::Vec3 min, max;
					for (int a = 0; a < 3; ++a)
					{
						min[a] = (extentSet[s[a] + 0])[a];
						max[a] = (extentSet[s[a] + 1])[a];
					}

					//这么求id是为了确保唯一性
					int id = s[0] + (2 * s[1]) + (4 * s[2]);
					childNodes[id] = build(depth + 1, osg::BoundingBox(min, max), childData);
				}
			}
		}

		//八个子结点构建完毕后,加入到根结点当中
		for (unsigned int i = 0; i < 8; ++i)
		{
			if (childNodes[i] && childNodes[i]->getNumChildren())
				group->addChild(childNodes[i]);
		}
	}
	else //找到叶结点,递归就结束了
	{
		for (unsigned int i = 0; i < childData.size(); ++i)
		{
			const ElementInfo& obj = childData[i];
			osg::Vec3 center = (obj.second._max + obj.second._min) * 0.5;
			float radius = (obj.second._max - obj.second._min).length() * 0.5f;
			//创建一个球
			group->addChild(createElement(obj.first, center, radius));
		}
	}

	osg::Vec3 center = (total._max + total._min) * 0.5;
	float radius = (total._max - total._min).length() * 0.5f;

	//最后创建一个LOD,离的远了显示调试盒子,离的近了显示分的组
	osg::LOD* level = createNewLevel(depth, center, radius);
	level->insertChild(0, createBoxForDebug(total._max, total._min));  // For debug use
	level->insertChild(1, group.get());
	return level;
}

osg::LOD* OctreeBuilder::createNewLevel(int level, const osg::Vec3& center, float radius)
{
	osg::ref_ptr<osg::LOD> lod = new osg::LOD;
	lod->setCenterMode(osg::LOD::USER_DEFINED_CENTER);
	lod->setCenter(center);
	lod->setRadius(radius);
	lod->setRange(0, radius * 5.0f, FLT_MAX);
	lod->setRange(1, 0.0f, radius * 5.0f);

	if (_maxLevel < level) _maxLevel = level;
	return lod.release();
}

osg::Node* OctreeBuilder::createElement(const std::string& id, const osg::Vec3& center, float radius)
{
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(new osg::ShapeDrawable(new osg::Sphere(center, radius)));
	geode->setName(id);

	// 创建顶点着色器
	osg::ref_ptr<osg::Shader> vertexShader = new osg::Shader(osg::Shader::VERTEX, R"(
        varying vec3 normal;
        void main() {
            gl_Position = ftransform();
            normal = normalize(gl_NormalMatrix * gl_Normal);
        }
    )");

	// 创建片段着色器
	osg::ref_ptr<osg::Shader> fragmentShader = new osg::Shader(osg::Shader::FRAGMENT, R"(
        varying vec3 normal;
        void main() {
            vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
            float diff = max(dot(normal, lightDir), 0.0);
            vec4 diffuseColor = vec4(1.0, 0.0, 0.0, 1.0);
            gl_FragColor = diffuseColor * diff;
        }
    )");

	// 创建光源
	osg::ref_ptr<osg::Light> light = new osg::Light;
	light->setLightNum(0);
	light->setPosition(osg::Vec4(0.0, 0.0, 1000.0, 1.0));

	// 创建光照模型
	osg::ref_ptr<osg::LightModel> lightModel = new osg::LightModel;
	lightModel->setTwoSided(true);

	// 创建着色器程序
	osg::ref_ptr<osg::Program> program = new osg::Program;
	program->addShader(vertexShader.get());
	program->addShader(fragmentShader.get());

	// 设置着色器程序到 StateSet
	osg::StateSet* stateSet = geode->getOrCreateStateSet();
	stateSet->setAttributeAndModes(program.get(), osg::StateAttribute::ON);

	return geode.release();
}

osg::Geode* OctreeBuilder::createBoxForDebug(const osg::Vec3& max, const osg::Vec3& min)
{
	osg::Vec3 dir = max - min;
	osg::ref_ptr<osg::Vec3Array> va = new osg::Vec3Array(10);
	(*va)[0] = min + osg::Vec3(0.0f, 0.0f, 0.0f);
	(*va)[1] = min + osg::Vec3(0.0f, 0.0f, dir[2]);
	(*va)[2] = min + osg::Vec3(dir[0], 0.0f, 0.0f);
	(*va)[3] = min + osg::Vec3(dir[0], 0.0f, dir[2]);
	(*va)[4] = min + osg::Vec3(dir[0], dir[1], 0.0f);
	(*va)[5] = min + osg::Vec3(dir[0], dir[1], dir[2]);
	(*va)[6] = min + osg::Vec3(0.0f, dir[1], 0.0f);
	(*va)[7] = min + osg::Vec3(0.0f, dir[1], dir[2]);
	(*va)[8] = min + osg::Vec3(0.0f, 0.0f, 0.0f);
	(*va)[9] = min + osg::Vec3(0.0f, 0.0f, dir[2]);

	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
	geom->setVertexArray(va.get());
	geom->addPrimitiveSet(new osg::DrawArrays(GL_QUAD_STRIP, 0, 10));

	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(geom.get());
	geode->getOrCreateStateSet()->setAttribute(
		new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE));
	geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
	return geode.release();
}

main.cpp

// osgPro227.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include <osg/Group>
#include <osgDB/ReadFile>
#include <osgUtil/PrintVisitor>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers> //事件监听
#include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制

#include "OctreeBuilder.h"

float randomValue(float min, float max)
{
	return (min + (float)rand() / (RAND_MAX + 1.0f) * (max - min));
}

osg::Vec3 randomVector(float min, float max)
{
	return osg::Vec3(randomValue(min, max), randomValue(min, max), randomValue(min, max));
}
class PrintNameVisitor : public osgUtil::PrintVisitor
{
public:
	PrintNameVisitor(std::ostream& out) : osgUtil::PrintVisitor(out) {}

	void apply(osg::Node& node)
	{
		if (!node.getName().empty())
		{
			output() << node.getName() << std::endl;
			enter();
			traverse(node);
			leave();
		}
		else osgUtil::PrintVisitor::apply(node);
	}
};

int main(int argc, char** argv)
{
	osg::BoundingBox globalBound;
	std::vector<OctreeBuilder::ElementInfo> globalElements;
	for (unsigned int i = 0; i < 5000; ++i)
	{
		osg::Vec3 pos = randomVector(-500.0f, 500.0f);
		float radius = randomValue(0.5f, 2.0f);
		std::stringstream ss; ss << "Ball-" << i + 1;

		osg::Vec3 min = pos - osg::Vec3(radius, radius, radius);
		osg::Vec3 max = pos + osg::Vec3(radius, radius, radius);
		osg::BoundingBox region(min, max);
		globalBound.expandBy(region);
		globalElements.push_back(OctreeBuilder::ElementInfo(ss.str(), region));
	}

	OctreeBuilder octree;
	osg::ref_ptr<osg::Group> root = octree.build(0, globalBound, globalElements);

	std::ofstream out("octree_output.txt");
	PrintNameVisitor printer(out);
	root->accept(printer);

	osg::ref_ptr <osgViewer::Viewer> viewer = new osgViewer::Viewer;
	viewer->setSceneData(root.get());
	viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
	viewer->addEventHandler(new osgViewer::StatsHandler());//实现状态信息统计
	viewer->addEventHandler(new osgViewer::WindowSizeHandler());
	viewer->setUpViewInWindow(50, 50, 1600, 800, 0);

	return viewer->run();
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值