Skip to content

Material

Material Overview

The engine adds a corresponding material object to an object through the MeshRenderer component, used to simulate its color, lighting, texture, and other properties. Currently, the engine has 2 built-in classic material models:

NameDescription
UnLitMaterialDoes not compute lighting and shadows, only renders the original color and texture information
LitMaterialPBR, i.e. Physically-Based Rendering, aims to simulate real-world lighting effects

We recommend using the glTF format file, which contains the object's material information. Users can design and modify model files through common modeling software. After the engine parses the model file, it automatically assigns the corresponding material to the object.

Basic Usage

ts
import {Object3D. MeshRenderer, LitMaterial, SphereGeometry, Color} from '@orillusion/core'

let object = new Object3D();
// Add MeshRenderer component
let mesh = object.addComponent(MeshRenderer);

// Set the component material
mesh.material = new UnLitMaterial();
mesh.material.baseColor = new Color(1, 1, 1, 1);

// Switch Material
mesh.material = new LitMaterial();

Unlit Material

The engine provides the UnLitMaterial material, which only renders the physical base color and does not compute lighting and shadows.

PropertyDescription
baseColorBase color
baseMapBase texture

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { Camera3D, DirectLight, Engine3D, AtmosphericComponent, View3D, HoverCameraController, KelvinUtil, MeshRenderer, Object3D, Scene3D, SphereGeometry, UnLitMaterial, Color } from '@orillusion/core';

class Sample_Materials {
    scene: Scene3D;
    lightObj: Object3D;

    constructor() {}

    async run() {
        let engine = await Engine3D.init();
        this.scene = new Scene3D();

        let cameraObj = new Object3D();
        let mainCamera = cameraObj.addComponent(Camera3D);
        this.scene.addChild(cameraObj);
        mainCamera.perspective(60, engine.aspect, 1, 5000.0);
        mainCamera.object3D.addComponent(HoverCameraController);

        await this.initScene();

        // add an Atmospheric sky enviroment
        this.scene.addComponent(AtmosphericComponent).sunY = 0.6;
        // create a view with target scene and camera
        let view = new View3D();
        view.scene = this.scene;
        view.camera = mainCamera;
        // start render
        engine.startRenderView(view);
    }

    async initScene() {
        {
            this.lightObj = new Object3D();
            this.lightObj.x = 0;
            this.lightObj.y = 0;
            this.lightObj.z = 0;
            this.lightObj.rotationX = 0;
            this.lightObj.rotationY = 0;
            this.lightObj.rotationZ = 0;
            let lc = this.lightObj.addComponent(DirectLight);
            lc.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
            lc.intensity = 1.7;
            this.scene.addChild(this.lightObj);
        }

        {
            // UnLitMaterial
            let sphere = new Object3D();
            let mr = sphere.addComponent(MeshRenderer);
            mr.geometry = new SphereGeometry(2.5, 30, 30);
            let mat = new UnLitMaterial();
            mat.baseColor = new Color(1, 1, 1, 1);
            mr.material = mat;
            this.scene.addChild(sphere);
            sphere.localPosition.set(0, 0, 0);
        }
    }
}

new Sample_Materials().run();

PBR Material

We advocate using LitMaterial, i.e. the PBR material, for rendering. PBR is the abbreviation of Physically-Based Rendering, referring to a material based on physical rendering, which better conforms to a real physical lighting model.

PropertyDescription
baseColorMaterial base color
emissiveColorMaterial emissive color
emissiveIntensityMaterial emissive intensity; BloomPost must be enabled to display the emissive effect
roughnessMaterial roughness
metallicMaterial metalness
normalScaleThe degree to which the normal map affects the material
clearCoatClear coat intensity
envMapEnvironment map
envIntensityEnvironment light intensity
materialF0Material reflectance
aoAmbient Occlusion, handles the effect of ambient light occlusion on objects
aoMapAmbient occlusion map
baseMapBase texture
normalMapNormal map
maskMapMask map
emissiveMapMaterial emissive map
brdfLUTBRDF lookup table

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { Camera3D, DirectLight, Engine3D, AtmosphericComponent, View3D, HoverCameraController, MeshRenderer, Object3D, Scene3D, SphereGeometry, LitMaterial } from '@orillusion/core';

class Sample_Materials {
    scene: Scene3D;
    lightObj: Object3D;

    constructor() {}

    async run() {
        let engine = await Engine3D.init();
        this.scene = new Scene3D();
        let cameraObj = new Object3D();
        let mainCamera = cameraObj.addComponent(Camera3D);
        this.scene.addChild(cameraObj);
        mainCamera.perspective(60, engine.aspect, 1, 5000.0);
        mainCamera.object3D.addComponent(HoverCameraController);

        await this.initScene();

        // add an Atmospheric sky enviroment
        this.scene.addComponent(AtmosphericComponent).sunY = 0.6;
        // create a view with target scene and camera
        let view = new View3D();
        view.scene = this.scene;
        view.camera = mainCamera;
        // start render
        engine.startRenderView(view);
    }

    async initScene() {
        {
            this.lightObj = new Object3D();
            this.lightObj.x = -20;
            this.lightObj.y = 20;
            this.lightObj.z = -20;
            this.lightObj.rotationX = 45;
            this.lightObj.rotationY = 45;
            this.lightObj.rotationZ = 0;
            let lc = this.lightObj.addComponent(DirectLight);
            lc.intensity = 0.2;
            this.scene.addChild(this.lightObj);
        }
        {
            // PRB
            let sphere = new Object3D();
            let mr = sphere.addComponent(MeshRenderer);
            mr.geometry = new SphereGeometry(2.5, 30, 30);
            let mat = new LitMaterial();
            mr.material = mat;
            this.scene.addChild(sphere);
            sphere.localPosition.set(0, 0, 0);
        }
    }
}

new Sample_Materials().run();

Material Settings

Color

ts
let mat = new UnLitMaterial();
mat.baseColor = new Color(1, 0, 0, 1);

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

Transparency & Blend Mode

ts
let mat = new UnLitMaterial();
mat.transparent = true;
mat.blendMode = BlendMode.ALPHA;
mat.baseColor = new Color(1, 0, 0, 0.5);

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

For blend mode details, please refer to BlendMode

Switching Cull Mode

To save GPU performance, Orillusion uses the back cull mode by default, i.e. only rendering the front-facing material of an object. We can set the material's cullMode property to switch the display mode

ts
let mat = new LitMaterial();
mat.cullMode = GPUCullMode.none; // No culling, double-sided display
mat.cullMode = GPUCullMode.front; // Cull the front, display the back
mat.cullMode = GPUCullMode.back; // Cull the back, display the front

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

Depth State

A material can control how it participates in the depth buffer (Depth Buffer), which is commonly used to handle transparent object sorting, avoid Z-fighting, and implement "always visible" overlay layers, etc.

ts
let mat = new LitMaterial();

// Whether to write to the depth buffer. Transparent objects usually turn this off to avoid sorting errors caused by mutual occlusion
mat.depthWriteEnabled = false;

// Depth compare function (GPUCompareFunction). Default 'less-equal'
// For example, set to 'always' to make the object always pass the depth test (overlay layer / outline)
mat.depthCompare = 'always' as GPUCompareFunction;
PropertyTypeDescription
depthWriteEnabledbooleanWhether to write this material's fragment depth to the depth buffer
depthCompareGPUCompareFunctionDepth compare function, such as 'less', 'less-equal', 'greater', 'always', etc.

Stencil Buffer (Stencil)

Version Notes

The stencil buffer API was introduced in @orillusion/core 0.9.0. It can be used to implement effects such as outline strokes, mirror masks, and projected decals that require "marking a region and then drawing it a second time".

The stencil test uses an additional 8-bit buffer to perform a "mark—compare—decide whether to draw" operation on each fragment. Material exposes the complete stencil state:

ts
let mat = new LitMaterial();

// Reference value and read/write masks
mat.stencilRef = 1;          // The reference value used when comparing / writing
mat.stencilReadMask = 0xFF;  // The mask AND-ed with the buffer value when comparing
mat.stencilWriteMask = 0xFF; // The bits allowed to be modified when writing

// Stencil operations for front / back faces (GPUStencilFaceState)
mat.stencilFront = {
    compare: 'always',     // Compare function: always pass
    failOp: 'keep',        // When the stencil test fails
    depthFailOp: 'keep',   // When the stencil passes but the depth test fails
    passOp: 'replace',     // When all pass: replace the buffer value with stencilRef (write the mark)
};
mat.stencilBack = {
    compare: 'always',
    failOp: 'keep',
    depthFailOp: 'keep',
    passOp: 'keep',
};
PropertyTypeDescription
stencilFrontGPUStencilFaceStateStencil operations for front-facing triangles (compare function + three operations)
stencilBackGPUStencilFaceStateStencil operations for back-facing triangles
stencilReadMasknumberThe read mask in the compare stage, default 0xFF
stencilWriteMasknumberThe write mask in the write stage, default 0xFF
stencilRefnumberThe stencil reference value, default 0

The typical two-pass method (mark first, then use the mark to restrict the drawing area): in the first pass, the material uses passOp: 'replace' to write the covered area as stencilRef; in the second pass, the material uses compare: 'equal' and passOp: 'keep' to draw only in the marked area.

Effects such as the engine's built-in projected decal are implemented based on the stencil buffer and generally do not require manual configuration.

UV Transform

Model vertices store multiple sets of texture mapping coordinates, which define the 2D coordinate of that vertex in the texture. It is usually represented by a two-dimensional variable (u,v), so it is also called UV coordinates.
We can customize the texture mapping relationship by performing a matrix transformation on the model's uv coordinates. For example, set the transformUV1 variable in the material shader to perform offset and scaling coordinate transformations on the original uv:

ts
let mat = new LitMaterial();
// Get uv - Vector4(offsetU, offsetV, scalingU, scalingV)
let uv: Vector4 = mat.getUniformV4(`transformUV1`);
// Set uv
uv.set(1, 1, 2, 2);
// Update uv
mat.setUniformVector4(`transformUV1`, uv);

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

TIP

Starting from v0.8, LitMaterial uses xxxMapOffsetSize to get and set the uv transform of each texture:

ts
let mat = new LitMaterial();
let baseUV = mat.getUniformV4('baseMapOffsetSize');
let normalUV = mat.getUniformV4('normalMapOffsetSize');
let emissiveUV = mat.getUniformV4('emissiveMapOffsetSize');
let roughnessUV = mat.getUniformV4('roughnessMapOffsetSize');
let metallicUV = mat.getUniformV4('metallicMapOffsetSize');
let aoUV = mat.getUniformV4('aoMapOffsetSize');

Released under the MIT License