材质

上次修改时间:2021-07-19 15:56:50

材质分类

PBRMaterial

引擎和编辑器全面提倡使用 PBR 材质 PBRMaterial 。PBR 全称是 Physically Based Rendering,中文意思是基于物理的渲染,最早由迪士尼在 2012 年提出,后来被游戏界广泛使用。跟传统的 Blinn-Phong 等渲染方法相比,PBR 遵循能量守恒,符合物理规则,美术们只需要调整几个简单的参数,即使在复杂的场景中也能保证正确的渲染效果。

/**
 * @title PBR Helmet
 * @category Material
 */
import { OrbitControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import {
  AssetType,
  BackgroundMode,
  Camera,
  Color,
  DiffuseMode,
  DirectLight,
  GLTFResource,
  PrimitiveMesh,
  SkyBoxMaterial,
  TextureCubeMap,
  Vector3,
  WebGLEngine
} from "oasis-engine";

//-- create engine object
let engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

let scene = engine.sceneManager.activeScene;
const { ambientLight, background } = scene;
const rootEntity = scene.createRootEntity();

const color2glColor = (color) => new Color(color[0] / 255, color[1] / 255, color[2] / 255);
const glColor2Color = (color) => new Color(color[0] * 255, color[1] * 255, color[2] * 255);
const gui = new dat.GUI();
gui.domElement.style = "position:absolute;top:0px;left:50vw";

let envFolder = gui.addFolder("EnvironmentMapLight");
envFolder.add(ambientLight, "specularIntensity", 0, 1);
envFolder.add(ambientLight, "diffuseIntensity", 0, 1);

let directLightColor = { color: [255, 255, 255] };
let directLightNode = rootEntity.createChild("dir_light");
let directLight = directLightNode.addComponent(DirectLight);
directLight.color = new Color(1, 1, 1);
let dirFolder = gui.addFolder("DirectionalLight1");
dirFolder.add(directLight, "enabled");
dirFolder.addColor(directLightColor, "color").onChange((v) => (directLight.color = color2glColor(v)));
dirFolder.add(directLight, "intensity", 0, 1);

//Create camera
let cameraNode = rootEntity.createChild("camera_node");
cameraNode.transform.position = new Vector3(0, 0, 5);
cameraNode.addComponent(Camera);
cameraNode.addComponent(OrbitControl);

// Create sky
const sky = background.sky;
const skyMaterial = new SkyBoxMaterial(engine);
background.mode = BackgroundMode.Sky;
sky.material = skyMaterial;
sky.mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1);

Promise.all([
  engine.resourceManager
    .load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/150e44f6-7810-4c45-8029-3575d36aff30.gltf")
    .then((gltf) => {
      rootEntity.addChild(gltf.defaultSceneRoot);
      console.log(gltf);
    }),
  engine.resourceManager
    .load<TextureCubeMap>({
      urls: [
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*Bk5FQKGOir4AAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*_cPhR7JMDjkAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*trqjQp1nOMQAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*_RXwRqwMK3EAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*q4Q6TroyuXcAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*DP5QTbTSAYgAAAAAAAAAAAAAARQnAQ"
      ],
      type: AssetType.TextureCube
    })
    .then((cubeMap) => {
      ambientLight.diffuseMode = DiffuseMode.Texture;
      ambientLight.diffuseTexture = cubeMap;
    }),
  engine.resourceManager
    .load<TextureCubeMap>({
      urls: [
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*5w6_Rr6ML6IAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*TiT2TbN5cG4AAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*8GF6Q4LZefUAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*D5pdRqUHC3IAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*_FooTIp6pNIAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*CYGZR7ogZfoAAAAAAAAAAAAAARQnAQ"
      ],
      type: AssetType.TextureCube
    })
    .then((cubeMap) => {
      ambientLight.specularTexture = cubeMap;
      skyMaterial.textureCubeMap = cubeMap;
    })
]).then(() => {
  engine.run();
});

引擎提供了 金属-粗糙度/高光-光泽度 两种工作流,分别对应 PBRMaterialPBRSpecularMaterial

/**
 * @title PBR Base
 * @category Material
 */
import { OrbitControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import {
  AssetType,
  BackgroundMode,
  Camera,
  Color,
  DiffuseMode,
  DirectLight,
  GLTFResource,
  PrimitiveMesh,
  SkyBoxMaterial,
  TextureCubeMap,
  Vector3,
  WebGLEngine
} from "oasis-engine";

//-- create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const { ambientLight, background } = scene;
const rootEntity = scene.createRootEntity();

const color2glColor = (color) => new Color(color[0] / 255, color[1] / 255, color[2] / 255);
const gui = new dat.GUI();

const envFolder = gui.addFolder("EnvironmentMapLight");
envFolder.add(ambientLight, "specularIntensity", 0, 1);
envFolder.add(ambientLight, "diffuseIntensity", 0, 1);

const directLightColor = { color: [255, 255, 255] };
const directLightNode = rootEntity.createChild("dir_light");
const directLight = directLightNode.addComponent(DirectLight);
directLight.color = new Color(1, 1, 1);
const dirFolder = gui.addFolder("DirectionalLight1");
dirFolder.add(directLight, "enabled");
dirFolder.addColor(directLightColor, "color").onChange((v) => (directLight.color = color2glColor(v)));
dirFolder.add(directLight, "intensity", 0, 1);

//Create camera
const cameraNode = rootEntity.createChild("camera_node");
cameraNode.transform.position = new Vector3(0.25, 0.5, 1.5);
cameraNode.addComponent(Camera);
const control = cameraNode.addComponent(OrbitControl);
control.target.setValue(0.25, 0.25, 0);

// Create sky
const sky = background.sky;
const skyMaterial = new SkyBoxMaterial(engine);
background.mode = BackgroundMode.Sky;
sky.material = skyMaterial;
sky.mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1);

Promise.all([
  engine.resourceManager
    .load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/dda73ec2-6921-42c7-b109-b5cd386f4410.glb")
    .then((gltf) => {
      rootEntity.addChild(gltf.defaultSceneRoot);
      gltf.defaultSceneRoot.transform.setScale(100, 100, 100);
      console.log(gltf);
    }),
  engine.resourceManager
    .load<TextureCubeMap>({
      urls: [
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*Bk5FQKGOir4AAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*_cPhR7JMDjkAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*trqjQp1nOMQAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*_RXwRqwMK3EAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*q4Q6TroyuXcAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*DP5QTbTSAYgAAAAAAAAAAAAAARQnAQ"
      ],
      type: AssetType.TextureCube
    })
    .then((cubeMap) => {
      ambientLight.diffuseMode = DiffuseMode.Texture;
      ambientLight.diffuseTexture = cubeMap;
    }),
  engine.resourceManager
    .load<TextureCubeMap>({
      urls: [
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*5bs-Sb80qcUAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*rLUCT4VPBeEAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*LjSHTI5iSPoAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*pgCvTJ85RUYAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*0BKxR6jgRDAAAAAAAAAAAAAAARQnAQ",
        "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*Pir4RoxLm3EAAAAAAAAAAAAAARQnAQ"
      ],
      type: AssetType.TextureCube
    })
    .then((cubeMap) => {
      ambientLight.specularTexture = cubeMap;
      skyMaterial.textureCubeMap = cubeMap;
    })
]).then(() => {
  engine.run();
});

通用参数介绍

参数应用
baseColor基础颜色。基础颜色 * 基础颜色纹理最后的基础颜色。基础颜色是物体的反照率值,与传统的漫反射颜色不同,它会同时贡献镜面反射和漫反射的颜色,我们可以通过上面提到过的金属度、粗糙度,来控制贡献比。
emissiveColor自发光颜色。使得即使没有光照也能渲染出颜色。
opacity透明度。当设置为透明模式后,可以通过透明度来调整透明度。
baseTexture基础颜色纹理。搭配基础颜色使用,是个相乘的关系。
opacityTexture透明度纹理。搭配透明度使用,是相乘的关系,注意透明度模式的切换。
normalTexture法线纹理。可以设置法线纹理 ,在视觉上造成一种凹凸感,还可以通过法线强度来控制凹凸程度。
emissiveTexture自发射光纹理。我们可以设置自发光纹理和自发光颜色(emissiveFactor)达到自发光的效果,即使没有光照也能渲染出颜色。
occlusionTexture阴影遮蔽纹理。我们可以设置阴影遮蔽纹理来提升物体的阴影细节。
tilingOffset纹理坐标的缩放与偏移。是一个 Vector4 数据,分别控制纹理坐标在 uv 方向上的缩放和偏移。

tilingOffset 案例:

/**
 * @title Tiling Offset
 * @category Material
 */
import { OrbitControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import {
  AssetType,
  Camera,
  MeshRenderer,
  PrimitiveMesh,
  RenderFace,
  Script,
  Texture2D,
  UnlitMaterial,
  Vector3,
  WebGLEngine
} from "oasis-engine";

init();

function init(): void {
  // Create engine object
  const engine = new WebGLEngine("canvas");
  engine.canvas.resizeByClientSize();

  // Load texture
  engine.resourceManager
    .load<Texture2D>({
      url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*Umw_RJGiZLYAAAAAAAAAAAAAARQnAQ",
      type: AssetType.Texture2D
    })
    .then((texture) => {
      const scene = engine.sceneManager.activeScene;
      const rootEntity = scene.createRootEntity();

      // Create camera
      const cameraEntity = rootEntity.createChild("Camera");
      cameraEntity.transform.position = new Vector3(0, 0, 20);
      cameraEntity.addComponent(Camera);
      cameraEntity.addComponent(OrbitControl);

      // Create plane
      const entity = rootEntity.createChild();
      const renderer = entity.addComponent(MeshRenderer);
      const mesh = PrimitiveMesh.createPlane(engine, 10, 10);
      const material = new UnlitMaterial(engine);

      texture.anisoLevel = 16;
      material.renderFace = RenderFace.Double;
      material.baseTexture = texture;

      renderer.mesh = mesh;
      renderer.setMaterial(material);

      // Add animation script
      const animationScript = rootEntity.addComponent(AnimateScript);

      // Add data GUI
      const guiData = addDataGUI(material, animationScript);
      animationScript.guiData = guiData;
      animationScript.material = material;

      // Run engine
      engine.run();
    });
}

/**
 * Add data GUI.
 */
function addDataGUI(material: UnlitMaterial, animationScript: AnimateScript): any {
  const gui = new dat.GUI();
  const guiData = {
    tilingX: 1,
    tilingY: 1,
    offsetX: 0,
    offsetY: 0,
    reset: function () {
      guiData.tilingX = 1;
      guiData.tilingY = 1;
      guiData.offsetX = 0;
      guiData.offsetY = 0;
      material.tilingOffset.setValue(1, 1, 0, 0);
    },
    pause: function () {
      animationScript.enabled = false;
    },
    resume: function () {
      animationScript.enabled = true;
    }
  };

  gui
    .add(guiData, "tilingX", 0, 10)
    .onChange((value: number) => {
      material.tilingOffset.x = value;
    })
    .listen();
  gui
    .add(guiData, "tilingY", 0, 10)
    .onChange((value: number) => {
      material.tilingOffset.y = value;
    })
    .listen();
  gui
    .add(guiData, "offsetX", 0, 1)
    .onChange((value: number) => {
      material.tilingOffset.z = value;
    })
    .listen();
  gui
    .add(guiData, "offsetY", 0, 1)
    .onChange((value: number) => {
      material.tilingOffset.w = value;
    })
    .listen();
  gui.add(guiData, "reset").name("重置");
  gui.add(guiData, "pause").name("暂停动画");
  gui.add(guiData, "resume").name("继续动画");

  return guiData;
}

/**
 * Animation script.
 */
class AnimateScript extends Script {
  guiData: any;

  /**
   * The main loop, called frame by frame.
   * @param deltaTime - The deltaTime when the script update.
   */
  onUpdate(deltaTime: number): void {
    const { material, guiData } = this;
    material.tilingOffset.x = guiData.tilingX = ((guiData.tilingX - 1 + deltaTime * 0.001) % 9) + 1;
    material.tilingOffset.y = guiData.tilingY = ((guiData.tilingY - 1 + deltaTime * 0.001) % 9) + 1;
  }
}

金属-粗糙度模式 参数介绍

参数应用
metallicFactor金属度。模拟材质的金属程度,金属值越大,镜面反射越强,即能反射更多周边环境。
roughnessFactor粗糙度。模拟材质的粗糙程度,粗糙度越大,微表面越不平坦,镜面反射越模糊。
metallicRoughnessTexture金属粗糙度纹理。搭配金属粗糙度使用,是相乘的关系。
metallicTexture金属度纹理。搭配金属度使用,是相乘的关系。
roughnessTexture粗糙度纹理。搭配粗糙度使用,是相乘的关系。

高光-光泽度 参数介绍

参数应用
specularColor高光度。不同于金属粗糙度工作流的根据金属度和基础颜色计算镜面反射,而是直接使用高光度来表示镜面反射颜色。(注,只有关闭金属粗糙工作流才生效)
glossinessFactor光泽度。模拟光滑程度,与粗糙度相反。(注,只有关闭金属粗糙工作流才生效)
specularGlossinessTexture高光光泽度纹理。搭配高光光泽度使用,是相乘的关系。

:如果您使用了 PBR 材质,千万别忘了开启环境光的 IBL 模式~只有添加了之后,属于 PBR 的金属粗糙度、镜面反射、物理守恒、全局光照才会展现出效果。

BlinnPhongMaterial

BlinnPhongMaterial 虽然不是基于物理渲染,但是其高效的渲染算法和基本齐全的光学部分,还是有很多的应用场景。

常用参数介绍

参数应用
baseColor基础颜色。 基础颜色 * 基础纹理 = 最后的基础颜色。
baseTexture基础纹理。搭配基础颜色使用,是个相乘的关系。
specularColor镜面反射颜色。镜面反射颜色 * 镜面反射纹理 = 最后的镜面反射颜色。
specularTexture镜面反射纹理。搭配镜面反射颜色使用,是个相乘的关系。
normalTexture法线纹理。可以设置法线纹理 ,在视觉上造成一种凹凸感,还可以通过法线强度来控制凹凸程度。
normalIntensity 法线强度。法线强度,用来控制凹凸程度。
emissiveColor自发光颜色。自发光颜色 * 自发光纹理 = 最后的自发光颜色。即使没有光照也能渲染出颜色。
emissiveTexture自发光纹理。搭配自发光颜色使用,是个相乘的关系。
shininess镜面反射系数。值越大镜面反射效果越聚拢。
tilingOffset纹理坐标的缩放与偏移。是一个 Vector4 数据,分别控制纹理坐标在 uv 方向上的缩放和偏移。
/**
 * @title Blinn Phong Material
 * @category Material
 */
import { OrbitControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import {
  AssetType,
  BlinnPhongMaterial,
  Camera,
  DirectLight,
  GLTFResource,
  MeshRenderer,
  RenderFace,
  Texture2D,
  Vector3,
  WebGLEngine
} from "oasis-engine";
const gui = new dat.GUI();

// Create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.setPosition(0, 10, 30);
cameraEntity.addComponent(Camera);
const control = cameraEntity.addComponent(OrbitControl);
control.target.y = 5;

// Create Direct Light
const light1 = rootEntity.createChild();
const light2 = rootEntity.createChild();
light1.transform.lookAt(new Vector3(-1, -1, -1));
light2.transform.lookAt(new Vector3(1, 1, 1));
light1.addComponent(DirectLight);
light2.addComponent(DirectLight);

engine.run();

engine.resourceManager
  .load([
    {
      type: AssetType.Texture2D,
      url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*g_HIRqQdNUcAAAAAAAAAAAAAARQnAQ"
    },
    {
      type: AssetType.Texture2D,
      url: "https://gw.alipayobjects.com/mdn/rms_7c464e/afts/img/A*H7nMRY2SuWcAAAAAAAAAAAAAARQnAQ"
    },
    {
      type: AssetType.Perfab,
      url: "https://gw.alipayobjects.com/os/bmw-prod/72a8e335-01da-4234-9e81-5f8b56464044.gltf"
    }
  ])
  .then((res) => {
    const baseTexture = res[0] as Texture2D;
    const normalTexture = res[1] as Texture2D;
    const gltf = res[2] as GLTFResource;

    const { defaultSceneRoot } = gltf;
    const rendererArray = new Array<MeshRenderer>();
    const materials = new Array<BlinnPhongMaterial>();

    rootEntity.addChild(defaultSceneRoot);
    defaultSceneRoot.getComponentsIncludeChildren(MeshRenderer, rendererArray);

    rendererArray.forEach((renderer) => {
      const material = new BlinnPhongMaterial(engine);
      material.baseTexture = baseTexture;
      material.normalTexture = normalTexture;
      material.shininess = 64;
      material.renderFace = RenderFace.Double;
      renderer.setMaterial(material);
      materials.push(material);
    });

    addGUI(materials);
  });

function addGUI(materials: BlinnPhongMaterial[]): void {
  const state = {
    baseColor: [255, 255, 255],
    specularColor: [255, 255, 255],
    shininess: 64,
    normalIntensity: 1,
    isTransparent: false,
    opacity: 1
  };

  gui.addColor(state, "baseColor").onChange((v) => {
    materials.forEach((material) => {
      material.baseColor.setValue(v[0] / 255, v[1] / 255, v[2] / 255, state.opacity);
    });
  });

  gui.addColor(state, "specularColor").onChange((v) => {
    materials.forEach((material) => {
      material.specularColor.setValue(v[0] / 255, v[1] / 255, v[2] / 255, 1);
    });
  });
  gui.add(state, "shininess", 0, 100).onChange((v) => {
    materials.forEach((material) => {
      material.shininess = v;
    });
  });
  gui.add(state, "normalIntensity", 0, 1, 0.1).onChange((v) => {
    materials.forEach((material) => {
      material.normalIntensity = v;
    });
  });
  gui.add(state, "isTransparent").onChange((v) => {
    materials.forEach((material) => {
      material.isTransparent = v;
    });
  });
  gui.add(state, "opacity", 0, 1, 0.1).onChange((v) => {
    materials.forEach((material) => {
      material.baseColor.a = v;
    });
  });
}

UnlitMaterial

在一些简单的场景中,可能不希望计算光照,引擎提供了 UnlitMaterial,使用了最精简的 shader 代码,只需要提供颜色或者纹理即可渲染。

参数应用
baseColor基础颜色。基础颜色 * 基础颜色纹理 = 最后的颜色。
baseTexture基础纹理。搭配基础颜色使用,是个相乘的关系。
tilingOffset纹理坐标的缩放与偏移。是一个 Vector4 数据,分别控制纹理坐标在 uv 方向上的缩放和偏移。
/**
 * @title Unlit Material
 * @category Material
 */
import { OrbitControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import { Animation, Camera, GLTFResource, UnlitMaterial, Vector3, WebGLEngine } from "oasis-engine";
const gui = new dat.GUI();

// Create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.position = new Vector3(0, 0, 5);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

engine.run();

engine.resourceManager
  .load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/8d36415b-5905-461f-9336-68a23d41518e.gltf")
  .then((gltf) => {
    const { materials, animations, defaultSceneRoot } = gltf;
    rootEntity.addChild(defaultSceneRoot);

    const animator = defaultSceneRoot.getComponent(Animation);
    animator.playAnimationClip(animations[0].name);
    addGUI(materials as UnlitMaterial[]);
  });

function addGUI(materials: UnlitMaterial[]) {
  const state = {
    baseColor: [255, 255, 255]
  };

  gui.addColor(state, "baseColor").onChange((v) => {
    materials.forEach((material) => {
      material.baseColor.setValue(v[0] / 255, v[1] / 255, v[2] / 255, 1);
    });
  });
}

如何使用材质

用户在 Unity、3ds Max、C4D、Blender 等建模软件调试后可以输出 glTF 文件,GLTF文件里面包含了场景、模型实体、纹理、动画、材质等资源,Oasis 支持使用资源管理器加载解析这个 glTF 文件,解析后模型已经自动赋予了对应的材质,我们也可以拿到模型的材质,进行一些后期加工,比如修改颜色。

// 获取想要修改的 renderer
const renderer = entity.getComponent(MeshRenderer);
// 通过 `getMaterial` 获取当前 renderer 的第 i 个材质, 默认第 0 个。
const material = renderer.getMaterial();
// 修改材质颜色
material.baseColor.r = 0;

我们也可以直接替换材质类型,比如将模型重新赋予一个 blinn-phong 材质:

// 获取想要修改的 renderer
const renderer = entity.getComponent(MeshRenderer);

// 创建 blinn-phong 材质
const material = new BlinnPhongMaterial(engine);
material.baseColor.r = 0;

// 通过 `setMaterial` 设置当前 renderer 的第 i 个材质, 默认第 0 个。
const material = renderer.setMaterial(material);

材质通用属性

以下属性都可以直接在 UnlitMaterialBlinnPhongMaterialPBRMaterialPBRSpecularMaterial 材质中使用。

参数应用
isTransparent是否透明。可以设置材质是否透明。如果设置为透明,可以通过 BlendMode 来设置颜色混合模式。
alphaCutoff透明度裁剪值。可以设置裁剪值,来指定在着色器中,裁剪透明度小于此数值的片元。
renderFace渲染面。可以决定渲染正面、背面、双面。
blendMode颜色混合模式。当设置材质为透明后,可以设置此枚举来决定颜色混合模式。
/**
 * @title Blend Mode
 * @category Material
 */
import { OrbitControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import { Camera, GLTFResource, PBRMaterial, Vector3, WebGLEngine } from "oasis-engine";
const gui = new dat.GUI();

// Create engine object
const engine = new WebGLEngine("canvas");
engine.canvas.resizeByClientSize();

const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// Create camera
const cameraEntity = rootEntity.createChild("Camera");
cameraEntity.transform.position = new Vector3(0, 3, 10);
cameraEntity.addComponent(Camera);
cameraEntity.addComponent(OrbitControl);

scene.ambientLight.diffuseSolidColor.setValue(1, 1, 1, 1);

engine.resourceManager
  .load<GLTFResource>("https://gw.alipayobjects.com/os/bmw-prod/d099b30b-59a3-42e4-99eb-b158afa8e65d.glb")
  .then((asset) => {
    const { defaultSceneRoot, materials } = asset;
    rootEntity.addChild(defaultSceneRoot);

    const state = {
      alphaCutoff: 0,
      isTransparent: false,
      opacity: 0
    };

    // Do not debug first material
    const debugMaterials = materials.slice(1);
    gui.add(state, "alphaCutoff", 0, 1, 0.01).onChange((v) => {
      debugMaterials.forEach((material) => {
        (material as PBRMaterial).alphaCutoff = v;
      });
    });

    gui.add(state, "isTransparent").onChange((v) => {
      debugMaterials.forEach((material) => {
        (material as PBRMaterial).isTransparent = v;
      });
    });

    gui.add(state, "opacity", 0, 1, 0.01).onChange((v) => {
      debugMaterials.forEach((material) => {
        (material as PBRMaterial).baseColor.a = v;
      });
    });
  });

engine.run();

常见 QA

1. 透明渲染异常?

  • 请先确保材质开启了透明度模式,即材质的 isTransparent 属性设置为了 true
  • 相应的材质的 baseColor 需要设置正确的透明度。如 material.baseColor.a = 0.5。透明度范围为 【0~1】,数值越小,越透明。
  • 如果还上传了透明度纹理,请先确保透明纹理是否含有透明通道,即是正常的 png 图片,如果不是的话,可以开启 getOpacityFromRGB 为 true 代表希望采样亮度值作为透明度。
  • 如果透明度渲染仍有异常,请确保材质的颜色混合度模式(blendMode)为期望的组合。
  • 有一些透明度渲染异常可能是因为没有关闭背面剔除,可以通过 renderFace 来设置想要渲染的面。
  • 如果是自定义材质,请确保设置了正确的混合模式,混合因子,关闭了深度写入,设置了正确的渲染队列。

2. 为什么模型背面没有渲染?

  • 请确保关闭背面了剔除,可以通过 RasterState.cullMode 来设置,也可以通过材质内置的 renderFace 来设置想要渲染的面。

3. 一般需要打几个光?

  • 一般场景只需要使用默认的环境光(AmbientLight)就可以了,它可以支持基于图片的照明实现直接光照和间接光照,也可以拥有普通的颜色叠加。
  • 如果 环境光 无法满足需求,可以适当添加方向光(DirectLight)和点光源(PointLight)来补充光照细节。
  • 出于性能考虑,尽量不要超过 4 个直接光 。

4. 为什么渲染的不够立体?