使用React-Three-Fiber创建动态图像动画:3D纹理与运动效果实战

本教程详细讲解如何利用React-Three-Fiber实现动态图像效果,包括3D几何体构建、Canvas纹理映射、自定义着色器材质开发以及动画控制技术,最终打造出具有视觉冲击力的旋转图像塔效果。

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

视图与相机设置

通过低视场角(FOV=7)的透视相机模拟正交投影效果:

1
2
3
4
5
6
7
<PerspectiveCamera 
  makeDefault 
  fov={7} 
  position={[0, 0, 70]} 
  near={0.01} 
  far={100000} 
/>

3D几何体构建

1. 广告牌组件(Billboard)

创建圆柱体作为图像展示载体:

1
2
3
4
5
6
7
8
function Billboard({ radius = 5 }) {
  return (
    <mesh>
      <cylinderGeometry args={[radius, radius, 2, 100, 1, true]} />
      <meshBasicMaterial color="red" side={THREE.DoubleSide} />
    </mesh>
  );
}

2. 横幅组件(Banner)

实现动态横幅效果:

1
2
3
4
5
6
7
8
function Banner({ radius = 1.6 }) {
  return (
    <mesh>
      <cylinderGeometry args={[radius, radius, radius*0.07, radius*80, 10, true]} />
      <meshBasicMaterial color="blue" side={THREE.DoubleSide} />
    </mesh>
  );
}

场景组装与布局

采用循环结构创建多层堆叠效果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const COUNT = 10;
const GAP = 3.2;

{Array.from({ length: COUNT }).map((_, index) => [
  <Billboard
    key={`billboard-${index}`}
    position={[0, (index - (COUNT/2 - 1)) * GAP, 0]}
    rotation={[0, index * Math.PI * 0.5, 0]}
  />,
  <Banner
    key={`banner-${index}`}
    position={[0, (index - (COUNT/2 - 1)) * GAP - GAP*0.5, 0]}
    rotation={[0, 0, 0.085]}
  />
])}

Canvas纹理处理

图像预处理函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
async function preloadImage(imageUrl, axis, canvasHeight, canvasWidth) {
  const img = new Image();
  await new Promise((resolve) => {
    img.onload = resolve;
    img.src = imageUrl;
  });
  
  const aspectRatio = img.naturalWidth / img.naturalHeight;
  return {
    img,
    width: axis === 'x' ? canvasHeight * aspectRatio : canvasWidth,
    height: axis === 'x' ? canvasHeight : canvasWidth / aspectRatio
  };
}

纹理映射技术

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function setupCylinderTextureMapping(texture, dimensions, radius) {
  const circumference = 2 * Math.PI * radius;
  const aspectRatio = circumference / 2;
  
  if(dimensions.aspectRatio > aspectRatio) {
    texture.repeat.x = aspectRatio / dimensions.aspectRatio;
    texture.offset.x = (1 - texture.repeat.x)/2;
  } else {
    texture.repeat.y = dimensions.aspectRatio / aspectRatio;
  }
  texture.offset.y = (1 - texture.repeat.y)/2;
}

动画实现

使用useFrame钩子实现纹理位移动画:

1
2
3
useFrame((_, delta) => {
  if(texture) texture.offset.x += delta * 0.001;
});

高级材质开发

图像材质(背面变暗效果)

1
2
3
4
if(!gl_FrontFacing) {
  vec3 blackCol = vec3(0.0);
  diffuseColor.rgb = mix(diffuseColor.rgb, blackCol, 0.7);
}

横幅材质(渐变背景)

1
2
3
4
5
6
7
vec3 pal(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
  return a + b*cos(6.28318*(c*t+d));
}

if(!gl_FrontFacing) {
  diffuseColor.rgb = pal(vMapUv.x * repeatX, vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.0,0.1,0.2));
}

最终效果优化

  • 整体倾斜角度调整:rotation={[-0.15, 0, -0.2]}
  • 纹理各向异性过滤:map-anisotropy={16}
  • 色调映射禁用:toneMapped={false}

查看完整Demo | 获取源代码

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计