# HLSL Multitexturing with more than 4 textures

I've come up with an idea that should work in multitexturing a terrain with more than 4 textures, up to a 64 if a supported ShaderModel is used.

I'm trying to use the RGBA color values as both values for blending and identifying which textures should be used.

I'm currently using 16 per textures. Each color has 256 values (R, G, B, A) so by identifying:

Texture 1 Red = 0-16, Blend = (Value / (16 - (16 * 0)) Texture 2 Red = 17-32, Blend = (Value / (16 - (16 * 1))

and by changing from 16 to 32 the blend is better because more rgba values can be included.

...and the closer that value is to 16 the higher blend value it gets with that texture.

All I'm stuck at is this calculation because no matter what I do I can't seem to convert the values correctly or get the values in the right way.

// VS_OUTPUT contains Position, TextureCoordinates and Color

float4 MultiTexturing_Pixel(VS_OUTPUT input) : COLOR
{
float4 color = float4(0,0,0,0);

int scale = 16;

float red = input.Color.r * 256;

// gets an index between 0~16, add +16 for each color after this
int redIndex = ceil(red / scale);
int redValue = 0;

if ( (red % scale) == 0)
{
redValue = 1.0;
}
else
{
redValue = (color % scale) / scale; // should give a value between 0.0 and 1.0
}

// function with alot of ifs that checks the index and returns the Texture Sampler
color += GetColor(index, value, input.TexCoord);

// and the same for blue, green, alpa
// but add blueIndex + 16, greenIndex + 32, alphaIndex + 48

return color;
}


The math is there, but converting values back and forth is really the problem and since HLSL is basic C it's weird that it doesn't support basic math calculations.

Since I'm not that good at how the calculations should work I hope someone can provide an answer on how to actually perform the calculations below.

I've tried every possible way, all day and I'm totally out of ideas.

UPDATE: Problems with the calculations:

As far as my math knowledge goes, this is how it should go.

A color value is 0~255 (256) In HLSL this translates to a value of 0.0-1.0, which means 32 = (32 / 256) = 0.125

Step 1 I set the Vertex color to:

new Color(32, 0, 0, 0)


Step 2 In the pixel shader this value would translate to the second texture this way:

float each = 16.0;
float max = 256.0;

float redIndex = (input.Color.r * max) / each;


At this point redIndex should be "2.0" if Color value is (32,0,0,0) But it's not. Why? This is the flow for me in any other programming language:

float each = 16.0;
float max = 256.0;

float redIndex = 0.125 * 256; // = 32
redIndex /= 16.0; // = 2.0

int index = redIndex; // = 2
int index = ceil(redIndex); // = 2


If I try to use ceil(redIndex) nothing better happens, it still can't get that "index" value. And this is basic MATH. And ceil (available from SM v1.1) takes the value up to the nearest integer, so if it's 1.9 == 2, 0.1 = 1 and so on. But it cannot do this. Because the values are wrong and the calculation is wrong.

Step 3 To get a value between 0-1 for texture, the closer you are to 16, 32, 48, 64 the more color from texture 1, 2, 3, 4 you get. So: 16 = 1, 8 = 0.5 4 = 0.25

float redValue = 0.0;
float red = input.Color.r * max;

if ( fmod(red, each) == 0.0) // or (16.0 % 16.0), which basically is 0.0
{
redValue = 1.0;
}
else
{
redValue = fmod(red, each) / each;
// or (8.0 % 16.0) / 16.0
// which is (8 % 16.0) = 8 and 8 / 16 = 0.5
// which means the texture should be blended 0.5 or 50%.
}


Step 4 And finally by using the function GetColor I collect colors for each texture.

color += GetColor(index, value, input.TexCoord);

// and then do the same for green, blue, alpha values only add + 16
// because colors at (0,16,0,0) would be 16 + 1
// and color (0,0,32,0) would be 32 + 2
// and color (0,0,0,64) would be 48 + 4

• It's not at all clear what exactly it is you're trying to do here. – Nicol Bolas Apr 2 '13 at 3:21
• I explained it, the color value is to identify the texture and the blend weight. – Deukalion Apr 2 '13 at 4:31
• I can edit my post and explain what is wrong with the calculations. – Deukalion Apr 2 '13 at 5:47

It's really difficult to understand what you're trying to do and what's going wrong with it. It's not the amount of information, but the way in which you wrote it that makes it difficult to understand. I'll give it a try though.

As far as I understood, you want to blend multiple textures. The blending factors are encoded inside the RGBA values of a different texture, and you want a way to extract these values from your pixel shader.

If that is right, supposing you encode 8 blending factors in RGBA8, 2 in each component using 4 bits each, you can do:

float blendFactor0 = ((input.Color.r * 256) % 16) / 16;
float blendFactor1 = ((input.Color.r *  16) % 16) / 16;
float blendFactor2 = ((input.Color.g * 256) % 16) / 16;
float blendFactor3 = ((input.Color.g *  16) % 16) / 16;
float blendFactor4 = ((input.Color.b * 256) % 16) / 16;
float blendFactor5 = ((input.Color.b *  16) % 16) / 16;
float blendFactor6 = ((input.Color.a * 256) % 16) / 16;
float blendFactor7 = ((input.Color.a *  16) % 16) / 16;


You can also do this in integer math using bitwise operations (probably unnecessary since you're on the GPU)

int encodedBlendR = input.Color.r * 256;
int encodedBlendG = input.Color.g * 256;
int encodedBlendB = input.Color.b * 256;
int encodedBlendA = input.Color.a * 256;

float blendFactor0 = ((encodedBlendR >> 0) & 0xF) / 16;
float blendFactor1 = ((encodedBlendR >> 4) & 0xF) / 16;
float blendFactor2 = ((encodedBlendG >> 0) & 0xF) / 16;
float blendFactor3 = ((encodedBlendG >> 4) & 0xF) / 16;
float blendFactor4 = ((encodedBlendB >> 0) & 0xF) / 16;
float blendFactor5 = ((encodedBlendB >> 4) & 0xF) / 16;
float blendFactor6 = ((encodedBlendA >> 0) & 0xF) / 16;
float blendFactor7 = ((encodedBlendA >> 4) & 0xF) / 16;


Texture 1 Red = 0-16, Blend = (Value / (16 - (16 * 0)) Texture 2 Red = 17-32, Blend = (Value / (16 - (16 * 1)) and by changing from 16 to 32 the blend is better because more rgba values can be included.

You can't do this. If you want 16 values, you will need 4 bits, so you can store two values per 8-bit element. One value will be the first hex digit, and the other one the second. If you want 32 values, you will need 5 bits, so you can only store one value per 8-bit element.

• Basically it's an idea but I've yet to see how it works because of complications of calculating the correct values, getting a value between 0.0 and 1.0 with each color channel, so red = 256 / 16, which means r = 16, texture = 1 and blendFactor = 1, r = 24 texture = 2, blendFactor = 0.5 ((24 % 16) / 16) = 0.5 for blend factor. This could easily be done manually but it's not very handy and I can't find any good information on how to convert between datatypes and values so I can calculate them correctly. My algorithm works outside of HLSL, but the same algorithm can't be calculated in HLSL. – Deukalion Apr 2 '13 at 8:22
• Also, using your method of blendFactor = ((encodedBlendR >> 0) & 0xF) / 16 isn't supported in XNA, is there a another way to calculate the same value? – Deukalion Apr 2 '13 at 8:26
• >> 4 is equivalent to / 16, and & 0xF is equivalent to % 16. I proposed the bitwise operations because they use integer math, but floating point math is probably faster in the GPU, so it's probably better to do it with division and modulus. – Panda Pajama Apr 2 '13 at 8:45
• Well, it seems worthless anyway. I got some results and it fades from one texture to black correctly but the second texture gets covered by the blend fade out effect and I have no idea what I'd expected or how to solve this. As I said, just an idea but XNA also seems to limited for certain things, to many instructions for this for example. – Deukalion Apr 3 '13 at 6:08
• Since this question was more about the calculations in HLSL rather than actually achieving a multitexturing effect I will mark this as the answer because it solved my calculation problems but not the texturing. – Deukalion Apr 3 '13 at 6:33

Since this "idea" of mine wasn't working at all I just had an idea that silly enough worked. I've been looking for something like this for some while without finding it.

This works so that you can multitexture a terrain with 16 textures or less.

Here is as close as an answer that this question gets:

http://pastebin.com/mbneZxJ6

C# Code

public struct MultiTextureVertex : IVertexType
{

public MultiTextureVertex(Vector3 position, Vector3 normal, Vector2 textureCoordinate, uint texture)
{
Position = position;
TextureCoordinate = textureCoordinate;

A = new Color(0, 0, 0, 0);
B = new Color(0, 0, 0, 0);
C = new Color(0, 0, 0, 0);
D = new Color(0, 0, 0, 0);

Normal = normal;

switch (texture)
{
case 1:
A.R = 255;
break;
case 2:
A.G = 255;
break;
case 3:
A.B = 255;
break;
case 4:
A.A = 255;
break;
case 5:
B.R = 255;
break;
case 6:
B.G = 255;
break;
case 7:
B.B = 255;
break;
case 8:
B.A = 255;
break;
case 9:
C.R = 255;
break;
case 10:
C.G = 255;
break;
case 11:
C.B = 255;
break;
case 12:
C.A = 255;
break;
case 13:
D.R = 255;
break;
case 14:
D.G = 255;
break;
case 15:
D.B = 255;
break;
case 16:
D.A = 255;
break;
}
}

public MultiTextureVertex(Vector3 position, Vector3 normal, Vector2 textureCoordinate, Color a, Color b, Color c, Color d)
{
Position = position;
TextureCoordinate = textureCoordinate;

A = a;
B = b;
C = c;
D = d;

Normal = normal;
}

public void SetTexture(uint texture)
{
A = new Color(0, 0, 0, 0);
B = new Color(0, 0, 0, 0);
C = new Color(0, 0, 0, 0);
D = new Color(0, 0, 0, 0);

switch (texture)
{
case 1:
A.R = 255;
break;
case 2:
A.G = 255;
break;
case 3:
A.B = 255;
break;
case 4:
A.A = 255;
break;
case 5:
B.R = 255;
break;
case 6:
B.G = 255;
break;
case 7:
B.B = 255;
break;
case 8:
B.A = 255;
break;
case 9:
C.R = 255;
break;
case 10:
C.G = 255;
break;
case 11:
C.B = 255;
break;
case 12:
C.A = 255;
break;
case 13:
D.R = 255;
break;
case 14:
D.G = 255;
break;
case 15:
D.B = 255;
break;
case 16:
D.A = 255;
break;
}
}

public Vector3 Position;
public Vector3 Normal;
public Vector2 TextureCoordinate;
public Color A, B, C, D;

public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration
(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement((sizeof(float) * 3), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
new VertexElement((sizeof(float) * 6), VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
new VertexElement((sizeof(float) * 8), VertexElementFormat.Color, VertexElementUsage.Color, 0),
new VertexElement((sizeof(float) * 8) + (sizeof(byte) * 4), VertexElementFormat.Color, VertexElementUsage.Color, 1),
new VertexElement((sizeof(float) * 8) + (sizeof(byte) * 8), VertexElementFormat.Color, VertexElementUsage.Color, 2),
new VertexElement((sizeof(float) * 8) + (sizeof(byte) * 12), VertexElementFormat.Color, VertexElementUsage.Color, 3)

);

VertexDeclaration IVertexType.VertexDeclaration
{
get { return MultiTextureVertex.VertexDeclaration; }
}
}


Then simply create a vertex with this Vertex type, say a plane or anything and it can use 16 textures as long as they're set.