Why does using args vs array/itemSize arguments do not give the same results in bufferAttribute?

Hello,

I have the following code, I want to write it with array and itemSize arguments instead of args like the second code, but it does not give me the same results. I tried to add also the “count” argument to see if it made a difference ( count = {cellData.positions.length/3} ) but this still doesn’t give me the same answer.

   <mesh>
      <bufferGeometry attach="geometry">
        <bufferAttribute attachObject={['attributes', 'position']} args={[cellData.positions, 3]} />
        <bufferAttribute attach="index" args={[cellData.indices, 1]} />
      </bufferGeometry>
      <PixelImageMaterial img={textureAtlas} attach="material" />
    </mesh>
<mesh>
     <bufferGeometry attach="geometry">
        <bufferAttribute attachObject={['attributes', 'position']} array={cellData.positions} itemSize={3} />
        <bufferAttribute attach="index" args={[cellData.indices, 1]} />
      </bufferGeometry>
      <PixelImageMaterial img={textureAtlas} attach="material" />
</mesh>

args is usually better because here you just provide constructor arguments which are safe, typed and documented in the threejs docs. you can leave args behind, i guess props look better, but now you’re dealing with internals. this is how threejs does it: https://github.com/mrdoob/three.js/blob/efbfc67edc7f65cfcc61a389ffc5fd43ea702bc6/src/core/BufferAttribute.js#L22-L24

this.array = array;
this.itemSize = itemSize;
this.count = array !== undefined ? array.length / itemSize : 0;

you should get the same results. you can see an example w/o args here: Threejs journey - CodeSandbox but again, if you don’t, three internals have other stuff in it or they add something tomorrow and your code will fail. i would suggest using args bc why wouldn’t you want to use the constructor arguments that threejs prescribes?

btw in v8 the awkward object/array-attach stuff goes away, you just use attach for everything and dash notation

<mesh>
  <bufferGeometry>
    <bufferAttribute attach="attributes-position" args={[positionArray, 3]} />
    <bufferAttribute attach="attributes-aScale" args={[scaleArray, 1]} />
  </bufferGeometry>

would be the equivalent of

const mesh = new THREE.Mesh()
mesh.geometry = new THREE.BufferGeometry()
geometry.attributes.position = new THREE.BufferAttribute(positionArray, 3)
geometry.attributes.aScale = new THREE.BufferAttribute(scaleArray, 1)
1 Like

Ok, thank you I kept the args.
trying to apply what you explained I also tried to modify the states via refs.
But with this code, the refs do change, but the rendering does not update. I don’t understand how this is possible. To be more precise the update works only if I remove the bufferAttribute attach=“index”…, but I need it in order to display the correct geometries.

useFrame(() => {
    worldData = chunksHook.getState().worldData
    currentData = worldData[cellId]

    if (currentData) {
      mainRef.current.attributes.position.array = currentData.positions
      mainRef.current.attributes.position.count = currentData.positions.length / 3

      mainRef.current.attributes.normal.array = currentData.normals
      mainRef.current.attributes.normal.count = currentData.normals.length / 3

      mainRef.current.attributes.uv.array = currentData.uvs
      mainRef.current.attributes.uv.count = currentData.uvs.length / 2

      mainRef.current.index.array = currentData.indices
      mainRef.current.index.count = currentData.indices.length

    }
  })
  return (
    <mesh ref={ref} castShadow receiveShadow position={cellData.meshPos}>
      <bufferGeometry ref={mainRef} attach="geometry">
        <bufferAttribute attachObject={['attributes', 'position']} args={[cellData.positions, 3]} />
        <bufferAttribute attachObject={['attributes', 'normal']} args={[cellData.normals, 3]} />
        <bufferAttribute attachObject={['attributes', 'uv']} args={[cellData.uvs, 2]} />
        <bufferAttribute attach="index" args={[cellData.indices, 1]} />
      </bufferGeometry>
      <PixelImageMaterial img={textureAtlas} attach="material" />
    </mesh>

Edit:
Some more info, I also got the following error on the console sometimes when there is an update on index ref.

[.WebGL-000067E202396300] GL_INVALID_OPERATION: Insufficient buffer size.

and PixelImageMaterial component contains this code, the problem is maybe linked to there.

const PixelImageMaterial = React.memo(({ img, ...rest }) => (
  <meshBasicMaterial side={THREE.DoubleSide} {...rest}>
    <texture
      attach="map"
      image={img}
      minFilter={THREE.NearestFilter}
      magFilter={THREE.NearestFilter}
      onUpdate={(self) => (self.needsUpdate = true)}
    />
  </meshBasicMaterial>
))

there’s a great article in three docs about this, how to update things: three.js docs

generally you can’t replace buffers and attributes, you can mutate them, but you can’t exceed the length which is most likely where you get that error from. the article above covers buffergeometries and attributes as well. if you do exchange buffers entirely you need to call needsUpdate somewhere, the entire shader will re-compile - i would not recommend it.

generally react+three is just a way to express code, it won’t change how three works or the rules it adheres to. sticking more to the three docs when in doubt will always help.

1 Like