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:
| Name | Description |
|---|---|
| UnLitMaterial | Does not compute lighting and shadows, only renders the original color and texture information |
| LitMaterial | PBR, 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
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.
| Property | Description |
|---|---|
| baseColor | Base color |
| baseMap | Base texture |
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.
| Property | Description |
|---|---|
| baseColor | Material base color |
| emissiveColor | Material emissive color |
| emissiveIntensity | Material emissive intensity; BloomPost must be enabled to display the emissive effect |
| roughness | Material roughness |
| metallic | Material metalness |
| normalScale | The degree to which the normal map affects the material |
| clearCoat | Clear coat intensity |
| envMap | Environment map |
| envIntensity | Environment light intensity |
| materialF0 | Material reflectance |
| ao | Ambient Occlusion, handles the effect of ambient light occlusion on objects |
| aoMap | Ambient occlusion map |
| baseMap | Base texture |
| normalMap | Normal map |
| maskMap | Mask map |
| emissiveMap | Material emissive map |
| brdfLUT | BRDF lookup table |
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
let mat = new UnLitMaterial();
mat.baseColor = new Color(1, 0, 0, 1);Transparency & Blend Mode
let mat = new UnLitMaterial();
mat.transparent = true;
mat.blendMode = BlendMode.ALPHA;
mat.baseColor = new Color(1, 0, 0, 0.5);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
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 frontDepth 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.
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;| Property | Type | Description |
|---|---|---|
depthWriteEnabled | boolean | Whether to write this material's fragment depth to the depth buffer |
depthCompare | GPUCompareFunction | Depth 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:
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',
};| Property | Type | Description |
|---|---|---|
stencilFront | GPUStencilFaceState | Stencil operations for front-facing triangles (compare function + three operations) |
stencilBack | GPUStencilFaceState | Stencil operations for back-facing triangles |
stencilReadMask | number | The read mask in the compare stage, default 0xFF |
stencilWriteMask | number | The write mask in the write stage, default 0xFF |
stencilRef | number | The 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:
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);TIP
Starting from v0.8, LitMaterial uses xxxMapOffsetSize to get and set the uv transform of each texture:
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');
