Game Development Community

dev|Pro Game Development Curriculum

Plan for Brian Ramage

by Brian Ramage · 07/26/2005 (3:00 pm) · 7 comments

Alex came to me the other day wondering if pixel shaders could be used to break up textures that are tiled a lot over large surfaces. After he explained exactly what he was looking for, I was pretty sure it could be done. Turns out, it was a piece of cake. I was able to whip up the shader in about 10 minutes yesterday on my first try (not even a compile error!).

The shader just uses the texture coordinates of the vertices to index into a simple noisy texture I made in photoshop. Here are the before and after pics:

public.garagegames.com/brianr/planPics/07_26_2005/noiseTile01.jpgpublic.garagegames.com/brianr/planPics/07_26_2005/noiseTile02.jpg
And a blown up sample of the noise texture (the real one is 64x64 with each pixel being a different shade).

public.garagegames.com/brianr/planPics/07_26_2005/noiseSample.jpg
Here's what the shader looks like (shader model 2.0):

VERTEX:
//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct VertData
{
   float2 texCoord        : TEXCOORD0;
   float2 lmCoord         : TEXCOORD1;
   float3 T               : TEXCOORD2;
   float3 B               : TEXCOORD3;
   float3 N               : TEXCOORD4;
   float3 normal          : NORMAL;
   float4 position        : POSITION;
};


struct ConnectData
{
   float4 hpos            : POSITION;
   float2 outTexCoord     : TEXCOORD0;
   float3 pos             : TEXCOORD2;
   float3 outEyePos       : TEXCOORD3;
   float4 outLightVec     : TEXCOORD4;
};


//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
ConnectData main( VertData IN,
                  uniform float4x4 modelview       : register(C0),
                  uniform float3   inLightVec      : register(C24),
                  uniform float3   eyePos          : register(C20),
                  uniform float3   fogData         : register(C22),
                  uniform float4x4 objTrans        : register(C12)
)
{
   ConnectData OUT;

   OUT.hpos = mul(modelview, IN.position);
   OUT.outTexCoord = IN.texCoord;

   float3x3 objToTangentSpace;
   objToTangentSpace[0] = IN.T;
   objToTangentSpace[1] = IN.B;
   objToTangentSpace[2] = IN.N;

   OUT.outLightVec.xyz = -inLightVec;
   OUT.outLightVec.xyz = mul(objToTangentSpace, OUT.outLightVec);
   OUT.pos = mul(objToTangentSpace, IN.position.xyz / 100.0);;
   OUT.outEyePos.xyz = mul(objToTangentSpace, eyePos.xyz / 100.0);;
   OUT.outLightVec.w = step( 0.0, dot( -inLightVec, IN.normal ) );

   return OUT;
}


PIXEL:
//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct ConnectData
{
   float2 texCoord        : TEXCOORD0;
   float3 normal          : TEXCOORD1;
   float3 pixPos          : TEXCOORD2;
   float3 eyePos          : TEXCOORD3;
   float4 lightVec        : TEXCOORD4;
};


struct Fragout
{
   float4 col : COLOR0;
};


//-----------------------------------------------------------------------------
// Main                                                                        
//-----------------------------------------------------------------------------
Fragout main( ConnectData IN,
              uniform float4    ambient         : register(C2),
              uniform sampler2D diffuseMap      : register(S0),
              uniform sampler2D bumpMap         : register(S1),
              uniform sampler2D noiseMap        : register(S2),
              uniform float4    specularColor   : register(C0),
              uniform float     specularPower   : register(C1)
)
{
   Fragout OUT;

   float4 bumpNormal = tex2D(bumpMap, IN.texCoord);
   float4 bumpDot = saturate( dot(bumpNormal.xyz * 2.0 - 1.0, IN.lightVec.xyz) );

   float4 diffuse = tex2D(diffuseMap, IN.texCoord);

   float2 noiseIndex;
   noiseIndex.x = floor( IN.texCoord.x ) / 63.0 + 0.5/64.0;  // uses 64x64 noise texture
   noiseIndex.y = floor( IN.texCoord.y ) / 63.0 + 0.5/64.0;
   float4 noiseColor = tex2D( noiseMap, noiseIndex ) * 1.0 - 0.5;   
   
   OUT.col = diffuse + noiseColor * diffuse.a;
   OUT.col *= bumpDot + ambient;

   float3 eyeVec = normalize(IN.eyePos - IN.pixPos);
   float3 halfAng = normalize(eyeVec + IN.lightVec.xyz);
   float specular = saturate( dot(bumpNormal.xyz * 2.0 - 1.0, halfAng) ) * IN.lightVec.w;
   specular = pow(specular, specularPower);
   OUT.col += specularColor * specular * diffuse.a;
   return OUT;
}


This technique could be used in many places where you want to break up tiling like on brick walls, floors, etc. The artist has full control over how subtle the effect is by controlling the noise texture. If you really want to get funky, you could draw a picture in the noise texture and it would show up as a mosiac pattern rather than noise. Thanks to Alex for thinking this stuff up!

I may well make this a feature in TSE's procedural shaders.

About the author

I have over 16 years of professional game development experience at both AAA studios like Dynamix, to indie studios like GarageGames and my own Black Jacket Games. I worked for 5 years at GarageGames as the lead developer on TGEA (precursor to T3D).


#1
07/26/2005 (3:27 pm)
awsome. keep it up... would be kinda cool to see a mosaic of wombats ;)
#2
07/26/2005 (3:27 pm)
Now THAT kicks ASS!!
#3
07/26/2005 (3:29 pm)
this is very cool! I am not a 3d guy so all i can say is it is a very cool concept, glad it is being implemented!
#4
07/26/2005 (9:27 pm)
Sweet.
#5
07/27/2005 (9:12 am)
great idea guys..
#6
07/28/2005 (12:34 pm)
This rocks - really makes your 512x512 texture look like it has even more resolution. Almost immediately made our tile shader go from looking "sort of like tile" to uncannily good (still need to work on the speculat properties though). Amazing what a little color variation will do to get past that typical mechanical look of textures.

Note that since this is a color offset for all 3 channels you could easily use it to make cool color mosaics as well. No examples of this yet, but I'll play around with it and perhaps post a .plan of the results.
#7
12/17/2007 (12:17 am)
Is their any chance of a material example on how to apply it to a texture? Getting no shader errors but doesn't appear to be mapping (assuming it still works in 1.02).

Thanks,