Fix: bind sampler uniforms to a 1x1 transparent texture — null samplers broke shader linking, so no ghosts rendered

This commit is contained in:
2026-07-24 15:43:17 +10:00
parent 5357c8464d
commit 2e37ca1001
+15 -4
View File
@@ -20,10 +20,20 @@ const objCache = new Map();
const texLoader = new THREE.TextureLoader();
const texCache = new Map();
/* A sampler2D uniform must ALWAYS be bound to a real texture: leaving it null makes
* the shader program fail to link on many GPUs, which would break every untextured
* ghost. This 1x1 fully-transparent pixel is the safe no-op binding. */
const EMPTY_TEX = (() => {
const t = new THREE.DataTexture(new Uint8Array([0, 0, 0, 0]), 1, 1, THREE.RGBAFormat);
t.needsUpdate = true;
return t;
})();
function loadTexture(url) {
if (!url) return null;
if (!texCache.has(url)) {
const t = texLoader.load(url);
const t = texLoader.load(url, undefined, undefined,
() => console.warn('texture failed to load:', url)); // keeps the ghost alive
t.colorSpace = THREE.SRGBColorSpace;
texCache.set(url, t);
}
@@ -41,8 +51,8 @@ function gradientMaterial(top, bottom, opacity, faceTex, torsoTex, bands) {
uMinY: { value: 0 },
uMaxY: { value: 1 },
uTime: { value: 0 },
faceMap: { value: faceTex || null },
torsoMap: { value: torsoTex || null },
faceMap: { value: faceTex || EMPTY_TEX },
torsoMap: { value: torsoTex || EMPTY_TEX },
hasFace: { value: faceTex ? 1 : 0 },
hasTorso: { value: torsoTex ? 1 : 0 },
// [centreY, halfHeight, halfWidth] in normalized model space
@@ -136,7 +146,8 @@ function pickModel(manifest, ov) {
export async function buildGhost(ghost, gradients, manifest, characters) {
const ov = resolveOverrides(ghost, characters);
const grad = gradients[ghost.color] || gradients.Blue;
const grad = (gradients && (gradients[ghost.color] || gradients.Blue))
|| { top: '#51eaf1', bottom: '#529eff' }; // survive a missing/!oddly-shaped gradient payload
const top = ov.topColor || grad.top;
const bottom = ov.bottomColor || grad.bottom;
const opacity = ov.opacity != null ? Number(ov.opacity) : 0.92;