Skip to content

Commit

Permalink
feat: instancedattribute
Browse files Browse the repository at this point in the history
  • Loading branch information
drcmda committed Aug 16, 2024
1 parent 5cf9132 commit 1063a8d
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3473,6 +3473,31 @@ function App() {
}
```

If your custom materials need instanced attributes you can create them using the `InstancedAttribute` component. It will automatically create the buffer and update it when the component changes. The `defaultValue` can have any stride, from single floats to arrays.

```jsx
<Instances ref={ref} limit={20}>
<boxGeometry />
<someSpecialMaterial />
<InstancedAttribute name="foo" defaultValue={1} />
<Instance position={[-1.2, 0, 0]} foo={10} />
</Instances>
```

```glsl
# vertex
attribute float foo;
varying float vFoo;
void main() {
...
vFoo = foo;
# fragment
varying float vFoo;
void main() {
...
```

👉 Note: While creating instances declaratively keeps all the power of components with reduced draw calls, it comes at the cost of CPU overhead. For cases like foliage where you want no CPU overhead with thousands of intances you should use THREE.InstancedMesh such as in this [example](https://codesandbox.io/s/grass-shader-5xho4?file=/src/Grass.js).

#### Merged
Expand Down
50 changes: 49 additions & 1 deletion src/core/Instances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export type InstanceProps = JSX.IntrinsicElements['positionMesh'] & {
context?: React.Context<Api>
}

export type InstancedAttributeProps = JSX.IntrinsicElements['instancedBufferAttribute'] & {
name: string
defaultValue: any
}

type InstancedMesh = Omit<THREE.InstancedMesh, 'instanceMatrix' | 'instanceColor'> & {
instanceMatrix: THREE.InstancedBufferAttribute
instanceColor: THREE.InstancedBufferAttribute
Expand Down Expand Up @@ -101,6 +106,9 @@ const translation = /* @__PURE__ */ new THREE.Vector3()
const rotation = /* @__PURE__ */ new THREE.Quaternion()
const scale = /* @__PURE__ */ new THREE.Vector3()

const isInstancedBufferAttribute = (attr: any): attr is THREE.InstancedBufferAttribute =>
attr.isInstancedBufferAttribute

export const Instance = /* @__PURE__ */ React.forwardRef(({ context, children, ...props }: InstanceProps, ref) => {
React.useMemo(() => extend({ PositionMesh }), [])
const group = React.useRef<JSX.IntrinsicElements['positionMesh']>()
Expand Down Expand Up @@ -144,6 +152,14 @@ export const Instances: ForwardRefComponent<InstancesProps, THREE.InstancedMesh>

let iterations = 0
let count = 0

const attributes = React.useRef<[string, THREE.InstancedBufferAttribute][]>([])
React.useLayoutEffect(() => {
attributes.current = Object.entries(parentRef.current.geometry.attributes).filter(([name, value]) =>
isInstancedBufferAttribute(value)
) as [string, THREE.InstancedBufferAttribute][]
})

useFrame(() => {
if (frames === Infinity || iterations < frames) {
parentRef.current.updateMatrix()
Expand Down Expand Up @@ -183,7 +199,7 @@ export const Instances: ForwardRefComponent<InstancesProps, THREE.InstancedMesh>

return (
<instancedMesh
userData={{ instances }}
userData={{ instances, limit, frames }}
matrixAutoUpdate={false}
ref={parentRef}
args={[null as any, null as any, 0]}
Expand Down Expand Up @@ -258,3 +274,35 @@ export function createInstances() {
)),
]
}

export const InstancedAttribute = React.forwardRef(({ name, defaultValue }: InstancedAttributeProps, fref) => {
const ref = React.useRef<THREE.InstancedBufferAttribute>(null!)
React.useImperativeHandle(fref, () => ref.current, [])
React.useLayoutEffect(() => {
const parent = (ref.current as any).__r3f.parent
const value = Array.isArray(defaultValue) ? defaultValue : [defaultValue]
const array = Array.from({ length: parent.userData.limit }, () => value).flat()
ref.current.array = new Float32Array(array)
ref.current.itemSize = value.length
ref.current.count = array.length / ref.current.itemSize
}, [name])
let iterations = 0
useFrame(() => {
const parent = (ref.current as any).__r3f.parent
if (parent.userData.frames === Infinity || iterations < parent.userData.frames) {
for (let i = 0; i < parent.userData.instances.length; i++) {
const instance = parent.userData.instances[i].current
const value = instance[name]
if (value !== undefined) {
ref.current.set(
Array.isArray(value) ? value : typeof value.toArray === 'function' ? value.toArray() : [value],
i * ref.current.itemSize
)
ref.current.needsUpdate = true
}
}
iterations++
}
})
return <instancedBufferAttribute ref={ref} attach={`geometry-attributes-${name}`} usage={THREE.DynamicDrawUsage} />
})

0 comments on commit 1063a8d

Please sign in to comment.