通过OSG实现对模型的日照模拟

news2024/11/30 11:43:01

1. 加载模型

通过OpenSceneGraph加载一个倾斜摄影的场景模型数据:

#include <iostream>
#include <Windows.h>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>

using namespace std;

int main()
{
	string osgPath = "D:/Data/Dayanta_OSGB/Data/MultiFoderReader.osgb";
	osg::Node * node = osgDB::readNodeFile(osgPath);
	osgViewer::Viewer viewer;
	viewer.setSceneData(node);
	viewer.setUpViewInWindow(100, 100, 800, 600);
	return viewer.run();
}

复制

运行结果显示的场景如下:

想要对模型进行日照模拟,就需要用到光照和阴影技术。注意此时模型上的部分阴影是纹理上自带的。

2. 光照

osgViewer的默认场景中是有灯光的,调整上述的场景的视角,某些地方是全黑的,而且场景效果偏暗。这里需要设置自己需要的环境反射和漫反射。

1) 环境反射

环境反射是针对环境光而言的,在环境反射中,环境光照射物体是各方面均匀、强度相等的,因此环境光不用设置位置和方向,只需要指定颜色。

2) 漫反射

漫反射是针对平行光和点光源光而言的。太阳光照就是平行光,由于太阳距离地球很远,阳光到达地球的时可以认为是平行的。平行光可以用一个方向和一个颜色来定义。当然,对于像灯泡那样的点光源光,还需要指定光源的位置。

3) 日照方向

(1) 太阳高度角和太阳方位角

对于太阳光照来说,其方向并不是随便设置的。这里需要引入太阳高度角和太阳方位角两个概念,通过这两个角度,可以确定日照的方向。

太阳高度角指的就是太阳光的入射方向和地平面之间的夹角;而太阳方位角略微复杂点,指的是太阳光线在地平面上的投影与当地子午线的夹角,可近似地看作是竖立在地面上的直线在阳光下的阴影与正南方向的夹角。其中方位角以正南方向为0,由南向东向北为负,有南向西向北为正。例如太阳在正东方,则其方位角为-90度;在正东北方时,方位角为-135度;在正西方时,方位角是90度,在正西北方为135度;当然在正北方时方位角可以表示为正负180度。

(2) 计算过程

根据上述定义,对于空间某一点的日照光线,可以有如下示意图。

令太阳光线长度L1=1,有如下推算过程:

α是太阳高度角,则日照方向Z长度L3=sin(α); L1在地平面(XY)平面的长度L2 = cos(α); β是太阳方位角,则日照方向X长度L5 = L2cos(β); 同时日照方向Y长度L4 = L2sin(β)。

因此,对于太阳高度角α和太阳方位角β,日照光线的单位向量n(x,y,z)为:

X = cos(α)*cos(β); Y = cos(α)*sin(β); Z = sin(α);

4) 改进实现

在OSG中是通过设置光照节点加入到场景节点中来实现光照的。这里把太阳高度角设置成45度,太阳方位角度设置成315度。通过上述转换,得到光照方向。有一点要注意的是osg::Light没有显式的设置平行光的接口,请教大牛才知道只需要在setPosition()函数中设置w分量为0就可以了。关于这一点我也确实有点不理解。

#include <iostream>
#include <Windows.h>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Light>

using namespace std;
using namespace osg;

//添加灯光节点
void AddLight(osg::ref_ptr<osg::Group> group)
{
	double solarAltitude = 45.0;
	double solarAzimuth = 315.0;

	//开启光照
	osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
	stateset = group->getOrCreateStateSet();
	stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);				// 启用光照
	stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON);				// 启用指定光源

	//创建一个Light对象
	osg::ref_ptr<osg::Light> light = new osg::Light();
	light->setLightNum(0);

	//设置方向:平行光
	osg::Vec3 arrayvector(0.0f, 0.0f, -1.0f);
	double fAltitude = osg::DegreesToRadians(solarAltitude);				//光源高度角
	double fAzimuth = osg::DegreesToRadians(solarAzimuth);			//光源方位角
	arrayvector[0] = cos(fAltitude)*cos(fAzimuth);
	arrayvector[1] = cos(fAltitude)*sin(fAzimuth);
	arrayvector[2] = sin(fAltitude);
	light->setDirection(arrayvector);

	//平行光位置任意,但是w分量要为0
	osg::Vec4 lightpos(arrayvector[0], arrayvector[1], arrayvector[2], 0.0f);
	light->setPosition(lightpos);   

	//设置环境光的颜色
	light->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

	//设置散射光颜色
	light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));     

	//    //设置恒衰减指数
	//    light->setConstantAttenuation(1.0f);
	//    //设置线形衰减指数
	//    light->setLinearAttenuation(0.0f);
	//    //设置二次方衰减指数
	//    light->setQuadraticAttenuation(0.0f);

	//创建光源
	osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
	lightSource->setLight(light);

	group->addChild(lightSource);
}


int main()
{
	//根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;	
	root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);		//默认去掉光照

	//
	string osgPath = "C:/Data/baoli/Production_3/Data/MultiFoderReader.osgb";
	osg::Node * node = osgDB::readNodeFile(osgPath);
	root->addChild(node);

	//
	AddLight(root);

	//
	osgViewer::Viewer viewer;
	viewer.setSceneData(root);
	viewer.setUpViewInWindow(100, 100, 800, 600);
	return viewer.run();
}

复制

最终运行结果是模型整体有了亮度,但是由于纹理的效果,光照的明暗效果的效果没有显现出来。但是如果是白模,将会看到很明显的明暗效果。

3. 阴影

在OSG中已经实现了生成阴影的组件osgShadow。其具体调用方式也比较简单,首先将节点和灯光加入到ShadowedScene对象,然后标明投射者和被投射者,最后选择一种阴影渲染算法应用到场景就可以了。

注意这里的阴影渲染算法应该选用ShadowMap,因为我这里的投射者和被投射者都是同一个物体,很多例子里面用的ShadowTexture算法是不支持自投影的。

#include <iostream>
#include <Windows.h>

#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osg/Light>

#include <osgShadow/ShadowedScene>
#include <osgShadow/ShadowMap>

using namespace std;
using namespace osg;

//添加灯光节点
void AddLight(osg::ref_ptr<osg::Group> group)
{
	double solarAltitude = 45.0;
	double solarAzimuth = 315.0;

	//开启光照
	osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
	stateset = group->getOrCreateStateSet();
	stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON);				// 启用光照
	stateset->setMode(GL_LIGHT0, osg::StateAttribute::ON);				// 启用指定光源

	//创建一个Light对象
	osg::ref_ptr<osg::Light> light = new osg::Light();
	light->setLightNum(0);

	//设置方向:平行光
	osg::Vec3 arrayvector(0.0f, 0.0f, -1.0f);
	double fAltitude = osg::DegreesToRadians(solarAltitude);				//光源高度角
	double fAzimuth = osg::DegreesToRadians(solarAzimuth);			//光源方位角
	arrayvector[0] = cos(fAltitude)*cos(fAzimuth);
	arrayvector[1] = cos(fAltitude)*sin(fAzimuth);
	arrayvector[2] = sin(fAltitude);
	light->setDirection(arrayvector);

	//平行光位置任意,但是w分量要为0
	osg::Vec4 lightpos(arrayvector[0], arrayvector[1], arrayvector[2], 0.0f);
	light->setPosition(lightpos);   

	//设置环境光的颜色
	light->setAmbient(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

	//设置散射光颜色
	light->setDiffuse(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));     

	//    //设置恒衰减指数
	//    light->setConstantAttenuation(1.0f);
	//    //设置线形衰减指数
	//    light->setLinearAttenuation(0.0f);
	//    //设置二次方衰减指数
	//    light->setQuadraticAttenuation(0.0f);

	//创建光源
	osg::ref_ptr<osg::LightSource> lightSource = new osg::LightSource();
	lightSource->setLight(light);

	group->addChild(lightSource);
}


int main()
{
	//根节点
	osg::ref_ptr<osg::Group> root = new osg::Group;	
	root->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);		//默认去掉光照

	//标识阴影接收对象																														
	const int ReceivesShadowTraversalMask = 0x1;
	//标识阴影投影对象
	const int CastsShadowTraversalMask = 0x2;

	//阴影节点
	osg::ref_ptr<osgShadow::ShadowedScene> shadowedScene = new osgShadow::ShadowedScene();
	shadowedScene->setReceivesShadowTraversalMask(ReceivesShadowTraversalMask);
	shadowedScene->setCastsShadowTraversalMask(CastsShadowTraversalMask);	
	root->addChild(shadowedScene);
	
	//场景节点
	string osgPath = "C:/Data/baoli/Production_3/Data/MultiFoderReader.osgb";
	osg::Node * node = osgDB::readNodeFile(osgPath);
	shadowedScene->addChild(node);
	
	//设置投射者
	node->setNodeMask(CastsShadowTraversalMask);               //只需要设置投射体,那么默认情况下所有的物体都是被投物体

	//ShadowMap阴影算法
	osg::ref_ptr<osgShadow::ShadowMap> sm = new osgShadow::ShadowMap;
	shadowedScene->setShadowTechnique(sm.get());

	//
	AddLight(shadowedScene);

	//
	osgViewer::Viewer viewer;
	viewer.setSceneData(root);
	viewer.setUpViewInWindow(100, 100, 800, 600);
	return viewer.run();
}

复制

最后的实现效果如下,可以看到很明显的阴影效果:

4. 太阳高度角与太阳方位角的计算

到这里光照和阴影的效果就已经完全实现了,但是我这里模拟的是太阳日照的效果,那么一个新的问题又产生了。前面说根据太阳高度角与太阳方位角计算光照的方向。那么太阳高度角与太阳方位角又是怎么计算出来的呢?这里推荐一篇写的不错的文章:太阳高度角方位角计算。可惜这篇文章的图片已失效,我这里就把四个计算公式再贴一下:

1) 太阳高度角计算公式

2) 太阳方位角计算公式

3) 太阳赤纬计算公式

4) 时角计算公式

5) 真太阳时

那篇文章中其他的公式都很清晰,但是关于真太阳时的描述其实我觉得没有讲清楚,看的是一头雾水。后来我也在网上查阅一些资料,令人好笑的是这个真太阳时关联的最多的却是算命算生辰八字。那我就通过这个一步步来讲这个真太阳时是怎么来的。

我们知道,古代是通过日晷等方式来计时的,例如午时就是影子最短的时候。但是由于日照到达地球的差异,乌鲁木齐和北京的午时肯定不是同一时刻。古代的人没有那个技术条件,将各地的时间统一起来,都是各地用各自的地方时来计时。所以算生辰八字和计算日照一样,都需要当地最精确的太阳光照造成的时间,这个时间就是真太阳时。

但是我们现在都是有行政时间的,无论在北京或者乌鲁木齐,用的都是东经120度的中国北京时间。而在世界上是分24个时区的,每15度就是一个时区。那么可以算算北京时间12点整在乌鲁木齐的真太阳时是多少。

经查阅乌鲁木齐的经度大约为87.68,那么时差为(87.68- 120.0)/15.0=-2.154667,也就是负2小时9分钟16.8秒,因此可算得乌鲁木齐的地方时就是9时50分43.2秒。那么这个算出来的地方时是不是就是真太阳时呢?

其实也不是的。这个时间其实是平太阳时。平太阳时假设地球绕太阳是标准的圆形,一年中每天都是均匀的。但是地球绕日运行的轨道是椭圆的,则地球相对于太阳的自转并不是均匀的,每天并不都是24小时,有时候少有时候多。这个时间差异就是真太阳时差。

在查阅真太阳时差的时候发现资料真的挺少,而且各有说法。有的说真太阳时差每年都不一样,是根据天文信息计算出来的,每年都会发布一次;而在维基百科上面给出了每天的真太阳时差的模拟计算公式;更多的是给了一张表,按照表的日期取值就行了[什么是真太阳时]。我这里只能采信第三种,例如5月29日的真太阳时差是+2分22秒,那么将上面计算的平太阳时加上这个时差,为9时53分5.2秒。即5月29日北京时间乌鲁木齐的真太阳时为9时53分5.2秒。
 

参考文档

  1. Shadows
  2. 太阳高度角方位角计算
  3. 什么是真太阳时
  4. (转载)关于太阳(卫星)天顶角,太阳高度角,太阳方位角的整理
  5. DEM-地貌晕渲图的生成原理
  6. OSG 学习第四天:光照

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/768636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Appium自动化测试知识点

一、App环境搭建 1、安装jdk&#xff0c;配置jdk环境变量 2、Android SDK环境安装 3、Appium server安装 4、模拟器的安装&#xff08;夜神模拟器&#xff09; 5、安装appium-python-client Python第三方库 二、App自动化测试原理 如何通过代码操作不同操作系统&#xff08;…

基于Dubbo分布式网上售票系统

一、项目介绍 民航售票是一个高度依赖信息业的行业。但在机票销售的管理和规范这方面上存在着很多各种各样的问题。例如订票是客运行业中的一个最基本的业务,表面上看,它只是机票站业务的一个简单的部分,但是它涉及到管理与客户服务等多方面,关系到民航公司能否正常运作。…

log4j--动态打印日志文件到指定文件夹

文章目录 log4j--动态打印日志文件到指定文件夹1、添加Maven依赖2、配置文件 log4j.properties3、编写日志打印工具类 LogUtil4、工具类调用 log4j–动态打印日志文件到指定文件夹 1、添加Maven依赖 <!-- log4j日志相关坐标 --><dependency><groupId>org.s…

无虚拟 DOM 版 Vue 进行到哪一步了?

前言 就在一年前的 Vue Conf 2022&#xff0c;尤雨溪向大家分享了一个非常令人期待的新模式&#xff1a;无虚拟 DOM 模式&#xff01; 我看了回放之后非常兴奋&#xff0c;感觉这是个非常牛逼的新 feature&#xff0c;鉴于可能会有部分人还不知道或者还没听过什么是 Vue 无虚…

哈佛“聘请”AI担任导师,主讲教授:别全信它的,学生应“批判性地思考”

就在人们为AI聊天机器人的利弊争论不休时&#xff0c;哈佛宣布了一个重磅决定&#xff1a;将利用类似ChatGPT的聊天机器人来帮助授课了。 负责的还是计算机系的旗舰项目 —— 计算机科学导论&#xff0c;也就是著名的 CS50。借助机器人导师&#xff0c;哈佛的 CS50 项目将拥有…

Python、Selenium实现问卷星自动填写(内含适配个人问卷的方法)

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Py…

有哪些好用的远程传输大文件的软件

随着网络技术的日益进步和普及&#xff0c;大文件远程传输已经成为了人们生活和工作中必不可少的一部分。然而&#xff0c;在远程传输大文件的过程中&#xff0c;经常会遇到传输速度慢、容易受到干扰等问题&#xff0c;因此需要一款高效、稳定、安全的大文件远程传输软件来解决…

CCF真题练习:202209-1如此编码

题目背景 某次测验后&#xff0c;顿顿老师在黑板上留下了一串数字 23333 便飘然而去。凝望着这个神秘数字&#xff0c;小 P 同学不禁陷入了沉思…… 题目描述 已知某次测验包含 n 道单项选择题&#xff0c;其中第 i 题&#xff08;1≤i≤n&#xff09;有 个选项&#xff0c;…

【Vue 面试题10道】我好像之前想过要写,不过之前JavaScript面试题比较多,就暂时略过了,这些应该几乎把常问的都包括了

博主&#xff1a;_LJaXi Or 東方幻想郷 专栏&#xff1a; 前端面试题 开发工具&#xff1a;Vs Code 本题针对 Vue2 这些几乎把常用的都包括了&#xff0c;问别的就没意思了&#xff0c;毕竟工作拧螺丝嘛 我都好久不用Vue了&#xff0c;不过用了React再回看Vue感觉好简单啊… 其…

数据库性能优化中的查询优化

数据库性能优化中的查询优化 概述 在数据库应用中&#xff0c;查询操作是最常见的操作之一。查询优化是数据库性能优化的关键一环&#xff0c;通过对查询语句和查询执行计划的优化&#xff0c;可以显著提高数据库系统的性能和效率。本文将介绍查询优化的相关知识&#xff0c;…

结构型模式 - 代理模式

概述 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时&#xff0c;访问对象不适合或者不能直接引用目标对象&#xff0c;代理对象作为访问对象和目标对象之间的中介。 Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期…

Docker 基础知识解析:容器与传统虚拟化对比:资源利用、启动时间、隔离性和部署效率

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

Vscode配置grpc+c#+proto

首先是环境配置&#xff0c;用的dotnet5.0的sdk&#xff0c;所以Vscode的C#插件版本要选择1.24&#xff0c;然后需要配置C# Snippets、NuGget Package Manager、vscode-proto3、vscode-solution-extension&#xff08;可选&#xff09;。 以vscode-solution-extension为例新建A…

【大数据之Hive】二十四、HQL语法优化之任务并行度

1 优化说明 Hive的计算任务由MapReduce完成&#xff0c;并行度调整分为Map端和Reduce端。 1.1 Map端并行度 Map端的并行度及Map的个数&#xff0c;由输入文件的切片数决定&#xff0c;一般情况下Map端并行度不需要手动调整。   在特殊情况下&#xff08;查询的表中存在大量小…

大数据学习03-Hive分布式集群部部署

系统环境&#xff1a;centos7 软件版本&#xff1a;jdk1.8、zookeeper3.4.8、hadoop2.8.5、hive1.1.0 一、下载安装 下载hive安装包&#xff0c;上传到linux服务器上&#xff0c; 解压安装包 tar -zxvf apache-hive-1.1.0-bin.tar.gz -C /home/local/重命名文件 mv apache-…

手机VPN的连接方法分享

1、首先在手机屏幕左右的滑动,找到手机的设置图标。 2、在打开的小米手机设置页面中点击“更多连接方式”的菜单项。 3、然后在打开的更多连接方式页面中点击VPN的菜单项。 4、在打开的VPN设置页面中点击“添加VPN”的按钮。 5、在打开的添加VPN的页面中输入VPN的信息后点击右…

Element-UI 实现动态增加多个不同类型的输入框并校验(双重v-for表单验证)

文章目录 前言定义表单格式表单渲染和验证扩展 前言 在做复杂的动态表单&#xff0c;实现业务动态变动&#xff0c;比如有一条需要动态添加的el-form-item中包含了多个输入框&#xff0c;并实现表单验证&#xff0c;但在element-ui组件库中给出的表单校验中没有这样的格式&…

简析三相电能预付费控制系统的设计与产品选型

摘要&#xff1a;介绍了一种电能预付费系统&#xff0c;主要用于三相动力用户。采用预付费控制器终端和电能表分离的方式&#xff0c;从原有电表读取相关数据用于比较&#xff0c;在尽量少改动原有接线的情况下安装预付费控制系统。采用安全性和稳定性高的RFID卡&#xff0c;数…

真正的理解WPF中的TemplatedParent

童鞋们在WPF中经常看到 TemplatedParent ,或者经常看到下面的用法: {Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content} 是不是看的一脸蒙圈? 先看官方文档: 意思是 和这个控件的 模板上的 父亲,如果这个控件不是模板创建的,那么这个值就…

Servlet 会话跟踪基础

文章目录 前言Cookie实例Cookie缺点Cookie案例代码 SessionSession的创建与销毁Session的创建Session的销毁 Session和Cookie的区别不同联系 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 在Web应用程序中&#xff0c;会话跟踪是一种技术&#xff0c;…