A lightweight deffered rendering WebGL 2.0 framework in JavaScript.
The example code below show a simple way how to set up a scene for rendering a triangle.
const canvas = new RC.Canvas(document.body);
const renderer = new RC.MeshRenderer(canvas, RC.WEBGL2);
renderer.addShaderLoaderUrls(shaderLocation);
const scene = new RC.Scene();
const camera = new RC.OrthographicCamera(-1, 1, 1, -1, 0, 10);
const geometry = new RC.Geometry({vertices: new RC.Float32Attribute([-1, -1, 0, 1, -1, 0, 0, 1, 0], 3)});
const material = new RC.MeshBasicMaterial();
const object = new RC.Mesh(geometry, material);
scene.add(object);The example shows some of the shapes awailable in the framework, how to create, color, and render them.
//mesh (point)
const pointGeometry = new RC.Geometry({vertices: new RC.Float32Attribute([0, 0, 0], 3)});
const point = new RC.Point(pointGeometry);
point.material.pointSize = 64.0;
...
//mesh (line)
const lineGeometry = new RC.Geometry({vertices: new RC.Float32Attribute([-1, -1, 0, 1, 1, 0], 3)});
const line = new RC.Line(lineGeometry);
...
//mesh (triangle)
const triangleGeometry = new RC.Geometry({vertices: new RC.Float32Attribute([-1, -1, 0, 1, -1, 0, 0, 1, 0], 3)});
const triangle = new RC.Mesh(triangleGeometry);
triangle.material.side = RC.FRONT_AND_BACK_SIDE;
...
//circle
const circle = new RC.Circle();
...
//quad
const quad = new RC.Quad({x: -1, y: 1}, {x: 1, y: -1});
quad.material.side = RC.FRONT_AND_BACK_SIDE;
...
//cube
const cube = new RC.Cube(1.0, new RC.Color());
...The example shows how to load an OBJ model and render it with a texture.
The example demonstrates how to use ambient and directional light. The example also shows how to link keyboard interaction.
...
// Initialize lights and add them to the scene
aLight = new RC.AmbientLight(new RC.Color("#FFFFFF"), 0.1);
scene.add(aLight);
dLight = new RC.DirectionalLight(new RC.Color("#FF0000"), 1.0);
dLight.position = new RC.Vector3(1.0, 0.39, 0.7);
scene.add(dLight);
...This example demonstrates how to use different types lights.
const aLight = new RC.AmbientLight(new RC.Color("#FFFFFF"), 0.1);
scene.add(aLight);
const dLight = new RC.DirectionalLight(new RC.Color("#FF0000"), 0.5);
dLight.rotation = new RC.Euler(-1.3, -1.2, 0.0);
scene.add(dLight);
const pLight = new RC.PointLight(new RC.Color("#0000FF"), 0.5);
pLight.position = new RC.Vector3(0.0, 1.0, 1.0);
scene.add(pLight);
const sLight = new RC.SpotLight(new RC.Color("#FFFFFF"), 0.5);
sLight.position = new RC.Vector3(0.0, 1.5, 0.0);
sLight.rotation = new RC.Euler(-Math.PI/2, 0.0, 0.0);
scene.add(sLight);The example demonstrates how to use ambient and directional light. The example also shows how to link keyboard interaction.
...
// Initialise scenes first
this.initScenes();
// Initialize render Queue
this.renderQueue = this.initRenderQueue();
...
// Example of RenderQueue initialization function
initRenderQueue() {
// Create first pass
this.firstPass = new RC.RenderPass(
RC.RenderPass.BASIC,
(textureMap, additionalData) => {},
(textureMap, additionalData) => {
return {scene: this.firstScene, camera: this.firstCamera};
},
(textureMap, additionalData) => {},
RC.RenderPass.TEXTURE,
{width: 512, height: 512},
"depthTexture",
[{
id: "color",
textureConfig: RC.RenderPass.DEFAULT_RGBA_TEXTURE_CONFIG
}]
);
// Create second pass
this.secondPass = new RC.RenderPass(
RC.RenderPass.BASIC,
(textureMap, additionalData) => {
this.quad.material.clearMaps();
this.quad.material.addMap(textureMap.color);
},
(textureMap, additionalData) => {
return {scene: this.secondScene, camera: this.secondCamera};
},
(textureMap, additionalData) => {},
RC.RenderPass.SCREEN,
{width: this.canvas.width, height: this.canvas.height}
);
// Create a RenderQueue and add both render passes to it
let renderQueue = new RC.RenderQueue(this.renderer);
renderQueue.pushRenderPass(this.firstPass);
renderQueue.pushRenderPass(this.secondPass);
return renderQueue;
}Here we demonstrates a technique to get a unique identifier of a clicked rendered object.
const canvas = new RC.Canvas(document.body);
const renderer = new RC.MeshRenderer(canvas, RC.WEBGL2);
renderer.addShaderLoaderUrls(shaderLocation);
const renderQueue = new RC.RenderQueue(renderer);
const scene = new RC.Scene();
const camera = new RC.PerspectiveCamera(75, canvas.width/canvas.height, 0.125, 128);
const picker = new RC.PickerFX(
renderer,
{scene: scene, camera: camera},
{},
new RC.FX.output("color_picker")
);
renderQueue.pushRenderQueue(picker);
const MainPass = new RC.RenderPass(
// Rendering pass type
RC.RenderPass.BASIC,
// Initialize function
(textureMap, additionalData) => { },
// Preprocess function
(textureMap, additionalData) => { return { scene: scene, camera: camera }; },
// PostprocesWs function
(textureMap, additionalData) => { },
// Target
RC.RenderPass.SCREEN,
// Viewport
{ width: canvas.width, height: canvas.height },
// Bind depth texture to this ID
"depth_main",
// Outputs
[{id: "color_main", textureConfig: RC.RenderPass.DEFAULT_RGBA_TEXTURE_CONFIG}]
);
renderQueue.pushRenderPass(MainPass);This example show how to draw a lot of models with same set of vertex data.
const geometry = new RC.Geometry();
geometry.vertices = new RC.Float32Attribute(cubeVertices, 3);
geometry.vertices.divisor = 0;
geometry.computeVertexNormals();
geometry.normals.divisor = 0;
geometry.MMat = new RC.Float32Attribute(instances.reduce((acc, x) => {acc.push(...x.matrix.elements); return acc;}, new Array()), 16);
geometry.MMat.divisor = 1;
geometry.MMat.drawType = RC.BufferAttribute.DRAW_TYPE.DYNAMIC;
const material = new RC.MeshBasicMaterial();
cubeMaterial.emissive.set(0.0, 0.0, 0.0);
const object = new RC.Mesh(geometry, material);
object.position.set(0.0, 0.0, 0.0);
object.scale.set(0.1, 0.1, 0.1);
object.instanced = true;
object.instanceCount = instances.length;
scene.add(cubeObject);This example show how to draw a lot of models with same set of vertex data.
const spriteGeometry = new RC.SpriteGeometry({baseGeometry: spriteBaseGeometry});
spriteGeometry.vertices.divisor = 0;
spriteGeometry.normals.divisor = 0;
spriteGeometry.translation = new RC.Float32Attribute(spriteTranslations.reduce((acc, x) => {acc.push(...x.toArray()); return acc;}, new Array()), 4);
spriteGeometry.translation.divisor = 1;
spriteGeometry.translation.drawType = RC.BufferAttribute.DRAW_TYPE.DYNAMIC;
const spriteMaterial = new RC.SpriteBasicMaterial({baseGeometry: spriteBaseGeometry, color: new RC.Color(Math.random(), Math.random(), Math.random()), spriteSize: [8, 8], drawCircles: true});
spriteMaterial.emissive.set(0.0, 0.0, 0.0);
const spriteObject = new RC.Sprite({geometry: spriteGeometry, material: spriteMaterial});
spriteObject.position.set(0.0, 0.0, 0.0);
spriteObject.scale.set(0.1, 0.1, 0.1);
spriteObject.instancedTranslation = true;
spriteObject.instanceCount = spriteInstances.length;
scene.add(spriteObject);A BufferAttribute or a Texture holds data. The WebGL buffer or texture built
from it is only a cache of that data, owned by the GLManager of the context
that built it. Nothing in RenderCore registers, reference-counts or disposes a GL
resource: an object that stops being drawn simply stops being asked for, and the
buffer behind it can be dropped and rebuilt from the data, which is still there.
Two consequences worth knowing.
The same attribute or texture can be used by several contexts at once. Each
GLManager keeps its own cache entry -- the GL object, the version it last
uploaded, its idle counter and the attrib locations it is bound to -- so two
canvases sharing one geometry each upload it once, independently. Nothing that
is per context lives on the attribute; the attribute carries only data and a
version counter, bumped whenever the data changes.
Freeing memory is a question only the application can answer. The renderer sees a stream of draw calls and cannot tell an object that was removed from one that merely was not visible this frame. So the application brackets a change to the scene:
renderer.ageResources(); // before: age everything this context holds
// ... rebuild the scene: add, remove, replace ...
renderer.collectResources(); // after: drop whatever the rebuild did not use"Still needed" is then answered by whether anything reached for the resource
while the scene was being rebuilt -- no bookkeeping, no ownership, no destructors.
collectResources(n) takes the number of cycles a resource may go untouched
before it is dropped; the default of 2 gives one cycle of grace, so a resource
used by every second rebuild is not thrashed. Dropping writes nothing back to the
attribute or texture, so a resource collected too eagerly costs an upload and
nothing else.
An application that loads a scene once and keeps it never has to call either one,
which is why neither is on the render path. An application whose scene changes --
a new dataset, a new event -- calls them around each change and its GPU memory
follows the scene on its own. ROOT's REve does exactly this, once per update
cycle, in EveManager around the point where the scenes finish applying changes.
Framework contains some functionalities adopted from Three.js 3D library.
The list of such functionalities is as follows: