Multiple UVs per vertex with texture from each UV set overlaying

Hi, I’m looking to define multiple UVs per vertex where if part of the texture from uvs1 has transparency the part of the texture from uvs2 will be rendered (and so on)

Is there an out-of-the-box way to do this? Otherwise, will MaterialPluginBase serve my purposes or will I need to use a Custom Shader? (First time venturing into shaders :slight_smile: )

I’m CPU render bound so prefer to put in extra effort for shaders than have overlapping geometry/more draw calls

the use case for this is character customisation where the user selects different hair/face/skin colour, etc

someone asking a similar question: How to use vertexData.uvs2?

Hello! You need to define a shader for that, but thankfully there is the Node Material Editor to make that much easier :smiley: Node Material | Babylon.js Documentation (babylonjs.com)

You can access all the UV sets of a mesh in NME, and use them to sample any texture you want - I tried making a simple example here that samples a pixel from one texture using uv1, checks for the red value and uses that value if greater than 0.5, or another texture, this one sampled with uv2, otherwise: Babylon.js Node Material Editor (babylonjs.com)

3 Likes

Thanks for this, it provided me with a better sense of what I was doing. I ended up using a Material Plugin as I wanted to get my hands a bit dirty. For anyone who finds this thread in the future, here’s my material plugin:

Use registerInstancedBuffer to register each attribute and instance.instancedBuffers[attributeName] = x to set values :slight_smile:

export class PlayerMaterialPlugin extends MaterialPluginBase {
	constructor(material) {
		const priority = 200
		const defines = { 'ATLAS_LEN': `yourintegeratlastextures.0` }

		// Should be removed once updated babylon as babylon will call getAttributes itself!!
		material.customShaderNameResolve = (shaderName, uniforms, uniformBuffers, samplers, defines, attributes) => {
			if (attributes) {
				this.getAttributes(attributes)
			}
			return shaderName;
		}

		super(material, 'Player', priority, defines)
		this._enable(true)
	}

	getClassName() {
		return 'PlayerMaterialPlugin'
	}

	getAttributes(attributes) {
		if (!attributes.includes('aAtlasIdx1')) {
			// Can be simplified (no .includes check needed) once upgraded to babylon 5.20.0 or later

			attributes.push('aAtlasIdx1')
			attributes.push('aAtlasIdx2')
			attributes.push('aAtlasIdx3')
			attributes.push('aSkinColour')
		}
	}

	getCustomCode(shaderType) {
		if (shaderType === 'vertex') return {
			'CUSTOM_VERTEX_DEFINITIONS': `
                attribute highp float aAtlasIdx1;
                attribute highp float aAtlasIdx2;
                attribute highp float aAtlasIdx3;
                attribute lowp vec3 aSkinColour;
                
                // vMainUV1 is inbuilt into the babylon shader
                varying highp vec2 vMainUV2;
                varying highp vec2 vMainUV3;
                varying lowp vec3 vSkinColour;
            `,
			'CUSTOM_VERTEX_MAIN_END': `
				vMainUV2 = vec2((vMainUV1.x+aAtlasIdx2)/ATLAS_LEN, vMainUV1.y);
				vMainUV3 = vec2((vMainUV1.x+aAtlasIdx3)/ATLAS_LEN, vMainUV1.y);
				
				vMainUV1 = vec2((vMainUV1.x+aAtlasIdx1)/ATLAS_LEN, vMainUV1.y);
				
				vSkinColour = aSkinColour;
			`,
		}
		if (shaderType === 'fragment') return {
			'CUSTOM_FRAGMENT_DEFINITIONS': `
                varying highp vec2 vMainUV2;
                varying highp vec2 vMainUV3;
                varying lowp vec3 vSkinColour;
            `,
			'!baseColor\\=texture2D\\(diffuseSampler,vDiffuseUV\\+uvOffset\\);': `
				vec4 col1 = texture2D(diffuseSampler, vMainUV1);
				vec4 col2 = texture2D(diffuseSampler, vMainUV2);
				vec4 col3 = texture2D(diffuseSampler, vMainUV3);
				baseColor = col1+col2*(1.0-col1.a)+col3*(max(1.0-col1.a-col2.a, 0.0))+vec4(vSkinColour, 1)*(max(1.0-col1.a-col2.a-col3.a, 0.0));
			`,
		}
		return null
	}
}
2 Likes