承上
在第一篇# 3D数字孪生 – Three.js 项目介绍与基础环境搭建(一)有介绍,在使用 three/filber
的Canvas
组件时,默认情况下,会自动添加初始化一个透视相机(PerspectiveCamera),模拟了人眼的视角,可以呈现出远近物体的大小和透视效果。
接下来,我们将在实战案例中,讲解如何改变默认相机以及参数、控制器加载、默认控制器设置。
相机
在 three.js文档中对相机的定义很模糊,有很多的文章对相机都有介绍,以下仅对开发中常用的三种相机,以案例的形式,形象的介绍一下~
常见相机
在讲解相机的过程中,在确保大家能理解的基础上,我将试着从源码层面带大家理解它的原理。
1. PerspectiveCamera(透视相机)
- 透视相机模拟了人眼的视角,远处的物体看起来较小,近处的物体看起来较大。
- 使用透视相机可以创建逼真的三维效果,适用于大多数场景。
- 透视相机的参数包括视角(FOV)、宽高比(aspect ratio)、近裁剪面(near)和远裁剪面(far )。
OK,接下来我们就以实际的案例讲解:
上述案例完成代码:codesandbox
“ps”: 掘金的代码块功能太难使用了,有可能也是我没找到,希望提供一些基础模板,依赖包还得找CDN
import * as React from 'react'
import { Canvas } from '@react-three/fiber'
import { Icosahedron, PerspectiveCamera, OrbitControls } from '@react-three/drei'
const NUM = 3
interface Positions {
id: string
position: [number, number, number]
}
function PerspectiveCameraScene() {
const positions = React.useMemo(() => {
const pos: Positions[] = []
const half = (NUM - 1) / 2
for (let x = 0; x < NUM; x++) {
for (let y = 0; y < NUM; y++) {
pos.push({
id: `${x}-${y}`,
position: [(x - half) * 4, (y - half) * 4, 0],
})
}
}
return pos
}, [])
return (
<Canvas>
<PerspectiveCamera makeDefault position={[0, 0, 10]} />
<group position={[0, 0, -10]}>
{positions.map(({ id, position }) => (
<Icosahedron key={id} position={position} args={[1, 1]}>
<meshBasicMaterial color="white" wireframe />
</Icosahedron>
))}
</group>
<OrbitControls />
</Canvas>
)
}
代码中 <PerspectiveCamera makeDefault position={[0, 0, 10]} />
makeDefault是将当前相机设置为场景默认相机,当然默认情况下,场景相机也是透视相机,只是如果我们动态设置makeDefault属性,在使用useThree
或useCamera
时,就能获取到相应的相机对象。
对于参数部分,除了drei扩展的几个参数以外,与 threejs 中 PerspectiveCameraImpl 保持一致,以下是源码部分:
import * as THREE from 'three'
import * as React from 'react'
import { PerspectiveCamera as PerspectiveCameraImpl } from 'three'
//...
import mergeRefs from 'react-merge-refs'
//...
type Props = Omit<JSX.IntrinsicElements['perspectiveCamera'], 'children'> & {
/** Registers the camera as the system default, fiber will start rendering with it */
makeDefault?: boolean
/** Making it manual will stop responsiveness and you have to calculate aspect ratio yourself. */
manual?: boolean
/** The contents will either follow the camera, or be hidden when filming if you pass a function */
children?: React.ReactNode | ((texture: THREE.Texture) => React.ReactNode)
/** Number of frames to render, Infinity */
frames?: number
/** Resolution of the FBO, 256 */
resolution?: number
/** Optional environment map for functional use */
envMap?: THREE.Texture
}
export const PerspectiveCamera = React.forwardRef(
({ envMap, resolution = 256, frames = Infinity, makeDefault, children, ...props }: Props, ref) => {
// 省略啦,感兴趣的小伙伴可以自己查看源码:https://github.com/pmndrs/drei/blob/abfbd9feb368be4f859845ee68cae25d35f1c79b/src/core/PerspectiveCamera.tsx#L3
return (
<>
<perspectiveCamera ref={mergeRefs([cameraRef, ref])} {...props}>
{!functional && children}
</perspectiveCamera>
<group ref={groupRef}>{functional && children(fbo.texture)}</group>
</>
)
}
)
OK,接下来,进入实战演练环节~
回到 three=>Canvas=>index.tsx
文件中(不知道的小伙伴,可以阅读# 3D数字孪生 – Three.js 项目介绍与基础环境搭建(一))
修改代码:
import { Canvas } from '@react-three/fiber';
import React, { useState, useContext } from 'react';
import BaseSence from './components/BaseSence';
import Factory from './components/Factory';
// 创建Canva组件
const Canva = (props) => {
return (
<>
<Canvas
shadows
//camera={{ near: 0.1, far: 40, fov: 75 }}
gl={{
logarithmicDepthBuffer: true,
}}
>
{/* 场景类 */}
<BaseSence />
{/* 工厂类 */}
<Factory />
{props.children}
{/* 帮助类 */}
</Canvas>
</>
);
};
export default Canva;
将Canvas中的camera属性去除,同时将BaseSence中的Camera组件去除;
// 基础场景
import { useThree } from '@react-three/fiber';
import React from 'react';
import Camera from './components/Camera';
import GridModule from './components/Grid';
import Lights from './components/Lights';
import SkyBox from './components/Sky';
import Man from './components/others/dance';
import { useContext } from 'react';
import TreeGroup from './components/others/treeGroup';
import Buildings from './components/Buidings';
import { ThreeStoreContext, observer, ThreeStore } from 'mobx-threejs-store';
import { CameraControls } from '@react-three/drei';
// 创建Canva组件
const BaseSence = () => {
const threeStore = useContext(ThreeStoreContext);
// console.log('3D中的', ThreeStore.visible);
return (
<group>
<Lights />
{/* <Camera /> */}
<GridModule />
</group>
);
};
export default observer(BaseSence);
看下当前效果~
此时场景无法拖动,因为目前,我们还未在场景中添加任何的相机控制器。
在控制台打印我们的camera对象。
正如Canvas中camera属性初始值一样,整个场景看起来就是我们人眼看东西的视角~
2. OrthographicCamera(正交相机)
- 正交相机没有远近之分,物体在视野中的大小保持不变。
- 正交相机适用于需要保持物体大小一致的场景,如平面视图或类似 2D 的渲染。
- 正交相机的参数包括左、右、上、下、近裁剪面和远裁剪面。
我们仍以案例作为讲解:
可以看出,不管我们的相机视角如何变化,物体的大小始终不会改变,这类相机,在平面设计上用的较多,如CAD图的渲染。 此项目中没有涉及到,感兴趣的伙伴,可以查看案例代码OrthographicCamera 实例。
3. CubeCamera(立方体相机)
- 立方体相机是一种特殊的相机,用于渲染环境贴图(environment map)。
- 立方体相机以相机位置为中心,生成六个方向的图像,并将其存储在立方体贴图中,以供后续的环境映射使用。
- 立方体相机主要用于模拟反射和折射效果,并可用于创建逼真的环境光照。
同样,我们先看一个案例。
案例地址:立方体相机
从上述例子对比使用CubeCamera与PerspectiveCamera不难看出他们之间的区别。
立方体相机(CubeCamera)用于生成环境贴图(environment map),主要用于模拟环境光照和反射效果。立方体相机以相机位置为中心,捕捉六个不同方向的图像,并将它们存储在一个立方体贴图中。这个立方体贴图可以应用于材质的环境映射通道,为物体提供反射和折射的效果,增加真实感和光照效果。
启下
介绍完我们场景中的相机,目前我们还是无法控制我们的场景,下一个篇幅我们将着重围绕camera组件讲解如何在场景中添加相机控制器,以及我们都用到了哪些控制器。
camera组件的代码:
// 添加场景相机
import { useThree, PerspectiveCameraProps } from '@react-three/fiber';
import {
PerspectiveCamera,
CameraControls,
PresentationControls,
FlyControls,
OrbitControls,
FirstPersonControls,
MapControls,
} from '@react-three/drei';
import * as THREE from 'three';
import PointerCtrl from './CtrlPointerLock';
import React, { useContext, useEffect } from 'react';
import { observer, ThreeStoreContext } from 'mobx-threejs-store';
const Camera = (props: any) => {
const threeObj = useThree();
const camera = threeObj.camera;
const mobxStore = useContext(ThreeStoreContext);
const flyCtrl = () => {
return (
<group>
<FirstPersonControls
far={100000}
movementSpeed={100}
activeLook={false}
lookVertical={true}
></FirstPersonControls>
{/* <OrbitControls /> */}
<MapControls zoomSpeed={0.1} />
</group>
);
};
const ctrl = () => {
return (
<group>
{/* 相机控制器 */}
{/* <PresentationControls /> */}
<CameraControls />
{/* <PointerCtrl /> */}
</group>
);
};
return (
<>
{mobxStore.threeStore.cameraCtrls.choiceCtrls === '1' ? ctrl() : flyCtrl()}
<PerspectiveCamera
makeDefault
position={[-100, 200, 1000]}
fov={48}
near={1}
far={100000}
maxDistance={10}
{...props}
/>
</>
);
};
export default observer(Camera);
好了,枯燥的理论部分相信你如果认真看完,一定会有所收获!
感兴趣的伙伴,欢迎继续跟踪本系列教程,并提出宝贵意见~