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.
In addition to the sections below, see the Favorite Features: The Marker Batch Primitive blog post for more information on the marker batch.
Note |
---|
The marker batch requires a video card that supports OpenGL 2.0 or 1.5 with the proper extensions. See the System Requirements topic and the supported method for more information. |
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:
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 3000.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 3000.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-90.25), Trig.degreesToRadians(29.98), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-121.92), Trig.degreesToRadians(37.37), 0.0)); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(); markerBatch.setTexture(SceneManager.getTextures().fromUri(texturePath)); markerBatch.setCartographic(earth, positions); SceneManager.getPrimitives().add(markerBatch);
Each marker is rendered with the same texture. Since the marker batch's SizeSource (get) is MarkerBatchSizeSource.FROM_TEXTURE by default, the size of each marker is the size of the texture in pixels.
The Texture (get / set), Color (get / set), Size (get / set), and Rotation (get / set) properties define the appearance of markers. When Texture (get / set) is null, markers are rendered as a solid color, as shown below.
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(); markerBatch.setCartographic(earth, positions); markerBatch.setColor(Color.YELLOW); SceneManager.getPrimitives().add(markerBatch);
If instead of setting Color (get / set) in the above example, we instead set the marker batch's Texture (get / set), the result would be:
markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png")));
A marker batch's color and texture can be used together. The Color (get / set) property is multiplied with the texture color at each pixel. By default, Color (get / set) is white, so just the texture color is shown. Setting Color (get / set) 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 (get) defaults to MarkerBatchSizeSource.FROM_TEXTURE. To use a custom marker size, the marker batch can be initialized with a SizeSource (get) of MarkerBatchSizeSource.USER_DEFINED and then the Size (get / set) property can be set. For example:
MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.USER_DEFINED); markerBatch.setSize(new DimensionF(128.0f, 64.0f));
The marker batch's Rotation (get / set) property is used to apply a screen-space rotation. The following example rotates each marker 30 degrees counter-clockwise.
markerBatch.setRotation(Trig.degreesToRadians(30.0));
Properties, such as Texture (get / set) and Color (get / set), 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 topic 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 set method, along with the positions for the markers. The following example creates a marker batch with three markers, each with a unique texture.
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); ArrayList<Texture2D> textures = new ArrayList<Texture2D>(); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png"))); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/oldLogo.png"))); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/agiLogo.png"))); MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters(); parameters.setTextures(textures); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(); markerBatch.setCartographic(earth, positions, parameters); SceneManager.getPrimitives().add(markerBatch);
When per-marker properties are used, the corresponding per-batch property is ignored. In the above example, the marker batch's Texture (get / set) 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.
ArrayList<Double> rotations = new ArrayList<Double>(); rotations.add(Trig.degreesToRadians(25.0)); rotations.add(Trig.degreesToRadians(50.0)); rotations.add(Trig.degreesToRadians(75.0)); parameters.setRotations(rotations); markerBatch.setCartographic(earth, positions, parameters); Color lightGreen = new Color(0x90EE90); markerBatch.setColor(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.
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 (get / set) property to MarkerBatchUnit.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.
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 (get / set) and SortOrder (get) 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.
In order to show the texture's translucency, the marker batch's RenderPass (get / set) must be set to MarkerBatchRenderPass.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 (get) of MarkerBatchSortOrder.BACK_TO_FRONT. 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 MarkerBatchSortOrder.BACK_TO_FRONT is used, it is clear that the triangle is really in front. Example code to create the image on the right is shown below:
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); ArrayList<Texture2D> textures = new ArrayList<Texture2D>(); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/TranslucentCircle64.png"))); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/TranslucentHoles64.png"))); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/TranslucentTriangle64.png"))); MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters(); parameters.setTextures(textures); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.FROM_TEXTURE, MarkerBatchSortOrder.BACK_TO_FRONT); markerBatch.setRenderPass(MarkerBatchRenderPass.TRANSLUCENT); markerBatch.setCartographic(earth, positions, parameters); SceneManager.getPrimitives().add(markerBatch);
Using MarkerBatchSortOrder.BACK_TO_FRONT is not as efficient as the default, MarkerBatchSortOrder.BY_TEXTURE. MarkerBatchSortOrder.BACK_TO_FRONT 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, it is recommended to only use MarkerBatchSortOrder.BACK_TO_FRONT when it is visually noticeable.
Tip |
---|
An opaque marker can be made translucent by setting the marker batch's Translucency (get / set) property and its RenderPass (get / set) to MarkerBatchRenderPass.BASED_ON_TRANSLUCENCY, which is the default. |
Similar to the Text Batch Primitive, 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 the Origin (get / set) property or the setOrigins method 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 so the markers and points are not rendered on top of each other.
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); ArrayList<Texture2D> textures = new ArrayList<Texture2D>(); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png"))); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/oldLogo.png"))); textures.add(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/agiLogo.png"))); ArrayList<Origin> origins = new ArrayList<Origin>(); origins.add(Origin.TOP_CENTER); origins.add(Origin.BOTTOM_LEFT); origins.add(Origin.BOTTOM_CENTER); MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters(); parameters.setTextures(textures); parameters.setOrigins(origins); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(); markerBatch.setCartographic(earth, positions, parameters); SceneManager.getPrimitives().add(markerBatch); PointBatchPrimitive pointBatch = new PointBatchPrimitive(); pointBatch.setColor(Color.RED); pointBatch.setPixelSize(5.0f); pointBatch.setCartographic(earth, positions); SceneManager.getPrimitives().add(pointBatch);
Similar to the Text Batch Primitive, the marker batch supports per-batch or per-marker pixel and eye offsets.
Pixel offsets are set using the PixelOffset (get / set) property or the setPixelOffsets method. 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.
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(); markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logo64.png"))); markerBatch.setOrigin(Origin.BOTTOM_LEFT); markerBatch.setPixelOffset(new PointF(80.0f, 40.0f)); markerBatch.setCartographic(earth, positions); SceneManager.getPrimitives().add(markerBatch); PointBatchPrimitive pointBatch = new PointBatchPrimitive(); pointBatch.setColor(Color.RED); pointBatch.setPixelSize(5.0f); pointBatch.setCartographic(earth, positions); SceneManager.getPrimitives().add(pointBatch);
In addition to pixel offsets, an eye space offset can be applied per-batch or per-marker using the EyeOffset (get / set) property or the setEyeOffsets method. 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).
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(); markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/oldLogo.png"))); markerBatch.setEyeOffset(new Cartesian(0.0, 100000.0, 0.0)); markerBatch.setCartographic(earth, positions); SceneManager.getPrimitives().add(markerBatch); PointBatchPrimitive pointBatch = new PointBatchPrimitive(); pointBatch.setColor(Color.RED); pointBatch.setPixelSize(5.0f); pointBatch.setCartographic(earth, positions); SceneManager.getPrimitives().add(pointBatch);
The marker batch supports per batch alignment options. The alignToNorth method will orient each marker so that its up vector is pointing towards a central body's north axis. Similarly, the alignToAxis method will orient each marker so that its up vector is pointing towards an arbitrary axis. The default behavior for a marker batch is to align each marker's up vector to the up vector of the camera, which can be set with the alignToScreen method. The following image shows markers aligned to the Earth's north axis:
As explained in the Dynamic Updates topic, the marker batch provides set and setPartial methods for efficient dynamic updates of positions and per-marker properties such as color or rotation. Use the set method to completely redefine the marker batch. If this is done frequently (e.g. every few frames), initialize the marker batch with SetHint.FREQUENT.
Use the setPartial method 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 the position and size of two markers.
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); ArrayList<DimensionF> sizes = new ArrayList<DimensionF>(); sizes.add(new DimensionF(32.0f, 32.0f)); sizes.add(new DimensionF(64.0f, 64.0f)); sizes.add(new DimensionF(128.0f, 128.0f)); MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters(); parameters.setSizes(sizes); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.USER_DEFINED, MarkerBatchSortOrder.BY_TEXTURE, SetHint.PARTIAL); markerBatch.setCartographic(earth, positions, parameters); SceneManager.getPrimitives().add(markerBatch); // Modify 2 markers in the batch ArrayList<Cartographic> newPositions = new ArrayList<Cartographic>(); newPositions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 100000.0)); newPositions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 100000.0)); ArrayList<DimensionF> newSizes = new ArrayList<DimensionF>(); newSizes.add(new DimensionF(256.0f, 256.0f)); newSizes.add(new DimensionF(512.0f, 512.0f)); MarkerBatchPrimitiveOptionalParameters newParameters = new MarkerBatchPrimitiveOptionalParameters(); newParameters.setSizes(newSizes); markerBatch.setPartialCartographic(earth, newPositions, newParameters, Arrays.asList(0, 2));
When a marker batch uses a significant number of textures, changing textures during rendering can become a performance problem even when using the default SortOrder (get), which is MarkerBatchSortOrder.BY_TEXTURE. The problem can become worse when using MarkerBatchSortOrder.BACK_TO_FRONT is used, as described in the Translucency section above.
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. However, note that in Insight3D, you should use the Text Batch Primitive to render text.
In the per-marker properties section above, 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:
Tip |
---|
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.
CentralBody earth = CentralBodiesFacet.getFromContext().getEarth(); ArrayList<Cartographic> positions = new ArrayList<Cartographic>(); positions.add(new Cartographic(Trig.degreesToRadians(-73.58), Trig.degreesToRadians(40.47), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-75.25), Trig.degreesToRadians(39.88), 0.0)); positions.add(new Cartographic(Trig.degreesToRadians(-77.04), Trig.degreesToRadians(38.85), 0.0)); ArrayList<TextureCoordinate4DF> textureCoordinates = new ArrayList<TextureCoordinate4DF>(); textureCoordinates.add(new TextureCoordinate4DF(0.0f, 0.0f, 93.0f / 235.0f, 1.0f)); textureCoordinates.add(new TextureCoordinate4DF(95.0f / 235.0f, 0.0f, 155.0f / 235.0f, 1.0f)); textureCoordinates.add(new TextureCoordinate4DF(157.0f / 235.0f, 0.0f, 1.0f, 1.0f)); ArrayList<DimensionF> sizes = new ArrayList<DimensionF>(); sizes.add(new DimensionF(93.0f, 64.0f)); sizes.add(new DimensionF(60.0f, 64.0f)); sizes.add(new DimensionF(78.0f, 64.0f)); MarkerBatchPrimitiveOptionalParameters parameters = new MarkerBatchPrimitiveOptionalParameters(); parameters.setTextureCoordinates(textureCoordinates); parameters.setSizes(sizes); MarkerBatchPrimitive markerBatch = new MarkerBatchPrimitive(MarkerBatchSizeSource.USER_DEFINED); markerBatch.setTexture(SceneManager.getTextures().fromUri(baseUri.resolve("Textures/logoTextureAtlas.png"))); markerBatch.setCartographic(earth, positions, parameters); SceneManager.getPrimitives().add(markerBatch);