LoginSignup
2
2

More than 1 year has passed since last update.

【React】react-three-fibarで3D表現をする(文字表示)

Posted at

概要

Three.jsのReact用ライブラリ react-three-fibar を使用して、文字の表示方法を3パターン実装します。

パッケージのバージョンやインストール方法は、以下を参照してください。

実装

立体感のある文字

厚みのある文字です。Boxオブジェクトなどと同様に影を落としたりできます。

スクリーンショット 2021-09-17 224341.png

import React, { VFC } from 'react';
import { FontLoader, TextGeometry } from 'three';
import threeFontJson from 'three/examples/fonts/helvetiker_bold.typeface.json';
import { OrbitControls } from '@react-three/drei';
import { TDirectionalLight } from './objects/TDirectionalLight';
import { TFloorPlane } from './objects/TFloorPlane';
import { TCanvas } from './TCanvas';

export const Text1: VFC = () => {
    return (
        <TCanvas>
            <OrbitControls />
            <TDirectionalLight position={[-1, 3, 5]} />

            <TextObject text="Hello! Three.js" />

            <TFloorPlane />
        </TCanvas>
    )
}

// ----------------------------------------------
const TextObject: VFC<{ text: string }> = ({ text }) => {
    // 位置の調整
    const textGeo = new TextGeometry(text, {
        font: new FontLoader().parse(threeFontJson),
        size: 1,
        height: 0.1
    })
    textGeo.computeBoundingBox()
    const centerOffset = -(textGeo.boundingBox!.max.x - textGeo.boundingBox!.min.x) / 2

    return (
        <mesh position={[centerOffset, 1, 0]} args={[textGeo]} castShadow receiveShadow>
            {/* <textGeometry args={['Hello! Three.js', config]} /> */}
            <meshPhongMaterial color="royalblue" />
        </mesh>
    )
}

TCanvas、TDirectionalLight、TFloorPlaneは、それぞれreact-three-fiberのCanvas、directionalLight、planeGeometryをラップしたコンポーネントです。 実装の詳細は、おまけ載せています。

テキストの表示位置の調整のために、TextGeometryのインスタンスを作成して、それを使ってオフセットを割り出しています。
[x, y] = [0, 0]の位置がテキストの左・下になっているので、オフセットを使用して中心・下に調整します。
この調整が必要ない場合は、コメントアウトしているtextGeometryが使えます。

元ネタ


平面な文字

厚みのない平面な文字です。

スクリーンショット 2021-09-17 224415.png

import { loremIpsum } from 'lorem-ipsum';
import React, { VFC } from 'react';
import { Text } from '@react-three/drei';
import { useRotate } from './hooks/useRotate';
import { TCanvas } from './TCanvas';

export const Text2: VFC = () => {
    const lorem = loremIpsum({ count: 6 })

    return (
        <TCanvas position={[0, 0, 10]}>
            <TextObject text={lorem} />
        </TCanvas>
    )
}

// ----------------------------------------------
const TextObject: VFC<{ text: string }> = ({ text }) => {
    const textRef = useRotate([0, 0.1, 0])

    return (
        <Text
            ref={textRef}
            color="#24ACF2"
            fontSize={0.5}
            maxWidth={10}
            lineHeight={1}
            letterSpacing={0.02}
            textAlign={'left'}
            font="https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwK4vaqI.woff"
            anchorX="center"
            anchorY="middle">
            {text}
        </Text>
    )
}

useRotateは、オブジェクトをフレーム単位で回転させるためのカスタムフックです。 実装の詳細は、おまけ載せています。

元ネタ


オブジェクトに追従する文字

オブジェクトに追従して、常に前面を向いている文字です。
また、カメラとオブジェクトの距離によって文字サイズも変化します。

output(video-cutter-js.com) (3).gif

import React, { VFC } from 'react';
import { css } from '@emotion/css';
import { Html, OrbitControls } from '@react-three/drei';
import { useRotate } from './hooks/useRotate';
import { TDirectionalLight } from './objects/TDirectionalLight';
import { TFloorPlane } from './objects/TFloorPlane';
import { TCanvas } from './TCanvas';

export const Text3: VFC = () => {
    return (
        <TCanvas>
            <OrbitControls />
            <TDirectionalLight position={[5, 5, 5]} />

            <CubeWithText text="Hello! Three.js" />

            <TFloorPlane />
        </TCanvas>
    )
}

// ----------------------------------------------

const CubeWithText: VFC<{ text: string }> = ({ text }) => {
    const ref = useRotate()
    const groupRef = useRotate([0, 0.5, 0])

    return (
        <group ref={groupRef}>
            <mesh ref={ref} position={[2, 2, 2]} castShadow>
                <boxGeometry args={[1, 1, 1]} />
                <meshPhongMaterial color="royalblue" />
                <Html className={styles.text} center distanceFactor={10}>
                    {text}
                </Html>
            </mesh>
        </group>
    )
}

const styles = {
    text: css`
        white-space: nowrap;
        font-size: 2rem;
        color: red;
        margin-top: 100px;
        user-select: none;
    `
}

meshに回転アニメーションのrefを追加すると、そのオブジェクトが回転(自転)します。 meshをgroupで囲って、そのgroupに回転アニメーションのrefを追加すると、そのオブジェクトが中心軸まわりに回転(公転)します。

元ネタ

まとめ

文字の扱い方についてまとめました。(実装はほぼコピーですが...)
また違う表現方法を見つけたら追加します。

おまけ

TCanvas

Canvasのラッパーコンポーネントです。

import React, { VFC } from 'react';
import { css } from '@emotion/css';
import { Canvas } from '@react-three/fiber';

type Props = {
    children: React.ReactNode
    fov?: number
    position?: [number, number, number]
}

export const TCanvas: VFC<Props> = props => {
    const { children, fov = 50, position = [0, 3, 10] } = props
    return (
        <Canvas className={styles.canvas} camera={{ fov, position }} dpr={[1, 2]} shadows>
            {children}
        </Canvas>
    )
}

const styles = {
    canvas: css`
        width: 100%;
        height: 100%;
    `
}

TDirectionalLight

光源です。オプションでHelperを表示することができます。

import React, { useRef, VFC } from 'react';
import { DirectionalLightHelper } from 'three';
import { useHelper } from '@react-three/drei';

type Props = {
    position: [number, number, number]
    isHelper?: boolean
}

export const TDirectionalLight: VFC<Props> = props => {
    const { position, isHelper = false } = props

    const lightRef = useRef()
    useHelper(lightRef, DirectionalLightHelper)

    return (
        <>
            {isHelper ? (
                <directionalLight
                    ref={lightRef}
                    position={position}
                    intensity={1} // 光の強さ
                    shadow-mapSize-width={2048} // 描画精度
                    shadow-mapSize-height={2048}
                    castShadow
                />
            ) : (
                <directionalLight
                    position={position}
                    intensity={1} // 光の強さ
                    shadow-mapSize-width={2048} // 描画精度
                    shadow-mapSize-height={2048}
                    castShadow
                />
            )}
        </>
    )
}

TFloorPlane

床パネルです。

import React, { VFC } from 'react';
import { DoubleSide } from 'three';

type Props = {
    size?: [number, number]
    color?: string
    isGridHelper?: boolean
}

export const TFloorPlane: VFC<Props> = props => {
    const { size = [10, 10], color = 'white', isGridHelper = false } = props

    return (
        <>
            <mesh position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
                <planeGeometry args={size} />
                <meshPhongMaterial color={color} side={DoubleSide} />
            </mesh>

            {isGridHelper && (
                <gridHelper position={[0, 0.01, 0]} args={[size[0], size[0], 'red', 'black']} />
            )}
        </>
    )
}

useRotate

オブジェクトを回転させるためのカスタムフックです。

import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';

export const useRotate = (speed = [1, 1, 1]) => {
    const ref = useRef<any>(null)
    useFrame(({ clock }) => {
        const a = clock.getElapsedTime()
        ref.current.rotation.x = a * speed[0]
        ref.current.rotation.y = a * speed[1]
        ref.current.rotation.z = a * speed[2]
    })
    return ref
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2