OpenSceneGraph 相机与视图

视图与相机

三维视景体

视景体是指成像景物所在空间的集合。它是一个空间集合体。

注意视景体指的是一个空间集合。一般来说,集合中的每个空间都是个立方体。然后所有的这些空间堆在一起,在外侧取一个最大的外接立方体,就是可显示的区域,该可显示区域也是一个视景体。在这个可显示区域内,放置着可以显示的物体。注意,可以显示的物体仅仅是拥有显示权限,但是不一定显示,这取决于相机等其他因素。

单个的视景体,比如一个球体,若要完全显示,其视景体应该是该球体的最小外接立方体;若要只显示上半部分,则取上半球,其视景体是上半球的最小外接立方体。对于半球而言,上半球是视景体,那么其只有上半球有显示权限,下半球没有,所以就算是将该球体位置拉远,也只能看到上半球。

比如一个球体,视景体为该球体的最小外接立方体。可以这样理解:该视景体的内容完完全全绘制在一张大小正好的画布(投影)上,画布(投影)以外都是背景色,取黑色。然后摄像机刚好看着这张画布(投影),画布(投影)上的内容完全显示在摄像机屏幕上。接着,对摄像机进行了右移,在摄像机的画面上,左侧是画布上的部分内容,右侧是黑色背景色。也就是说此时立方体虽然有显示权限,但是却并没有完全显示。但未显示的部分依然是有显示的可能的。

若视景体仅仅为上半球,那么默认情况下,上半球所映射的画布刚好充满摄像机。将摄像机下移,则摄像机画面中显示的是上半球切面的水平线,水平线以下是黑色背景色。即使此时从理论上来说摄像机对着下半球,但由于视景体仅为上半球,所以下半球是没有显示权限的,是不会被显示的。

视景体就意味着可显示空间,在该空间内的一切物体都可以被显示,都可以被看到,该空间外的一切物体都不能被看到。相机若要看到该空间中的物体,则相机本身就必须处于该视景体空间中。若相机在视景体空间外,哪怕该视景体就在相机正对的面前,相机依然无法看到。

视点变换

视点变换就是设置视点的方向和位置。默认情况下,视点定为坐标原点,指向Y正方向。
image.png

投影变换

投影的作用:将要显示的三维视景体按照指定角度和方向投影到二维平面上。

投影分为透视投影和正视投影。根据这两种投影方式的不同,映射出的二维图像也不同:透视投影会出现近大远小的透视效果;正视投影不会影响物体的相对大小,即同样长的边,在远处与在近处看到的长度是相同的。

透视投影分为透视视景体对称透视视景体:

  • 透视视景体
    image.png
    这个函数的前4个参数定义了近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near)。如下图(近大远小):
    image.png
  • 对称透视视景体

image.png
该函数的参数fovy定义视野的角度,以z轴为中心向两侧平分,范围是[0.0, 180.0],也就是说最大为上下各90°;参数aspect是投影平面宽度与高度的比,或者说是视景体的宽高比;参数zNear和Far分别是远近裁剪面沿Z负轴到视点的距离,它们总为正值。

以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向

正视投影分为正射投影特殊正射投影:

  • 正射投影视景体
    image.png
    这个函数的前4个参数定义了近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near)。
    捕获.PNG
  • 特殊正射投影视景体
    image.png

视口

视口就是计算器屏幕的一块矩形区域,三维物体投影为二维平面,而这个二维平面就将显示在视口上。

image.png

裁剪变换

在OSG中,可以设置裁剪平面,将没必要显示的物体去除掉,裁剪平面法线是哪个方向就留下哪个部分

使用osg::ClipPlane类来进行裁剪。

image.png
image.png

也可以使用 osg::Scissor类来进行裁剪。
image.png

相机节点

osg::Camera类可以用于获得三维物体的自定义角度和方向的二维映射(拍照)。

该类方法除了上面视口,投影的相关函数还有如下:
image.png

image.png

图形环境

为了更方便的控制图形渲染,可以设置各种图形渲染的属性,也可以直接采用默认的。

通过osg::GraphicsContext::Traits类来设置图形环境特性。

image.png

通过osg::GraphicsContext类createGraphicsContext()函数来创建图形环境特性。

image.png

图形环境属性:
image.png

宽屏变形实例

#include <osgViewer/Viewer>
 
#include <osg/Node>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Camera>
 
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
 
#include <osgUtil/Optimizer>
 
#include <iostream>
 
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("cow.osg");
 
	root->addChild(node.get());
 
 
	//设置图形环境特性
	osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits();
	traits->x = 0;
	traits->y = 0;
	traits->width = 1000;
	traits->height = 800;
	traits->windowDecoration = true;
	traits->doubleBuffer = true;
	traits->sharedContext = 0;
 
	//场景图形环境特性
	osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext( traits.get());
	if (gc.valid())
	{
		osg::notify(osg::INFO)<< "GraphicsWindow has been created successfully."<< std::endl;
 
		//清楚窗口颜色及清楚颜色和深度缓存
		gc->setClearColor(osg::Vec4f(0.2f, 0.2f, 0.6f, 1.0f));
		gc->setClearMask(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
	}
	else
	{
		osg::notify(osg::NOTICE)<< "GraphicsWindow has not been created successfully."<< std::endl;
	}
 
	//根据分辨率确定合适的投影来保证显示的图形不变形
	double fovy, aspectRatio, zNear, zFar;
	viewer->getCamera()->getProjectionMatrixAsPerspective(fovy, aspectRatio, zNear, zFar);
	double newAspectRatio = double(traits->width) / double(traits->height);
	double aspectRatioChange = newAspectRatio / aspectRatio;
	if (aspectRatioChange != 1.0)
	{
		//设置投影矩阵
		viewer->getCamera()->getProjectionMatrix() *= osg::Matrix::scale(1.0 / aspectRatioChange, 1.0, 1.0);
	}
 
	//设置视口
	viewer->getCamera()->setViewport(new osg::Viewport(0, 0, traits->width, traits->height));
	//设置图形环境
	viewer->getCamera()->setGraphicsContext(gc.get());
 
	//优化场景
	osgUtil::Optimizer optimizer;
	optimizer.optimize(root.get());
 
	viewer->setSceneData(root.get());
	viewer->realize();
	viewer->run();
	return 0;
}

单窗口多相机渲染实例

捕获.PNG

// stdafx.h
 
#include <osg/Node>
#include <osg/Geode>  
#include <osg/Geometry>
#include <osg/Group>
 
#include <osg/MatrixTransform> //移动节点的矩阵类,最常用的移动节点的类。可随动、旋转控制节点
#include <osg/PositionAttitudeTransform> //位置变换节点类,提供模型的位置变换、大小缩放、原点位置的设置、坐标系的变换
#include <osg/Camera> //相机节点,管理OSG中的模型——视图矩阵,相机的管理主要是通过各种变换实现的
 
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
 
#include <osgUtil/Optimizer>
#include <osgViewer/Viewer> //只允许单视图,单视图可以同时包含多个相机渲染,也可以在多窗口中渲染
#include <osgViewer/ViewerEventHandlers> //事件监听



//.cpp
 
/*
 *单窗口多相机渲染
 *
 *创建图形环境的步骤:
 *通过WindowingSystemInterface类得到系统窗口接口,该系统接口主要是为了管理窗口系统与图形环境
 *根据需要设置图形环境特性的参数
 *通过图形环境特性创建图形环境
 *通过图形环境创建窗口
 *
 *建立视口的关键步骤:
 *创建图形环境(创建一个设备上下文对象)
 *将该对象和一个相机关联
 *设置视口位置
 *添加相机到窗口中
 */
void singleWindowMultipleCameras(osg::ref_ptr<osgViewer::Viewer> viewer)
{
	//创建窗口系统接口(调用底层API得到绘图设备、窗口特性)
	osg::ref_ptr<osg::GraphicsContext::WindowingSystemInterface> wsi = osg::GraphicsContext::getWindowingSystemInterface();
 
	if (!wsi)
	{
		osg::notify(osg::NOTICE) << "Error, no WindowSystemInterface available, cannot create windows." << std::endl;
		return;
	}
 
	//得到当前窗口分辨率
	unsigned int width, height;
	wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0), width, height);
 
	//设置图形环境特性(设置要创建的图形设备上下文)
	osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits();
	//设置图形设备的尺寸
	traits->x = 0;
	traits->y = 0;
	traits->width = width;
	traits->height = height;
	//显示标题栏
	traits->windowDecoration = true;
	traits->doubleBuffer = true;
	traits->sharedContext = 0;
 
	//创建图形环境(创建图形设备上下文)
	osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());
 
	if (gc->valid())
	{
		osg::notify(osg::INFO) << "GraphicsWindow has been created successfully." << std::endl;
 
		gc->setClearColor(osg::Vec4f(0.2f, 0.2f, 1.6f, 1.0f));
		gc->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}
	else
	{
		osg::notify(osg::NOTICE) << "GraphicsWindow has not been created successfully." << std::endl;
	}
 
	//设置第一个视口(创建cameraMaster主相机)
	osg::ref_ptr<osg::Camera> cameraMaster = viewer->getCamera();
	//设置图形环境
	cameraMaster->setGraphicsContext(gc.get());
 
	//根据分辨率确定合适的投影来保证显示的图形不变形
	double fovy, aspectRatio, zNear, zFar;
	cameraMaster->getProjectionMatrixAsPerspective(fovy, aspectRatio, zNear, zFar);
	double newAspectRatio = double(traits->width) / double(traits->height);
	double aspectRatioChange = newAspectRatio / aspectRatio;
	if (aspectRatioChange != 1.0)
	{
		cameraMaster->getProjectionMatrix() *= osg::Matrix::scale(1.0 / aspectRatioChange, 1.0, 1.0);
	}
 
	//设置视口位置
	cameraMaster->setViewport(new osg::Viewport(0, 0, width, height));
	//根据是否使用双缓存,确定绘制哪个缓存,如果使用双缓存,先绘制前缓存
	GLenum bufferMaster = traits->doubleBuffer ? GL_BACK : GL_FRONT;
	//设置缓冲区(设置每帧开始绘制时,绘制哪个缓存)
	cameraMaster->setDrawBuffer(bufferMaster);
	cameraMaster->setReadBuffer(bufferMaster);
 
	//设置第二个视口(创建从属相机)
	osg::ref_ptr<osg::Camera> cameraClient = new osg::Camera();
	//设置图形环境
	cameraClient->setGraphicsContext(gc.get());
	//设置视口位置,视口的坐标为左下角为坐标原点
	cameraClient->setViewport(new osg::Viewport(0, 0, 400, 400));
	//根据是否使用双缓存,确定绘制哪个缓存,如果使用双缓存,先绘制前缓存
	GLenum bufferClient = traits->doubleBuffer ? GL_BACK : GL_FRONT;
	//设置缓冲区(设置每帧开始绘制时,绘制哪个缓存)
	cameraClient->setDrawBuffer(bufferClient);
	cameraClient->setReadBuffer(bufferClient);
 
	//添加从属相机
	viewer->addSlave(cameraClient, osg::Matrix::scale(aspectRatio, 1.0, 1.0), osg::Matrix());
}
 
int main()
{
	osg::ref_ptr<osg::Node> root = osgDB::readNodeFile("cow.osg");
 
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
	//启用单视图多相机渲染
	singleWindowMultipleCameras(viewer.get());
 
	//优化场景数据
	osgUtil::Optimizer optimizer;
	optimizer.optimize(root.get());
 
	viewer->setSceneData(root.get());
	viewer->realize();
 
	return viewer->run();
}

效果图:

image.png

  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

freejackman

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值