Previous Blog Next Blog
Prev/Next Blog
by date

Plan for Brian Ramage

Plan for Brian Ramage
Name:Brian Ramage
Date Posted:Jul 26, 2005
Rating:Not Rated
Public:YES
Comments:YES
RSS Feed:GarageGames Blog feedor Subscribe with .
Profile Page:View profile page for Brian Ramage

Blog post
Using quasi-procedural noise to break up texture tiling
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:




And a blown up sample of the noise texture (the real one is 64x64 with each pixel being a different shade).



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.

Recent Blog Posts
List:01/24/07 - TGEA Milestone 4.2 release
10/30/06 - TGEA Milestone 4 Demo
08/17/06 - TSE Milestone 4
06/07/06 - TSE optimizations
07/26/05 - Plan for Brian Ramage
06/24/05 - Plan for Brian Ramage
02/25/05 - Plan for Brian Ramage
10/25/04 - Plan for Brian Ramage

Submit ResourceSubmit your own resources!

Samuel Lopez De Victoria   (Jul 26, 2005 at 22:27 GMT)
awsome. keep it up... would be kinda cool to see a mosaic of wombats ;)

Anton Bursch   (Jul 26, 2005 at 22:27 GMT)
Now THAT kicks ASS!!

Jason Swearingen   (Jul 26, 2005 at 22:29 GMT)
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!

Mark McCoy   (Jul 27, 2005 at 04:27 GMT)
Sweet.

Kevin Johnson   (Jul 27, 2005 at 16:12 GMT)
great idea guys..

Alex Swanson   (Jul 28, 2005 at 19:34 GMT)
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.

PuG   (Dec 17, 2007 at 08:17 GMT)
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,

You must be a member and be logged in to either append comments or rate this resource.