perf(web): share card texture via shader UV transform

Move the card sprite repeat/offset out of per-card texture clones and
into a uniform injected into the material shader. Cards now share the
deck sheet (one GPU upload), the shader, and the geometry, with only
per-card material uniforms differing, so navigating a deck no longer
re-uploads the sheet on every step.
This commit is contained in:
2026-08-14 10:46:09 +08:00
parent c665212209
commit 002bcb324b
5 changed files with 173 additions and 48 deletions
@@ -0,0 +1,44 @@
import * as THREE from 'three';
/**
* Per-card UV transform injected into a `MeshStandardMaterial` shader.
*
* Cards share one texture (the deck's sprite sheet, cached by drei) and one
* geometry, but each card samples a different sprite cell. Rather than cloning
* the texture per card (which re-uploads the sheet on every GPU bind), the
* repeat/offset is pushed into the material as a uniform. The shader source is
* identical across cards, so three.js still compiles a single shared program.
*
* We inject our own uniform instead of setting `texture.repeat`/`offset`
* because three r185 derives the map UVs from a `mapTransform` matrix that is
* refreshed from `map.matrix` every frame, overwriting any per-material
* transform we set on the shared texture.
*/
const VERTEX_INJECT = /* glsl */ `
#include <uv_vertex>
vMapUv = uv * uMapRepeat + uMapOffset;
`;
/**
* Apply a repeat/offset to a card material's map sampling. Call once per
* material (the transform is baked into the shader). `repeat`/`offset` are
* copied, so the caller may reuse the vectors.
*/
export function applyMapTransform(
material: THREE.MeshStandardMaterial,
repeat: THREE.Vector2,
offset: THREE.Vector2,
): void {
// Clone eagerly so later mutation of the caller's vectors can't leak into
// the uniform once the material is compiled.
const r = repeat.clone();
const o = offset.clone();
material.onBeforeCompile = (shader) => {
shader.uniforms.uMapRepeat = { value: r };
shader.uniforms.uMapOffset = { value: o };
shader.vertexShader = shader.vertexShader.replace(
'#include <uv_vertex>',
VERTEX_INJECT,
);
};
}