orillusion入门系列四 | 材质
-
如果说人类最难以满足的两个器官是舌头和眼睛,舌头费调料眼睛费显卡,你同意吗?今天继续学习如何愉悦我们视觉的技术,那就是材质(Material)。在上一期的示例已经可以创造几种规则的几何体了,但是看起来光秃秃的,这些几何体很难称之为物体,今天我们从认识材质开始,到使用材质使物体的外观丰富起来。
什么是材质(Material)
材质是由shader定义的一组特性集合,用来定义对光的处理。光对于材质来说起着决定的作用,当没有光时我们看不到任何物体,我们视觉看到的物体是由光来决定的,光照射到物体的表面会产生反射、折射,物体也会吸收一部分,当然这些反射又会照射到其它物体上又会产生反复的吸收以及折射,那么如何在计算性能和逼真度上做取舍呢,这里不做原理上的探究,只聊一聊感性上的体会。一个立方体看起来是一块砖头还是一块金锭,是由不同的材质定义的,材质可以使物体看起来更像什么,以及是否足够逼真。
材质、贴图、纹理的关系
如果只有材质还是比较好理解的,忽然面向三个相近的概念需要在理解上做个区分。
笼统的来说材质包含贴图,贴图包含纹理。材质是一个集合,内部包含了光学特性,贴图资源等等。贴图是物理的表面,可以对图片视频等效果的处理,例如拉伸,缩放,与物体的贴合参数等。纹理可以简单理解成图片视频等原材料。材质类型
材质的类型是引擎给我们提供的又一有力工具,类同于前面引擎封装的几款几何体,引擎同样为实际应用中的几种典型材质做了封装。在使用引擎开发时选择与实际需要最贴近类型的材质组件就可以,材质类型是可以自定义的,我们先从熟悉引擎提供的材质开始。
还记得前文介绍的网格(Mesh)吗,每个网格至少由几何数据和材质数据组成,不同的类型对应不同的材质类,需要对应的材质类型直接实例化该类型的材质,赋值于网络的材质属性。UnLitMaterial
这种材质可以简单称为无光材质,忽略所有的光照的计算,所以计算性能非常高,可以作为背景使用,由于没有光的影响也可以作为一些不受光影响的特殊效果。
支持的参数
这里试用几个参数,更多的参数还要看官方文档。
- baseColor(基础颜色):对材质的基础颜色进行设置,设置后覆盖了该材质的物体看起来会叠加上基础颜色。
let color:Color = new Color(); let material:UnLitMaterial = new UnLitMaterial(); material.baseColor = color;
- baseMap(基础贴图):对材质的基础贴图进行设置,设置后覆盖了该材质的物体会由该贴图覆盖
完成代码
前几次我们对一个立方体非常熟悉了,今天我们仍然创建一个立方体,但是使用UnLitMaterial材质,并覆盖一张贴图,同时可以动态的调整基础颜色。
import { Engine3D, Scene3D, Object3D, Camera3D, HoverCameraController, View3D, BoxGeometry, ForwardRenderJob, Stats, UnLitMaterial, Color } from "@orillusion/core"; import * as dat from 'dat.gui'; export default class Unlit { cameraObj: Object3D; camera: Camera3D; scene: Scene3D; boxObj: Object3D; material:UnLitMaterial; async run() { await this.init(); await this.setup(); await this.start(); } /*** * 配置并初始化引擎 */ private async init() { // 初始化引擎 await Engine3D.init(); } /** * 引擎功能代码 */ private async setup() { // 创建一个场景 this.scene = new Scene3D(); // 添加性能监控面板 this.scene.addComponent(Stats); // 创建一个相机 this.cameraObj = new Object3D(); this.camera = this.cameraObj.addComponent(Camera3D); // 设置相机类型 this.camera.perspective(60, window.innerWidth / window.innerHeight, 1, 5000.0); // 设置相机控制器 let controller = this.cameraObj.addComponent(HoverCameraController); controller.setCamera(20, -20, 25); // 添加相机至场景 this.scene.addChild(this.cameraObj); this.createBox(); this.addGUI(); } /** * 启动渲染 */ private async start() { let view = new View3D(); // 指定渲染的场景 view.scene = this.scene; // 指定使用的相机 view.camera = this.camera; // 开始渲染 Engine3D.startRenderView(view); } /** * 创建立方体 */ private async createBox() { // 创建一个对象 this.boxObj = new Object3D(); // 创建渲染组件 let mr: MeshRenderer = this.boxObj.addComponent(MeshRenderer); // 设置形状 mr.geometry = new BoxGeometry(5, 5, 5); this.material = new UnLitMaterial(); // 设置基础贴图 this.material.baseMap = await Engine3D.res.loadTexture('textures/diffuse.jpg'); // 设置材质 mr.material = this.material; // 添加到场景 this.scene.addChild(this.boxObj); } private async addGUI(){ // 创建 dat 实例 const gui = new dat.GUI(); // 创建保存属性值对象 const materialInfo = { baseColor:'#fff', }; // 设置材质颜色 gui.addColor(materialInfo, "baseColor").onChange((v) => { console.log('baseColor:', v); let color:Color = new Color(); color.setHex(v); this.material.baseColor = color; }); } }
运行效果如下:
调整颜色会看到颜色会叠加到物体上。LitMaterial(物理材质)
LitMaterial是物理材质,计算的因素多了起来,与实际效果更接近了。看到各大引擎都是主推物理材质,所以除非计算机性能实在拉垮,否则还是多熟悉使用物理材质吧,大势所趋。过去我们的例子默认也是用的这种材质。
支持参数
物理材质支持的参数比较多,这里我们只验证个别几个属性,更多的属性可以对着文档一一验证。
- baseColor(基础颜色):材质本身的颜色,可以使用一个Color类型的变量设置
- roughness(粗糙度):模拟物体表面粗糙的程度,随着数值变大物体会看起来更粗糙
- metallic(金属度):模拟物体表面金属的程度,随着数值的变化物体会看起来更光滑
使用这三个参数调节显示效果如下:
综合示例
不同的材质主要还是对光的处理不同,这里我们写一个综合的实例,将两种材质放到一个场景下,在一个聚光灯照射下观察效果,这里我们提前借用一下光源的组件,可以暂时不必在意,后续会专门介绍。
运行效果
在右侧面板我们增加了Light操作面板,可以调整光的颜色和方向,使光照分别照射在不同的立方体上,立方体分别对应着UnLitMaterial,LitMaterial二种材质,同时提供了两种材质的基本参数操作按钮,可以自行调整观察效果。完成代码
import { Engine3D, Scene3D, Object3D, Camera3D, HoverCameraController, View3D, BoxGeometry, LitMaterial, ForwardRenderJob, Color, UnLitMaterial, Vector3, MaterialBase, SpotLight, SphereGeometry, defaultTexture, GUIHelp, DirectLight } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; import * as dat from 'dat.gui'; export default class Materials { light: SpotLight; cameraObj: Object3D; camera: Camera3D; scene: Scene3D; boxObj: Object3D; litMaterial: LitMaterial; unlitMaterial: UnLitMaterial; async run() { await this.init(); await this.setup(); await this.start(); } /*** * 配置并初始化引擎 */ private async init() { // 初始化引擎 Engine3D.setting.shadow.enable = true; Engine3D.setting.shadow.debug = true; Engine3D.setting.shadow.shadowBound = 10; Engine3D.setting.shadow.shadowBias = -0.00016; Engine3D.setting.shadow.autoUpdate = true; Engine3D.setting.shadow.updateFrameRate = 1; Engine3D.setting.shadow.pointShadowBias = 3; await Engine3D.init(); // GUIHelp.init(); } /** * 引擎功能代码 */ private async setup() { // 创建一个场景 this.scene = new Scene3D(); // 添加性能监控面板 this.scene.addComponent(Stats); // 创建一个相机 this.cameraObj = new Object3D(); this.camera = this.cameraObj.addComponent(Camera3D); // 设置相机类型 this.camera.perspective(60, window.innerWidth / window.innerHeight, 1, 5000.0); // 设置相机控制器 let controller = this.cameraObj.addComponent(HoverCameraController); controller.setCamera(20, -20, 25); // 添加相机至场景 this.scene.addChild(this.cameraObj); this.litMaterial = new LitMaterial(); this.unlitMaterial = new UnLitMaterial(); this.createBox(new Vector3(-10, 0, 0), this.unlitMaterial); this.createBox(new Vector3(10, 0, 0), this.litMaterial); this.createLight(); this.createFloor(); this.addGUI(); } /** * 启动渲染 */ private async start() { let view = new View3D(); // 指定渲染的场景 view.scene = this.scene; // 指定使用的相机 view.camera = this.camera; // 开始渲染 Engine3D.startRenderView(view); } private async createLight() { let sp = new SphereGeometry(0.5, 10, 10); let lightObj = new Object3D(); lightObj.z = -20; let mr = lightObj.addComponent(MeshRenderer); mr.geometry = sp; mr.material = new LitMaterial(); this.light = lightObj.addComponent(SpotLight); this.light.intensity = 60; this.light.castShadow = true; this.scene.addChild(lightObj); lightObj.y = 10; } /** * 创建立方体 */ private async createBox(location: Vector3, matrix: MaterialBase) { // 创建一个对象 this.boxObj = new Object3D(); this.boxObj.localPosition = location; // 创建渲染组件 let mr: MeshRenderer = this.boxObj.addComponent(MeshRenderer); mr.castShadow = true; // 设置形状 mr.geometry = new BoxGeometry(5, 5, 5); // 设置材质 mr.material = matrix; // 添加到场景 this.scene.addChild(this.boxObj); } private async createFloor(){ let floor = new Object3D(); floor.y = -2.5; let mat = new LitMaterial(); mat.baseMap = defaultTexture.grayTexture; let mr = floor.addComponent(MeshRenderer); mr.receiveShadow = true; mr.geometry = new BoxGeometry(40, 1, 30); mr.material = mat; this.scene.addChild(floor); } private async addGUI() { // 创建 dat 实例 const gui = new dat.GUI(); const lightInfo = { x:0, y:0, z:0, rotationX:0, rotationY:0, rotationZ:0, lightColor:'#fff', } let lightFolder = gui.addFolder("Light"); lightFolder.add(lightInfo, "x", -10, 10).onChange((v) => { console.log('x:', v); this.light.transform.x = v; }); lightFolder.add(lightInfo, "y", -10, 10).onChange((v) => { console.log('y:', v); this.light.transform.y = v; }); lightFolder.add(lightInfo, "z", -10, 10).onChange((v) => { console.log('z:', v); this.light.transform.z = v; }); lightFolder.add(lightInfo, "rotationX", -360, 360).onChange((v) => { console.log('rotationX:', v); this.light.transform.rotationX = v; }); lightFolder.add(lightInfo, "rotationY", -360, 360).onChange((v) => { console.log('rotationY:', v); this.light.transform.rotationY = v; }); lightFolder.add(lightInfo, "rotationZ", -360, 360).onChange((v) => { console.log('rotationZ:', v); this.light.transform.rotationZ = v; }); lightFolder.addColor(lightInfo, "lightColor").onChange((v) => { console.log('lightColor:', v); let color: Color = new Color(); color.setHex(v); this.light.lightColor = color; }); // 创建保存属性值对象 const unlitInfo = { baseColor: '#fff' }; let unlitFolder = gui.addFolder("UnlitMaterial"); // 设置材质颜色 unlitFolder.addColor(unlitInfo, "baseColor").onChange((v) => { console.log('baseColor:', v); let color: Color = new Color(); color.setHex(v); this.unlitMaterial.baseColor = color; }); // 创建保存属性值对象 const litInfo = { baseColor: '#fff', roughness: 0.01, metallic: 0.01, }; let hdrFolder = gui.addFolder("LitMaterial"); // 设置材质颜色 hdrFolder.addColor(litInfo, "baseColor").onChange((v) => { console.log('baseColor:', v); let color: Color = new Color(); color.setHex(v); this.litMaterial.baseColor = color; }); // 材质粗糙程度 hdrFolder.add(litInfo, "roughness", 0, 1).onChange((v) => { console.log('roughness:', v); this.litMaterial.roughness = v; }); // 材质粗糙程度 hdrFolder.add(litInfo, "metallic", 0, 1).onChange((v) => { console.log('metallic:', v); this.litMaterial.metallic = v; }); } }
小结
继几何体后,引擎对常用材质做了封装,使用户可以不必自己编写shader,创建一个对应的类,设置参数就可以得到一个可用的材质,用来丰富物体。材质的种类还有一些,不过当前只发现了三种,期待后续会有更多的材质可以使用。
作为3D新手,后续会不断的记录学习过程,期待与你一起学习一起飞!