# 摄像机

`PerspectiveCamera` 定义了一个 视锥frustum. frustum 是一个切掉顶的三角锥或者说实心金字塔型. 说到实心体solid, 在这里通常是指一个立方体, 一个圆锥, 一个球, 一个圆柱或锥台.

`PerspectiveCamera`通过四个属性来定义一个视锥. `near`定义了视锥的前端, `far`定义了后端, `fov`是视野, 通过计算正确的高度来从摄像机的位置获得指定的以`near`为单位的视野, 定义的是视锥的前端和后端的高度. `aspect`间接地定义了视锥前端和后端的宽度, 实际上视锥的宽度是通过高度乘以aspect来得到的.

```class MinMaxGUIHelper {
constructor(obj, minProp, maxProp, minDif) {
this.obj = obj;
this.minProp = minProp;
this.maxProp = maxProp;
this.minDif = minDif;
}
get min() {
return this.obj[this.minProp];
}
set min(v) {
this.obj[this.minProp] = v;
this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);
}
get max() {
return this.obj[this.maxProp];
}
set max(v) {
this.obj[this.maxProp] = v;
this.min = this.min;  // 这将调用min的setter
}
}
```

```function updateCamera() {
camera.updateProjectionMatrix();
}

const gui = new GUI();
const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
```

```<body>
<canvas id="c"></canvas>
+  <div class="split">
+     <div id="view1" tabindex="1"></div>
+     <div id="view2" tabindex="2"></div>
+  </div>
</body>
```

CSS将控制两个视窗并排显示在canvas中

```.split {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
}
.split>div {
width: 100%;
height: 100%;
}
```

```const cameraHelper = new THREE.CameraHelper(camera);

...

```

```const view1Elem = document.querySelector('#view1');
const view2Elem = document.querySelector('#view2');
```

```-const controls = new OrbitControls(camera, canvas);
+const controls = new OrbitControls(camera, view1Elem);
```
```const camera2 = new THREE.PerspectiveCamera(
60,  // fov
2,   // aspect
0.1, // near
500, // far
);
camera2.position.set(40, 10, 30);
camera2.lookAt(0, 5, 0);

const controls2 = new OrbitControls(camera2, view2Elem);
controls2.target.set(0, 5, 0);
controls2.update();
```

```function setScissorForElement(elem) {
const canvasRect = canvas.getBoundingClientRect();
const elemRect = elem.getBoundingClientRect();

// 计算canvas的尺寸
const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left;
const left = Math.max(0, elemRect.left - canvasRect.left);
const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top;
const top = Math.max(0, elemRect.top - canvasRect.top);

const width = Math.min(canvasRect.width, right - left);
const height = Math.min(canvasRect.height, bottom - top);

// 设置剪函数以仅渲染一部分场景
const positiveYUpBottom = canvasRect.height - bottom;
renderer.setScissor(left, positiveYUpBottom, width, height);
renderer.setViewport(left, positiveYUpBottom, width, height);

// 返回aspect
return width / height;
}
```

```  function render() {

-    if (resizeRendererToDisplaySize(renderer)) {
-      const canvas = renderer.domElement;
-      camera.aspect = canvas.clientWidth / canvas.clientHeight;
-      camera.updateProjectionMatrix();
-    }

+    resizeRendererToDisplaySize(renderer);
+
+    // 启用剪刀函数
+    renderer.setScissorTest(true);
+
+    // 渲染主视野
+    {
+      const aspect = setScissorForElement(view1Elem);
+
+      // 用计算出的aspect修改摄像机参数
+      camera.aspect = aspect;
+      camera.updateProjectionMatrix();
+      cameraHelper.update();
+
+      // 来原视野中不要绘制cameraHelper
+      cameraHelper.visible = false;
+
+      scene.background.set(0x000000);
+
+      // 渲染
+      renderer.render(scene, camera);
+    }
+
+    // 渲染第二台摄像机
+    {
+      const aspect = setScissorForElement(view2Elem);
+
+      // 调整aspect
+      camera2.aspect = aspect;
+      camera2.updateProjectionMatrix();
+
+      // 在第二台摄像机中绘制cameraHelper
+      cameraHelper.visible = true;
+
+      scene.background.set(0x000040);
+
+      renderer.render(scene, camera2);
+    }

-    renderer.render(scene, camera);

requestAnimationFrame(render);
}

requestAnimationFrame(render);
}
```

```-function updateCamera() {
-  camera.updateProjectionMatrix();
-}

const gui = new GUI();
const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1);
```

`near`调整到大概20左右, 前景就会在视锥中消失. `far`低于35时, 远景也不复存在.

```{
const sphereWidthDivisions = 32;
const sphereHeightDivisions = 16;
const sphereGeo = new THREE.SphereBufferGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
const numSpheres = 20;
for (let i = 0; i < numSpheres; ++i) {
const sphereMat = new THREE.MeshPhongMaterial();
sphereMat.color.setHSL(i * .73, 1, 0.5);
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
}
}
```

`near` 设置成0.00001

```const fov = 45;
const aspect = 2;  // canvas 默认
-const near = 0.1;
+const near = 0.00001;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
```

```-gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near').onChange(updateCamera);
```

```-const renderer = new THREE.WebGLRenderer({canvas});
+const renderer = new THREE.WebGLRenderer({
+  canvas,
+  logarithmicDepthBuffer: true,
+});
```

```const left = -1;
const right = 1;
const top = 1;
const bottom = -1;
const near = 5;
const far = 50;
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
camera.zoom = 0.2;
```

```const gui = new GUI();
```

`listen`调用告诉lil-gui去监视属性的变化. 写在这里是因为`OrbitControls`同样可以控制缩放. 在这个例子中, 鼠标滚轮将会通过`OrbitControls`控件来控制缩放.

```{
const aspect = setScissorForElement(view1Elem);

// 使用aspect更新摄像机
-  camera.aspect = aspect;
+  camera.left   = -aspect;
+  camera.right  =  aspect;
camera.updateProjectionMatrix();
cameraHelper.update();

// 在主摄像机中不绘制视野辅助线
cameraHelper.visible = false;

scene.background.set(0x000000);
renderer.render(scene, camera);
}
```

```camera.left = -canvas.width / 2;
camera.right = canvas.width / 2;
camera.top = canvas.height / 2;
camera.bottom = -canvas.height / 2;
camera.near = -1;
camera.far = 1;
camera.zoom = 1;
```

```camera.left = 0;
camera.right = canvas.width;
camera.top = 0;
camera.bottom = canvas.height;
camera.near = -1;
camera.far = 1;
camera.zoom = 1;
```

```const left = 0;
const right = 300;  // 默认的canvas大小
const top = 0;
const bottom = 150;  // 默认的canvas大小
const near = -1;
const far = 1;
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
camera.zoom = 1;
```

```const loader = new THREE.TextureLoader();
const textures = [
];
const planeSize = 256;
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planes = textures.map((texture) => {
const planePivot = new THREE.Object3D();
texture.magFilter = THREE.NearestFilter;
const planeMat = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
// 调整平面使得左上角为原点
mesh.position.set(planeSize / 2, planeSize / 2, 0);
return planePivot;
});
```

```function render() {

if (resizeRendererToDisplaySize(renderer)) {
camera.right = canvas.width;
camera.bottom = canvas.height;
camera.updateProjectionMatrix();
}

...
```

`planes``THREE.Mesh`的数组, 每一个对应一个平面. 现在让它随着时间移动

```function render(time) {
time *= 0.001;  // 转换为秒;

...

const distAcross = Math.max(20, canvas.width - planeSize);
const distDown = Math.max(20, canvas.height - planeSize);

// 来回运动的总距离
const xRange = distAcross * 2;
const yRange = distDown * 2;
const speed = 180;

planes.forEach((plane, ndx) => {
// 为每个平面单独计算时间
const t = time * speed + ndx * 300;

// 在0到最远距离之间获取一个值
const xt = t % xRange;
const yt = t % yRange;

// 0到距离的一半, 向前运动
// 另一半的时候往回运动
const x = xt < distAcross ? xt : xRange - xt;
const y = yt < distDown   ? yt : yRange - yt;

plane.position.set(x, y, 0);
});

renderer.render(scene, camera);
```