使用React-Three-Fiber创建动态图像动画:从3D几何到纹理映射的完整指南

96 阅读2分钟

如何使用React-Three-Fiber创建动态图像动画

通过旋转纹理、3D几何体和React Three Fiber驱动的流畅运动,让静态视觉效果焕发生机。

视图与相机设置

相机的视野(FOV)在这个项目中起着关键作用。我们将其设置为较低值以模拟正交相机的效果,但使用透视相机可以方便后续尝试不同的FOV值。

<PerspectiveCamera makeDefault fov={7} position={[0, 0, 70]} near={0.01} far={100000} />

设置3D形状

创建基本组件

Billboard.tsx - 显示图像堆栈的圆柱体组件:

'use client';

import { useRef } from 'react';
import * as THREE from 'three';

function Billboard({ radius = 5, ...props }) {
    const ref = useRef(null);

    return (
        <mesh ref={ref} {...props}>
            <cylinderGeometry args={[radius, radius, 2, 100, 1, true]} />
            <meshBasicMaterial color="red" side={THREE.DoubleSide} />
        </mesh>
    );
}

Banner.tsx - 类似移动横幅的圆柱体组件:

'use client';

import * as THREE from 'three';
import { useRef } from 'react';

function Banner({ radius = 1.6, ...props }) {
    const ref = useRef(null);

    return (
        <mesh ref={ref} {...props}>
            <cylinderGeometry
            args={[radius, radius, radius * 0.07, radius * 80, radius * 10, true]}
            />
            <meshBasicMaterial
            color="blue"
            side={THREE.DoubleSide}
            />
        </mesh>
    );
}

构建完整形状结构

'use client';

import styles from './page.module.scss';
import Billboard from '@/components/webgl/Billboard/Billboard';
import Banner from '@/components/webgl/Banner/Banner';
import { View } from '@/webgl/View';
import { PerspectiveCamera } from '@react-three/drei';

const COUNT = 10;
const GAP = 3.2;

export default function Home() {
    return (
        <div className={styles.page}>
            <View className={styles.view} orbit={false}>
            <PerspectiveCamera makeDefault fov={7} position={[0, 0, 70]} near={0.01} far={100000} />
                <group rotation={[-0.15, 0, -0.2]}>
                    {Array.from({ length: COUNT }).map((_, index) => [
                        <Billboard
                        key={`billboard-${index}`}
                        radius={5}
                        position={[0, (index - (Math.ceil(COUNT / 2) - 1)) * GAP, 0]}
                        rotation={[0, index * Math.PI * 0.5, 0]}
                        />,
                        <Banner
                        key={`banner-${index}`}
                        radius={5}
                        rotation={[0, 0, 0.085]}
                        position={[0, (index - (Math.ceil(COUNT / 2) - 1)) * GAP - GAP * 0.5, 0]}
                        />,
                    ])}
                </group>
            </View>
        </div>
    );
}

使用Canvas创建图像纹理

纹理生成工具函数

import * as THREE from 'three';

async function preloadImage(imageUrl, axis, canvasHeight, canvasWidth) {
    // 图像预加载实现
}

export async function getCanvasTexture({
    images,
    gap = 10,
    canvasHeight = 512,
    canvasWidth = 512,
    canvas,
    ctx,
    axis = 'x',
}) {
    // 完整纹理生成逻辑
}

React Hook封装

import { useState, useEffect, useCallback } from 'react';
import { getCanvasTexture } from '@/webgl/helpers/getCanvasTexture';

export function useCollageTexture(images, options = {}) {
    // Hook实现细节
}

纹理映射与动画

Billboard组件纹理映射

'use client';

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

function setupCylinderTextureMapping(texture, dimensions, radius, height) {
    // 纹理映射计算逻辑
}

function Billboard({ texture, dimensions, radius = 5, ...props }) {
    const ref = useRef(null);

    setupCylinderTextureMapping(texture, dimensions, radius, 2);

    useFrame((state, delta) => {
        if (texture) texture.offset.x += delta * 0.001;
    });

    return (
        <mesh ref={ref} {...props}>
            <cylinderGeometry args={[radius, radius, 2, 100, 1, true]} />
            <meshImageMaterial map={texture} side={THREE.DoubleSide} toneMapped={false} />
        </mesh>
    );
}

自定义材质开发

MeshImageMaterial.js - 背面变暗效果:

import * as THREE from 'three';
import { extend } from '@react-three/fiber';

export class MeshImageMaterial extends THREE.MeshBasicMaterial {
    constructor(parameters = {}) {
        super(parameters);
        this.setValues(parameters);
    }

    onBeforeCompile = (shader) => {
        // 着色器修改逻辑
    };
}

MeshBannerMaterial.js - 渐变背面效果:

import * as THREE from 'three';
import { extend } from '@react-three/fiber';

export class MeshBannerMaterial extends THREE.MeshBasicMaterial {
    constructor(parameters = {}) {
        super(parameters);
        // 材质参数设置
    }

    onBeforeCompile = (shader) => {
        // 渐变着色器实现
    };
}

最终实现效果

通过组合这些技术组件,我们成功创建了具有以下特性的动态图像动画系统:

  • 基于Canvas的动态纹理生成
  • 精确的3D几何体纹理映射
  • 实时纹理偏移动画
  • 自定义着色器材质效果
  • 响应式3D场景构建

这种技术方法可以扩展到其他形状和动画效果,为Web端的3D图形展示提供了强大的技术基础。