Files
tts-workshop/apps/web/src/components/viewers/cardMaterial.ts
T
hypercross 7163451d1f fix(web): declare card shader UV uniforms
three.js does not auto-declare uniforms added via onBeforeCompile, so the
injected uMapRepeat/uMapOffset were undeclared and the card shader failed
to compile, rendering cards transparent. Prepend the declarations to the
vertex shader.
2026-08-14 10:52:54 +08:00

51 lines
1.9 KiB
TypeScript

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.
*/
// Uniforms added via `onBeforeCompile` are not auto-declared by three.js, so
// they must be declared in the GLSL explicitly (the built-in `mapTransform` is
// declared in `uv_pars_vertex.glsl.js`).
const UNIFORM_DECLS = /* glsl */ `
uniform vec2 uMapRepeat;
uniform vec2 uMapOffset;
`;
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 =
UNIFORM_DECLS +
shader.vertexShader.replace('#include <uv_vertex>', VERTEX_INJECT);
};
}