Skip to content

RFC: should useTexture accept an options object (colorSpace, repeat, wrap...)? #1480

Description

@alvarosabu

Summary

Right now useTexture only takes a path. Any texture setting you need (colorSpace, repeat, wrapS, flipY, anisotropy...) has to be applied by hand after the texture loads, with a watch.

We would like to hear from the community before changing the API. Should useTexture (and useTextures) accept an options object?

How it works today

import { useTexture } from '@tresjs/cientos'
import { SRGBColorSpace, RepeatWrapping } from 'three'

const { state: grassDiffuseMap } = useTexture('/textures/grass/splat.jpg')

watch(grassDiffuseMap, (tex) => {
  if (!tex) { return }
  tex.colorSpace = SRGBColorSpace
  tex.wrapS = tex.wrapT = RepeatWrapping
  tex.repeat.set(4, 4)
}, { immediate: true })

This works, but there are a few sharp edges:

  1. The if (tex) guard doesn't do what it looks like it does. useTexture passes initialValue: new Texture(), so state holds a truthy placeholder Texture from the very first tick. The immediate run applies your settings to a throwaway object, and then they get applied again when the real texture arrives.

  2. Reloading resets everything. When the path is a ref and it changes, useLoader re-executes and returns a new Texture. A watch catches that. onMounted, whenever, or a one-shot watch without immediate do not. It is an easy trap to fall into.

  3. There is a window where the wrong texture is live. The texture reaches the material through reactivity before the watcher callback runs. In practice you can get a frame with the wrong color space or wrapping. Applying settings inside the load callback removes that window entirely.

  4. It is a lot of ceremony for "this texture is sRGB".

Proposal: an options object

const { state: grassDiffuseMap } = useTexture('/textures/grass/splat.jpg', {
  colorSpace: SRGBColorSpace,
  wrapS: RepeatWrapping,
  wrapT: RepeatWrapping,
  repeat: [4, 4],
  anisotropy: 8,
  manager,                     // currently not supported at all, see below
  onLoad: (tex) => { /* escape hatch for anything the object can't express */ },
})

Settings would be applied inside the loader callback, before the texture ever becomes reactive state. Reloads reapply them automatically.

Design questions we'd like input on

1. Flat partial-texture object, or a transform callback?

Option A, a Partial<Texture>-ish object (as above). Reads like template props, discoverable through autocomplete, declarative.

Option B, just a callback:

useTexture('/textures/grass/splat.jpg', {
  onLoad: (tex) => {
    tex.colorSpace = SRGBColorSpace
    tex.repeat.set(4, 4)
  },
})

Simpler to implement and zero API surface to maintain, but less declarative and doesn't read like the rest of Tres.

Our current leaning is both: the object for the common 90%, the callback as an escape hatch.

2. Should repeat: [4, 4] work?

repeat, offset and center are Vector2, so a plain Object.assign would silently do nothing useful with an array. To make it behave like a template prop (:repeat="[4, 4]") we need the same coercion patchProp does in core (packages/core/src/core/nodeOps.ts). That logic currently lives inside the nodeOps closure and isn't exported, so this means either extracting it into a reusable util in core, or writing a smaller local applier in cientos.

Is array-to-vector coercion worth that, or would you rather write repeat: new Vector2(4, 4) and keep the implementation dumb?

3. Should this also cover useTextures?

useTextures has the same gap. Per-texture options, or one shared options object for the whole batch, or both?

4. Anything else that belongs in there?

manager is an obvious one. useTexture doesn't accept a LoadingManager today, even though the <UseTexture /> component declares a manager prop and never passes it along. Whatever we do here should fix that.

Prior art

Both react-three-fiber's useTexture and drei accept a callback for exactly this reason. Curious whether people prefer that shape or something more declarative.


If you use useTexture in a real project, we'd love to know which settings you find yourself applying after load most often. That tells us whether the flat object is worth it or the callback is enough.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions