OSG数学、场景组织+基本图元/几何体+OpenGL方式绘制——b站OSG72讲5-6节(Part1)

一、数学基础

1.坐标系统

  • 世界坐标系,建立了描述其他坐标系所需要的参考框架(能够用世界坐标系来描述其他坐标系的位置,而不能用更大、外部的坐标系来描述世界坐标系,所以它也被称为全局坐标系)。
    世界坐标系通常描述的问题是一些对象的初始位置及场景中对象的变换过程,对象包括摄像机和绘制的物体。

  • 物体坐标系,针对某一特定的物体而建立的独立坐标系,每一个物体都有自己的坐标系,当物体发生变换时,实际上是它本身的坐标系相对于世界坐标系发生变换的过程。
    物体坐标系通常描述的问题是特定物体的内部对象,主要包括物体的顶点、物体的法向量和物体的方向。

  • 摄像机坐标系,适合观察者密切相关的坐标系。和屏幕坐标系相似,但是差别在于摄像机坐标系处于3D空间,屏幕坐标系处于2D平面里。
    在这里插入图片描述
    在这里插入图片描述
    -OSG坐标系统可以理解为OpenGL坐标系绕x轴顺时针旋转90°(右手坐标系)。

2.坐标系统变换

  • 世界坐标系-物体坐标系变换:假设在世界坐标系中,一个人准备走向一栋建筑,那么他就面临世界坐标系到物体坐标系的变换过程。
    这种变换的意义在于世界坐标系下变换的运算。

osg::PositionAttitudeTransform // 位置变换类
osg::MatrixTransform // 矩阵变换类

  • 物体坐标系-世界坐标系:在场景图形中,每一个节点都有自己的父节点且有自己的变换矩阵,这些变换矩阵包含了相对坐标数据。那么,计算某一特定节点在世界坐标系下的坐标,只需要将该节点的根节点和该节点之间的所有变换矩阵相乘即可
    回调和访问器也可。

  • 世界坐标系-屏幕坐标系:所有实体需要经过一系列坐标变换才能正确显示在屏幕上,这些变换主要包括模型变换(将实体对象正确放置在场景中,例如物体坐标系-世界坐标系) ,投影变换(将场景中的实体对象投影到垂直于视线方向的二维成像平面上,例如乘上相机矩阵和透视矩阵)和视口变换(投影变换之后得到的顶点需要经过视区变换才能得到最后的窗口坐标)。

由模型变换和投影变换可得到归一化的设备坐标(即透视除法之后的标准化设备坐标),最后由视口变换到屏幕窗口坐标。

3.向量、矩阵及四元数

  • 向量

// 二维向量
osg::Vec2b/d/f/s
// 三维向量
osg::Vec3b/d/f/s
// 四维向量
osg::Vec4b/d/f/s
并且提供了长度计算和单位化函数,重载了向量加减乘除以及比较函数。

  • 矩阵

osg::Matrix2
osg::Matrix3
osg::Matrixd
osg::Matrixf
还定义了矩阵的基本运算、矩阵与向量的乘法、矩阵的平移
旋转与缩放、逆矩阵,重载了这些矩阵的比较函数。

  • 四元数

一个四元数由一个标量和一个矢量组成;四元数存在于3D数学中有一个重要原因是,它有一种slerp插值运算(一种空间的、平滑的球面线性插值)。
osg::Quat
还定义了一系列运算,例如四元数的旋转、四元数的长度计算、四元数与向量之间的运算,重载了基本运算,还实现了slerp插值运算。
在这里插入图片描述

  • 因为相对于在矩阵上进行插值,四元数能提供平滑的线性插值,但是矩阵能够在坐标系之间转换向量,而四元数不能,因此如果四元数需要在坐标系之间转换,则四元数需要转换为矩阵。

二、场景的组织及渲染

OSG中存在两颗树,即场景树和渲染树。
OSG采用包围体层次BVH,来实现场景图形的管理,采用树状结构保存信息。
在这里插入图片描述
在这里插入图片描述

  • 场景树是一棵由Node组成的树,这些Node可能是矩阵变换状态切换或者真正的可绘制对象,它反映了场景的空间结构,也反映了对象的状态

  • 渲染树是一棵以StateSet和RenderLeaf为节点的树,他可以做到StateSet相同的RenderLeaf同时渲染而不用切换OpenGL状态,并且做到尽量少但在多个不同State间切换。
    渲染树在CullVisitor的cull过程中逐渐创建。纹理、光照及材质都是关于渲染状态的控制。

1.OSG中的父节点与子节点

OSG主要包含4大基本类节点,即Node、Group(组节点)、Geode(叶节点)和Drawable(可绘制对象纯基类),前面三个关于场景的组织和渲染,最后一个是绘图类。

OSG中并不是简单的树结构,而是有向无环图,可以多个父节点共享同一个子节点。
而一般只用作为绘制者的节点适合被多个父节点共享,这时在遍历场景时,每一个父节点都会应用共享子节点的渲染指令。
在这里插入图片描述
在这里插入图片描述

2. osg::Node,基本节点

继承于osg::Object(应用程序无法直接实例化的虚基类,它提供了一系列接口,用于保存于获取节点名称,指定保存数据是静态还是动态更改的),还继承于osg::Referenced(内存管理机制)。

Node最基本的结构:
在这里插入图片描述
OSG节点中包围球会根据子树结构的变换自动重新进行计算;但是对于自定义节点,可能需要在某些特殊时刻更改节点包围体时,强制重新计算:调用dirtyBound提醒更新。

3.osg::Geode,叶节点

包含几何体信息/可绘制实体(用Drawable类容纳),用于管理和保存几何信息绘制信息以便渲染,不会再包含子节点。
也提供了**addDrawable()**函数来关联应用程序中需要渲染的几何体信息。

osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(drawable1);

4.osg::Group,枝节节点

  • osg::Group类是从osg::Referrenced类派生出来的,在通常情况下,只有父节点引用了这个Group对象,所以当释放场景图的根节点时,会引发连锁的内存释放动作(类似qt对象树),这样子有效防止内存泄露。

    允许为其添加任意数量的子节点,子节点也可以继续分发子节点,它作为基类,派生出了很多实用的节点类,如osg::LOD、osg::Switch、osg::Transform、osg::PositionAttitudeTransform、osg::PagedLOD、osg::Impostor和osg::AutoTransform等。
    详见这里o( ̄▽ ̄)ブ

需要注意的是如果使用内存引用计数的机制(ref_ptr)来保存子节点或者可绘制对象:
std::vector<osg::ref_ptr<osg::Node>> _children;
那么例如Group的方法removeChild等,都会引发子节点的内存引用计数变化,进而实现子节点在内存中释放东西,引用计数减1;
vector的erase也会引其其中的ref_ptrosg::Node对象析构,引用计数减1。
最好不要混用内存引用计数机制。

三、基本图元(几何对象和几何体绘制)

在OpenGL中一般分为顶点数据和像素数据,然后这些数据都会被传递到OpenGL渲染管线中。

  • 像素数据经过缩放偏移和裁取之后,被装配到纹理内存,与一同传入的几何数据共同进行光栅化处理,转换为片元,片元再经过一些列操作,包括纹理映射、雾效计算、Alpha测试、深度测试、模板测试等混合与测试之后,绘制到帧缓存Frame Buffer中,并最终显示为屏幕上的一个像素。
  • 帧缓存将显示的画面抽象成一块可以进行读写操作的内存区域,其中每一个存储单元都对应显示屏幕上的一个像素,而整个缓存空间对应当前屏幕上渲染的一帧。PS:一般后期处理都和帧缓存有关。

OSG为用户提供了全面管理顶点数据和像素数据的可行性,以及实现特殊操作和功能扩展的多种接口。

0.几何元素的储存(OpenGL相关知识)

在这里插入图片描述

  • 而更高版本需要使用核心渲染模式(需要有顶点数组VAO)而不是立即渲染模式,例如充分使用各种缓冲区buffer(从cpu传到显存的高速缓存中,即由OpenGL维护的一块显存区域),例如VAO顶点数组对象,VBO顶点缓冲对象,VAP顶点属性指针,EBO/IBO索引缓冲对象等缓冲对象,将避免立即渲染模式造成的冗余和低效,各种缓冲对象都有对应的gl函数设置,启动或关闭特性

    并且支持直接将显卡缓存的数据映射到用户空间(参考glMapBuffer和glUnmapBuffer)。

另一个原因是因为着色器是在GPU执行的,而数据放在缓冲对象(显存)中,GPU访问显存的数据比访问CPU的快的多,所以着色器几乎能立即访问到缓冲区中的数据。

void glGenBuffer(GLSize n, GLunit *buffers);//分配缓存对象名字

void glBindBuffer(GLenum target, GLuint buffer);//绑定缓存对象到OpenGL的目标上,实现映射

void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage);// 给缓存对象分配显存,并将内存数据,传输到显存

glDeleteBuffers()//删除缓冲区对象

void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data);

void *glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access);

void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeptr size);

用另一个OpenGL强大特性——像素缓存对象(PBO),允许将像素数据保存到显卡高速缓存,从而实现纹理烘培RTT等功能。

  • 绘制指令:
    glDrawArrays用于启用的数组直接构建图元
    glDrawElements区别在于支持顶点索引,需要设置vertex attribute

  • 显示列表是一组存储在一起的OpenGL函数(该列表将会预编译,且存在GPU中,因为用的是缓冲区),可以在以后执行。调用一个显示列表时,它所存储的函数就会按照顺序执行。OpenGL的显示列表更像是命令缓存器,非动态数据库(因此其中数据不能动态进行修改,创建变形或者删除某些顶点,需要销毁和重新建立对应的显示列表)。
    创建显示列表以glNewList开始,以glEndList结束。通过glCallList可以调用显示列表。

  • 更多参考这里细节参考这里,以及这里,在这里重点需要知道一个不常见的OpenGL函数——glVertexAttrib(),它可以通过一个索引号来设置某个顶点属性的值,所设置的值都是通用顶点属性,例如坐标,颜色,法线,也可能是GLSL着色语言中专用的顶点属性数据(在顶点着色器中往往能实现关键数据传递的属性量)。

但是需要注意的时当使用自定义着色器,为了兼容A卡和N卡,需要调用
viewer.getCamera()->getGraphicsContext()->getState()->setUseVertexAttributeAliasing(true);会倒是顺序发生变化。
在这里插入图片描述在这里插入图片描述

但是其实opengl中用的glVertexAtrribPointer这种自定义顶点属性更多。

  • Uniform变量及Uniform变量块

Uniform变量存储应用程序通过OpenGL ES API传入到着色器的只读值,对于保存着色器所需的所有数据类型(如变换矩阵、照明参数和颜色)都很有用。
unifrom变量通常保存在硬件中,这个区域被称为“常量寄存器”,是硬件中为存储常量值而分配的特殊空间。因为常量存储的大小一般是固定的,所以,程序中可以使用的uniform变量数量收到限制。也因此一般需要先获得Unifrom在着色器的location,然后通过location再set Uniform
Uniform变量缓冲区对象可以通过一个缓冲区对象支持uniform变量数据的存储。uniform变量缓冲区对象在某些条件下比单独的统一变量有更多的优势。例如,利用uniform变量缓冲区对象,可以在多个shader程序中共享,但是,只需要设置一次。此外,uniform变量缓冲区一般可以存储更大的数据。最后,在uniform变量缓冲对象之间切换,比一次单独一个uniform变量更高效。

  • Framebuffer Object参考这里
    一段存放图形渲染结果并与Window关联的内存。
  • 帧缓冲区(Framebuffer)是可用作渲染目标的缓冲区集合,自定义FBO其实至少要附加attachment一个颜色附件/颜色缓存(可多个)和至少一个缓冲,还可以添加深度缓存和模板缓存。
    在默认的framebuffer中,有一个颜色缓存,深度缓存和模板缓存
    在这里插入图片描述
  • OpenGL有两种framebuffer,一种是由OpenGL Context提供的默认Framebuffer,另外一种就是用户自己创建的framebuffer,这种被称为Framebuffer Objects(FBOs)。我们可以在画其中一帧的过程中,切换当前帧缓存,draw到自定义帧上面。
    同理不是渲染到屏幕上,叫做离屏渲染。
    同理双缓存其实就是创建两个default的framebuffer,而切换过程只是一个简单的指针转换。

回到OSG:
显示列表是大部分OSG程序所使用的默认绘制优化行为,顶点数组则是默认的几何体构建方式。

例如在OSG中使用派生自Drawable的Geometry类表达一个几何体对象,这个几何体对象通常可能包括一个顶点坐标数组、一个顶点法线数组、一个顶点颜色数组、多个纹理坐标数组、一个雾坐标数组等,共同表达了组成这个几何体的每一个顶点所具备的所有属性

1.场景基本绘图类

在这里插入图片描述

PrimitiveSet类(实现顶点索引机制)

主要松散封装了OpenGL的绘图基元/几何图元,通过指定绘图基元来指定几何体顶点将采用哪一种或几种基元绘制,如果没有设置任何图元组,几何体对象将无法正常工作。10种绘图基元如下:
在这里插入图片描述
在这里插入图片描述
PrimitiveSet类不能表达B样条或者NURBS等自由曲线和曲面形体,本身也不能直接记录顶点索引的信息,因此需要使用DrawArrays ,等同于glDrawArraysDrawElements,等同于glDrawElements()
在这里插入图片描述

如果有多组连续数据需要处理成图元,这些数据的起始元素和截止数据首尾相连,但是无法视为一个DrawArrays对象处理,可以使用DrawArrayLengths类:
在这里插入图片描述

  • 执行PrimitiveSet::dirty函数可以在动态地修改了图元索引数据之后,强制更新PrimitiveSet对象并使其作用在实际绘制的几何体上。
  • 通过例如Geometry::addPrimitiveSe可以传递给几何体。

可参考这里或者了解OpenGL绘制流程

向量与数组类

在OSG中定义了大量的类来保存数据,数据通过是以向量的形式来表示的,向量数据主要包括顶点坐标(用osg::Vec3保存)、纹理坐标(osg::Vec2)、颜色(osg::Vec4保存RGBA)和法线(osg::Vec3)等。
还定义了模版数组用来保存对象,例如可以用顶点索引对象osg::DrawElementsUint来保存顶点索引(类似可以用来设置索引缓冲对象数据),用颜色索引osg::TemplateIndexArray来保存颜色,但最常用的还是保存向量数据,如用osg::Vec3Array

Drawable类

作为可绘制对象基类的纯基类,无法实例化,但派生了很多类:
在这里插入图片描述
其中从OSG核心库派生出了3个类,osg::DrawPiexels(主要封装了OpenGL中glDrawPixels()的功能),osg::Geometry(绘制几何体的类,比较灵活),osg::ShapeDrawable(主要封装了一些已经定义好的几何体,不需要设置坐标即可直接调用,如长方体、正方体、球体等)。
其他的类中,有两个派生自粒子系统库,有两个派生自阴影库,还有两个派生自osgSim库和osgText文字库

更多参考下文。

2.基本几何体绘制/构建

OSG中的所有图元都是一维或二维对象,包括单个的点、直线和复杂的多边形。
如果使用离散的几何图元/绘图基元来绘制OSG中的几何体,需要考虑顶带坐标数组与其他属性数组内容不匹配的问题。

OpenGL顶点属性设置原则是每一个顶点对应一个坐标,法线,纹理单元和纹理坐标,颜色值和雾坐标。但是多个顶点可以关联同一个顶点属性。如果将他们关联,OpenGL中一般手动保证,但是osg::Geometry提供了完整对于顶点数组,图元构建和属性绑定的操作接口

几何体类osg::Geometry

主要作用是对指定绘制几何体的顶点数及对数据的解析,基本结构:

//设置/获取顶点空间位置坐标的数组
void setVertexArray (Array *array)
Array * getVertexArray ()

//设置/获取顶点法线的数组,绑定方式
void setNormalArray (Array *array)
void setNormalArray (Array *array, osg::Array::Binding binding)
Array * getNormalArray ()
//设置/获取顶点法线的绑定方式。可设置的枚举量
void setNormalBinding (AttributeBinding ab)
AttributeBinding getNormalBinding () const

//设置/获取顶点颜色的数组,绑定方式
void setColorArray (Array *array)
void setColorArray (Array *array, osg::Array::Binding binding)
Array * getColorArray ()
const Array * getColorArray () const
void setColorBinding (AttributeBinding ab)
AttributeBinding getColorBinding () const

//设置/获取顶点辅助颜色的数组,绑定方式
void setSecondaryColorArray (Array *array)
void setSecondaryColorArray (Array *array, osg::Array::Binding binding)
Array * getSecondaryColorArray ()
void setSecondaryColorBinding (AttributeBinding ab)
AttributeBinding getSecondaryColorBinding () const

//设置/获取顶点雾坐标的绑定方式
void setFogCoordArray (Array *array)
void setFogCoordArray (Array *array, osg::Array::Binding binding)
Array * getFogCoordArray ()
AttributeBinding getFogCoordBinding () const
void setFogCoordBinding (AttributeBinding ab)

//设置/获取指定纹理单元中顶点纹理坐标的数组
void setTexCoordArray (unsigned int unit, Array *array)
void setTexCoordArray (unsigned int unit, Array *array, osg::Array::Binding binding)
Array * getTexCoordArray (unsigned int unit)

//设置/获取指定顶点属性(0~15)的数组,绑定方式
//距离可以参考上文glVertexAttrib()所讲,可以通过索引设置某个顶点属性

void setVertexAttribArray (unsigned int index, Array *array)
void setVertexAttribArray (unsigned int index, Array *array, osg::Array::Binding binding)
Array * getVertexAttribArray (unsigned int index)
void setVertexAttribBinding (unsigned int index, AttributeBinding ab)
AttributeBinding getVertexAttribBinding (unsigned int index) const

//获取当前已设置的图元组的数目
unsigned int getNumPrimitiveSets () const
//获取指定位置的图元组
PrimitiveSet * getPrimitiveSet (unsigned int pos)
//追加一个图元组,即数据解析
bool addPrimitiveSet (PrimitiveSet *primitiveset)
//在指定位置设置一个新的图元组,并替换原有的图元组
bool setPrimitiveSet (unsigned int i, PrimitiveSet *primitiveset)
//在指定位置插入一个图元组
bool insertPrimitiveSet (unsigned int i, PrimitiveSet *primitiveset)
//从指定索引位置开始,删除指定数目的图元组
bool removePrimitiveSet (unsigned int i, unsigned int numElementsToRemove=1)
//获取某个图元组的索引位置
unsigned int 	getPrimitiveSetIndex (const PrimitiveSet *primitiveset) const
  • AttributeBinding
    简化了OpenGL顶点属性的设置过程,之后只能依次为每个顶点设置属性,或者使用顶点数组。

在这里插入图片描述

  • 图元组PrimitiveSet
    扩展上面的BIND_PER_PRIMITIVE_SET

一个osg::Geometry实例可以压入很多个PrimitiveSet,以便实现以下效果:
在这里插入图片描述

绘制并渲染几何体主要有如下3个大步骤
在这里插入图片描述

实例:创建简单图元—createSimpleShape,例如点(大小)、线(线宽)、平面

在这里插入图片描述

... ...
osg::ref_ptr<osg::Geode> CreateSimple()
{
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;

	// 申请顶点
	osg::ref_ptr<osg::Vec3Array> coords = new osg::Vec3Array;
	// 申请颜色
	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
	// 申请法向量
	osg::ref_ptr<osg::Vec3Array> norms = new osg::Vec3Array;
	// 限制线宽
	osg::ref_ptr<osg::LineWidth> width = new osg::LineWidth;
	// 设置虚线
	osg::ref_ptr<osg::LineStipple> stripple = new osg::LineStipple(1, 0x00FF);
	// 设置点大小
	osg::ref_ptr<osg::Point> pointSize = new osg::Point(15.0);

	geode->getOrCreateStateSet()->setMode(GL_BLEND,osg::StateAttribute::ON);
	geode->addDrawable(geom);

	 //设置线宽
	width->setWidth(15.0);
	geode->getOrCreateStateSet()->setAttributeAndModes(width,osg::StateAttribute::ON);
	// 设置虚线
	geode->getOrCreateStateSet()->setAttributeAndModes(stripple, osg::StateAttribute::ON);
	// 设置点大小
	geode->getOrCreateStateSet()->setAttributeAndModes(pointSize, osg::StateAttribute::ON);
	// 设置顶点
	geom->setVertexArray(coords.get());
	// 设置顶点的关联方式,osg::PrimitiveSet::Mode::QUADS为平面
	geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::Mode::LINE_LOOP,0,4));
	geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::Mode::POINTS, 4, 1));
	// 设置颜色
	geom->setColorArray(colors.get());
	geom->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_VERTEX);
	// 设置法向量
	geom->setNormalArray(norms.get());
	geom->setNormalBinding(osg::Geometry::AttributeBinding::BIND_OVERALL);

	// 顶点
	coords->push_back(osg::Vec3(-10.0,5.0,-10.0));
	coords->push_back(osg::Vec3(10.0, 5.0, -10.0));
	coords->push_back(osg::Vec3(10.0, 5.0, 10.0));
	coords->push_back(osg::Vec3(-10.0,5.0, 10.0));
	coords->push_back(osg::Vec3(5.0, 5.0, 5.0));
	// 颜色
	colors->push_back(osg::Vec4f(1.0, 0.0, 1.0, 0.2));
	colors->push_back(osg::Vec4f(1.0, 1.0, 1.0, 0.2));
	colors->push_back(osg::Vec4f(1.0, 0.0, 0.0, 0.2));
	colors->push_back(osg::Vec4f(0.0, 0.0, 0.0, 0.2));
	colors->push_back(osg::Vec4f(1.0, 0.0, 0.0, 1.0));

	// 法向量
	norms->push_back(osg::Vec3(0.0,-1.0,0.0));// 法向量朝外,光线反射也是朝外



	return geode;
}

int main()
{
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
	osg::ref_ptr<osg::Group> group = new osg::Group;
	// 想要blend(混合测试)有预期的效果,必须先渲染所有不透明的,再渲染深度值从大到小的透明的!这样子才能通过深度测试。
	group->addChild(osgDB::readNodeFile("D:\\vs2019 64位 3rdParty osg365 oe32\\OpenSceneGraph-Data\\glider.osg"));
	group->addChild(CreateSimple());

	viewer->setSceneData(group.get());
	return viewer->run();
}

实例:索引绑定几何体绘制

... ...
// 在CreateSimple()的基础上再添加:
// 申请顶点索引数组
	osg::ref_ptr<osg::DrawElementsUInt> quad = new osg::DrawElementsUInt(osg::PrimitiveSet::LINE_LOOP, 0);
	osg::ref_ptr<osg::DrawElementsUInt> point = new osg::DrawElementsUInt(osg::PrimitiveSet::POINTS, 0);
	
// 索引
	quad->push_back(0);
	quad->push_back(1);
	quad->push_back(2);
	quad->push_back(3);
	point->push_back(4);

// 并且将CreateSimple()中的设置顶点的关联方式替换为:
// 设置索引数组
	geom->addPrimitiveSet(quad);
	geom->addPrimitiveSet(point);
... ...

实例:简易房屋(OpenGL方式)

osg::Drawable *createHouseWall()
{
	// 创建顶点数组,逆时针添加
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
	vertices->push_back(osg::Vec3(0.0, 0.0, 4.0)); //0
	vertices->push_back(osg::Vec3(0.0, 0.0, 0.0)); //1
	vertices->push_back(osg::Vec3(6.0, 0.0, 4.0)); //2
	vertices->push_back(osg::Vec3(6.0, 0.0, 0.0)); //3
	vertices->push_back(osg::Vec3(6.0, 4.0, 4.0)); //4
	vertices->push_back(osg::Vec3(6.0, 4.0, 0.0)); //5
	vertices->push_back(osg::Vec3(0.0, 4.0, 4.0)); //6
	vertices->push_back(osg::Vec3(0.0, 4.0, 0.0)); //7
	vertices->push_back(osg::Vec3(0.0, 0.0, 4.0)); //8
	vertices->push_back(osg::Vec3(0.0, 0.0, 0.0)); //9
												   
	// 创建顶点法线数组
	osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array(10);
	(*normals)[0].set(osg::Vec3(-0.707, -0.707, 0.0)); //0
	(*normals)[1].set(osg::Vec3(-0.707, -0.707, 0.0)); //1
	(*normals)[2].set(osg::Vec3(0.707, -0.707, 0.0)); //2
	(*normals)[3].set(osg::Vec3(0.707, -0.707, 0.0)); //3
	(*normals)[4].set(osg::Vec3(0.707, 0.707, 0.0)); //4
	(*normals)[5].set(osg::Vec3(0.707, 0.707, 0.0)); //5
	(*normals)[6].set(osg::Vec3(-0.707, 0.707, 0.0)); //6
	(*normals)[7].set(osg::Vec3(-0.707, 0.707, 0.0)); //7
	(*normals)[8].set(osg::Vec3(-0.707, -0.707, 0.0)); //8
	(*normals)[9].set(osg::Vec3(-0.707, -0.707, 0.0)); //9
	
	//创建纹理坐标
	osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array(10);
	(*texcoords)[0].set(0.0, 1.0);
	(*texcoords)[1].set(0.0, 0.0);
	(*texcoords)[2].set(0.3, 1.0);
	(*texcoords)[3].set(0.3, .0);
	(*texcoords)[4].set(0.5, 1.0);
	(*texcoords)[5].set(0.5, 0.0);
	(*texcoords)[6].set(0.8, 1.0);
	(*texcoords)[7].set(0.8, 0.0);
	(*texcoords)[8].set(1.0, 1.0);
	(*texcoords)[9].set(1.0, 0.0);
 
	//创建一个几何对象
	osg::ref_ptr<osg::Geometry> houseWall = new osg::Geometry;
	//设置顶点数据、纹理坐标、法线数组
	houseWall->setVertexArray(vertices.get());
	houseWall->setTexCoordArray(0, texcoords.get());//使用默认的纹理单元
	houseWall->setNormalArray(normals.get());
	houseWall->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
	//添加图元,多段四边形条带,即一系列四边形
	houseWall->addPrimitiveSet(new osg::DrawArrays(osg::DrawArrays::QUAD_STRIP, 0, 10));
	
	//设置纹理贴图,通过设置StateSet,使用默认的着色器
	houseWall->getOrCreateStateSet()->setTextureAttributeAndModes(0, new osg::Texture2D(osgDB::readImageFile("../wall.bmp")));
 
	return houseWall.release();
}


osg::Drawable *createHouseRoof()
{
	//创建顶点数组,逆时针添加
	osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;

	vertices->push_back(osg::Vec3(-0.2, -0.5, 3.5)); //0
	vertices->push_back(osg::Vec3(6.2, -0.5, 3.5)); //1
	vertices->push_back(osg::Vec3(0.8, 2.0, 6.0)); //2
	vertices->push_back(osg::Vec3(5.2, 2.0, 6.0)); //3
	vertices->push_back(osg::Vec3(-0.2, 4.5, 3.5)); //4
	vertices->push_back(osg::Vec3(6.2, 4.5, 3.5)); //5
 
	//绘图基元为多段四边形条带
	osg::ref_ptr<osg::DrawArrays> roof = new osg::DrawArrays(osg::DrawArrays::QUAD_STRIP, 0, 6);
	//绘图基元为三角形,使用索引数组,对应顶点数组里面的索引
	osg::ref_ptr<osg::DrawElementsUInt> roofSide = new osg::DrawElementsUInt(osg::DrawElementsUInt::TRIANGLES, 6);
	(*roofSide)[0] = 0;
	(*roofSide)[1] = 2;
	(*roofSide)[2] = 4;
	(*roofSide)[3] = 5;
	(*roofSide)[4] = 3;
	(*roofSide)[5] = 1;
 
	//创建屋顶颜色数组
	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;

	colors->push_back(osg::Vec4(0.25, 0.0, 0.0, 1.0));
 
	//创建一个几何体对象
	osg::ref_ptr<osg::Geometry> houseRoof = new osg::Geometry;
	//设置顶点数据、颜色数组
	houseRoof->setVertexArray(vertices.get());
	houseRoof->setColorArray(colors.get());
	//设置颜色的绑定方式为一个属性与所有顶点绑定
	houseRoof->setColorBinding(osg::Geometry::BIND_OVERALL);
	//添加图元
	houseRoof->addPrimitiveSet(roof.get());
	houseRoof->addPrimitiveSet(roofSide.get());
	//由于顶面的法线计算比较复杂,这里使用OSG自带的快速法线生成工具osgUtil::SmoothingVisitor
	osgUtil::SmoothingVisitor smv;
	smv.smooth(*houseRoof);
	return houseRoof.release();
}

int main(int argc, char **argv)
{
	//将房屋和墙体整合到一个叶节点中进行渲染
	osg::ref_ptr<osg::Geode> geode = new osg::Geode;
	geode->addDrawable(createHouseWall());
	geode->addDrawable(createHouseRoof());
 
	osgViewer::Viewer viewer;
 
	viewer.setSceneData(geode.get());
	return viewer.run();
}

自动生成发现简易参考几何体操作部分,需要了解访问器机制

3.几何体/可绘制体的访问

osg::Drawable类

Drawable是可绘制体的容纳者,可以保存和渲染的数据包括顶点数据构建成的几何体、位图、文字等,由osg::Geode类进行管理。
有一系列操作接口:
在这里插入图片描述

  • 核心函数为draw(),对于每一个保存在Geode节点中的Drawable可绘制对象,OSG会在每一帧中调用该函数,基本内容可以如下编写:
    在这里插入图片描述

  • RenderInfo是记录OpenGL渲染状态、窗口信息以及图形设备的信息。

  • OSG中支持多窗口、多显示器的渲染,因此可绘制对象对应的显示列表对象可能有多个。

核心函数drawImplementation()——实现顶点绑定

  • 如果使用默认显示列表,则该函数只会在构建显示列表的时候执行一次,之后都不会再执行。
  • 如果编写自定义Drawable类,希望在其中实现自定义的场景绘制工作,可以使用setDisplayList设置为false,关闭使用显示列表,则每一次绘制这个对象,都会直接执行drawImplementation中的内容。
  • 该函数中将分别实现OpenGL立即模式,顶点数组和VBO绘制功能的调用,自行选中合适的方案,将用户传入的数据在场景中实现可视化。
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

核心函数drawImplementation()——OpenGL状态机osg::State

用于封装顶点数组和VBO的绘制操作

参考drawImplementation的主要工作内容在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 非常多别的接口,基本上默认自动绑定的操作都在State,例如设置内部渲染底层各种MVP矩阵

osg::BufferObject——VBO\EBO\PBO

在这里插入图片描述

4.几何体/可绘制体的数据更新显示

场景动画一般通过动态地改变几何体的顶点、法线、颜色等数据的内容。
最简单的顶点数据更新方法是预先获取setVertexArray()所用的数组数据,并对其进行更新,但是对于开启显示列表支持的几何体(默认使用,且显示列表中的数据是不能动态修改的,因为已经编译好了代码),任何对于顶点数据的修改都需要销毁和重新建立相应的显示列表:
在这里插入图片描述
所以其实这种情况我们多半会选择慢速通道或者VBO。
lineGeom->setUseDisplayList(false);
或者
lineGeom->setUseVertexBufferObjects(true);
在这里插入图片描述

5.几何体/可绘制体的更新回调和事件回调

osg::Drawable::UpdateCallback

在这里插入图片描述
在这里插入图片描述
使用Drawable::setUpdateCallback设置给几何体之后,这个回调函数将在系统运行的每一帧被调用。
在这里插入图片描述

  • 参数访问器用于遍历节点树并找到回调对象的位置,以及回调所在几何体的对象指针,以便对其进行操作。

osg::Drawable::EventCallback

同理我们可以为几何体设置事件回调,其工作原理等同于节点的事件回调,每当一个新的人机交互事件发生时,都会触发事件回调执行用户自定义的回调函数内容。
在这里插入图片描述

实例:跃动的线

class DynamicLineCallback : public osg::Drawable::UpdateCallback
{
public:
    DynamicLineCallback() : _angle(0.0)
    {

    }

    virtual void update(osg::NodeVisitor* nv, osg::Drawable* drawable)
    {
        osg::Geometry* geom = dynamic_cast<osg::Geometry*>(drawable);
        if (!geom)
        {
            return;
        }

        osg::Vec3Array* vertices = dynamic_cast<osg::Vec3Array*>(geom->getVertexArray());
        if (vertices)
        {
            for (osg::Vec3Array::iterator itr = vertices->begin(); itr != vertices->end() - 1; ++itr)
            {
                itr->set(*(itr + 1));
            }

            _angle += 1.0 / 10.0;
        }

        osg::Vec3& pt = vertices->back();
        pt.set(10.0 * cos(_angle), 0.0, 10.0 * sin(_angle));
        vertices->dirty();
    }
private:
    float _angle;
};
int main(int argc, char* argv[])
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(10);
    for (unsigned int i = 0; i < 10; ++i)
    {
        (*vertices)[i].set(float(i), 0.0, 0.0);
    }

    osg::ref_ptr<osg::Geometry> lineGeom = new osg::Geometry;
    // 设置顶点坐标数组
    lineGeom->setVertexArray(vertices.get());
    // 绘制一条直线带,并且设置改几何体的外包围盒大小
    lineGeom->addPrimitiveSet(new osg::DrawArrays(osg::DrawArrays::LINE_STRIP, 0, 10));
    lineGeom->setInitialBound(osg::BoundingBox(osg::Vec3(-10, -10, -10), osg::Vec3(10, 10, 10)));
    // 设置更新回调,再每一帧中,改几何体的顶点坐标都会被修改,并使用dirty函数将修改结果通知给VBO对象(使用VBO的话)
    lineGeom->setUpdateCallback(new DynamicLineCallback);
    // 下面两个的目的都是为了不使用显示列表,以便于drawImplementation能够每一帧都执行,即每一帧都重新绑定顶点坐标。
    lineGeom->setUseDisplayList(false);
    lineGeom->setUseVertexBufferObjects(true);

    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
    geode->getOrCreateStateSet()->setAttribute(new osg::LineWidth(2.0));
    geode->addDrawable(lineGeom.get());

    osgViewer::Viewer viewer;
    viewer.setSceneData(geode.get());
    return viewer.run();
}```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值