RenderNode

AMD: require(["esri/views/3d/webgl/RenderNode"], (RenderNode) => { /* code goes here */ });
ESM: import RenderNode from "@arcgis/core/views/3d/webgl/RenderNode.js";
Class: esri/views/3d/webgl/RenderNode
Inheritance: RenderNodeAccessor
Since: ArcGIS Maps SDK for JavaScript 4.29

The RenderNode provides low level access to the render pipeline of the SceneView to create custom visualizations and effects. Render nodes inject custom WebGL code in different stages of the render pipeline to alter their outputs.

Important guidelines

This interface is experimental. Please read the following information carefully before using it in a product:

  • It is not possible to shield users of this interface from SceneView internal implementation details. Therefore, this interface should be considered not stable and subject to changes in upcoming minor releases of the ArcGIS Maps SDK for JavaScript.
  • Because of the complex nature of WebGL and hardware-accelerated 3D rendering, this interface is targeting expert developers that are experienced with WebGL or OpenGL.
    • Improper use of WebGL might not only break the custom rendering, but also the rendering of SceneView itself.
    • Esri does not provide any support for issues related to WebGL rendering in custom rendering code, or for issues that arise in SceneView rendering while using custom rendering code.
  • Integration with third-party libraries is only possible under certain conditions. Specifically, the third-party library has to be capable of working on the same WebGL context as SceneView, and able to set the relevant parts of the WebGL state in every frame.

Usage

A RenderNode subclass is linked to a specific SceneView during construction:

new LuminanceRenderNode({ view });

A RenderNode subclass is created using createSubclass. This example node modifies the "composite-color" output of the render pipeline:

 const LuminanceRenderNode = RenderNode.createSubclass({
   consumes: { required: ["composite-color"] }
   produces: ["composite-color"]
   render(inputs) {
      // custom render code
   }
 });

Modifying render graph outputs

Rendering a single frame in SceneView traverses the individual nodes of the internal render graph of the SceneView. Every time a node is traversed, the render engine will modify or create framebuffers. For example, the render graph in the images shown below contains nodes which render buildings, one transparent cube, the terrain with textures, atmosphere effects, and post processing effects such as antialiasing.

Depending on the SceneView properties and layer configuration, the rendering engine modifies the render graph to traverse the nodes which are required to produce the configured rendering. The chronological render order of the render graph is given by the input-output dependencies between the nodes in the graph. For example, transparent geometry is rendered after all opaque geometry.

The RenderNode class offers a way to inject custom render code to this render pipeline. Currently the following outputs can be modified by custom render nodes:

  • opaque-color
  • transparent-color
  • composite-color
  • final-color

Opaque color contains all non-transparent 3D geometries. Transparent color contains opaque-color and all transparent 3D geometries. Composite color contains all 3D content, but not 2D content such as icons and highlights.

Important to note is that the chronological order for traversing the render graph does not correspond to the object location in the frame. For example, all opaque objects are rendered first even if they are behind transparent objects. Depth testing and alpha blending will create the correct visibility.

Once the injection point is declared with produces, the render function needs to return this output in a ManagedFBO for the RenderNode to be correctly traversed. The output is also provided as an input, and typically this input is read as a texture or bound as the framebuffer to create the output.

See produces and RenderNodeOutput for details.

RenderNode inputs

Every RenderNode requires some input framebuffer objects. Typically a node will modify the state of a framebuffer, using its output also as a required input. The RenderNode offers additional input targets to be used as inputs for a rendering code. These are used for advanced graphics effects. The following additional inputs are available:

composite-color composite-color depth attachment highlights normals

If one of the required inputs is not available then this RenderNode will be skipped during the frame. For example, a custom RenderNode using highlights as required input will only render if highlights are present in the scene. Optional inputs do not cause the render node to be skipped while rendering. If optional inputs are not available they will not be present in the input parameter of the render function.

Note that there are restrictions in availability due to the implicit ordering of the render graph as well. For example, opaque-color cannot require composite-color. See details in RenderNodeInput.

Managed framebuffer objects and attachments

All render nodes have in common that they alter the state of a framebuffer object. This happens either by simply drawing additional geometry "on top" of the input framebuffer, or by using the input as a Texture, e.g. to apply a post processing effect. See WebGL tutorials or the WebGL documentation to get familiar with the concept of framebuffer objects.

The ManagedFBO is a wrapper interface to request and provide framebuffer content to the render engine of the SceneView.The ManagedFBO exposes the necessary interface to reference count these framebuffer and attached textures to render nodes. See ManagedFBO for details.

Coordinate systems

When working with custom render nodes, coordinates have to be specified in the internal rendering coordinate system of SceneView. This coordinate system depends on the viewingMode of the view:

  • In local viewing mode, it is equal to the coordinate system defined by the spatial reference of the view.
  • In global viewing mode, it is an ECEF coordinate system where the X-axis points to 0°N 0°E, the Y-axis points to 0°N 90°E, and the Z-axis points to the North Pole. The virtual globe is drawn as a perfect sphere with a radius of 6378137, so the unit of the coordinate system should be considered meters.

rendernode-global-coordinate-system

You can use toRenderCoordinates() and fromRenderCoordinates() to transform to and from the rendering coordinate system without having to worry about viewingMode and the exact coordinate system definition.

Precision and local origins

In global scenes, the precision of 32-bit floating point arithmetic is not sufficient for visualizations that go beyond global scale (i.e. country scale to city scale). When zooming the view beyond a certain scale, geometries will appear to wobble or jitter, and generally appear displaced. The same applies to local scenes where geometries are far away from the origin of the coordinate system.

In general, you should ensure that all arithmetic done in JavaScript is done in double precision. This is the case for normal JavaScript arithmetic, but you should specifically avoid using Float32Array unless you can rule out precision issues.

However, WebGL does not support 64 bit floating point arithmetic. A simple way to work around this limitation is to render scenes with a local origin:

  • Pick a local origin position, approximately at the center of your data.
  • Subtract the local origin position from all positional data (vertex data, uniforms, etc.) before passing it into WebGL.
  • Translate the view transformation matrix by the origin (pre-multiply the view transformation matrix by the origin translation matrix)

This technique will cause the data to be rendered in a local coordinate frame, and thus avoid the large numbers which would otherwise be needed to place the data at the right location. Multiple local origins are needed if the data covers large extents with high detail. Note that the local origin has to be representable exactly in 32 bit floating point, which is best achieved by storing the local origin itself as a Float32Array.

See also

Constructors

RenderNode

Constructor
new RenderNode(properties)
Parameter
properties Object
optional

See the properties for a list of all the properties that may be passed into the constructor.

Property Overview

Any properties can be set, retrieved or listened to. See the Watch for changes topic.
Show inherited properties Hide inherited properties
Name Type Summary Class

Get the render representation of the current camera of a view.

RenderNode

Declare which inputs are needed from the engine for rendering.

RenderNode

The name of the class.

Accessor

Returns the current WebGL2RenderingContext instance.

RenderNode

Define the output produced by the render function.

RenderNode

The lighting used by SceneView to render the current frame.

RenderNode

The SceneView linked to this render node.

RenderNode

Property Details

camera

Property
camera RenderCamera

Get the render representation of the current camera of a view.

consumes

Property
consumes ConsumedNodes

Declare which inputs are needed from the engine for rendering.

For example, to request composite-color and normals, the function consumes() is specified as follows: consumes: { required: ["composite-color" , "normals"], optional: ["highlights"] }

declaredClass

Inherited
Property
declaredClass Stringreadonly
Inherited from Accessor

The name of the class. The declared class name is formatted as esri.folder.className.

gl

Property
gl WebGL2RenderingContext

Returns the current WebGL2RenderingContext instance. A context is available within the RenderNode once the view is ready.

produces

Property
produces RenderNodeOutput

Define the output produced by the render function.

The output is always given as one of the inputs to the render function. A post-processing render function would for example declare to produce the composite-color output: produces: "composite-color"

sunLight

Property
sunLight SunLight

The lighting used by SceneView to render the current frame.

view

Property
view SceneView

The SceneView linked to this render node.

Method Overview

Show inherited methods Hide inherited methods
Name Return Type Summary Class

Acquires and binds a managed framebuffer object to be written to and returned by the render function.

RenderNode

Adds one or more handles which are to be tied to the lifecycle of the object.

Accessor

Bind the color and depth buffers to render into and return the ManagedFBO.

RenderNode

Returns true if a named group of handles exist.

Accessor

Removes a group of handles owned by the object.

Accessor

The render function is called whenever a frame is rendered.

RenderNode

Request the SceneView to be redrawn.

RenderNode

Reset WebGL to a well-defined state.

RenderNode

Method Details

acquireOutputFramebuffer

Method
acquireOutputFramebuffer(){ManagedFBO}

Acquires and binds a managed framebuffer object to be written to and returned by the render function.

A custom RenderNode can either modify an input framebuffer by binding and rendering to it, or acquire a new output framebuffer and bind the input as a texture. For the second use case, this function returns a new framebuffer to use. The returned framebuffer will have the same resolution as the input framebuffer. This function will automatically bind and initialize the returned framebuffer.

The returned FBO has only a color0 attachment. The render function is however expected to return a ManagedFBO with the same attachments as the input framebuffer. Any additionally needed attachments can be allocated using acquireDepth() and acquireColor(), or reused from an input framebuffer using attachDepth() and attachColor().

Returns
Type Description
ManagedFBO The requested framebuffer object.
Example
// A grayscale RenderNode producing "composite-color" rendering into a color output
// framebuffer, and then reuses the unmodified input depth texture:
render(inputs) {
   const input = inputs.find(({ name }) => name === "composite-color")!;
   const output = this.acquireOutputFramebuffer();

   gl.activeTexture(gl.TEXTURE0);
   gl.bindTexture(gl.TEXTURE_2D, input.getTexture().glName);
   gl.uniform1i(this.textureUniformLocation, 0);

   // ...render grayscale using input texture

   output.attachDepth(input.getAttachment(gl.DEPTH_STENCIL_ATTACHMENT));
   return output;
 }

addHandles

Inherited
Method
addHandles(handleOrHandles, groupKey)
Inherited from Accessor

Adds one or more handles which are to be tied to the lifecycle of the object. The handles will be removed when the object is destroyed.

// Manually manage handles
const handle = reactiveUtils.when(
  () => !view.updating,
  () => {
    wkidSelect.disabled = false;
  },
  { once: true }
);

this.addHandles(handle);

// Destroy the object
this.destroy();
Parameters
handleOrHandles WatchHandle|WatchHandle[]

Handles marked for removal once the object is destroyed.

groupKey *
optional

Key identifying the group to which the handles should be added. All the handles in the group can later be removed with Accessor.removeHandles(). If no key is provided the handles are added to a default group.

bindRenderTarget

Method
bindRenderTarget(){ManagedFBO}

Bind the color and depth buffers to render into and return the ManagedFBO.

The 'produces' output framebuffer is always provided as an input to the render function. Depending on the implementation, a custom render node implementation will read this input buffer to produce a new output, or bind it as the active framebuffer to update it. This function will create this framebuffer binding for the second use case.

Returns
Type Description
ManagedFBO The bound managed framebuffer object.

hasHandles

Inherited
Method
hasHandles(groupKey){Boolean}
Inherited from Accessor

Returns true if a named group of handles exist.

Parameter
groupKey *
optional

A group key.

Returns
Type Description
Boolean Returns true if a named group of handles exist.
Example
// Remove a named group of handles if they exist.
if (obj.hasHandles("watch-view-updates")) {
  obj.removeHandles("watch-view-updates");
}

removeHandles

Inherited
Method
removeHandles(groupKey)
Inherited from Accessor

Removes a group of handles owned by the object.

Parameter
groupKey *
optional

A group key or an array or collection of group keys to remove.

Example
obj.removeHandles(); // removes handles from default group

obj.removeHandles("handle-group");
obj.removeHandles("other-handle-group");

render

Method
render(inputs){ManagedFBO}

The render function is called whenever a frame is rendered.

It has to return a framebuffer with the same attachments as the input "produces" framebuffer. For example, a render node producing "composite-color" is expected to produce a "composite-color" framebuffer with one color and depth attachment.

Typically the render function either uses bindRenderTarget() to render into this framebuffer, or acquireOutputFramebuffer() to get a new output framebuffer.

The returned framebuffer will be released once by the render engine once it is no longer needed. If the same framebuffer is returned over multiple frames it needs to be retained once per frame.

Parameter
inputs ManagedFBO[]

An array of currently provided fbos.

Returns
Type Description
ManagedFBO The framebuffer containing the modified input. to the render pipeline.

requestRender

Method
requestRender()

Request the SceneView to be redrawn.

SceneView only renders a frame when there have been changes to the view, for example when the camera has moved or new data is available for display. Frames are always redrawn from the ground up, which means that external renderers will get called in each frame that is drawn.

If a render node requires the view to be redrawn, for example because data has changed, it must call this function. This will trigger a single frame to be rendered. For continuous rendering, e.g. during animations, requestRender can be called in every frame from within render. It is important to note that calling requestRender() should be avoided if frame content stays the same for performance reasons.

Render requests are throttled to allow continuous background animations, and they do not affect SceneView.updating.

resetWebGLState

Method
resetWebGLState()

Reset WebGL to a well-defined state.

The ArcGIS Maps SDK for JavaScript offers no guarantee at the time of the call to render that all WebGL state variables are set to their respective defaults according to the WebGL 2.0 specification. Calling this function will reset the state to these defaults.

Because this function conservatively sets all WebGL state, it might incur a performance overhead. Therefore we suggest users instead keep track of the specific WebGL state that is modified, and reset that part of the state manually before returning from 'render'.

Type Definitions

ColorAndIntensity

Type Definition
ColorAndIntensity Object

Tuple of an RGB color value and an intensity value.

Properties
color Number[]

RGB color with values between 0 and 1.

intensity Number

Scalar intensity value by which the color should be scaled for compositing.

SunLight

Type Definition
SunLight Object

Describes the lighting used by SceneView, derived from its sun lighting model. It consists of a directional Lambertian (diffuse) and a constant (ambient) term, which should be treated in the sense of the Phong Reflection Model.

Properties
direction Number[]

The incident light direction in render coordinates.

The diffuse light color and intensity.

The ambient light color and intensity.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.