相机

上次修改时间:2021-07-03 18:41:44

相机是一个图形引擎对 3D 投影的抽象概念,作用好比现实世界中的摄像机或眼睛。Oasis Engine 的相机实现了自动视锥剔除,只渲染视锥体内的物体。

/**
 * @title Renderer Cull
 * @category Camera
 */
import { FreeControl } from "@oasis-engine/controls";
import * as dat from "dat.gui";
import {
  BlinnPhongMaterial,
  Camera,
  Color,
  DirectLight,
  MeshRenderer,
  PrimitiveMesh,
  Script,
  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_entity");
cameraEntity.transform.position = new Vector3(0, 0, 50);
cameraEntity.addComponent(Camera);
const control = cameraEntity.addComponent(FreeControl);
control.movementSpeed = 50;

engine.run();

// create two renderer
const cube = rootEntity.createChild("cube1");
const cube2 = rootEntity.createChild("cube2");
cube.transform.position = new Vector3(-10, 0, 0);
cube2.transform.position = new Vector3(10, 0, 0);

const lightNode = rootEntity.createChild("Light");
lightNode.transform.setRotation(-45, 0, 0);
lightNode.addComponent(DirectLight);

const material = new BlinnPhongMaterial(engine);
material.baseColor = new Color(1, 0, 0, 1);
const material2 = new BlinnPhongMaterial(engine);
material2.baseColor = new Color(0, 0, 1, 1);
const geometry = PrimitiveMesh.createCuboid(engine, 5, 5, 5);
const sphereGeometry = PrimitiveMesh.createSphere(engine, 5);

const cubeRenderer = cube.addComponent(MeshRenderer);
const cubeRenderer2 = cube2.addComponent(MeshRenderer);

cubeRenderer.mesh = geometry;
cubeRenderer.setMaterial(material);

cubeRenderer2.mesh = sphereGeometry;
cubeRenderer2.setMaterial(material2);

// rotate
class RotationScript extends Script {
  onUpdate() {
    this.entity.transform.rotate(1, 1, 1);
  }
}
cube.addComponent(RotationScript);
cube2.addComponent(RotationScript);

// observe renderer-cull
const state = {
  cube1: "正常渲染",
  cube2: "正常渲染"
};

class ObserverScript extends Script {
  onUpdate() {
    state.cube1 = cubeRenderer.isCulled ? "视锥体裁剪" : "正常渲染";
    state.cube2 = cubeRenderer2.isCulled ? "视锥体裁剪" : "正常渲染";
  }
}

rootEntity.addComponent(ObserverScript);

const folder = gui.addFolder("移动视角,观察视锥体裁剪情况");
folder.add(state, "cube1").name("红色立方体").listen();
folder.add(state, "cube2").name("蓝色球体").listen();
folder.open();

基本用法

// 创建实体
const entity = root.createChild('cameraEntity');
// 创建相机组件
const camera = entity.addComponent(Camera);

// 设置透视投影属性
camera.nearClipPlane = 0.1;
camera.farClipPlane = 100;
camera.fieldOfView = 60;

// 通过 entity 获取相机
entity.engine.sceneManager.activeScene._activeCameras[0]

属性

类型属性解释
通用isOrthographic是否正交投影,默认是 false
aspectRatio画布宽高比,一般是根据 canvas 大小自动计算,也可以手动改变(不推荐)
cullingMask裁剪遮罩,用来选择性地渲染场景中的渲染组件。
透视投影nearClipPlane近裁剪平面
farClipPlane远裁剪平面
fieldOfView视角
正交投影orthographicSize正交模式下相机的一半尺寸

cullingMask 案例:

/**
 * @title Culling Mask
 * @category Camera
 */
import * as dat from "dat.gui";
import * as o3 from "oasis-engine";
import { DirectLight, Logger } from "oasis-engine";

Logger.enable();
const engine = new o3.WebGLEngine("canvas");
engine.canvas.resizeByClientSize();
const rootEntity = engine.sceneManager.activeScene.createRootEntity();

// init camera
const cameraEntity = rootEntity.createChild("camera");
const camera = cameraEntity.addComponent(o3.Camera);
const pos = cameraEntity.transform.position;
pos.setValue(10, 10, 10);
cameraEntity.transform.position = pos;
cameraEntity.transform.lookAt(new o3.Vector3(0, 0, 0));

const lightNode = rootEntity.createChild("Light");
lightNode.transform.setRotation(-30, 0, 0);
lightNode.addComponent(DirectLight);

// init cube
const cubeEntity = rootEntity.createChild("cube");
const renderer = cubeEntity.addComponent(o3.MeshRenderer);
renderer.mesh = o3.PrimitiveMesh.createCuboid(engine, 1, 1, 1);
const material = new o3.BlinnPhongMaterial(engine);
material.baseColor = new o3.Color(1, 0.25, 0.25, 1);
renderer.setMaterial(material);

engine.run();

function addGUI() {
  const gui = new dat.GUI();
  const cameraFolder = gui.addFolder("camera cullingMask");
  cameraFolder.open();
  const constMap = {
    EveryThing: o3.Layer.Everything,
    Layer1: o3.Layer.Layer1,
    Layer2: o3.Layer.Layer2,
    Layer3: o3.Layer.Layer3
  };
  const cameraController = cameraFolder.add({ cullingMask: "EveryThing" }, "cullingMask", Object.keys(constMap));
  cameraController.onChange((v) => {
    camera.cullingMask = constMap[v];
  });

  const boxFolder = gui.addFolder("box layer");
  boxFolder.open();
  const boxController = boxFolder.add({ layer: "EveryThing" }, "layer", Object.keys(constMap));
  boxController.onChange((v) => {
    renderer.entity.layer = constMap[v];
  });
}

addGUI();

类型

通过设置 isOrthographic 来决定采用透视投影或正交投影。

透视投影

透视投影符合我们的近大远小模型,可以看一下透视模型示意图:

image.png

根据上图可以看出,近裁剪平面(nearClipPlane),远裁剪平面(farClipPlane)和 视角(fieldOfView) 会形成一个视椎体 (View Frustum)。在视椎体内部的物体是会被投影到摄像机里的,也就是会渲染在画布上,而视椎体外的物体则会被裁剪。

正交投影

正交投影就是可视区近处和远处看到的物体是等大小的。由正交投影模型产生的可视区称为盒状可视区,盒状可视区模型如下:

image.png

如上图所示,有 top、bottom、left 和 right,Oasis 对正交属性做了一些简化,更符合开发者的使用习惯,只有 orthographicSize(正交模式下相机的一半尺寸)。下面是针对各项属性和 orthographicSize 的关系

  • top = orthographicSize
  • bottom = -orthographicSize
  • right = orthographicSize * aspectRatio
  • left = -orthographicSize * aspectRatio

详情请查看 API 文档