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,40 @@
import { describe, expect, it } from 'vitest';
import * as THREE from 'three';
import { applyMapTransform } from './cardMaterial';
describe('applyMapTransform', () => {
it('injects the repeat/offset uniforms and UV transform into the shader', () => {
const mat = new THREE.MeshStandardMaterial();
applyMapTransform(mat, new THREE.Vector2(0.5, 0.25), new THREE.Vector2(0.1, 0.2));
expect(mat.onBeforeCompile).toBeTypeOf('function');
const shader = {
uniforms: {} as Record<string, { value: unknown }>,
vertexShader: '#include <uv_vertex>\nvoid main() {}',
};
mat.onBeforeCompile!(shader as never, {} as never);
// Uniforms are copied, so the caller's vectors stay reusable.
expect(shader.uniforms.uMapRepeat!.value).toEqual(new THREE.Vector2(0.5, 0.25));
expect(shader.uniforms.uMapOffset!.value).toEqual(new THREE.Vector2(0.1, 0.2));
// The override is injected right after the chunk include, which stays in
// place (it declares `vMapUv`/`uv`).
expect(shader.vertexShader).toContain('#include <uv_vertex>\n\tvMapUv = uv * uMapRepeat + uMapOffset;');
});
it('copies the vectors so later mutation of the inputs has no effect', () => {
const mat = new THREE.MeshStandardMaterial();
const repeat = new THREE.Vector2(1, 1);
const offset = new THREE.Vector2(0, 0);
applyMapTransform(mat, repeat, offset);
repeat.set(9, 9);
offset.set(9, 9);
const shader = { uniforms: {} as Record<string, { value: unknown }>, vertexShader: '' };
mat.onBeforeCompile!(shader as never, {} as never);
expect(shader.uniforms.uMapRepeat!.value).toEqual(new THREE.Vector2(1, 1));
expect(shader.uniforms.uMapOffset!.value).toEqual(new THREE.Vector2(0, 0));
});
});