You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Fiber与Three.js:同时更新颜色和位置属性时几何体渲染异常

问题分析与修复方案

核心问题

  1. BufferAttribute 数组复用错误:初始化attrPosSphere时错误使用了newColorSphere数组,导致球体的位置和颜色属性共享同一块内存空间,更新其中一个属性时会覆盖另一个的数据,这就是同时更新时其中一个失效的根本原因。
  2. 冗余的setAttribute调用:在useFrame中每次重新调用setAttribute会重置属性引用,干扰Three.js的属性更新逻辑,直接修改现有BufferAttribute的数组并标记needsUpdate即可。
  3. 顶点位置计算逻辑缺陷:当前更新球体位置时,每次基于已经修改过的顶点数据计算,会导致顶点持续偏移,应该基于原始的顶点初始值进行计算。

修复后的代码

import { useFrame } from '@react-three/fiber'
import React, { useRef, useEffect } from 'react'
import { createNoise3D, createNoise4D } from 'simplex-noise'
import alea from 'alea';
import * as THREE from 'three'

function Background(props) {
  const refPlane = useRef()
  const refSphere = useRef()
  const prng = alea('seed')
  const noise3D = createNoise3D(prng)
  const noise4D = createNoise4D(prng)
  let posPlane, posSphere;
  // 保存球体原始顶点数据,用于位置计算
  let originalSpherePositions = null;
  
  // 初始化各属性的数组,确保互不共享
  let newColorPlane = new Float32Array(12)
  let newColorSphere = new Float32Array(12675)
  let newPosSphere = new Float32Array(12675)
  
  // 修正attrPosSphere的初始化,使用newPosSphere数组
  let attrColorSphere = new THREE.BufferAttribute(newColorSphere, 3)
  let attrColorPlane = new THREE.BufferAttribute(newColorPlane, 3)
  let attrPosSphere = new THREE.BufferAttribute(newPosSphere, 3)

  function mapRange(value, a, b, c, d) {
    value = (value - a) / (b - a);
    return c + value * (d - c);
  }

  useEffect(() => {
    posPlane = refPlane.current.geometry.getAttribute('position')
    posSphere = refSphere.current.geometry.getAttribute('position')
    // 保存球体原始顶点数据的副本,避免后续修改影响计算
    originalSpherePositions = Float32Array.from(posSphere.array)
    // 初始化几何体的color属性
    refPlane.current.geometry.setAttribute('color', attrColorPlane)
    refSphere.current.geometry.setAttribute('color', attrColorSphere)
    refSphere.current.geometry.setAttribute('position', attrPosSphere)
  }, []);

  // 修正函数名:该函数实际更新平面的颜色,而非位置
  function updateColorPlane(positionPlane, time) {
    for (let i = 0; i < (positionPlane.count * 3); i += 3) {
      const x = positionPlane.array[i];
      const y = positionPlane.array[i + 1];
      const noise = noise3D(x, y, time / 2);
      const hue = mapRange(noise, -1, 1, 0, 1);
      const color = new THREE.Color();
      color.setHSL(hue, 0.9, 0.25);
      newColorPlane[i] = color.r;
      newColorPlane[i + 1] = color.g;
      newColorPlane[i + 2] = color.b;
    }
  }

  function updateColorSphere(time) {
    for (let i = 0; i < originalSpherePositions.length; i += 3) {
      const x = originalSpherePositions[i];
      const y = originalSpherePositions[i + 1];
      const z = originalSpherePositions[i + 2];
      const noise = noise4D(x / 5, y / 5, z / 5, time / 10);
      const hue = mapRange(noise, -1, 1, 0, 1);
      const color = new THREE.Color();
      color.setHSL(hue, 0.9, 0.25);
      newColorSphere[i] = color.r;
      newColorSphere[i + 1] = color.g;
      newColorSphere[i + 2] = color.b;
    }
  }

  function updatePosSphere(time) {
    for (let i = 0; i < originalSpherePositions.length; i += 3) {
      const x = originalSpherePositions[i];
      const y = originalSpherePositions[i + 1];
      const z = originalSpherePositions[i + 2];
      const noise = noise4D(x / 5, y / 5, z / 5, time);
      const vec = new THREE.Vector3(x, y, z);
      // 基于原始顶点计算偏移,避免持续累积
      const newVec = vec.addScaledVector(vec, noise / 2);
      newPosSphere[i] = newVec.x;
      newPosSphere[i + 1] = newVec.y;
      newPosSphere[i + 2] = newVec.z;
    }
  }

  useFrame(({ clock }) => {
    const a = clock.getElapsedTime()
    
    // 更新平面颜色
    updateColorPlane(posPlane, a)
    attrColorPlane.needsUpdate = true
    
    // 更新球体位置和颜色
    updatePosSphere(a)
    updateColorSphere(a)
    attrPosSphere.needsUpdate = true
    attrColorSphere.needsUpdate = true
    
    refSphere.current.rotation.y = a
  })

  return (
    <>
      <mesh
        {...props}
        ref={refPlane}
        position={[0, 0, 0]}
        scale={[20, 20, 0]}>
        <planeGeometry />
        <meshBasicMaterial color='white' vertexColors />
      </mesh>
      <mesh
        {...props}
        ref={refSphere}
        position={[0, 0, 0]}>
        <sphereGeometry args={[2, 64, 64]} />
        <meshBasicMaterial vertexColors />
      </mesh>
    </>
  )
}

export default Background

关键修改说明

  • 修正BufferAttribute数组复用:将attrPosSphere的初始化改为使用newPosSphere数组,确保位置和颜色属性的内存空间完全独立。
  • 保存原始顶点数据:在useEffect中保存球体原始顶点的副本,后续位置计算基于原始值,避免顶点偏移持续累积。
  • 移除冗余的setAttribute调用:在useEffect中一次性初始化几何体的color和position属性,useFrame中仅更新数组并标记needsUpdate,避免属性引用被频繁重置。
  • 修正函数命名:将updatePosPlane改为updateColorPlane,使其语义与功能匹配,提升代码可读性。
  • 移除Material上的冗余needsUpdate:needsUpdate是BufferAttribute的属性,Material上不需要设置该属性。

内容的提问来源于stack exchange,提问作者Luca Ponzanelli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 04:33:12