This sample is a minimal web page to render a MorphCharts scene in a browser. It creates a WebGPU ray-tracing renderer, loads a spec, and runs a render loop.
Before starting, make sure you have the prerequisites in place.
<canvas> element to the page.<!DOCTYPE html>
<html>
<head>
<script type="importmap">
{
"imports": {
"core": "https://microsoft.github.io/morphcharts/lib/morphcharts-core.js",
"spec": "https://microsoft.github.io/morphcharts/lib/morphcharts-spec.js",
"webgpuraytrace": "https://microsoft.github.io/morphcharts/lib/morphcharts-webgpuraytrace.js"
}
}
</script>
</head>
<body>
<!-- Add a canvas -->
<canvas id="canvas" width="1280" height="720"></canvas>
<script type="module">
// Module imports
import * as Core from 'core';
import * as Spec from 'spec';
import * as WebGPURenderer from 'webgpuraytrace';
// Create the renderer
const canvas = document.getElementById('canvas');
const renderer = new WebGPURenderer.Main(canvas);
// Initialize the renderer
await renderer.initializeAsync();
// Parse a spec and create a scene
const specJSON = {
"width": 640,
"height": 360,
"marks": [
{
"type": "rect",
"geometry": "sphere",
"material": "glossy",
"encode": {
"enter": {
"xc": {"value": 320},
"yc": {"value": 180},
"width": {"value": 360}
}
}
},
{
"type": "rect",
"encode": {
"enter": {
"xc": {"value": 320},
"width": {"value": 1280},
"depth": {"value": 1280},
"fill": {"value": "white"}
}
}
}
]
};
const scene = await Spec.Plot.createSceneAsync(specJSON);
// Load the scene
renderer.loadScene(scene);
// Run a render loop
const maxSamples = 1000;
let previousTime = performance.now();
function tick(currentTime) {
const elapsed = currentTime - previousTime;
previousTime = currentTime;
renderer.updateAsync(elapsed);
renderer.renderAsync(elapsed);
if (renderer.frameCount < maxSamples) {
requestAnimationFrame(tick);
}
}
requestAnimationFrame(tick);
</script>
</body>
</html>
Add a <canvas> element to the page with a width and height attribute.
<canvas id="canvas" width="1280" height="720"></canvas>
<script type="importmap">
{
"imports": {
"core": "https://microsoft.github.io/morphcharts/lib/morphcharts-core.js",
"spec": "https://microsoft.github.io/morphcharts/lib/morphcharts-spec.js",
"webgpuraytrace": "https://microsoft.github.io/morphcharts/lib/morphcharts-webgpuraytrace.js"
}
}
</script>
import * as Core from 'core';
import * as Spec from 'spec';
import * as WebGPURenderer from 'webgpuraytrace';
The import map and module descriptions are discussed in the prerequisites.
This example uses the WebGPU ray-tracing renderer. The constructor reads
reads the canvas width and height attributes to set
the render target size:
const renderer = new WebGPURenderer.Main(canvas);
If necessary, you can override the defaults with an options object:
const renderer = new WebGPURenderer.Main(canvas, {
width: 1920,
height: 1080,
renderMode: "raytrace", // default
});
await renderer.initializeAsync();
This performs one-time setup for the renderer (e.g. GPU device creation for WebGPU) and initializes default resources needed for rendering.
Here is a minimal spec — a sphere on a white ground plane:
const specJSON = {
"width": 640,
"height": 360,
"marks": [
{
"type": "rect",
"geometry": "sphere",
"material": "glossy",
"encode": {
"enter": {
"xc": {"value": 320},
"yc": {"value": 180},
"width": {"value": 360}
}
}
},
{
"type": "rect",
"encode": {
"enter": {
"xc": {"value": 320},
"width": {"value": 1280},
"depth": {"value": 1280},
"fill": {"value": "white"}
}
}
}
]
};
Parse the specJSON and create a scene:
const scene = await Spec.Plot.createSceneAsync(specJSON);
renderer.loadScene(scene);
Loading a scene resets the renderer's visual collections and populates them from the scene.
const maxSamples = 1000;
let previousTime = performance.now();
function tick(currentTime) {
const elapsed = currentTime - previousTime;
previousTime = currentTime;
renderer.updateAsync(elapsed);
renderer.renderAsync(elapsed);
if (renderer.frameCount < maxSamples) {
requestAnimationFrame(tick);
}
}
requestAnimationFrame(tick);
Each frame calls updateAsync (updates state) and renderAsync (dispatches a render
pass) on the renderer.
The ray-tracing renderer accumulates samples over multiple frames, so
you typically run the loop until renderer.frameCount
reaches a desired quality level (maxSamples in this example).