Skip to main content

Plugins

Plugins are the building blocks of features in a 3D Viewer. Each plugin handles its own individual feature along with serialisation and lifecycle management. WebGi viewer uses a plugin system to add new options, rendering styles, post processing passes, and more functionality. The plugin architecture is designed similar to other js frameworks like vue or webpack (but for 3d rendering).

All plugins follow the same basic structure, independent of the logic, with the API to add and remove plugins being always consistent (and one-liner). This makes it easy to debug, bundle, tree-shake, serialisation/deserialisation and extend functionality to the 3d viewer. It is also recommended to keep individual plugins small and handle one specific functionality.

Plugins can be dependant on other plugins. These dependencies are automatically resolved and added to the viewer at runtime. eg. SSAOPlugin is dependant on GBufferPlugin to get the depth and normal data. So, when SSAOPlugin is added to the viewer, it automatically adds GBufferPlugin before that.

note

Plugin dependencies are different from pass/filter dependencies, which specifies how passes should be arranged in the render pipeline (effect composer).

More information about this in later pages.

The webgi project ships with a library of plugins to achieve photorealistic rendering, generating user interfaces, handling events, loading and exporting assets, building 3d models etc.

Core plugins

The following plugins are available in webgi and can be added by importing them similar to the viewer.

  • AssetManagerPlugin - Importing 3D models, textures, materials, environment maps, scene config, and more. Check the Asset management article for more details.
  • ProgressivePlugin - Performs SuperSample AntiAliasing(SSAA) when camera stops moving. This plugin saves the last frame texture, which can be used by other screen space effect plugins(like SSR). So, ProgressivePlugin is added as a dependency for them. To disable SSAA but add effects like SSR, simply set maxFrameCount in ProgressivePlugin to 1.
  • GBufferPlugin - Creates a pre-render pass to render depth map, normals and other optional properties(like roughness/metalness) which can be used by other passes.
  • SSRPlugin - Performs ray-tracing in screen-space for producing realistic glossy Screen Space Reflections(SSR) based on roughness.
  • DiamondPlugin - Adds the loader to load diamonds materials in GLTF and renders accurate diamond materials, with options to specify a custom environment map and more settings.
  • GroundPlugin - Creates a ground at the right position below the 3d model, handles realtime baking of soft ground shadows, and renders realtime (physical/non-physical) ground reflections with high quality reflection render pass or SSR. Note: SSRPlugin must be added separately if screen space reflections are required.
  • SSAOPlugin - Computes soft occlusion shadows in screen space with fast and progressive Screen Space Ambient Occlusion(SSAO)
  • TemporalAAPlugin - Performs fast Temporal AntiAliasing(TAA) on the scene by detecting camera motion.
  • TODO: others
note

Checkout the plugin specific pages to get more details on the usage and API.

Adding a plugin

Plugins first need to be imported in your app to use. For that, simply import the named plugin in es6 import syntax.

// All plugins can be imported from `webgi`
import {
ViewerApp,
ProgressivePlugin, AssetManagerPlugin, TonemapPlugin, // ...others
} from "webgi";

Instantiating a plugin can be done manually(before/after creating the viewer) or it can be left to the viewer to instantiate a provided viewer class.

// creating a plugin.
const ssaa = new ProgressivePlugin(16); // 16 is an optional argument that specifies the number of frames to render before stopping.
// add to the viewer
await viewer.addPlugin(ssaa);

// or let the viewer initialize
const ssaa = await viewer.addPlugin(ProgressivePlugin, 16);

// remove the plugin when required
await viewer.removePlugin(ssaa)
note

It is not recommended to remove and re-add plugins that have custom passes or filters once is the pipeline has been built. To disable a plugin, set plugin.enable to false if it's supported. In the case where we need a completely new set of plugins it's best to initialise a new viewer

Refreshing the rendering pipeline

After adding a plugin to the viewer that makes changes to the rendering pipeline(like TonemapPlugin, SSRPlugin, etc), like adding a filter or a pass, we need to rebuild the pipeline. This is done by calling viewer.renderer.refreshdPipeline() after adding the plugin. This can be done once after adding all the plugins.

const viewer = new ViewerApp(...);

await viewer.addPlugin(ProgressivePlugin, 64);
await viewer.addPlugin(SSRPlugin);
await viewer.addPlugin(SSAOPlugin);
// add other plugins

viewer.renderer.refreshPipeline()

Get a plugin

Instance of a plugin that has been added to the viewer can be accessed by calling getPlugin or getPluginByType on the viewer. eg:

await viewer.addPlugin(TonemapPlugin);

// other code ...

// get the reference to the plugin
const tonemap = viewer.getPlugin(TonemapPlugin);
// or if the class is not available
const tonemap = viewer.getPluginByType(TonemapPlugin.PluginType);

// set any properties
tonemap.tonemapBackground = true;
tip

PluginType is a static string property required in all the plugins. This is a unique property that is used to identify the plugin and is not supposed to change.

Writing custom plugins

Plugins need to implement a simple interface to be attached to the viewer:

interface IViewerPlugin extends IEventDispatcher<string>, IUiConfigContainer, Partial<IJSONSerializable> {  
// all classes must have this static property with a unique identifier value for this plugin
static readonly PluginType: string

// these plugins will be added automatically(with default settings), if they are not added yet.
dependencies?: Class<IViewerPlugin<any>>[]

// the viewer will render the next frame if this is set to true
dirty?: boolean;

// Called when this plug-in is added to the viewer
onAdded(viewer: TViewer): Promise<void>;

// Called when this plug-in is removed from the viewer
onRemove(viewer: TViewer): Promise<void>;

// Called when the viewer is disposed
onDispose(viewer: TViewer): Promise<void>;
}

There are abstract classes to create new viewer plugins with minimal boilerplate. This includes cases for plugins with a single or multiple pre/post processing pass, connecting with the DOM, serialisation and handling user input/interactions.

Read more in the Writing custom plugins guide and the source of existing plugins to know more.