Well I’ll explain how the shader works and then try initializing some variables.
The following is done in a single pass.
uniform sampler2D _MainTex;
The main diffuse texture is a 4x4 splat 512x512 texture with 16 splats. Each splat is 128x128.
uniform sampler2D _RandTex;
The random texture is a 512x512 noise texture I generated in Gimp.
struct appdata {
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 color : COLOR;
};
This is the vertex program input.
POSITION is vertex positions currently ranging from (-1000, 0, -1000) to (3000, 0, 3000).
TEXCOORD0 are barycentric uv coordinates all with the same orientation. This is used in the splat blending process.
TEXCOORD1 is the wrap component. Values (1.0, 1.0) are no wrapping. Values (2.0, 4.0) would be wrap twice about the X and wrap 4 times about the Z.
COLOR indicates the splat index. The R channel is 1 of 16 splats for the top left. The G channel is the top right. The B channel is bottom left. The A channel is bottom right.
struct v2f {
float4 pos : POSITION;
float4 texcoord : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 color : COLOR;
int offsetX;
int offsetZ;
};
This is the vertex program output and the fragment program input.
v2f vert (appdata v) {
v2f o;
o.pos = mul( glstate.matrix.mvp, v.vertex);
o.texcoord = v.texcoord;
o.texcoord1 = v.texcoord1;
o.color = v.color;
o.offsetX = int(abs(v.vertex.x)) % 512;
o.offsetZ = int(abs(v.vertex.z)) % 512;
return o;
}
Start of vertex program.
The position is converted to world space.
TEXCOORD, TEXCOORD1, COLOR are just passed down the pipeline.
OffsetX is an int value. The vertex position X value is being wrapped every 512 units.
OffsetZ is an int value. The vertex position Z value is being wrapped every 512 units.
End of vertex program.
float GetScaledRemainder(float k, float scaler)
{
k *= scaler;
int reduction = k;
return k-reduction;
}
This is a helper function to scale a uv component and remove the integer value. I use these exclusively to wrap the uvs and cause them to repeat over the 0 to 1 range.
float4 GetTileWrapColor(float height, float intensity, float2 uvs, int wrapX, int wrapY)
{
int index = 15.99 * height;
int col = index % 4; // 0 to 3
int row = height * 3.99; // to 0 to 3
uvs.x = (col+GetScaledRemainder(uvs.x, wrapX))*0.25;
uvs.y = (row+GetScaledRemainder(uvs.y, wrapY))*0.25;
float4 color = tex2D(_MainTex, uvs);
return lerp(float4(0,0,0,0), color, intensity);
}
This is the splat blending process.
The height determines which splat to select of the 16 splats. Valid index values are 0 to 15. The column and row indicate which splat to select. Valid columns values are 0 to 3. Valid row values are 0 to 3. The uvs are adjusted to select the splat and the color is sampled from the diffuse texture. The intensity is used to cause a gradient using the uvs. The color is lerped from black so that I can use additive blending.
float4 frag(v2f i) : COLOR
{
//define the final splat color
float4 final;
//define the splat colors
float4 tile1Color;
float4 tile2Color;
float4 tile3Color;
float4 tile4Color;
Start of the fragment program.
I have the tile colors defined as such so I can debug how each splat is blended.
//get the wrap component
int wrapX = i.texcoord1.x;
int wrapY = i.texcoord1.y;
I grab the wrap components from TEXCOORD1. I only want the integer values so that the splat is tiled edge to edge.
i.texcoord.x * wrapX;
i.texcoord.y * wrapY;
This selects which subsplat is being wrapped. Given a wrap component of 2, valid values are in the 0, 1 or 2 integers.
// use the position offset to select the uvs from the random texture while also wrapping
int orientation =
tex2D(_RandTex, float2(
i.offsetX / 512.0 + int(i.texcoord.x*wrapX)/float(wrapX),
i.offsetZ / 512.0 + int(i.texcoord.y*wrapY)/float(wrapY)
)).x * 8;
A few things are happening here. The random noise texture is being sampled. I’m only using the Red channel of the texture. The value is being multipled by 8. Expected values are 0, 1, 2, 3, 4, 5, 6, 7, 8(rarely).
The position offset from the vertex program is being divided by 512. That gives a range of 0 to 1. The offset is being used to slide the uvs along the random noise texture.
The noise lookup is being staggered so that the same noise is being used for each subsplat. Otherwise I’d have a noise pattern within the splat blending pattern.
// use 4 orientations
orientation = orientation % 4;
Select between 4 orientations each rotated by 90 degrees.
//get the splat colors
float2 uv;
if (orientation == 0)
{
uv = float2(i.texcoord.x, i.texcoord.y);
}
else if (orientation == 1)
{
uv = float2(i.texcoord.y, 1-i.texcoord.x);
}
else if (orientation == 2)
{
uv = float2(1-i.texcoord.x, 1-i.texcoord.y);
}
else //if (orientation == 3)
{
uv = float2(1-i.texcoord.y, i.texcoord.x);
}
Each orientation rotates the uvs by 90 degrees. These are the uvs used to lookup the diffuse color of the subsplats.
tile1Color = GetTileWrapColor(i.color.r, (1-i.texcoord.x) * (1-i.texcoord.y), uv, wrapX, wrapY);
tile2Color = GetTileWrapColor(i.color.g, i.texcoord.x * (1-i.texcoord.y), uv, wrapX, wrapY);
tile3Color = GetTileWrapColor(i.color.b, (1-i.texcoord.x) * i.texcoord.y, uv, wrapX, wrapY);
tile4Color = GetTileWrapColor(i.color.a, i.texcoord.x * i.texcoord.y, uv, wrapX, wrapY);
The COLOR component selects with splat to use for topleft, topright, bottomleft, and bottomright.
The TEXCOORD component is used to specify the intensity of the splats to blend the splats with black so that additive blending can be used.
The UV specifies the texture coordinate and orientation for the diffuse color lookup. The wrapX and wrapY components specify how many times to wrap the subsplat.
final = tile1Color + tile2Color + tile3Color + tile4Color;
Combine the 4 splats together with additive blending.
// apply a color for debugging
const float debug = 0;
Debug is used to color each subsplat so I can see which noise values were selected. I change the value to 1 so I can see each subsplat orientation as a primary color.
if (orientation == 0)
{
final.r += debug;
}
else if (orientation == 1)
{
final.g += debug;
}
else if (orientation == 2)
{
final.b += debug;
}
else if (orientation == 3)
{
final.r += debug;
final.g += debug;
}
The debug color alterations are made to show what orientation was used.
return final;
The final color is returned.