3D数字孪生 – Three.js 项目实战之相机(四)

承上

在第一篇# 3D数字孪生 – Three.js 项目介绍与基础环境搭建(一)有介绍,在使用 three/filberCanvas组件时,默认情况下,会自动添加初始化一个透视相机(PerspectiveCamera),模拟了人眼的视角,可以呈现出远近物体的大小和透视效果。

image.png

接下来,我们将在实战案例中,讲解如何改变默认相机以及参数、控制器加载、默认控制器设置。

相机

three.js文档中对相机的定义很模糊,有很多的文章对相机都有介绍,以下仅对开发中常用的三种相机,以案例的形式,形象的介绍一下~

常见相机

在讲解相机的过程中,在确保大家能理解的基础上,我将试着从源码层面带大家理解它的原理。

1. PerspectiveCamera(透视相机)

  • 透视相机模拟了人眼的视角,远处的物体看起来较小,近处的物体看起来较大。
  • 使用透视相机可以创建逼真的三维效果,适用于大多数场景。
  • 透视相机的参数包括视角(FOV)、宽高比(aspect ratio)、近裁剪面(near)和远裁剪面(far )。

OK,接下来我们就以实际的案例讲解:

20230625172725_rec_.gif

上述案例完成代码: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属性,在使用useThreeuseCamera时,就能获取到相应的相机对象。

对于参数部分,除了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);

看下当前效果~

20230625193408_rec_.gif
此时场景无法拖动,因为目前,我们还未在场景中添加任何的相机控制器。

在控制台打印我们的camera对象。

image.png
正如Canvas中camera属性初始值一样,整个场景看起来就是我们人眼看东西的视角~

image.png

2. OrthographicCamera(正交相机)

  • 正交相机没有远近之分,物体在视野中的大小保持不变。
  • 正交相机适用于需要保持物体大小一致的场景,如平面视图或类似 2D 的渲染。
  • 正交相机的参数包括左、右、上、下、近裁剪面和远裁剪面。
    我们仍以案例作为讲解:

20230625193902_rec_.gif
可以看出,不管我们的相机视角如何变化,物体的大小始终不会改变,这类相机,在平面设计上用的较多,如CAD图的渲染。 此项目中没有涉及到,感兴趣的伙伴,可以查看案例代码OrthographicCamera 实例

3. CubeCamera(立方体相机)

  • 立方体相机是一种特殊的相机,用于渲染环境贴图(environment map)。
  • 立方体相机以相机位置为中心,生成六个方向的图像,并将其存储在立方体贴图中,以供后续的环境映射使用。
  • 立方体相机主要用于模拟反射和折射效果,并可用于创建逼真的环境光照。
    同样,我们先看一个案例。
    20230625194855_rec_.gif
    案例地址:立方体相机

从上述例子对比使用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);

好了,枯燥的理论部分相信你如果认真看完,一定会有所收获!

感兴趣的伙伴,欢迎继续跟踪本系列教程,并提出宝贵意见~

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYN7YL9K' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片