Device

First of all, create device object around a canvas. Device holds all entities and represent stage-material grid.

device = R.makeDevice(canvas);

You can also specify output color & depth formats and WebGL context attributes (see the reference page).

device = R.makeDevice(canvas, R.Format.RGBA, false,
    {
        antialias: false,
        premultipliedAlpha: false
    }
);

When you resize linked canvas, device triggers resize event.

device.on("resize", handler);

If the WebGL context is lost, the device will trigger lose event. When it is restored, the device will trigger restore event and restore all entities automatically.

device.
    on("lose", loseHandler).
    on("restore", restoreHandler);

There is a set of capabilities that vary with different hardware (like compressed & float textures, anisotropic filter, etc). You can check it using Caps structure:

supportsFloat16Textures = device.caps().textureFloat16;

The frame method performs a frame and returns information about execution.

info = device.frame();

Mesh

Mesh contains vertices and indexed primitives and provides interface around it. To create a mesh, use following maker method:

mesh = device.makeMesh();

Mesh supports only interleaved vertex data. You need to specify the vertex format via attributes.

There is a set of attribute types and commonly used aliases (which can be used in some calculations).

mesh.
    attribute("position", R.Attribute.POSITION).
    attribute("normal", R.Attribute.NORMAL).
    attribute("uv", R.Attribute.UV).
    attribute("customData", R.Attribute.VECTOR4);

Then you can upload vertex data to the mesh from an array (JS or Typed).

mesh.vertices(array);

Alternatively, you can allocate a number of vertices that you need. In this case the vertex data will be initialized by zero values.

mesh.vertices(4);

If you want to update vertices repeatedly, it's highly recommended to use DYNAMIC flag to avoid performance penalty.

mesh.vertices(4, R.Usage.DYNAMIC);

Then update your vertices.

mesh.updateVertices(array, offset);

Use the same approach to work with indices.

mesh.indices(3, R.Usage.DYNAMIC);

mesh.updateIndices(array);

Also you can specify a type of primitive which will be drawn.

mesh.primitive(R.Primitive.LINE);

Texture

Texture provides interface over a set of mip levels.

There are two ways to create a texture: from parameters and from HTML objects (see the reference page).

texture = device.makeTexture(R.Format.RGBA, 128, 128);

To get specified mip level, use the following method:

mip0 = texture.mip(0);

You can upload arbitrary data (array or HTML object) to any mip level via source method:

mip0.source(image);

Also you can generate full mip level chain from the largest mip level:

texture.buildMips();

To create cubemap you just need to set cube face count argument to CubeFace.COUNT.

cubemap = device.makeTexture(R.Format.RGBA, 128, 128, R.CubeFace.COUNT);

If you use cubemaps, you need to specify a cube face when you access to a mip-level:

mip0 = cubemap.mip(0, R.CubeFace.NEGATIVE_X);

Depth

Depth object represents a depth-stencil buffer.

depth = device.makeDepth(R.Format.DEPTH, 128, 128);

It can be used to write depth from the fragment shader and also to read it via sampler (if it's supported by your hardware).

Target

Target represents a rendering output (color and depth) for a stage (see next section).

You can construct a target from existent color and depth objects

target = device.makeTarget(texture, depth);

or from parameters.

target = device.makeTarget(R.Format.RGB, R.Format.DEPTH, 128, 128);

It's also possible to draw to multiple color targets (if your hardware supports it).

target = device.makeTarget([textureA, textureB], depth);

You can clone your target to a new one and specify scale factor.

downscaled = target.clone(0.5);

The device holds a specific type of target which wraps the canvas. If you want to draw directly to the display area, you need to use that target.

device.target()

Stage

Stage separates the rendering process within the frame.

Stage executes to the target object. For each stage you can setup view/projection matrices, viewport & scissor rects, depth range and cleanup parameters.

It's possible to implement any complex rendering pipeline via stage grid. There is an example of pipeline that uses shadow mapping and some post processing:

For simplicity, the "map" means a texture and a depth buffer wrapped into a target. A stage draws to the target and then the texture from the target is used as uniform value (see uniforms section) for another stage.

There is a code snippet of the example:

device.
    stage("shadow-cast").output(shadowMap).device().

    stage("main").output(mainMap).
        uniform("shadowMap", shadowMap.depth()).device().

    stage("bright").output(blurMap0).
        uniform("sourceMap", mainMap.color()).device().

    stage("h-blur").output(blurMap1).
        uniform("sourceMap", blurMap0.color()).device().

    stage("v-blur").output(blurMap0).
        uniform("sourceMap", blurMap1.color()).device().

    stage("combine").output(device.target()).
        uniform("map0", mainMap.color()).
        uniform("map1", blurMap0.color());

Pass

Pass defines a rendering configuration (shaders, samplers, states).

You can create a pass from vertex & fragment shaders and an array of shader macro (optionally):

pass = device.makePass(vs, fs, macros);

Also you can recompile existent pass with new shaders & macros:

pass.compile(vs, fs, macros);

It extracts vertex attributes from the vertex shader and generates bindings. And so you can draw any mesh by any pass. It also extracts information about all uniforms from both vertex and fragment shaders.

For each texture uniform you can configure sampler:

pass.
    sampler("shadowMap").filter(R.Filter.NONE).address(R.Address.CLAMP).pass().
    sampler("albedoMap").filter(R.Filter.TRILINEAR).anisotropy(4).pass().
    sampler("normalMap").filter(R.Filter.TRILINEAR).anisotropy(4);

There is a set of rendering states which you can change. For example:

pass.
    state(R.State.POLYGON).cull(false).pass().
    state(R.State.DEPTH).test(false).write(false).
    state(R.State.BLEND).src(R.Blend.SRC_ALPHA).dest(R.Blend.INV_SRC_ALPHA).eq(R.BlendEq.ADD);

Material

Material describes a rendering method that can be applied to an instance. It defines a set of passes for the stage grid.

There is an example of using materials in the pipeline which was described above.

Some names of materials in this example has technical meaning (for better understanding). Normally you should use more natural names such as "glass", "copper", "wood", etc.

Associated passes is executed from left to right and from top to bottom. And so if you want draw one material before another you need to add it first.

device.
    material("solid").
        pass("shadow-cast", passA).
        pass("main", passB).device().

    material("self-shadowed").
        pass("shadow-cast", passA).
        pass("main", passC).device().

    material("transparent").
        pass("main", passD).device().

    material("fx-bloom").
        pass("bright", passE).
        pass("h-blur", passF).
        pass("v-blur", passG).
        pass("combine", passH);

Instance

Instance is the elementary unit of rendering. It represent a transformed mesh and material.

instance = device.instance(material, mesh, transform);

You can transform the instance by matrices

instance.
    transform(mxA).
    transform(mxB).
    transform(mxC);

or you can use named methods.

instance.
    scale(factor).
    rotate(axis, angle).
    move(offset);

To remove the instance from the draw use free method:

instance.free();

Uniforms

Device, Stage, Material and Instance provide the uniform interface.

device.uniform("time", time);

stage.uniform("shadowMap", map);

material.uniform("refractionIndex", 0.95);

instance.uniform("color", value);

Uniforms override each other so you can use default values:

material.uniform("albedoMap", defaultMap);

instance.uniform("albedoMap", map);

Some objects provide uniform placeholders. If you set it as uniform's value it will apply some internal value automatically.

device.uniform("time", R.Device.TIME);
device.uniform("dt", R.Device.DELTA_TIME);

stage.uniform("mxViewProj", R.Stage.VIEW_PROJ);
stage.uniform("viewPos", R.Stage.VIEW_POS);
stage.uniform("viewDir", R.Stage.VIEW_DIR);

instance.uniform("mxTransform", R.Instance.TRANSFORM);
instance.uniform("mxNormalTransform", R.Instance.NORMAL_TRANSFORM);

Culling

The project implements the frustum culling approach.

If you want to use it you need to set bounds for your meshes

mesh.bounds(aabox);

or compute it from the POSITION attribute.

mesh.computeBounds();

An instance derives mesh's bounds and transform it by instance's transformation matrix.

instance.bounds();

Stage's view-projecting matrix will be used to compute frustum planes.

stage.view(mxView).proj(mxProj);

You can also disable frustum culling in the instance or the whole stage respectively.

stage.culling(false);

instance.culling(false);

TABLE OF CONTENT: