Skip to content

材质

材质总览

引擎通过 MeshRenderer 组件给物体添加相应的 材质 对象,用来模拟其颜色、光照、纹理等属性。目前引擎内置了2种经典材质模型:

名称描述
UnLitMaterial不计算光照和阴影,仅渲染原始颜色与纹理信息
LitMaterialPBR 即基于物理渲染,旨在模拟现实世界光照效果

我们推荐使用的 glTF 格式文件包含了物体材质信息。用户可以通过常用建模软件设计并修改模型文件。引擎解析模型文件后,自动赋予物体相应材质。

基础用法

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

let object = new Object3D();
// 添加 MeshRenderer 组件
let mesh = object.addComponent(MeshRenderer);

// 设置组件 material
mesh.material = new UnLitMaterial();
mesh.material.baseColor = new Color(1, 1, 1, 1);

// 切换 Material
mesh.material = new LitMaterial();

Unlit 材质

引擎提供了 UnLitMaterial 材质,紧渲染物理基础颜色,不计算光照和阴影。

属性描述
baseColor基础颜色
baseMap基础贴图

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 材质

我们提倡使用 LitMaterialPBR 材质进行渲染。PBRPhysically-Based Rendering 的缩写,是指基于物理渲染的材质,它更符合真实的物理光照模型。

属性描述
baseColor材质基础颜色
emissiveColor材质发光颜色
emissiveIntensity材质发光强度,需要开启 BloomPost 才能显示发光效果
roughness材质粗糙程度
metallic材料金属度
normalScale法线贴图对材质的影响程度
clearCoat透明涂层强度
envMap环境贴图
envIntensity环境光强度
materialF0材质反射率
aoAmbient Occlussion, 处理环境光对物体遮挡的效果
aoMap环境光遮挡贴图
baseMap基础贴图
normalMap法线贴图
maskMap遮罩贴图
emissiveMap材质发光贴图
brdfLUTBRDF查找表

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();

材质设置

颜色

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

透明度 & 混合模式

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

混合模式详情请参考 BlendMode

切换剔除模式

为了节省 GPU 性能,Orillusion 默认使用 back 剔除模式,即只渲染物体正面材质,我们可以设置材质 cullMode 属性来切换显示模式

ts
let mat = new LitMaterial();
mat.cullMode = GPUCullMode.none; // 不剔除,双面显示
mat.cullMode = GPUCullMode.front; // 剔除正面,显示后面
mat.cullMode = GPUCullMode.back; // 剔除后面,显示正面

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

深度状态

材质可以控制自身参与深度缓冲(Depth Buffer)的方式,常用于处理透明物体排序、避免 Z-fighting、实现"始终可见"的叠加层等。

ts
let mat = new LitMaterial();

// 是否写入深度缓冲。透明物体通常关闭,避免互相遮挡导致排序错误
mat.depthWriteEnabled = false;

// 深度比较函数(GPUCompareFunction)。默认 'less-equal'
// 例如设为 'always' 让物体始终通过深度测试(叠加层 / 描边)
mat.depthCompare = 'always' as GPUCompareFunction;
属性类型说明
depthWriteEnabledboolean是否把本材质的片元深度写入深度缓冲
depthCompareGPUCompareFunction深度比较函数,如 'less''less-equal''greater''always'

模版缓冲(Stencil)

版本说明

模版缓冲 API 在 @orillusion/core 0.9.0 引入,可用于实现轮廓描边、镜面遮罩、投影贴花等需要"标记区域再二次绘制"的效果。

模版测试通过一个额外的 8 位缓冲区,对每个片元做"标记—比较—决定是否绘制"的操作。Material 暴露了完整的模版状态:

ts
let mat = new LitMaterial();

// 参考值与读写掩码
mat.stencilRef = 1;          // 比较 / 写入时使用的参考值
mat.stencilReadMask = 0xFF;  // 比较时与缓冲值做与运算的掩码
mat.stencilWriteMask = 0xFF; // 写入时允许修改的位

// 正面 / 背面的模版操作(GPUStencilFaceState)
mat.stencilFront = {
    compare: 'always',     // 比较函数:总是通过
    failOp: 'keep',        // 模版测试失败时
    depthFailOp: 'keep',   // 模版通过但深度测试失败时
    passOp: 'replace',     // 全部通过时:用 stencilRef 替换缓冲值(写入标记)
};
mat.stencilBack = {
    compare: 'always',
    failOp: 'keep',
    depthFailOp: 'keep',
    passOp: 'keep',
};
属性类型说明
stencilFrontGPUStencilFaceState正面三角形的模版操作(比较函数 + 三种操作)
stencilBackGPUStencilFaceState背面三角形的模版操作
stencilReadMasknumber比较阶段的读掩码,默认 0xFF
stencilWriteMasknumber写入阶段的写掩码,默认 0xFF
stencilRefnumber模版参考值,默认 0

典型的两遍法(先标记、再用标记限制绘制区域):第一遍材质用 passOp: 'replace' 把覆盖区域写成 stencilRef;第二遍材质用 compare: 'equal'passOp: 'keep',只在被标记的区域绘制。

引擎内置的投影贴花等效果即基于模版缓冲实现,一般无需手动配置。

UV 变换

模型顶点会存有多组纹理映射坐标,定义了该顶点在纹理中对应的2D坐标,它通常由一个二维变量 (u,v) 表示,所以也被称为 UV坐标
我们可以通过对模型的 uv 坐标进行矩阵变换来自定义纹理的映射关系。比如,设置材质shader 中的 transformUV1 变量来对原始 uv 进行 位移(offset)缩放(scaling) 的坐标变换:

ts
let mat = new LitMaterial();
// 获取 uv - Vector4(offsetU, offsetV, scalingU, scalingV)
let uv: Vector4 = mat.getUniformV4(`transformUV1`);
// 设置 uv
uv.set(1, 1, 2, 2);
// 更新 uv
mat.setUniformVector4(`transformUV1`, uv);

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

TIP

v0.8 开始,LitMaterial 使用 xxxMapOffsetSize 获取和设置各个贴图的 uv 变换:

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');