[3D] 광원(Light)

jini.choi·2024년 5월 1일

3D(R3F)

목록 보기
4/10

Three.js, Drei에서 제공하는 광원 클래스

  • 실습 전 먼저 Canvas에 카메라 속성 추가
import { Canvas } from "@react-three/fiber";
import "./App.css";
import MYElement3DLight from "./MyElement3D_Light";

function App() {
  return (
    <>
      {/* fov - 카메라 렌즈 화곽을 75도로 지정(Fovy값 0 ~ 180) / position - 카메라 위치 x, y 각 7로 지정*/}
      <Canvas camera={{ fov: 75, position: [7, 7, 0] }}>
        <MYElement3DLight />
      </Canvas>
    </>
  );
}

export default App;

ambientLight(주변광)

  • 광원의 색상과 밝기 쌔기값을 속성으로 줄 수 있다.
<ambientLight color="#ffffff" intensity={5} />

hemisphereLight(주변광, 색상2개)

  • AmbientLight과 비슷하게 광원의 색상과 밝기 쌔기값을 속성으로 줄 수 있다.

  • 하지만 색상 2개값을 가질수 있고, 하늘에서 비추는 광원, 지상에서 비추는 광원으로 나뉜다.

<hemisphereLight args={["#00f", "#f00", 20]} />

//args={["하늘", "지상", 밝기]}

directionalLight(특정 방향으로 향하는 빛(feat.태양))

  • 태양과 같은 광원

  • 광원의 위치와 물체와의 거리와 상관없이 특정(일정)한 방향으로 향하는 빛

  • directionalLight의 position 위치에서 원점까지의 방향으로 결정

<directionalLight color={0xffffff} intensity={5} position={[0, 5, 0]} />

//position={[0, 5, 0]} 으로 광원의 위치를 설정


광원 위치 눈으로 확인하는 방법

  • 광원 useRef로 참조하고, useHelper로 light를 불러옴
const light = useRef();

useHelper(light, THREE.DirectionalLightHelper);

원점이 아닌 다른 Target으로 할 경우

  • target-position 속성으로 조정
<directionalLight
        ref={light}
        color={0xffffff}
        intensity={5}
        position={[0, 5, 0]}
        target-position={[1, 0, 0]}
      />
  • target을 조정할 경우 장면에 추가해줘야하는데, 그럴 경우 장면을 객체로 가져와야함

장면에 대한 객체가 scene에 저장

const { scene } = useThree();

useEffect를 통해 광원의 타겟을 장면에 추가하는 코드 작성

useEffect(() => {
    scene.add(light.current.target);
  
  	//개발자가 직접 장면에 추가했으므로, 클린업 코드도 넣어야됨
    return () => {
      scene.remove(light.current.target);
    };
  }, [light]);

광원이 바라보는 코드를 회전하고 있는 빨간 원으로 추적

// smallSpherePivot를 움직이게하기 위해 useFrame사용
  useFrame((state) => {
    // 프레임이 만들어진 이후 경과된 시간을 변수에 저장
    const time = state.clock.elapsedTime;

    // time은 경과된 시간이므로 계속 증가하게 될 것임
    const smallSpherePivot = state.scene.getObjectByName("smallSpherePivot");

    // smallSpherePivot를 rotation.y로 회전 시키고 각도는 time * 50로 설정
    smallSpherePivot.rotation.y = THREE.MathUtils.degToRad(time * 50);

    //mesh에 getWorldPosition로 월드좌표계에 위치를 구해서 그 결과를 광원 타겟 포지션에 지정
    ⭐️⭐️smallSpherePivot.children[0].getWorldPosition(
      light.current.target.position
    );
  });

directionalLight 전체코드

import { OrbitControls, useHelper } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react";
import * as THREE from "three";

const torusGetometry = new THREE.TorusGeometry(0.4, 0.1, 32, 32);
const torusMaterial = new THREE.MeshStandardMaterial({
  color: "#9b59b6",
  roughness: 0.5,
  metalness: 0.9,
});

function MYElement3DLight() {
  // smallSpherePivot를 움직이게하기 위해 useFrame사용
  useFrame((state) => {
    // 프레임이 만들어진 이후 경과된 시간을 변수에 저장
    const time = state.clock.elapsedTime;

    // time은 경과된 시간이므로 계속 증가하게 될 것임
    const smallSpherePivot = state.scene.getObjectByName("smallSpherePivot");

    // smallSpherePivot를 rotation.y로 회전 시키고 각도는 time * 50로 설정
    smallSpherePivot.rotation.y = THREE.MathUtils.degToRad(time * 50);

    //mesh에 getWorldPosition로 월드좌표계에 위치를 구해서 그 결과를 광원 타겟 포지션에 지정
    smallSpherePivot.children[0].getWorldPosition(
      light.current.target.position
    );
  });

  const light = useRef();

  useHelper(light, THREE.DirectionalLightHelper);

  const { scene } = useThree();

  useEffect(() => {
    scene.add(light.current.target);
    return () => {
      scene.remove(light.current.target);
    };
  }, [light]);

  return (
    <>
      <OrbitControls />

      <directionalLight
        ref={light}
        color={0xffffff}
        intensity={5}
        position={[0, 5, 0]}
        target-position={[1, 0, 0]}
      />

      <mesh rotation-x={THREE.MathUtils.degToRad(-90)}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial
          color="#2c3e50"
          roughness={0.5}
          metalness={0.5}
          side={THREE.DoubleSide}
        />
      </mesh>

      <mesh rotation-x={THREE.MathUtils.degToRad(-90)}>
        <sphereGeometry args={[1.5, 64, 64, 0, Math.PI]} />
        <meshStandardMaterial color="#fff" roughness={0.1} metalness={0.2} />
      </mesh>

      {new Array(8).fill().map((item, idx) => {
        return (
          <group key={idx} rotation-y={THREE.MathUtils.degToRad(45 * idx)}>
            <mesh
              geometry={torusGetometry}
              material={torusMaterial}
              position={[3, 0.5, 0]}
            ></mesh>
          </group>
        );
      })}

      {/* group에 name을 정하면 이를 통해 group객체를 참조할 수 있다. */}
      <group name="smallSpherePivot">
        <mesh position={[3, 0.5, 0]}>
          <sphereGeometry args={[0.3, 32, 32]} />
          <meshStandardMaterial
            color="#e74c3c"
            roughness={0.2}
            metalness={0.5}
          />
        </mesh>
      </group>
    </>
  );
}

export default MYElement3DLight;

pointLight(모든 방향으로 비추는 빛)

  • position 위치에서 포인트로 빛을 비춤
<pointLight
        ref={light}
        color="#ffffff"
        intensity={20}
        position={[0, 5, 0]}
      />
  • useHelper를 통해 눈으로 어디서 비추는지 확인 가능
useHelper(light, THREE.PointLightHelper, 0.5);

  • 빨간색원 추적해서 따라다니면서 포인트 빛 주기(useFrame)
//mesh에 getWorldPosition로 월드좌표계에 위치를 구해서 그 결과를 광원 타겟 포지션에 지정
smallSpherePivot.children[0].getWorldPosition(light.current.position);

  • distance속성으로 광원의 위치에서 지정된 거리까지만 광원의 영향을 미치게 함(0은 무한한 거리 (default값))
    • 1로 했을 경우

<pointLight
        ref={light}
        color="#ffffff"
        intensity={20}
        position={[0, 5, 0]}
        distance={1}
      />

spotLight(조명광)

  • 광원에 위치에서 무대의 조명처럼 깔때기 모양으로 퍼짐

  • SpotLightHelper사용해서 어떻게 퍼지는지 확인할 수 있음

  useHelper(light, THREE.SpotLightHelper);

  return (
    <>
      <OrbitControls />

      <spotLight
        ref={light}
        color={0xffffff}
        intensity={10}
        position={[0, 5, 0]}
        target-position={[0, 0, 0]}
        distance={20}  //영향을 주는 거리값
        angle={THREE.MathUtils.degToRad(30)} //깔때기 각도
        penumbra={0.2} //빛의 감쇠율 (빛의 번짐정도)
      />

  • target-position={[0, 0, 0]} 원점이 아닌 광원의 타겟을 자식으로 추가해야됨
const { scene } = useThree();

  useEffect(() => {
    scene.add(light.current.target);
    return () => {
      scene.remove(light.current.target);
    };
  }, [light]);
  • 빨간색원 추적해서 따라다니면서 포인트 빛 주기(useFrame)
//mesh에 getWorldPosition로 월드좌표계에 위치를 구해서 그 결과를 광원 타겟 포지션에 지정
smallSpherePivot.children[0].getWorldPosition(light.current.target.position);

rectAreaLight(형광등처럼 비추는 빛)

  • 추가적인 import 필요함
import {
  RectAreaLightUniformsLib,
  RectAreaLightHelper,
} from "three/examples/jsm/Addons.js";
 <rectAreaLight
        ref={light}
        color="#fff"
        intensity={20}
        width={1}
        height={3} //intensity과 크기로 밝기 조절 가능
        position={[0, 5, 0]}
        rotation-x={THREE.MathUtils.degToRad(-90)} //광원에서 비추는 각도
      />
  • 어디서 비추는지 확인용 코드
useHelper(light, RectAreaLightHelper);
  • 컴포넌트 밖에 초기화코드 추가
RectAreaLightUniformsLib.init();

rectAreaLight 전체 코드

import { OrbitControls, useHelper } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react";
import * as THREE from "three";

import {
  RectAreaLightUniformsLib,
  RectAreaLightHelper,
} from "three/examples/jsm/Addons.js";

const torusGetometry = new THREE.TorusGeometry(0.4, 0.1, 32, 32);
const torusMaterial = new THREE.MeshStandardMaterial({
  color: "#9b59b6",
  roughness: 0.5,
  metalness: 0.9,
});

RectAreaLightUniformsLib.init();


function MYElement3DLight() {
  // smallSpherePivot를 움직이게하기 위해 useFrame사용
  useFrame((state) => {
    // 프레임이 만들어진 이후 경과된 시간을 변수에 저장
    const time = state.clock.elapsedTime;

    // time은 경과된 시간이므로 계속 증가하게 될 것임
    const smallSpherePivot = state.scene.getObjectByName("smallSpherePivot");

    // smallSpherePivot를 rotation.y로 회전 시키고 각도는 time * 50로 설정
    smallSpherePivot.rotation.y = THREE.MathUtils.degToRad(time * 50);

  });

  const light = useRef();

  useHelper(light, RectAreaLightHelper);


  return (
    <>
      <OrbitControls />

      <rectAreaLight
        ref={light}
        color="#fff"
        intensity={20}
        width={1}
        height={3}
        position={[0, 5, 0]}
        rotation-x={THREE.MathUtils.degToRad(-90)}
      />

      <mesh rotation-x={THREE.MathUtils.degToRad(-90)}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial
          color="#2c3e50"
          roughness={0.5}
          metalness={0.5}
          side={THREE.DoubleSide}
        />
      </mesh>

      <mesh rotation-x={THREE.MathUtils.degToRad(-90)}>
        <sphereGeometry args={[1.5, 64, 64, 0, Math.PI]} />
        <meshStandardMaterial color="#fff" roughness={0.1} metalness={0.2} />
      </mesh>

      {new Array(8).fill().map((item, idx) => {
        return (
          <group key={idx} rotation-y={THREE.MathUtils.degToRad(45 * idx)}>
            <mesh
              geometry={torusGetometry}
              material={torusMaterial}
              position={[3, 0.5, 0]}
            ></mesh>
          </group>
        );
      })}

      {/* group에 name을 정하면 이를 통해 group객체를 참조할 수 있다. */}
      <group name="smallSpherePivot">
        <mesh position={[3, 0.5, 0]}>
          <sphereGeometry args={[0.3, 32, 32]} />
          <meshStandardMaterial
            color="#e74c3c"
            roughness={0.2}
            metalness={0.5}
          />
        </mesh>
      </group>
    </>
  );
}

export default MYElement3DLight;

Drei에서 제공하는 컴포넌트

Environment (주위 환경을 촬영한 이미지를 이용한 빛)

  • 실제 환경을 360도로 촬영한 이미지를 활용해서 광원효과를 효과적으로 나타냄

  • 이미지 다운로드 사이트 -> 다운받아서 public폴더에 넣음
    https://polyhaven.com/

  • Environment import후 광원 코드 추가

<Environment files={"./images/metro_noord_4k.hdr"} />

  • background속성으로 files를 이용해서 3차원 장면의 배경으로도 활용 가능
<Environment background files={"./images/metro_noord_4k.hdr"} />

  • blur속성으로 배경 블러 효과
<Environment
        blur={0.1}
        background
        files={"./images/metro_noord_4k.hdr"}
      />
  • hdr 효과 없이 주어진 속성으로 반사 빛 설정
<Environment preset="sunset" />

sunset - 일몰을 연출합니다.
dawn - 새벽의 풍경을 연출합니다.
night - 밤하늘을 연출합니다.
warehouse - 창고 내부의 환경을 연출합니다.
forest - 숲속의 환경을 연출합니다.
apartment - 아파트 내부의 환경을 연출합니다.
studio - 스튜디오 환경을 연출합니다.
city - 도시 풍경을 연출합니다.
park - 공원의 환경을 연출합니다.
lobby - 로비 공간을 연출합니다.


이 글은 아래 유투브 강의를 듣고 작성한 글 입니다.
https://www.youtube.com/watch?v=0jnGlLb_z7w&list=PLe6NQuuFBu7HUeJkowKRkLWwkdOlhwrje&index=4

profile
개발짜🏃‍♀️

0개의 댓글