OpenSceneGraph几何基础教程【OSG】

news2024/12/22 17:59:16

默认情况下,OSG 使用顶点数组法和显示列表法来渲染几何体。 但是,渲染策略可能会发生变化,具体取决于几何数据的呈现方式。 在本文中,我们将了解在 OSG 中处理几何体的基本技术。

在这里插入图片描述

OpenSceneGraph 后端的 OpenGL 使用几何图元(例如点、线、三角形和多边形面)来构建三维世界中的所有对象。

这些图元由它们的顶点指定,包括顶点坐标、法线分量、颜色数据和纹理坐标。 此数据存储在特殊数组中。 例如,可以通过为描述它们的对象指定顶点索引列表来形成图元。 这种方法称为顶点数组法;它消除了内存中冗余顶点的存储并且具有良好的速度。

此外,OpenGL 可以使用所谓的显示列表机制。在显存中准备好的图元可以重复使用,这大大加快了静态对象的显示速度。

1、Geode 和 Drawable 类

osg::Geode 类标识场景树的所谓“叶”节点。 它不能有子节点,但它包含渲染几何体的所有必要信息。 名字 Geode 是单词 geometry node 的缩写。

引擎要处理的几何数据被记录在 osg::Drawable类的一组对象中,由 osg::Geode类管理。 osg::Drawable 类是一个纯虚类。 它继承了一些子类,这些子类是通过OpenGL管线处理的三维模型、图像和文本。 OSG 中的 drawable 是指引擎可以绘制的所有元素。

osg::Geode 类提供了许多用于附加和分离可绘制对象的方法:

公共方法 addDrawable () - 将指向可绘制元素的指针传递给类 osg::Geode 的实例。 所有这些元素都由智能指针 osg::ref_ptr <> 控制。

公共方法 removeDrawable () 和 removeDrawables () 从 osg::Geode 中移除对象并减少对它的引用计数。 removeDrawable() 方法将一个指向感兴趣元素的指针作为其唯一参数, removeDrawables() 方法有两个参数:初始索引和要从 osg::Geode 对象数组中移除的元素数。

getDrawable () 方法返回指向作为参数传递的索引处的元素的指针。

getNumDrawables() 方法返回附加到 osg::Geode 的元素总数。 例如,要从 osg::Geode 中删除所有元素,可以使用这样的代码:

geode->removeDrawables(0, geode->getNumDrawables());

2、绘制最简单的形状

OSG提供了 osg::ShapeDrawable类,它派生自 osg::Drawable类,旨在创建最简单的三维图元。 这个类包括一个 osg::Shape 对象,它存储了关于特定几何体和更多参数的信息。 使用 setShape() 方法生成图元,例如:

shapeDrawable->setShape(new osg::Box(osg::Vec3(1.0f, 0.0f, 0.0f), 10.0f, 10.0f, 5.0f));

上面的代码创建一个长方体,其几何中心位于点 (1.0, 0.0, 0.0),宽度和高度均为 10,深度为 5 个单位。 osg::Vec3 类定义了三维空间中的向量,类似的,osg::Vec2 和osg::Vec4 类描述了相应维度的向量。

OSG 中最流行的原语由类 osg::Box、 osg::Capsule 、 osg::Cone、 osg::Cylinder 和 osg::Sphere 表示。

考虑这种机制的一个例子。

#ifndef     MAIN_H
#define     MAIN_H
#include<osg/ShapeDrawable>
#include<osg/Geode>
#include<osgViewer/Viewer>
#endif
// MAIN_H
#include"main.h"

int main(int argc, char *argv[]){
    (void) argc;
    (void) argv;
    
    osg::ref_ptr<osg::ShapeDrawable> shape1 = new osg::ShapeDrawable;
    shape1->setShape(new osg::Box(osg::Vec3(-3.0f, 0.0f, 0.0f), 2.0f, 2.0f, 1.0f));
    
    osg::ref_ptr<osg::ShapeDrawable> shape2 = new osg::ShapeDrawable;
    shape2->setShape(new osg::Cone(osg::Vec3(0.0f, 0.0f, 0.0f), 1.0f, 1.0f));
    shape2->setColor(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
    
    osg::ref_ptr<osg::ShapeDrawable> shape3 = new osg::ShapeDrawable;
    shape3->setShape(new osg::Sphere(osg::Vec3(3.0f, 0.0f, 0.0f), 1.0f));
    shape3->setColor(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
    
    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(shape1.get());
    root->addDrawable(shape2.get());
    root->addDrawable(shape3.get());
    
    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    
    return viewer.run();
}

这个例子不需要任何注释:程序创建了三个简单的形状,编译运行后,我们会看到这个结果。

在这里插入图片描述

示例中显示的机制简单明了,但它不是创建几何体的最有效方法,可以专门用于测试。 要在基于 OSG 的高性能应用程序中创建几何图形,可以使用 osg::Geometry 类。

3、几何数据存储

osg::Array 类是一个基本的抽象类,从中继承了几个后代,用于存储传递给 OpenGL 函数的数据。 使用此类类似于使用标准 C++ 库中的 std::vector。 以下代码说明了使用 push_back() 方法将向量添加到顶点数组

vertices->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));

OSG 数组在堆中分配并由智能指针控制。 但是,这不适用于数组元素,例如 osg::Vec3 或 osg::Vec2,它们也可以在堆栈上创建。

osg::Geometry 类是对处理顶点数组的 OpenGL 函数的包装。 它派生自 osg::Drawable 类,可以很容易地添加到 osg::Geode 对象列表中。 此类将上述数组作为输入,并使用它们使用 OpenGL 生成几何图形。

4、顶点及其属性

顶点是几何图元的原子单位。 它有许多描述二维或三维空间点的属性。 这些属性包括:位置、颜色、法向量、纹理坐标、雾坐标等。 顶点必须始终在空间中有一个位置,至于其他属性,它们可以选择性地存在。 OpenGL 支持 16 种基本的顶点属性,并且可以使用不同的数组来存储它们。

osg::Geometry类支持所有的属性数组,可以通过 set * Array()类型的方法设置。

AttributeData typeOsg method :: GeometryEquivalent OpenGL call
Position3-vectorsetVertexArray ()glVertexPointer ()
Normal3-vectorsetNormalArray ()glNormalPointer ()
Colour4-vectorsetColorArray ()glColorPointer ()
Secondary color4-vectorsetSecondaryColorArray ()glSecondaryColorPointerEXT ()
Fog CoordinatesfloatsetFogCoordArray ()glFogCoordPointerEXT ()
Texture coordinates2- or 3-vectorsetTexCoordArray ()glTexCoordPointer ()
Other attributesUser definedsetVertexArribArray ()glVertexAttribPointerARB ()

原则上,有必要为每个顶点设置属性,这会导致形成多个相同大小的属性数组 - 否则数组大小的不匹配会导致引擎出现未定义的行为。 OSG 支持各种链接顶点属性的方法,例如:

geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

表示每个顶点和每个顶点颜色是一一对应的。 但是,如果你查看这段代码:

geom->setColorBinding(osg::Geometry::BIND_OVERALL);

则将一种颜色应用于整个几何体。 类似地,可以通过调用 setNormalBinding()、 setSecondaryColorBinding()、 setFogCoordBinding()方法和 setVertexAttribBinding() 方法来配置其他属性之间的关系。

5、图元集合

确定顶点属性数组后的下一步是描述如何渲染顶点数据。 虚类 osg::PrimitiveSet 用于控制渲染器从一组顶点生成的几何图元。 osg::Geometry类提供了几种处理几何基元集的方法:

  • addPrimitiveSet () - 将指向一组图元的指针传递给对象 osg::Geometry。
  • removePrimitiveSet() - 移除一组图元。 作为参数,它采用集合的初始索引和要删除的集合数。
  • getPrimitiveSet () - 通过作为参数传递的索引返回一组图元。
  • getNumPrimitiveSets () - 返回与该几何关联的原始集的总数。

osg::PrimitiveSet类是抽象的,没有实例化,但是有几个派生类继承自它,封装了OpenGL操作的图元集,比如 osg::DrawArrays和 osg::DrawElementsUInt。

osg::DrawArrays 类使用顶点数组的几个连续元素来构造几何基元。 它可以通过调用方法创建并附加到几何体。

geom->addPrimitiveSet(new osg::DrawArrays(mode, first, count));

第一个参数指定图元模式的类型,类似于对应的OpenGL图元类型:GL_POINTS、GL_LINE_STRIP、GL_LINE_LOOP、GL_LINES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN、GL_TRIANGLES、GL_QUAD_STRIP、GL_QUADS和GL_POLYGON。

第二个和第三个参数指定顶点数组中的第一个索引以及应从中生成几何图形的顶点数。 而且,OSG 不会检查指定数量的顶点是否足以构建模式指定的几何体,这会导致应用程序崩溃!

6、绘制一个彩色正方形

我们将以上所有内容作为一个简单示例来实现。

#ifndef     
MAIN_H
#define     MAIN_H
#include<osg/Geometry>
#include<osg/Geode>
#include<osgViewer/Viewer>
#endif
// MAIN_H
#include"main.h"

int main(int argc, char *argv[]){
    
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
    vertices->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
    vertices->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
    vertices->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));
    
    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
    
    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
    colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
    colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
    colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
    colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));
    
    osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
    quad->setVertexArray(vertices.get());
    quad->setNormalArray(normals.get());
    quad->setNormalBinding(osg::Geometry::BIND_OVERALL);
    quad->setColorArray(colors.get());
    quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
    quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));
    
    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(quad.get());
    
    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    
    return viewer.run();
}

编译执行后我们会得到类似这样的结果:

在这里插入图片描述

这个例子需要一些说明。

首先我们创建一个存储坐标的正方形顶点数组。

osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3(0.0f, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(1.0f, 0.0f, 0.0f));
vertices->push_back(osg::Vec3(1.0f, 0.0f, 1.0f));
vertices->push_back(osg::Vec3(0.0f, 0.0f, 1.0f));

接下来,设置法线数组。 在我们的简单案例中,我们不需要为每个顶点创建法线; 描述一个垂直于正方形平面的单位向量就足够了。

osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));

为每个顶点设置颜色。

osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
colors->push_back(osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
colors->push_back(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f));
colors->push_back(osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
colors->push_back(osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

现在创建一个几何对象,其中将存储我们正方形的描述,并将对其进行渲染。 将顶点数组传递给此几何体。

osg::ref_ptr<osg::Geometry> quad = new osg::Geometry;
quad->setVertexArray(vertices.get());

传输法线数组,我们通知引擎所有顶点将使用单个法线,指示链接(“绑定”)法线的方法 BIND_OVAERALL:

quad->setNormalArray(normals.get());
quad->setNormalBinding(osg::Geometry::BIND_OVERALL);

相反,通过传递顶点的颜色,我们表明每个顶点都有自己的颜色:

quad->setColorArray(colors.get());
quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

现在我们为几何创建一组图元。 我们指出应该从顶点数组生成方形(GL_QUADS)面,以索引为0的顶点作为第一个顶点,顶点总数为4:

quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));

好吧,几何的转移和渲染的启动:

osg::ref_ptr<osg::Geode> root = new osg::Geode;
root->addDrawable(quad.get());

osgViewer::Viewer viewer;
viewer.setSceneData(root.get());

return viewer.run();

上面的代码等效于纯 OpenGL 上的实现:

static const GLfloat vertices[][3] = { … };
glEnableClientState( GL_VERTEX_ARRAY );
glVertexPointer( 4, GL_FLOAT, 0, vertices );
glDrawArrays( GL_QUADS, 0, 4 );

7、索引图元中的顶点

osg::DrawArrays 类在直接从数组读取顶点数据时效果很好,没有间隙。 但是,当同一个顶点可以属于一个对象的多个面时,它就不那么有效了。 考虑这个例子:

在这里插入图片描述

一个立方体有八个顶点。 然而,从图中可以看出(我们正在考虑将一个立方体扫到一个平面上),一些顶点属于多个面。 如果我们构建一个包含 12 个三角形面的立方体,那么这些顶点将重复,而不是 8 个顶点的数组,我们将得到 36 个顶点的数组,其中大部分实际上是相同的顶点!

在OSG中,有类 osg::DrawElementsUInt、 osg::DrawElementsUByte和 osg::DrawElementsUShort,它们使用顶点索引数组作为数据,旨在解决上述问题。 索引数组存储描述几何体的面和其他元素的图元顶点的索引。 将这些类应用于立方体时,存储八个顶点的数组就足够了,这些顶点通过索引数组与面相关联。

osg::DrawElements* 类型的类的设计方式与标准 std::vector 类的设计方式相同。 此代码可用于添加索引。

osg::ref_ptr<osg::DrawElementsUInt> de = new osg::DrawElementsUInt(GL_TRIANGLES);
de->push_back(0); de->push_back(1); de->push_back(2);
de->push_back(3); de->push_back(0); de->push_back(2); 

此代码定义图中所示的立方体的正面。

考虑另一个说明性的例子——八面体。
在这里插入图片描述

很有趣,因为它只包含六个顶点,但每个顶点已经在四个三角形面中了! 我们可以使用 osg::DrawArrays 创建一个包含 24 个顶点的数组来显示所有八个面。 然而,我们将采取不同的方式——我们将顶点存储在一个包含六个元素的数组中,并使用类 osg::DrawElementsUInt 生成面。

#ifndef     MAIN_H
#define     MAIN_H
#include<osg/Geometry>
#include<osg/Geode>
#include<osgUtil/SmoothingVisitor>
#include<osgViewer/Viewer>
#endif
#include"main.h"
int main(int argc, char *argv[]){
    
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(6);
    (*vertices)[0].set( 0.0f,  0.0f,  1.0f);
    (*vertices)[1].set(-0.5f, -0.5f,  0.0f);
    (*vertices)[2].set( 0.5f, -0.5f,  0.0f);
    (*vertices)[3].set( 0.5f,  0.5f,  0.0f);
    (*vertices)[4].set(-0.5f,  0.5f,  0.0f);
    (*vertices)[5].set( 0.0f,  0.0f, -1.0f);
    
    osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(GL_TRIANGLES, 24);
    (*indices)[ 0] = 0; (*indices)[ 1] = 1; (*indices)[ 2] = 2;
    (*indices)[ 3] = 0; (*indices)[ 4] = 4; (*indices)[ 5] = 1;
    (*indices)[ 6] = 4; (*indices)[ 7] = 5; (*indices)[ 8] = 1;
    (*indices)[ 9] = 4; (*indices)[10] = 3; (*indices)[11] = 5;
    (*indices)[12] = 3; (*indices)[13] = 2; (*indices)[14] = 5;
    (*indices)[15] = 1; (*indices)[16] = 5; (*indices)[17] = 2;
    (*indices)[18] = 3; (*indices)[19] = 0; (*indices)[20] = 2;
    (*indices)[21] = 0; (*indices)[22] = 3; (*indices)[23] = 4;
    
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setVertexArray(vertices.get());
    geom->addPrimitiveSet(indices.get());
    
    osgUtil::SmoothingVisitor::smooth(*geom);
    
    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(geom.get());
    
    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    
    return viewer.run();
}

让我们对这段代码进行更详细的说明。 当然首先我们创建一个有六个顶点的数组:

osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(6);
(*vertices)[0].set( 0.0f,  0.0f,  1.0f);
(*vertices)[1].set(-0.5f, -0.5f,  0.0f);
(*vertices)[2].set( 0.5f, -0.5f,  0.0f);
(*vertices)[3].set( 0.5f,  0.5f,  0.0f);
(*vertices)[4].set(-0.5f,  0.5f,  0.0f);
(*vertices)[5].set( 0.0f,  0.0f, -1.0f);

我们直接初始化每个顶点,使用指针解引用操作和 operator [] 操作符寻址其坐标向量 — 别忘了 osg::Array 和 std::vector 类似。

现在我们将面创建为顶点索引列表。

osg::ref_ptr<osg::DrawElementsUInt> indices = new osg::DrawElementsUInt(GL_TRIANGLES, 24);
(*indices)[ 0] = 0; (*indices)[ 1] = 1; (*indices)[ 2] = 2; // Грань 0
(*indices)[ 3] = 0; (*indices)[ 4] = 4; (*indices)[ 5] = 1; // Грань 1
(*indices)[ 6] = 4; (*indices)[ 7] = 5; (*indices)[ 8] = 1; // Грань 2
(*indices)[ 9] = 4; (*indices)[10] = 3; (*indices)[11] = 5; // Грань 3
(*indices)[12] = 3; (*indices)[13] = 2; (*indices)[14] = 5; // Грань 4
(*indices)[15] = 1; (*indices)[16] = 5; (*indices)[17] = 2; // Грань 5
(*indices)[18] = 3; (*indices)[19] = 0; (*indices)[20] = 2; // Грань 6
(*indices)[21] = 0; (*indices)[22] = 3; (*indices)[23] = 4; // Грань 7

面将是三角形的,共有 8 个,这意味着索引列表应包含 24 个元素。 面索引按顺序进入此数组:例如,面 0 由顶点 0、1 和 2 组成; 面 1 - 顶点 0、4 和 1; 面 2 - 顶点 4、5 和 1,依此类推。 顶点按逆时针顺序列出,如果你看正面(见上图)。

接下来是要执行的下一步。 我们唯一没有做的是自动生成平滑(平均)法线,我们在这个例子中通过调用 smooth 来完成:

osgUtil::SmoothingVisitor::smooth(*geom);

事实上,如果面的顶点是给定的,那么很容易计算出它的法线。 在多个面会聚的顶点处,计算某个平均法线 - 将会聚面的法线相加,并将所得总和再次归一化。 这些操作(以及更多!)可以由引擎本身使用 osgUtil 库中的类来执行。 因此,在我们的示例中,在 *.pro 文件中,我们将向链接器添加指示以构建我们的程序并使用此库:

CONFIG(debug, debug|release) {
    TARGET = $$join(TARGET,,,_d)
		.
		.
		.    
    LIBS += -L$$OSG_LIB_DIRECTORY -losgUtild
} else {
		.
		.
		.
    LIBS += -L$$OSG_LIB_DIRECTORY -losgUtil
}

最后的结果如下:

在这里插入图片描述

要了解其工作原理,请考虑 OpenGL 管道。
在这里插入图片描述

顶点数组机制减少了OpenGL的调用次数。 它将顶点数据存储在客户端使用的应用程序的内存中。 服务器端 OpenGL 管道访问各种顶点数组。 如图所示,OpenGL 从客户端的顶点缓冲区中检索数据,并以有序的方式组装图元。 这就是使用 osg::Geometry类的 set * Array()方法处理数据的方式。 osg::DrawArrays类直接遍历这些数组并显示出来。

使用 osg::DrawElements*时,顶点数组的维数减少,传递给管线的顶点数也减少。 索引数组允许你在服务器端创建顶点缓存。 OpenGL 从缓存中读取顶点数据,而不是从客户端的顶点缓冲区中读取。 这显着提高了整体渲染性能。

8、多边形网格处理技巧

OpenSceneGraph 支持用于处理场景几何对象的多边形网格的各种技术。 这些预处理方法,例如多边形缩减和曲面细分,通常用于创建和优化多边形模型。 它们有一个简单的界面,但是在这个过程中它们做了很多复杂的计算,不太适合即时执行。

这些技术包括:

  • osgUtil :: Simplifier - 减少几何中三角形的数量。 公共方法 simplify() 用于简化模型的几何形状。
  • osgUtil :: SmootingVisitor - 法线计算。 smooth() 方法可用于为模型生成平滑法线,而不是独立计算它们并通过法线数组显式指定它们。
  • osgUtil :: TangentSpaceGenerator - 为模型顶点生成切线基础向量。 它通过调用 generate() 方法启动,并存储 getTangentArray()、getNormalArray() 和 getBinormalArray() 方法返回的结果。 在 GLSL 上编写着色器时,这些结果可用于各种顶点属性。
  • osgUtil :: Tesselator - 执行多边形网格的细分 - 将复杂的基元拆分为一系列简单的(retesselatePolygons () 方法)
  • osgUtil :: TriStripVisitor - 将几何表面转换为一组三角形面条,允许以有效的内存消耗进行渲染。 stripify() 方法根据 GL_TRIANGLE_STRIP 集将一组模型图元转换为几何图形。

所有方法都接受对象几何作为参数,通过引用 osg::Geometry&传递,例如:

osgUtil::TriStripVisitor tsv;
tsv.stripify(*geom);

其中 geom 是一个几何实例,由智能指针描述。

osg::Simplifier、 osg::SmoothingVisitor 和 osg::TriStripVisitor 类可以直接与场景图的节点一起工作,例如:

osgUtil::TriStripVisitor tsv;
node->accept(tsv);

accept()方法处理所有的子节点,直到将指定的操作应用于存储在 osg::Geode等节点中的这部分场景树的所有端节点。

让我们尝试练习细分技术。

#include"main.h"
int main(int argc, char *argv[]){
	/*
		Создаем фигуру вида
		-----
		|  _|
		| |_
		|    |
		-----
	*/
    
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
    vertices->push_back( osg::Vec3(0.0f, 0.0f, 0.0f) ); // 0
    vertices->push_back( osg::Vec3(2.0f, 0.0f, 0.0f) ); // 1
    vertices->push_back( osg::Vec3(2.0f, 0.0f, 1.0f) ); // 2
    vertices->push_back( osg::Vec3(1.0f, 0.0f, 1.0f) ); // 3
    vertices->push_back( osg::Vec3(1.0f, 0.0f, 2.0f) ); // 4
    vertices->push_back( osg::Vec3(2.0f, 0.0f, 2.0f) ); // 5
    vertices->push_back( osg::Vec3(2.0f, 0.0f, 3.0f) ); // 6
    vertices->push_back( osg::Vec3(0.0f, 0.0f, 3.0f) ); // 7
    
    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
    normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) );
    
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setVertexArray(vertices.get());
    geom->setNormalArray(normals.get());
    geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
    geom->addPrimitiveSet(new osg::DrawArrays(GL_POLYGON, 0, 8));
    
    osg::ref_ptr<osg::Geode> root = new osg::Geode;
    root->addDrawable(geom.get());
    
    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    
    return viewer.run();
}

根据本例中顶点的空间位置,很明显我们正在尝试使用 GL_POLYGON 类型的一个面的生成来创建八个顶点的非凸多边形。 构建和执行此示例显示我们预期的结果不起作用——示例显示不正确。

在这里插入图片描述

要解决此问题,应在将构建的几何体传递给查看器之前对其进行细分。

osgUtil::Tessellator ts;
ts.retessellatePolygons(*geom);

现在我们得到正确的结果。
在这里插入图片描述

上面代码的原理是什么?

一个非凸多边形,如果没有使用正确的曲面细分,将不会像我们预期的那样显示,因为寻求优化性能的 OpenGL 会将它视为一个简单的凸多边形或简单地忽略,这可能会产生完全出乎意料的结果。

类 osgUtil::Tessellator 使用算法将凸多边形转换为一系列非凸多边形 - 在我们的例子中,它将几何转换为 GL_TRIANGLE_STRIP。

在这里插入图片描述

此类可以处理孔多边形和自相交多边形。 通过公开的 setWindingType()方法,可以定义各种处理规则,如GLU_TESS_WINDING_ODD或GLU_TESS_WINDING_NONZERO,定义复杂多边形的内外区域。

9、结束语

在本文中,我们对三维物体的几何图形在 OSG 引擎中是如何存储和处理的有了基本的了解。 不要认为文章中考虑的那些简单且不是很令人印象深刻的示例 - 引擎的限制。 简单地说,这些示例可以帮助开发人员理解 OpenSceneGraph 的机制,如果没有这种理解,就很难想象更复杂的事情的工作。


原文链接:OSG几何开发快速教程 — BimAnt

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

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

相关文章

Typora 图床教程(阿里云版)

由于码云现在需要登录才能看到相关图片文件后&#xff0c;导致我们已经不能愉快的使用它作为图床了&#xff0c;所以我们需要使用其他工具来作为图床使用了&#xff0c;本文使用阿里云OSS作为Typora的图床。 阿里云OSS相较于其他几个方法来说最大的优点就是稳定了&#xff0c;…

《图机器学习》-Machine Learning for Graphs

Machine Learning for Graphs一、Application of Graph ML一、Application of Graph ML 图机器学习的任务可以分为四个类型&#xff1a; NodelevelNode\ levelNode level(结点级别)EdgelevelEdge\ levelEdge level(边级别)Community(subgraph)levelCommunity(subgraph)\ level…

【rpm】源码包制作rpm包|修改rpm、重新制作rpm包

目录 前言 安装rpmbuild rpmbuild制作rpm 包 同时生成devel包 修改rpm、重新制作rpm包 RPM 打包 工具 SPEC文件 rpmbuild的目录和Spec宏变量和参数说明 preamble部分 Body 部分 标题宏变量/工作目录 spec文件信息 符号说明 CMake制作rpm包 HelloWorld 更多SPEC…

微信小程序开发——小程序的宿主环境—组件

一.小程序的宿主环境—组件1.小程序中组件的分类小程序中的组件也是由宿主环境提供的&#xff0c;开发者可以基于组件快速搭建出漂亮的页面结构。官方把小程序的组件分为了9大类&#xff0c;分别是&#xff1a;1.视图容器 2.基础内容 3.表单组件 4.导航组件5.媒体组件 6.map 地…

企业寄件管理系统使用教程

专为企业量身打造的寄件管理类平台&#xff0c;也就是企业寄件管理系统。其存在的意义在哪里&#xff1f;又是如何运用的&#xff1f;我们往下看看......讨论它存在的意义在哪里&#xff0c;我们先来看看企业普遍存在的寄件场景痛点&#xff1a;1、最早的手写快递单&#xff0c…

一维差分(例acwing重新排序)

一维差分是为了解决访问一个数组中的几个区间&#xff0c;降低时间复杂度使用的差分就是前缀和的逆运算&#xff08;a[i]b[1]b[2]…b[i]&#xff09;差分的作用就是快速实现将数组部分加上一个数。例如给定一个数组 A 和一些查询 Li,Ri&#xff0c;求数组中第 Li 至第 Ri 个元素…

Maven高级-属性-版本管理-资源配置-多环境开发配置-跳过测试

Maven高级-属性 4.2)属性类别 1.自定义属性 2.内置属性 3.Setting属性 4.Java系统属性 5.环境变量属性 4.3)属性类别&#xff1a;自定义属性 作用 等同于定义变量&#xff0c;方便统一维护 定义格式&#xff1a; <!--定义自定义属性--> <properties><…

STM32MP157驱动开发——Linux ADC驱动

STM32MP157驱动开发——Linux ADC驱动0.前言一、ADC 简介1.ADC 简介2.STM32MP157 ADC简介二、ADC 驱动源码解析1.设备树下的 ADC 节点2.ADC 驱动源码分析1&#xff09;stm32_adc 结构体2&#xff09;stm32_adc_probe 函数3&#xff09;stm32_adc_iio_info 结构体三、驱动开发1.…

【深度学习】经典算法解读及代码复现AlexNet-VGG-GoogLeNet-ResNet(二)

链接: 【深度学习】经典算法解读及代码复现AlexNet-VGG-GoogLeNet-ResNet(一) 4.GoogLeNet 4.1.网络模型 GoogLeNet的名字不是GoogleNet&#xff0c;而是GoogLeNet&#xff0c;这是为了致敬LeNet。GoogLeNet和AlexNet/VGGNet这类依靠加深网络结构的深度的思想不完全一样。Go…

创建Vue3项目以及引入Element-Plus

创建Vue3项目以及引入Element-Plus 前提条件&#xff1a;本地需要有node环境以及安装了npm&#xff0c;最好设置了镜像&#xff0c;这样下载包的时候会快些。 1、安装vue脚手架vue-cli3 npm install vue/cli -g2、安装后查看vue的版本 vue -V3、创建Vue项目&#xff0c;项目…

通信电子、嵌入式类面试题刷题计划01

文章目录001——什么是奈奎斯特采样定理&#xff1f;002——有源滤波器和无源滤波器的区别是什么&#xff1f;003——什么是反馈电路&#xff1f;请举出相关应用004——什么是竞争冒险现象&#xff1f;如何消除和避免此类现象005——什么是基尔霍夫定理&#xff1f;006——if e…

揣着一口袋的阳光满载而归--爱摸鱼的美工(13)

-----------作者&#xff1a;天涯小Y 揣着一口袋的阳光满载而归&#xff01; 慷懒周末 睡到自然醒&#xff0c;阳光洒在书桌上 套进宽松自在的衣服里 出门&#xff0c;去楼下坐坐 在阳光里吃午餐 在阳光里打个盹 在阳光里看猫咪上蹿下跳 在阳光里点个咖啡外卖 虚度时光&#xf…

【CANN训练营第三季】TBE算子开发

文章目录直播学习结业考核直播学习 安装准备&#xff1a;https://www.hiascend.com/document/detail/zh/mindstudio/50RC3/instg/instg_000022.html 开发参考: https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/600alpha003/operatordevelopment/opdevg/atla…

基础算法(八)——离散化

离散化 介绍 这里的离散化&#xff0c;特指整数的、保序的离散化 有些题目可能需要以数据作为下标来操作&#xff0c;但题目给出的数据的值比较大&#xff0c;但是数据个数比较小。此时就需要将数据映射到和数据个数数量级相同的区间&#xff0c;这就是离散化&#xff0c;即…

Java学习笔记——继承(上)

目录继承入门继承的好处继承的特点继承中成员变量的访问特点this和super访问成员的格式继承中成员方法的访问特点方法重写概述和应用场景方法重写的注意事项权限修饰符继承入门 继承的好处 好处&#xff1a; 提高了代码的复用性。 提高了代码的维护性。 让类与类之间产生了关系…

static关键字分别在C和C++中的作用

static用于实现多个对象之间的数据共享 隐藏使用静态成员不会破坏隐藏规则默认初始化为0 1. C语言中static的特性&#xff08;面向过程设计中&#xff09; 局部变量&#xff1a;在任意一个函数内部定义的变量&#xff08;不加static&#xff09;&#xff0c;初始值不确定&am…

11、JS笔记-内置内置对象

1.内置对象 js中对象分为三种&#xff1a; 自定义对象、内置对象、浏览器对象&#xff08;js独有&#xff09; 内置对象&#xff1a; js语言自带的对象&#xff0c;供开发者使用&#xff0c;提供一些常用或基本的功能&#xff08;属性和方法&#xff09; 2.Math对象 Math中所…

Docker核心概念总结

文章目录容器容器概念物理机,虚拟机与容器的区别Docker简介Docker介绍Docker思想Docker容器的特点使用Docker的原因容器VS虚拟机对比图容器与虚拟机总结容器与虚拟机可以共存Docker 基本概念镜像&#xff08;Image&#xff09;一个特殊的文件系统容器&#xff08;Container&…

SpringSecurity认证功能的快速上手

简介 SpringSecurity是Spring家族中的一个安全管理框架。相比于另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。一般来说中大型项目都是使用SpringSecurity来做安全框架&#xff0c;小项目用Shiro的比较多&#xff0c;因为相比于S…

LabVIEW VI服务器功能

LabVIEW VI服务器功能VI 服务器是在LabVIEW 5.0中引入的&#xff0c;它提供了允许用户动态控制前面板控件、VI和LabVIEW环境的一系列函数。使用VI服务器&#xff0c;您还可以在同一台机器或通过网络动态加载运行VI和LabVIEW。 VI服务器函数位于 函数应用程序控制 子面板上。所有…