Marker Batch Primitive

Markers are 2D images that always face the camera and generally remain a constant pixel size. Markers are sometimes called sprites or billboards. They have many uses, including: representing points of interest, visualizing a large amount of real-time data, replacing models to improve performance, and for rendering image-based effects such as clouds, smoke, fire, and vapor trails.

The marker batch, MarkerBatchPrimitive, renders a group of markers; each marker is defined by a position and optional properties, such as a texture, color, or rotation. Using the marker batch is similar to using other batch primitives, such as the text batch and point batch.

Topic Description
Basic Example Use the marker batch to render markers.
Basic Properties Use Texture, Color, Size, and Rotation properties to define the appearance of markers.
Per-Marker Properties Use MarkerBatchPrimitiveOptionalParameters to define properties per-marker instead of per-batch.
Size Units Define marker sizes in either pixels or meters using the marker batch's SizeUnit.
Translucency Get visually correct results with translucent markers by selecting the right RenderPass and SortOrder.
Horizontal and Vertical Origins Control the horizontal and vertical placement of markers relative to their position.
Pixel and Eye Offsets Use offsets to translate markers in pixel and/or eye space.
Dynamic Updates Use Set and SetPartial methods for efficient dynamic updates.
Advanced: Texture Atlas Avoid costly texture changes during rendering by assigning 1 texture with multiple sub-textures to a marker batch.

Technical Note: The marker batch requires a video card that supports OpenGL 2.0 or 1.5 with the proper extensions. See the Supported method for more information.

Basic Example

Using the marker batch can be as simple as creating an instance of MarkerBatchPrimitive, providing the instance with a texture and collection of positions, and then adding the instance to the primitive manager. This is shown in the following example from the GraphicsHowTo application:

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     39.88, -75.25, 0, // Philadelphia
     38.85, -77.04, 0, // Washington, D.C.
     29.98, -90.25, 0, // New Orleans
     37.37, -121.92, 0 // San Jose
};

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.Initialize();
markerBatch.Texture = manager.Textures.LoadFromStringUri(
texturePath);
markerBatch.SetCartographic("Earth", ref positions);

manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

Each marker is rendered with the same texture. Since the marker batch's SizeSource is FromTexture by default, the size of each marker is the size of the texture in pixels.

Basic Properties

The Texture, Color, Size, and Rotation properties define the appearance of markers. When Texture is null, markers are rendered as a solid color as shown below.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0,
     38.85, -77.04, 0
};

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.Initialize();
markerBatch.SetCartographic("Earth", ref positions);
((IAgStkGraphicsPrimitive)markerBatch).Color = Color.Yellow;

manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

If the Color assignment in the above example was replaced with setting the marker batch's Texture, the result would be:

[C#] Copy Code
markerBatch.Texture = SceneManager.Textures.LoadFromStringUri(texturePath);

A marker batch's color and texture can be used together. The Color property is multiplied with the texture color at each pixel. By default, Color is white, so just the texture color is shown. Setting Color to yellow in the above example produces the following:

By default, when a texture is assigned to a marker batch, the size of each marker is the same size as the texture in pixels. This is because the marker batch's SizeSource defaults to FromTexture. When a user defined marker size is desired, the marker batch can be initialized with a SizeSource of UserDefined and then the Size property can be set. For example:

[C#] Copy Code
IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.InitializeWithSizeSource(
AgEStkGraphicsMarkerBatchSizeSource.eStkGraphicsMarkerBatchSizeSourceUserDefined);
markerBatch.Size = new object[] { 128, 64 };

The marker batch's Rotation property is used to apply a screen-space rotation. The following example rotates each marker 30 degrees counter-clockwise.

[C#] Copy Code
markerBatch.Rotation = 30;

Per-Marker Properties

Properties, such as Texture and Color, apply to every marker in a marker batch. In many cases, it is useful to assign different properties to each marker in a marker batch. This allows one marker batch to represent several markers, which is drastically more efficient than creating one marker batch per marker. See the Batching Performance Overview for additional performance tips.

To assign per-marker properties to a marker batch, create an instance of MarkerBatchPrimitiveOptionalParameters, provide it with collections for per-marker properties, then pass it to a marker batch's SetCartographicWithOptionalParameters method along with the positions for the markers. The following example creates a marker batch with three markers, each with a unique texture.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0,
     38.85, -77.04, 0
};
Array textures = new object[]
{
     manager.Textures.LoadFromStringUri(texture1Path),
     manager.Textures.LoadFromStringUri(texture2Path),
     manager.Textures.LoadFromStringUri(texture3Path),
};

IAgStkGraphicsMarkerBatchPrimitiveOptionalParameters parameters = manager.Initializers.MarkerBatchPrimitiveOptionalParameters.Initialize();
parameters.SetTextures(ref textures);

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.Initialize();
markerBatch.SetCartographicWithOptionalParameters("Earth", ref positions, parameters);

manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

When per-marker properties are used, the corresponding per-batch property is ignored. In the above example, the marker batch's Texture property is ignored since per-batch textures were provided. You can mix and match per-batch and per-marker properties. For example, the above code can be modified to also include per-marker rotations and a per-batch color.

[C#] Copy Code
Array rotations = new object[]
{
     25,50,75
};
parameters.SetRotations(ref rotations);

markerBatch.SetCartographicWithOptionalParameters("Earth", ref positions, parameters);
((IAgStkGraphicsPrimitive)markerBatch).Color = Color.LightGreen;

Besides enabling a lot of flexibility, per-batch and per-marker properties allow the marker batch to automatically optimize for the specified mix of per-batch and per-marker properties. The marker batch generates source code for an optimized shader program that runs on the video card based on the specified properties. No extra memory is used nor is any extra computation performed for properties that are not used.

Size Units

The size of markers can be defined in either pixels or meters. By default, the size is in pixels. No matter how far away the camera is from a marker, it will take up the same number of pixels on the screen as shown below.

To set the size to meters, set the marker batch's SizeUnit property to Meters. When the units are meters, a marker's size depends on the camera's position, similar to the model primitive, as shown below.

Sizing markers in meters has several uses including a quick and dirty way to implement imposters to improve performance, and image-based rendering effects such as clouds, smoke, fire, and vapor trails.

Translucency

Most textures used for markers have texels that are either completely opaque or completely transparent, like all of the markers shown thus far. Some textures have translucent texels, that is texels that are neither completely opaque nor completely transparent. An example of three such textures is shown below.

The marker batch's RenderPass and SortOrder properties are used to support translucent textures. By default, these properties are initialized to efficiently support opaque textures, which includes textures with completely transparent texels.

If the above translucent textures were assigned to a default initialized marker batch, the translucency will not be shown, and the result would be similar to the image on the left below.

RenderPass: Opaque
SortOrder: ByTexture

RenderPass: Translucent
SortOrder: ByTexture

RenderPass: Translucent
SortOrder: BackToFront

In order to show the texture's translucency, the marker batch's RenderPass must be set to Translucent. This will result in the middle image above. In most cases, this is all that is necessary to support translucent textures.

When translucent markers overlap each other, initialize the marker batch with a SortOrder of BackToFront. This ensures the correct blending of translucent markers, which makes it clear which marker is in front of which marker. Compare the above middle and right images. In the middle image, it looks like the triangle is behind the purple marker. On the right, when BackToFront is used, it is clear that the triangle is really in front. Example code to create the image on the right is shown below.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0,
     38.85, -77.04, 0
};

Array textures = new object[]
{
     manager.Textures.LoadFromStringUri(texture1Path),
     manager.Textures.LoadFromStringUri(texture2Path),
     manager.Textures.LoadFromStringUri(texture3Path),
};

IAgStkGraphicsMarkerBatchPrimitiveOptionalParameters parameters = manager.Initializers.MarkerBatchPrimitiveOptionalParameters.Initialize();
parameters.SetTextures(ref textures);

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.InitializeWithSizeSourceAndSortOrder(
AgEStkGraphicsMarkerBatchSizeSource.eStkGraphicsMarkerBatchSizeSourceFromTexture,
AgEStkGraphicsMarkerBatchSortOrder.eStkGraphicsMarkerBatchSortOrderBackToFront);
markerBatch.RenderPass = AgEStkGraphicsMarkerBatchRenderPass.eStkGraphicsMarkerBatchRenderPassTranslucent;
markerBatch.SetCartographicWithOptionalParameters("Earth", ref positions, parameters);

manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

Using BackToFront is not as efficient as the default, ByTexture. BackToFront requires the CPU to sort the markers before rendering and can increase the number of costly textures changes (unless a texture atlas is used). Therefore, the best practice is to only use BackToFront when it is visually noticeable.

An opaque marker can be made translucent by setting the marker batch's Translucency property and its RenderPass to BasedOnTranslucency, which is the default.

Horizontal and Vertical Origins

Similar to the text batch, the marker batch supports per-batch or per-marker horizontal and vertical origins. Origins are commonly used when two primitives are at the same position and need to be separated. By default, a marker's position defines where its center is. Use Origin or SetOrigins to change the origin.

The following example creates a point batch with three points and a marker batch with three markers. Per-marker origins are defined using SetOrigins so the markers and points are not rendered on top of each other.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0,
     38.85, -77.04, 0
};

Array textures = new object[]
{
     manager.Textures.LoadFromStringUri(texture1Path),
     manager.Textures.LoadFromStringUri(texture2Path),
     manager.Textures.LoadFromStringUri(texture3Path),
};

Array origins = new object[]
{
     AgEStkGraphicsOrigin.eStkGraphicsOriginTopCenter,
     AgEStkGraphicsOrigin.eStkGraphicsOriginBottomLeft,
     AgEStkGraphicsOrigin.eStkGraphicsOriginBottomCenter
};
IAgStkGraphicsMarkerBatchPrimitiveOptionalParameters parameters = manager.Initializers.MarkerBatchPrimitiveOptionalParameters.Initialize();
parameters.SetTextures(ref textures);
parameters.SetOrigins(ref origins);

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.Initialize();
markerBatch.SetCartographicWithOptionalParameters("Earth", ref positions, parameters);
manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

IAgStkGraphicsPointBatchPrimitive pointBatch = manager.Initializers.PointBatchPrimitive.Initialize();
((IAgStkGraphicsPrimitive)pointBatch).Color = Color.Red;
pointBatch.PixelSize = 5;
pointBatch.SetCartographic("Earth", ref positions);
manager.Primitives.Add((IAgStkGraphicsPrimitive)pointBatch);

Pixel and Eye Offsets

Similar to the text batch, the marker batch supports per-batch or per-marker pixel and eye offsets.

Pixel offsets are set using PixelOffset or SetPixelOffsets. The pixel offset defines a translation to apply from the marker's origin. In pixel coordinates, increasing x moves from the left toward the right side of the window, and increasing y moves from the bottom to the top. The following example applies a pixel offset of 80 pixels in the x direction and 40 in the y direction.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0
};

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.Initialize();
markerBatch.Texture = manager.Textures.LoadFromStringUri(texturePath);
markerBatch.Origin = AgEStkGraphicsOrigin.eStkGraphicsOriginBottomLeft;
markerBatch.PixelOffset = new object[] { 80, 40 };
markerBatch.SetCartographic("Earth", ref positions);
manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

IAgStkGraphicsPointBatchPrimitive pointBatch = manager.Initializers.PointBatchPrimitive.Initialize();
((IAgStkGraphicsPrimitive)pointBatch).Color = Color.Red;
pointBatch.PixelSize = 5;
pointBatch.SetCartographic("Earth", ref positions);
manager.Primitives.Add((IAgStkGraphicsPrimitive)pointBatch);

In addition to pixel offsets, an eye space offset can be applied per-batch or per-marker using EyeOffset or SetEyeOffsets. Each unit in eye space is 1 meter. Positive x points towards the camera's right, positive y points up, and negative z points along the view direction. For example, a positive y offset may be applied to translate a marker so it is always "on top" of a model primitive.

The following example demonstrates how applying an eye offset of (0, 100000, 0) affects the position of a marker from different view points. Note that an eye offset is different than a pixel offset or a world translation (e.g. offset the altitude by 10 meters).

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0
};

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.Initialize();
markerBatch.Texture = manager.Textures.LoadFromStringUri(texturePath);
markerBatch.EyeOffset = new object[] { 0, 100000, 0 };
markerBatch.SetCartographic("Earth", ref positions);
manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

IAgStkGraphicsPointBatchPrimitive pointBatch = manager.Initializers.PointBatchPrimitive.Initialize();
((IAgStkGraphicsPrimitive)pointBatch).Color = Color.Red;
pointBatch.PixelSize = 5;
pointBatch.SetCartographic("Earth", ref positions);
manager.Primitives.Add((IAgStkGraphicsPrimitive)pointBatch);

From this view, the eye offset moves the markers approximately north.

From a different view, the offset moves the markers approximately along their surface normals.

Dynamic Updates

As explained in the dynamic updates overview, the marker batch provides Set and SetPartial methods for efficient dynamic updates of positions and per-marker properties such as color or rotation. Use Set to completely redefine the marker batch. If this is done frequently (e.g. every few frames), initialize the marker batch with SetHint.Frequent.

Use SetPartial to update a subset of positions and/or per-marker properties in the marker batch. Make sure to initialize the marker batch with SetHint.SetPartial. The following example creates a marker batch with three markers with per-marker sizes. SetPartialCartographic is used to modify two of markers' position and size.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0,
     38.85, -77.04, 0
};

Array sizes = new object[]
{
     32, 32,
     64, 64,
     128, 128
};

IAgStkGraphicsMarkerBatchPrimitiveOptionalParameters parameters = manager.Initializers.MarkerBatchPrimitiveOptionalParameters.Initialize();
parameters.SetSizes(ref sizes);

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.InitializeSizeSourceSortOrderAndSetHint(
AgEStkGraphicsMarkerBatchSizeSource.eStkGraphicsMarkerBatchSizeSourceUserDefined,
AgEStkGraphicsMarkerBatchSortOrder.eStkGraphicsMarkerBatchSortOrderByTexture,
AgEStkGraphicsSetHint.eStkGraphicsSetHintPartial);
markerBatch.SetCartographicWithOptionalParameters("Earth", ref positions, parameters);
manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);

//
// Modify 2 markers in the batch
//
Array newPositions = new object[]
{
     40.47, -73.58, 100000,
     38.85, -77.04, 100000
};

Array newSizes = new object[]
{
     256, 256,
     512, 512
};

Array indices = new object[] { 0, 2 };

IAgStkGraphicsMarkerBatchPrimitiveOptionalParameters newParameters = manager.Initializers.MarkerBatchPrimitiveOptionalParameters.Initialize();
newParameters.SetSizes(ref newSizes);

markerBatch.SetPartialCartographicWithOptionalParameters("Earth", ref newPositions, newParameters, ref indices);

Advanced: Texture Atlas

When a marker batch uses a significant number of textures, changing textures during rendering can become a performance problem even if the default marker batch SortOrder of ByTexture is used. The problem becomes worse when a SortOrder of BackToFront is used as described in the translucency section.

A texture atlas is a technique that improves rendering performance by combining many small textures into one large texture; texture coordinates are used to index to each small texture in the large texture. If your marker batch uses many small textures, you should consider combining them. For example, text is commonly rendered with a texture atlas that contains all of the font's characters:

(Use the Text Batch primitive to render text in STK Engine).

In the per-marker properties section, a marker batch with three markers and three textures was created:

The same results can be achieved with a single texture atlas. First, combine the three textures into a single texture:

You may find NVIDIA's Texture Atlas Tools helpful for creating texture atlases.

Instead of assigning per-marker textures, a per-batch texture is used along with per-marker texture coordinates and sizes. In this case, each sub-texture is the same height so the t and q texture coordinates are 0 and 1. The s and p texture coordinates are based on the x position and width of the sub-texture in the texture.

[C#] Copy Code
IAgStkGraphicsSceneManager manager = ((IAgScenario)root.CurrentScenario).SceneManager;
Array positions = new object[]
{
     40.47, -73.58, 0,
     39.88, -75.25, 0,
     38.85, -77.04, 0
};

Array textureCoordinates = new object[]
{
     0, 0, 93.0f / 235.0f, 1,
     95.0f / 235.0f, 0, 155.0f / 235.0f, 1,
     157.0f / 235.0f, 0, 1, 1
};

Array sizes = new object[]
{
     93, 64,
     60, 64,
     78, 64
};

IAgStkGraphicsMarkerBatchPrimitiveOptionalParameters parameters = manager.Initializers.MarkerBatchPrimitiveOptionalParameters.Initialize();
parameters.SetTextureCoordinates(ref textureCoordinates);
parameters.SetSizes(ref sizes);

IAgStkGraphicsMarkerBatchPrimitive markerBatch = manager.Initializers.MarkerBatchPrimitive.InitializeWithSizeSource(
AgEStkGraphicsMarkerBatchSizeSource.eStkGraphicsMarkerBatchSizeSourceUserDefined);
markerBatch.Texture = manager.Textures.LoadFromStringUri(textureAtlasPath);
markerBatch.SetCartographicWithOptionalParameters("Earth", ref positions, parameters);

manager.Primitives.Add((IAgStkGraphicsPrimitive)markerBatch);