Mapping an image onto a sphere

I have a mesh with an onBeforeCompile shader, and would like to prevent the image from stretching, like so

Issue is, I already have a predefined shader, and was wondering how I could integrate it into this code?

material.onBeforeCompile = shader => {
	const custom_map_fragment = THREE.ShaderChunk.map_fragment.replace(
		`diffuseColor *= sampledDiffuseColor;`,
		`diffuseColor = vec4( mix( diffuse, sampledDiffuseColor.rgb, sampledDiffuseColor.a ), opacity );`
	);
		
	shader.fragmentShader = shader.fragmentShader.replace( '#include <map_fragment>', custom_map_fragment );
		
	shader.uniforms.atlasDimensions = { value: new THREE.Vector2( ).copy( new THREE.Vector2(4, 4) ) };
	shader.vertexShader = `
	uniform vec2 atlasDimensions;
	attribute float textureChunkIndex;
	${shader.vertexShader}
	`.replace(
	`#include <uv_vertex>`,
	`#include <uv_vertex>

	float chunkId = floor(textureChunkIndex + 0.1);
	vec2 idUV = vec2(mod(chunkId, atlasDimensions.x), floor(chunkId / atlasDimensions.x));

	vec2 normalizedDimensions = 1. / atlasDimensions;
		
	vUv = (uv + idUV) * normalizedDimensions;

	`
	);
}

Can you share picture of what you have, ie. in the scene not the code itself, vs what you’d like it to look like?

1 Like

Hello. I had this question posted - Add texture to sphere without distortion

Essentially, I want my texture-atlas to look like this on a sphere: https://www.youtube.com/watch?app=desktop&v=qRYu0GDU-qM&ab_channel=JenAbbottCreates (first few seconds of video)

Did you try using just basic material with .map = your texture?
The default sphere mapping might be fine…

1 Like

Hi, yes but the texture naturally becomes distorted on a sphere. Trying to find a way to apply the texture without the distortion effect (examples in my previous reply)

Can u try texture.offset.set(0, .5 )

And texture.wrapS=texture.wrapT=THREE.RepeatWrapping

?

2 Likes

My textures have this by default on load

texture.colorSpace = "srgb";
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( repeat, repeat );
texture.encoding = THREE.sRGBEncoding;

resources.textures[name] = texture;

Code

var atlasTexture = resources.textures['faces-atlas'].clone( );
atlasTexture.needsUpdate = true;
var toonTexture = resources.textures['tone-3'].clone( );
toonTexture.minFilter = toonTexture.magFilter = THREE.NearestFilter;
	
let characterGeo = new THREE.SphereGeometry( 0.5, 32, 16 );
let characterMat = new THREE.MeshToonMaterial( {
	map: atlasTexture,
	gradientMap: toonTexture
} );
	
characterMat.onBeforeCompile = shader => {
	const custom_map_fragment = THREE.ShaderChunk.map_fragment.replace(
		`diffuseColor *= sampledDiffuseColor;`,
		`diffuseColor = vec4( mix( diffuse, sampledDiffuseColor.rgb, sampledDiffuseColor.a ), opacity );`
	);
		
	shader.fragmentShader = shader.fragmentShader.replace( '#include <map_fragment>', custom_map_fragment );
		
	shader.uniforms.atlasDimensions = { value: new THREE.Vector2( ).copy( new THREE.Vector2(4, 4) ) };
	shader.vertexShader = `
	uniform vec2 atlasDimensions;
	attribute float textureChunkIndex;
	${shader.vertexShader}
	`.replace(
	`#include <uv_vertex>`,
	`#include <uv_vertex>

	float chunkId = floor(textureChunkIndex + 0.1);
	vec2 idUV = vec2(mod(chunkId, atlasDimensions.x), floor(chunkId / atlasDimensions.x));

	vec2 normalizedDimensions = 1. / atlasDimensions;
		
	vUv = (uv + idUV) * normalizedDimensions;

	`
	);
}
	
this.characterInstanceMesh = new THREE.InstancedMesh( characterGeo, characterMat, amount );
this.characterInstanceMesh.material.needsUpdate = true;

Texture atlas

Result (becomes stretched)

The classic, here is the link to a similar chat recently.

1 Like

Hi Attila, sorry I’m having trouble integrating into the shader code without breaking it, as I don’t know much about it. I’m already using the coordinates of the atlasDimensions, I don’t know how to apply this as well

An attempt from scratch, how to use a texture atlas, with some modifications for fragment shader

Demo: https://codepen.io/prisoner849/full/WNBrpJO

Maybe will be helpful )

8 Likes

Hi Prisoner849,

how would this work when the spheres are instanced?

Also side question, how do I go about learning this custom shader code?

An instanced buffer attribute of vec2 to store tile’s coords, for example.

Trying to change numbers in shader code, looking what it changes in visuals.
Reading the forum, docs and other resources.
In general, the same way you learn other things )

2 Likes

I think this works for a code drawn texture, not for a .png image, since I think the opacity is not specified

When I replace
diffuseColor *= mix(sampledDiffuseColor, vec4(1), step(0.5, max(absUV.x, absUV.y)));
with
diffuseColor = vec4( mix( diffuse, sampledDiffuseColor.rgb, sampledDiffuseColor.a ), opacity ); I get this

Try to add that cut-off part from my approach to yours.
Something like this:

float cutoff = 1. - step(0.5, max(absUV.x, absUV.y));
diffuseColor = vec4( mix( diffuse, sampledDiffuseColor.rgb, sampledDiffuseColor.a * cutoff), opacity );
1 Like

Works great! Up to the point I try to add a float attribute for the textureChunkIndex and I just constantly get ERROR: 0:78: 'attribute' : Illegal use of reserved word

onBeforeCompile: shader => {				
    shader.uniforms.atlasSize = { value: new THREE.Vector2( ).copy( new THREE.Vector2(4, 4) ) };
    shader.uniforms.tile = { value: new THREE.Vector2( 2, 0 ) }
    shader.fragmentShader = `
    uniform vec2 atlasSize;
    uniform vec2 tile;
    attribute float textureChunkIndex; // <-- ERROR: 0:78: 'attribute' : Illegal use of reserved word
    ${shader.fragmentShader}
    `.replace(
    `#include <map_fragment>`,
    `
    vec2 mUV = vMapUv;
    vec2 centerUV = ((mUV - 0.5) * vec2(2., 1.) + vec2(0.5, 0.)) * PI / 2.;
    mUV = centerUV + 0.5;
    vec2 atlasTile = 1. / atlasSize;

    mUV = clamp((mUV + tile) * atlasTile, vec2(0.), vec2(1.));

    vec4 sampledDiffuseColor = texture2D( map, mUV );
			
    vec2 absUV = abs(centerUV);
							
    float cutoff = 1. - step(0.5, max(absUV.x, absUV.y));
    diffuseColor = vec4( mix( diffuse, sampledDiffuseColor.rgb, sampledDiffuseColor.a * cutoff), opacity );
    `
    );
}

I’m trying to change the faces via geometry.setAttribute( 'textureChunkIndex', new THREE.InstancedBufferAttribute( new Uint8Array( textureChunkIndex ), 1 ) ); like in my previous code

Could you provide a minimal live-code working example? jsfiddle, codepen etc.

PS attribute belongs to vertex shader only.

2 Likes

I figured as much, albeit I got confused after everything was written in the fragmentShader

Modification from your code

Hoping for a simple function that changes the face and color of selected instance dynamically

I got this result:

Demo: https://codepen.io/prisoner849/full/abrdgVW

UPD Now animated :crazy_face:

5 Likes

Beautiful, works like a charm! Appreciate your time and patience, thank you!

1 Like

Hi @Vardan_Betikyan ,

I had prepared this example page to send You:

https://jrlazz.eu5.org/anim/image_on_sphere.html

but I think @prisoner849 solution is the best way.!

PS: Based on the stackoverflow.com page that You have referred…

2 Likes