Triangles are used in real-time computer graphics because they are planar and can be rasterized very quickly in hardware. Several triangles form a mesh. Meshes are used to represent just about anything in a 3D scene. For example, the globe is represented using meshes.
|
|
|
|
The Globe |
Triangle meshes that represent the globe |
The triangle mesh primitive is used to render arbitrary triangle meshes. This includes polygons on the globe (e.g. states or countries), terrain and imagery extents, ellipses, and walls. The triangle mesh is a generic rendering object that takes input from other objects that compute triangles. For example, the surface polygon triangulator takes boundary points on the globe as input and outputs triangles that represent the filled interior region. These triangles are then used as input to the triangle mesh.
Insight3D provides the following triangulators:
| Topic | Description |
|---|---|
| Surface Extent | Computes triangles for a rectangular extent on the surface or at a constant altitude. |
| Surface Polygon | Computes triangles for a polygon, potentially with holes, on the surface or at a constant altitude. |
| Wall | Computes triangles for walls perpendicular to the surface. |
The extent triangulator, AgGxTriangulatorSurfaceExtent, computes triangles for a rectangular extent on the surface or at a constant altitude. The example code below uses the extent triangulator and the triangle mesh primitive to render the extent around Louisiana.
IAgGxTriangulatorSurfaceExtent triangles = new AgGxTriangulatorSurfaceExtent(); triangles.Initialize(earth, Trig.DegreesToRadians(-94), // West Trig.DegreesToRadians(29), // South Trig.DegreesToRadians(-89), // East Trig.DegreesToRadians(33)); // North IAgGxPrimitiveTriangleMesh mesh = new AgGxPrimitiveTriangleMesh(); mesh.InitializeFromTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles); mesh.Color = (uint)ColorTranslator.ToOle(Color.White); sceneManager.Primitives.Add(mesh); sceneManager.DrawAllScenes();
The image on the left shows the output of the extent triangulator, and the image on the right shows the actual results of executing the example.
|
|
|
|
Triangles from Extent Triangulator |
Rendered using Triangle Mesh |
To create the extent at altitude, use the extent triangulator's InitializeWithAltitude method. In the previous example, replace Initialize with the following InitializeWithAltitude:
triangles.InitializeWithAltitude(earth,
Trig.DegreesToRadians(-94), // West
Trig.DegreesToRadians(29), // South
Trig.DegreesToRadians(-89), // East
Trig.DegreesToRadians(33), // North
75000, // Altitude in meters
Trig.DegreesToRadians(1)); // Granularity
This results in the following image:
In addition to an altitude, this method also takes a granularity that defines how many triangles should be used when computing the mesh. A mesh with more triangles will conform to the surface better but consume more memory and render slower. The default granularity is 1 degree. The following images demonstrate the effects of changing the granularity.
|
|
|
|
|
Granularity: 0.25 degrees |
Granularity: 1 degrees |
Granularity: 2 degrees |
The polygon triangulator, AgGxTriangulatorSurfacePolygon, computes triangles for a polygon on the surface or at a constant altitude. This is commonly used for country and state borders, sensor footprints, and ground ellipses. The polygon triangulator works similarly to the extent triangulator except its input is a set of points on the surface that define a boundary to compute a mesh for, as opposed to just a rectangular extent.
The following example passes the boundary points for Washington D.C. to the polygon triangulator, which is then used as input to the triangle mesh.
double[] pts = new double[] { Trig.DegreesToRadians(38.9665570), Trig.DegreesToRadians(-77.008232), Trig.DegreesToRadians(38.8899880), Trig.DegreesToRadians(-76.911209), Trig.DegreesToRadians(38.7881200), Trig.DegreesToRadians(-77.045448), Trig.DegreesToRadians(38.8139150), Trig.DegreesToRadians(-77.035248), Trig.DegreesToRadians(38.8293650), Trig.DegreesToRadians(-77.045189), Trig.DegreesToRadians(38.838413), Trig.DegreesToRadians(-77.040405), Trig.DegreesToRadians(38.862431), Trig.DegreesToRadians(-77.039078), Trig.DegreesToRadians(38.886101), Trig.DegreesToRadians(-77.067886), Trig.DegreesToRadians(38.915600), Trig.DegreesToRadians(-77.078949), Trig.DegreesToRadians(38.932060), Trig.DegreesToRadians(-77.122627), Trig.DegreesToRadians(38.993431), Trig.DegreesToRadians(-77.042389), Trig.DegreesToRadians(38.966557), Trig.DegreesToRadians(-77.008232) }; Array ary = pts; IAgGxTriangulatorSurfacePolygon triangles = new AgGxTriangulatorSurfacePolygon(); triangles.InitializeCartographic(earth, ref ary); IAgGxPrimitiveTriangleMesh mesh = new AgGxPrimitiveTriangleMesh(); mesh.InitializeFromTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles); mesh.Color = (uint)ColorTranslator.ToOle(Color.Red); sceneManager.Primitives.Add(mesh);
The polygon triangulator is efficient and supports dynamic polygons, which is commonly used for sensor footprints. At each time step, the triangulator can be reinitialized with new boundary points then used as input to the triangle mesh's SetFromTriangulator method.
Similar to the extent triangulator, the polygon triangulator provides InitializeWithAltitude and related methods to create the mesh at altitude or with a user-defined granularity.
The polygon triangulator can triangulate polygons with interior holes, like the one shown below, when AgGxSurfacePolygonHoles is used as input.
AgGxSurfacePolygonHoles represents a polygon with interior holes. It is initialized with the polygon's boundary points then holes are added using one of the AddHole methods. The winding order of a hole does not have to be the same as the winding order of the boundary. The following example triangulates a polygon with one hole, and creates a triangle mesh primitive for the interior fill and two polyline primitives that outline the mesh.
Array boundaryAry = STKUtil.ReadAreaTargetPoints(Program.localDataPath + "AreaTargets/LogoBoundary.at"); Array holeAry = STKUtil.ReadAreaTargetPoints(Program.localDataPath + "AreaTargets/LogoHole.at"); IAgGxCentralBody earth = sceneManager.CentralBodies("Earth"); IAgGxSurfacePolygonHoles polygon = new AgGxSurfacePolygonHoles(); polygon.Initialize(earth, ref boundaryAry); polygon.AddHole(ref holeAry); IAgGxTriangulatorSurfacePolygon triangles = new AgGxTriangulatorSurfacePolygon(); triangles.InitializeFromSurfacePolygonHoles(polygon); IAgGxPrimitiveTriangleMesh mesh = new AgGxPrimitiveTriangleMesh(); mesh.InitializeFromTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles); mesh.Color = (uint)ColorTranslator.ToOle(Color.Gray); mesh.Translucency = 50; sceneManager.Primitives.Add(mesh); IAgGxPrimitivePolyline boundaryLine = new AgGxPrimitivePolyline(); boundaryLine.InitializeFromSurfaceTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles, null); boundaryLine.Color = (uint)ColorTranslator.ToOle(Color.Red); boundaryLine.Width = 2; sceneManager.Primitives.Add(boundaryLine); IAgGxPrimitivePolyline holeLine = new AgGxPrimitivePolyline(); holeLine.Initialize(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, AgGxPolylineType.PolylineTypeLineStrip, ref holeAry, null); holeLine.Color = (uint)ColorTranslator.ToOle(Color.Red); holeLine.Width = 2; sceneManager.Primitives.Add(holeLine);
The wall triangulator computes triangles for walls perpendicular to the surface. The wall is defined by boundary points, similar to the input to the surface polygon triangulator. In addition, the altitude and altitude reference for both the bottom and top of the wall are specified. The bottom of the wall is not required to be on the ground as the following example from the HowTo demonstrates.
IAgGxTriangulatorWall triangles = new AgGxTriangulatorWall(); triangles.Initialize(earth, ref aryPts, AgGxAltitudeReference.AltitudeReferenceEllipsoid, 25000, // Top AgGxAltitudeReference.AltitudeReferenceEllipsoid, 10000, // Bottom AgGxWindingOrder.WindingOrderCounterClockWise); IAgGxPrimitiveTriangleMesh mesh = new AgGxPrimitiveTriangleMesh(); mesh.InitializeFromTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles); mesh.Color = (uint)ColorTranslator.ToOle(Color.Blue); mesh.Translucency = 40; sceneManager.Primitives.Add(mesh);
The surface and wall triangulators can take their input from surface points objects. These objects encapsulate computing boundaries for areas on the surface such as ellipses and rectangles. They enable the triangle mesh to render these areas filled and the polyline to render the area's boundary. The pipeline of surface points, triangulators and primitives is shown below.
When fill is not required, surface points objects can be input to the polyline primitive to render just the boundary. When fill is used, the boundary from the triangulator should be input to the polyline. In many cases, the boundary computed by the triangulator will be different then the boundary input to the triangulator. By allowing the triangulator to add detail to the boundary, the triangulator can guarantee that the boundary it outputs precisely lines up with the triangle mesh it computed. This results in a watertight connection between the boundary and the mesh.
The following code example uses a surface points object to compute the boundary for a circle. The object is then input to the surface triangulator, which outputs triangles for the triangle mesh and a boundary for the polyline.
IAgGxPointsCircle circlePts = new AgGxPointsCircle(); circlePts.InitializeCartographic(earth, Trig.DegreesToRadians(39.88), Trig.DegreesToRadians(-75.25), 0, 10000); IAgGxTriangulatorSurfacePolygon triangles = new AgGxTriangulatorSurfacePolygon(); triangles.InitializeFromSurfacePoints(circlePts); IAgGxPrimitiveTriangleMesh mesh = new AgGxPrimitiveTriangleMesh(); // Fill mesh.InitializeFromTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles); mesh.SetRGBA(255, 255, 255, 127); IAgGxPrimitivePolyline line = new AgGxPrimitivePolyline(); // Boundary line.InitializeFromSurfaceTriangulator(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, triangles, null); line.Width = 2; sceneManager.Primitives.Add(mesh); sceneManager.Primitives.Add(line);
In addition to triangulator objects, the triangle mesh can be initialized using an indexed triangle list representation of a triangle mesh. First, we describe the motivation and data structures for indexed triangle lists. Then, code examples are provided.
A naive way to represent a mesh in memory is an array of vertices (x, y, z triplets), where every three vertices represents one triangle. This is referred to as triangle soup and is grossly inefficient in terms of memory usages and GPU vertex cache awareness.
Since most triangles in a mesh are connected to other triangles in a mesh, the preferred representation is the indexed triangle list. All of the unique vertices in the mesh are stored in an array. A second array contains indices of vertices in the first array. Every 3 indices represents 1 triangle. Since the size of an index (typically 2 or 4 bytes) is much less then the size of a vertex (typically 12 or 24 bytes), less memory is used compared to triangle soup representations. Furthermore, Insight3D is capable of organizing the indices and vertices to get the best performance out of GPU vertex caches, which improves rendering performance.
An indexed triangle list for a mesh with two triangles is shown below.
For proper lighting, meshes must have surface normals defined for each vertex. A surface normal is a vector of unit length that is perpendicular to the surface at that point as shown below.
Normals are computed in a variety of ways. A straightforward algorithm to compute a normal for a vertex is to normalize the average the cross products of all adjacent incident edges to that vertex. Once computed, normals should be stored in a separate array that has a one-to-one mapping with the vertex array. The normal at index i in the normal array is the normal for the vertex at index i in the vertex array as shown below.
The following code example using the Initialize method to create a triangle mesh with two triangles. The normals are the surface normals of the ellipsoid when the vertex is projected onto the surface.
Cartographic v0 = new Cartographic(Trig.DegreesToRadians(-5), Trig.DegreesToRadians(-5), 75000); Cartographic v1 = new Cartographic(Trig.DegreesToRadians(5), Trig.DegreesToRadians(-5), 75000); Cartographic v2 = new Cartographic(Trig.DegreesToRadians(-5), Trig.DegreesToRadians(5), 75000); Cartographic v3 = new Cartographic(Trig.DegreesToRadians(5), Trig.DegreesToRadians(5), 75000); Cartesian n0 = Ellipsoid.SurfaceNormal(v0); Cartesian n1 = Ellipsoid.SurfaceNormal(v1); Cartesian n2 = Ellipsoid.SurfaceNormal(v2); Cartesian n3 = Ellipsoid.SurfaceNormal(v3); Ellipsoid shape = CentralBodiesFacet.GetFromContext().Earth.Shape; Cartesian vv0 = shape.CartographicToCartesian(v0); Cartesian vv1 = shape.CartographicToCartesian(v1); Cartesian vv2 = shape.CartographicToCartesian(v2); Cartesian vv3 = shape.CartographicToCartesian(v3); double[] vertices = new double[] { vv0.X, vv0.Y, vv0.Z, vv1.X, vv1.Y, vv1.Z, vv2.X, vv2.Y, vv2.Z, vv3.X, vv3.Y, vv3.Z }; double[] normals = new double[] { n0.X, n0.Y, n0.Z, n1.X, n1.Y, n1.Z, n2.X, n2.Y, n2.Z, n3.X, n3.Y, n3.Z }; int[] indices = new int[] { 0, 1, 2, 1, 3, 2 }; Array aryVertices = vertices; Array aryNormals = normals; Array aryIndices = indices; IAgGxPrimitiveTriangleMesh mesh = new AgGxPrimitiveTriangleMesh(); mesh.Initialize(earth, AgGxReferenceFrame.ReferenceFrameFixed, AgGxVertexUpdate.VertexUpdateNone, ref aryVertices, ref aryNormals, ref aryIndices); mesh.Wireframe = true; sceneManager.Primitives.Add(mesh);