How can I swap texture images based on camera distance?

I’m working to create a virtual art gallery of my photography. Each “gallery” will show around 30-50 photos, which will be a mix of 2D, “flat” stereographs, and 180-degree stereographs photos. As you can imagine, the textures could be sizable.

Is there a “built-in” mechanism for loading a low-resolution texture and replacing it with a higher-resolution version when the camera is a certain distance away – and ideally, swapping it back out when the camera moves away?

The other only solution I can think of would be to create a collision detection mesh in front of each photograph and use those events to swap out the textures manually.

Is there a simpler way?

If your textures are on a mesh like a plan, you can calculate the distance between the camera and the mesh, then change the texture as soon as the agreed distance is passed.
I don’t know of an easier way.

1 Like

Yup, they’re on individual planes.

If there’s no auto-hot-swap Texture variant, I guess the question would be whether the “usual” or “recommended” way of handling this (this must be A Thing Other People Do, right?) would be to rely on mesh collision events, or if there’s a performance or other benefit to looping through the photos on each frame render manually and checking signedDistanceTo(scene.activeCamera.position).

I wonder if there’s a way we could use Levels of Detail (LOD) | Babylon.js Documentation (variable mesh detail based on distance to viewer) for this

PG from LOD Documentation: Babylon.js Playground

Definitely seems like a possibility! Though it would require swapping out the entire mesh rather than just the texture.

Here’s what I came up with – polling on each frame. Seems to work fine. I don’t have a Playground for it, but hopefully this gets the gist across in case someone wanders through looking for an answer.

interface IPhoto {
	url: string
	urlThumbnail: string
	isLowRes: boolean
	material: StandardMaterial
	textureLow?: Texture
	textureMed?: Texture
}

function setPhotoTexture(scene: Scene, photo: IMountedPhoto, isLowRes: boolean): void {
	photo.isLowRes = isLowRes
	const currentTexture = isLowRes ? photo.textureLow : photo.textureMed
	if (currentTexture == undefined) {
		const t = new Texture(isLowRes ? photo.urlThumbnail : photo.url, scene, undefined, undefined, undefined, function () {
			photo.material.diffuseTexture = t
			if (isLowRes) {
				photo.textureLow = t
			} else {
				photo.textureMed = t
			}
		})
	} else {
		photo.material.diffuseTexture = currentTexture
	}
}

scene.onBeforeCameraRenderObservable.add((camera: Camera) => {
	for (const p of mountedPhotos) {
		const d = p.mesh.getDistanceToCamera(camera)
		const isFarAway = d > 10
		if (isFarAway && !p.isLowRes) {
			setPhotoTexture(scene, p, true)
		} else if (!isFarAway && p.isLowRes) {
			setPhotoTexture(scene, p, false)
		}
	}
})
3 Likes