How to use spritesheet in this special case?

Hi,

My application looks like this :

Actually i know how to set my scene to put a texture on the top face but not an individual sprite texture…(5x5)

I would like on each top surfaces of my cubes put this texture below separate by 5 by 5:

lion

So i use this online tools who help me to divide the picture into spritesheet :

result :

After i upload all my separate textures to have one picture with this :
https://www.codeandweb.com/tp-online/index.html?layout=horizontal&show-settings=false

The result is this :

Question

My question is how to apply this spritesheet on the faces on my cubes ?

If you have a better solution, i take it :slight_smile:

Ressources

Here is my codepen :

Thanks …

Hi!

A very rough example with computed UVs for instances: https://jsfiddle.net/prisoner849/kb3rtzag/

2 Likes

Waouw and moreover you use instances :grin:

Just two questions to understand your magical code :

1

How do you do to convert the picture to this :

2

I have always find that shader is very difficult to understand… what doesn’t mean minFilter and magFilter

this line also, i don’t understand nothing, could you teach me ? :
vec4 texelColor = texture2D( map, instUv + ${texStep} * vUv );
texelColor = mapTexelToLinear( texelColor );
diffuseColor *= texelColor;

let m = new THREE.MeshBasicMaterial({
  map: new THREE.TextureLoader().load(imgData, tex => {
    tex.minFilter = THREE.NearestFilter;
    tex.magFilter = THREE.NearestFilter;
  }),
  onBeforeCompile: shader => {
  	shader.vertexShader = `
    	attribute vec2 iUv;
      varying vec2 instUv;
      ${shader.vertexShader}
    `.replace(
    	`#include <begin_vertex>`,
      `#include <begin_vertex>
      	instUv = iUv;
      `
    );
    console.log(shader.vertexShader);
  	shader.fragmentShader = `
    	varying vec2 instUv;
    	${shader.fragmentShader}
    `.replace(
    	`#include <map_fragment>`,
      `
      #ifdef USE_MAP
        vec4 texelColor = texture2D( map, instUv + ${texStep} * vUv );
        texelColor = mapTexelToLinear( texelColor );
        diffuseColor *= texelColor;
      #endif
      `
    );
  	console.log(shader.fragmentShader)
  }
});
  1. Any online image to base64 converter.
  2. instUv + ${texStep} * vUv: multiply UV coords (that are in range 0..1) with the value of division of resolution by amount of cubes per line, plus computed uv coord for an instance.

About .minFilter and .magFilter you can read in the docs on Texture: three.js docs
I used THREE.NearestFilter to make picture pixelated, so you can see how many texels are on a box’s sides.

1 Like

Hi @prisoner849 ,

I’m searching how to update your code to have for example 8 cubes, 2 on x axis, 2 on y axis and 2 on z axis.

In the past, i have write this instance class who works very well for this use :

import * as THREE from "https://threejs.org/build/three.module.js";
import {
	TWEEN
} from 'https://unpkg.com/three@0.125.2/examples/jsm/libs/tween.module.min'
import * as utils from "../utilities/utils.js"
import create_geo from "./Geometry.js";
import create_mat from "./Material.js";


class Instance {
	constructor(config) {
		this.config = config;
		g.cube = create_geo(this.config);
		m.cube = create_mat(this.config);
		this.mesh = new THREE.InstancedMesh(g.cube, m.cube, this.config.count);
		this.config["scene"].add(this.mesh);
		this.sum = this.config.count
		this.dummy = []
		this.set_position_dummies();
	}

	set_position_dummies() {
		for (let i = 0; i < this.config.units; i++) {
			this.dummy[i] = []
			for (let j = 0; j < this.config.units; j++) {
				this.dummy[i][j] = []
				for (let k = 0; k < this.config.units; k++) {
					this.dummy[i][j][k] = new THREE.Object3D();
					this.dummy[i][j][k].position.set(i * this.config.geometry_size_x, .5 + j * this.config.geometry_size_x, k * this.config.geometry_size_x)
					this.config["scene"].add(this.dummy[i][j][k]);
					this.dummy[i][j][k].updateMatrix();
				}
			}
		}
	}

	animate() {
		this.mesh.instanceMatrix.needsUpdate = true;
		for (let i = 0; i < this.config.units; i++) {
			for (let j = 0; j < this.config.units; j++) {
				for (let k = 0; k < this.config.units; k++) {

					this.dummy[i][j][k].updateMatrix()
					this.mesh.setMatrixAt(i, this.dummy[i][j][k].matrix);
				}
			}
		}
	}
}

export default Instance;

how do you adapt this lines of code with my instance class to project the texture correctly ?

    uvs.push(j / size, i / size);
    o.setMatrixAt(j * size + i, dummy.matrix);

I try a lot of combinations but without success :grimacing:

I admit that I am a little lost to transpose this correctly

Thanks in advance.

I success to draw my instance but not the projection of the texture :frowning:
Could you help me ?

import * as THREE from "https://cdn.skypack.dev/three";
import {
	OrbitControls
} from "https://cdn.skypack.dev/three/examples/jsm/controls/OrbitControls.js";

let size = 2;
let halfSize = size / 2;

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(halfSize, halfSize, halfSize / 2).multiplyScalar(6.5);
let renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x404040);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
	camera.aspect = innerWidth / innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize(innerWidth, innerHeight);
})

let controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(halfSize, halfSize, 0).multiplyScalar(1.5);
controls.update();

let texStep = 1 / size;

let g = new THREE.BoxGeometry();
let m = [
	new THREE.MeshBasicMaterial({}),
	new THREE.MeshBasicMaterial({}),
	new THREE.MeshBasicMaterial({}),
	new THREE.MeshBasicMaterial({}),
	//new THREE.MeshBasicMaterial({}),
	new THREE.MeshBasicMaterial({
	map: new THREE.TextureLoader().load(imgData, tex => {
		tex.minFilter = THREE.NearestFilter;
		tex.magFilter = THREE.NearestFilter;
	}),
	onBeforeCompile: shader => {
		shader.vertexShader = `
	attribute vec2 iUv;
      varying vec2 instUv;
      ${shader.vertexShader}
    `.replace(
	    `#include <begin_vertex>`,
	    `#include <begin_vertex>
	instUv = iUv;
      `
    );
		shader.fragmentShader = `
	varying vec2 instUv;
	${shader.fragmentShader}
    `.replace(
	    `#include <map_fragment>`,
	    `
      #ifdef USE_MAP
	vec4 texelColor = texture2D( map, instUv + ${texStep} * vUv );
	texelColor = mapTexelToLinear( texelColor );
	diffuseColor *= texelColor;
      #endif
      `
    );
	}
}),
]

let o = new THREE.InstancedMesh(g, m, size * size * size);
scene.add(o);

let dummy = new THREE.Object3D();
let uvs = [];
let b =0 // for increment
for (let _x = 0; _x < size; _x++) {
	for (let _z = 0; _z < size; _z++) {
		dummy[_x]=[]
		for (let _y = 0; _y < size; _y++) {
			dummy[_x][_y]= new THREE.Object3D();
			// x, y, z
			dummy[_x][_y].position.set(_x, _y, _z).multiplyScalar(1.1);
			dummy[_x][_y].updateMatrix();
			uvs.push(_y / size, _x / size,_z);
			o.setMatrixAt(b, dummy[_x][_y].matrix);
			b++
		}
	}
}
g.setAttribute("iUv", new THREE.InstancedBufferAttribute(new Float32Array(uvs), 2));


renderer.setAnimationLoop(() => {
	renderer.render(scene, camera);
})


01

Here a better jsfiddle but i have still a problem with the uv projection of my top face. The picture of the lion don’t appear on the top but on the side…

i have tested many combinations without success

			//uvs.push(_y / size, _x / size,_z);
			//uvs.push(_x / size, _z /size ,_y/size);
			uvs.push(_x / size, _y /size);

https://jsfiddle.net/espace3d/raLfbmyk/2/

Try this: uvs.push(_x / size, 1 - ((_z + 1) /size));

1 Like

thanks a lot

Just one last question: why the shader is not well projected when I replace the instance by meshes?

Normally it should be the same?

What should I do… I need to use the mesh instead of the instance for the game I’m working on now.

What is the trick?

https://jsfiddle.net/espace3d/9ok0jah7/3/

import * as THREE from "https://cdn.skypack.dev/three";
import {
	OrbitControls
} from "https://cdn.skypack.dev/three/examples/jsm/controls/OrbitControls.js";

let size = 5;
let halfSize = size / 2;

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(halfSize, halfSize, halfSize / 2).multiplyScalar(6.5);
let renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x404040);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
	camera.aspect = innerWidth / innerHeight;
	camera.updateProjectionMatrix();
	renderer.setSize(innerWidth, innerHeight);
})

let controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(halfSize, halfSize, 0).multiplyScalar(1.5);
controls.update();

let texStep = 1 / size;

let g = new THREE.BoxGeometry();
let m = [
	new THREE.MeshBasicMaterial({
		transparent :true,
		opacity :.1,
	}),
	new THREE.MeshBasicMaterial({
		transparent :true,
		opacity :.1,
	}),
	new THREE.MeshBasicMaterial({
	map: new THREE.TextureLoader().load(imgData, tex => {
		tex.minFilter = THREE.NearestFilter;
		tex.magFilter = THREE.NearestFilter;
	}),
	onBeforeCompile: shader => {
		shader.vertexShader = `
	attribute vec2 iUv;
      varying vec2 instUv;
      ${shader.vertexShader}
    `.replace(
	    `#include <begin_vertex>`,
	    `#include <begin_vertex>
	instUv = iUv;
      `
    );
		shader.fragmentShader = `
	varying vec2 instUv;
	${shader.fragmentShader}
    `.replace(
	    `#include <map_fragment>`,
	    `
      #ifdef USE_MAP
	//vec4 texelColor = texture2D( map, instUv + ${texStep} * vUv );
	vec4 texelColor = texture2D( map, instUv + ${texStep} * vUv );
	texelColor = mapTexelToLinear( texelColor );
	diffuseColor *= texelColor;
      #endif
      `
    );
	}
}),
	new THREE.MeshBasicMaterial({
		transparent :true,
		opacity :.1,
	}),
	new THREE.MeshBasicMaterial({
		transparent :true,
		opacity :.1,
	}),
]

//let o = new THREE.InstancedMesh(g, m, size * size * size);
//scene.add(o);

let dummy =[]

let uvs = [];
for (let _x = 0; _x < size; _x++) {
	for (let _z = 0; _z < size; _z++) {
		dummy[_x]=[]
		for (let _y = 0; _y < size; _y++) {
			dummy[_x][_y]= new THREE.Mesh(g,m);
			// x, y, z
			dummy[_x][_y].position.set(_x, _y, _z).multiplyScalar(1.1);
			scene.add(dummy[_x][_y])
                        dummy[_x][_y].updateMatrix();
 			uvs.push(_x / size, 1 - ((_z + 1) /size));
		}
	}
}
g.setAttribute("iUv", new THREE.InstancedBufferAttribute(new Float32Array(uvs), 2));


renderer.setAnimationLoop(() => {
	renderer.render(scene, camera);
})


Then you don’t need to modify materials, you have to calculate UV for each box’s geometry. :thinking:

Hello Prisoner,

How do you calculate the UV for the geometry of each box?

I understand that the shader is a general material and then you cut it to project it on each box.

While searching, I found this which explains that the texture must be at 0.5,0.5 in object coordinates:

http://paulyg.f2s.com/uv.htm

So I tried to increment the texture in different ways:

      //uvs.push(_x / size, 1 - ((_z + 1) /size));
      //uvs.push(1 / size*_x, 1/size* _z);
      //uvs.push(.5 + _x / size,.5 + _z/size);
      //uvs.push(.5 + _x / size,_z/size);
      //uvs.push(.5 + _x / size,.5 * _z/size);
      //uvs.push(_x,0.5 * _z/size);
      //uvs.push(_z,0.5 * _x/size);
       //uvs.push(_x,_z)
       //uvs.push(_z,0.5 + _x/size)
       //uvs.push(_z * _z/size,_x/size)

02

But the texture is still stuck on a single image that repeats in the z and x axis. No way at least to have different images, so I can figure out how to change my increment.

By having fun to cut the image in several images I succeeded, but it is laborious and your solution is obviously better. Can you point me in the right direction, as it is I don’t see a way out of this?

01

function create_grid() {
		const geometry = new THREE.BoxGeometry(1, 1, 1);
		const material = new THREE.MeshBasicMaterial({
			color: 0xffd839
		});
		const texture = []
		let f = 0
		for (let i = 0; i < 5; i++) {
			texture[i] = []
			for (let j = 0; j < 5; j++) {
				texture[i][j] = new THREE.TextureLoader().load('img/spritesheet/' + 'tile' + f + '.png');
				f++
			}
		}

		const cube = []
		for (let i = 0; i < 5; i++) {
			cube[i] = []
			for (let j = 0; j < 5; j++) {
				cube[i][j] = []
				for (let k = 0; k < 2; k++) {
					cube[i][j][k] = new THREE.Mesh(geometry, material);
					cube[i][j][k].position.set(i * 1.1, k, j * 1.1)
					scene.add(cube[i][j][k]);
				}
			}
		}
		let b = 0
		for (let i = 0; i < 5; i++) {
			for (let j = 0; j < 5; j++) {

				cube[i][j][1].material = [
					new THREE.MeshBasicMaterial({
						color: 0xfaad80,
					}),
					new THREE.MeshBasicMaterial({
						color: 0xfaad80,
					}),

					//top surface
					new THREE.MeshPhongMaterial({
						map: texture[j][i]
					}),
					new THREE.MeshBasicMaterial({
						color: 0xfaad80,
					}),
					new THREE.MeshBasicMaterial({
						color: 0xfaad80,
					}),
					new THREE.MeshBasicMaterial({
						color: 0xfaad80,
					}),
				]
				b++
			}

		}
	}

I used my previous jsfiddle as a base: https://jsfiddle.net/prisoner849/tpL54uyq/

2 Likes