require(["esri/views/3d/webgl/RenderNode"], (RenderNode) => { /* code goes here */ });
import RenderNode from "@arcgis/core/views/3d/webgl/RenderNode.js";
esri/views/3d/webgl/RenderNode
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.
- 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.
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
-
Parameterproperties Objectoptional
See the properties for a list of all the properties that may be passed into the constructor.
Property Overview
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
camera RenderCamera
-
Get the render representation of the current camera of a view.
-
consumes
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"] }
-
gl
gl WebGL2RenderingContext
-
Returns the current WebGL2RenderingContext instance. A context is available within the RenderNode once the view is ready.
-
produces
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"
-
view
view SceneView
-
The SceneView linked to this render node.
Method Overview
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
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().
ReturnsType 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; }
-
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();
ParametershandleOrHandles WatchHandle|WatchHandle[]Handles marked for removal once the object is destroyed.
groupKey *optionalKey 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
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.
ReturnsType Description ManagedFBO The bound managed framebuffer object.
-
hasHandles
InheritedMethodhasHandles(groupKey){Boolean}
Inherited from Accessor -
Returns true if a named group of handles exist.
ParametergroupKey *optionalA group key.
ReturnsType 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"); }
-
Inherited from Accessor
-
Removes a group of handles owned by the object.
ParametergroupKey *optionalA group key or an array or collection of group keys to remove.
Exampleobj.removeHandles(); // removes handles from default group obj.removeHandles("handle-group"); obj.removeHandles("other-handle-group");
-
render
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.
Parameterinputs ManagedFBO[]An array of currently provided fbos.
ReturnsType Description ManagedFBO The framebuffer containing the modified input. to the render pipeline.
-
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 do not affect SceneView.updating.
-
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
-
SunLight
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 Vec3
The incident light direction in render coordinates.
diffuse ColorAndIntensityThe diffuse light color and intensity.
ambient ColorAndIntensityThe ambient light color and intensity.