Another part of my Ultimate Shader(tm) series
This shader is probably excessive, but it seems to run fine for me and looks fantastic when applied well. It is based on the Wiki terrain multitextureing shader developed by Ventilator, Steempipe, and JCL. Here is what it does in total:
--4 color textures, blended by the rga channels of a blender texture in entskin1. entskin 2-4 then are the first three color textures, mtl.skin3 is the 4th color texture. Also blends in a shadow map in the blender's b channel
--2 detail textures, both in mtl.skin1 ..one is in the rgb channels, and the other is in the alpha.. of course you could also encode a maximum of 4 detail maps here if you wanted, using the same methods as in the texture blender. I only needed 2 detail maps.
--a tiled normalmap diffuse per-pixel lighting pass to give some bumpiness to the terrain.
--the standard vertex lighting from the WIKI example.. you could change this to make all the dynamic light per pixel, but that's really excessive..maybe later.
--finally, an environmental bumpmapping shader, applied to the 4th color texture. it uses a cubemap stored in mtl.skin4. My reason for this effect is that i wanted the 4th color, mud, to be reflective as though it's wet. You can eliminate this pass if you want.. just comment out the third pass in the technique. But it looks pretty cool(could be improved however if can find a way to environment mapping in HLSL) Then environment mapping is additionally given an attenuated view distance(sort of like a light), for speed and fogging reasons.. this can be set in the code..
OK..that's about it as far as functionality..as you can see this really just a combination of standard effects. This is a complicated, 3 pass shader that eats up about all texture stages possible, using a total of 8 textures. Its probably not elegantly or perfectly designed, but as said i get no problems. Just make sure you use a far_clip range thats not too far away. One problem with shadrs of this type(multipass) is dealing with fogging correctly. Basically i had to add fogging in the first 2 passes, relying on the attenuation in the 3rd pass to eliminate the fog altogether there. The reason this is a problem is that if you add ofging in alphabelded pass, you usually get fog color acumulation. That means the final fogging color will be differnet form the standard A6 pass.. it wil be the same rgb hues, but usually noticably lighter. In any case this can play hell with your fogging system, since models and trees for instance may not use pass material and thus will appear the wrong color when fogged..
In my game i fortunately use a 2 pass shader for about everything so i don't have a problem. However.. if you can have this issue, then you can look at the distance attentuation formula used in the third pass for the environment bump, and apply variation of this to the normal mapping pass, and elmiminate fogging from that pass. Not a perfect solution but i can't think of a better way.. in my research I've found that this problem seems general for every engine using fogging and multipass shaders.. it seems most poeple do some kind of fakery to make it work. If you are using a shader like this, dont expect to just "plug-n-play".. it takes a lot of work to make it suited to your game. I have posted this because of a user's request, not as a panacea to everyone's terrain needs.
Here are some screenshots:
Here is the script needed:
Code:
/////////////////////////////////////////
// Title: Terrain multitexture material
// Conitec / jcl 2004
/////////////////////////////////////////
// Blends 3 textures together, according to
// the red and green channel in the first skin.
// The blue channel can be used for a shadow map.
// Skin1 - Terrain RGB blend & shadow mask
// Skin2 - Base terrain texture
// Skin3 - Red channel masked tiled texture
// Skin4 - Green channel masked tiled texture
// Skin5 - Replacement base texture for non-shader hardware
// Skin6 - Replacement detail texture for non-shader hardware
// Skill41 - Skin2 scale, float(1..50)
// Skill42 - Skin3 scale, float(1..50)
// Skill43 - Skin4 scale, float(1..50)
// Skill44 - ambient, float(-1..1)
// Based on concepts by Thomas 'ventilator' Oppl
// and Eric 'Steempipe' Hendrickson-Lambert
// Requires A6.31, VS 1.1, PS 1.1
//todo: pass real sun color
//todo: blend 2 more textures on blue and alpha in a second pass
bmap terrain_normal = <terrain_bump.tga>;
bmap grass_detail = <grass_detail.tga>;
bmap mud = <dirt_1.tga>;
bmap reflect=<reflect_1+6.tga>;
MATERIAL mtl_terrainmulti3
{
skin1=terrain_normal;
skin2=grass_detail;
skin3=mud;
skin4=reflect;
flags=tangent;
}
// C-Script stuff /////////////////////////////////////////////////////
define ScaleSkin2,SKILL2;
define ScaleSkin3,SKILL3;
define ScaleSkin4,SKILL4;
// uses: ScaleSkin2 ScaleSkin3 ScaleSkin4
action terrain_multi3
{
ent_terrain=me; //set this as the terrain entity for script stuff
my.material = mtl_terrainmulti3;
if (my.ScaleSkin2) {
my.skill41 = my.ScaleSkin2; }
else {
//entry: Skin2 default scale
//help: Number of tiles in x/y direction for Skin 2 (Base Texture).
//help: Set this to 1 for an untiled texture.
my.skill41 = 40; }
if (my.ScaleSkin3) {
my.skill42 = my.ScaleSkin3; }
else {
// entry: Skin3 default scale
// help: Number of tiles in x/y direction for Skin 3 (red masked texture).
my.skill42 = 40; }
if (my.ScaleSkin4) {
my.skill43 = my.ScaleSkin4; }
else {
// entry: Skin4 default scale
// help: Number of tiles in x/y direction for Skin 4 (green masked texture).
my.skill43 = 35; }
my.skill41 = float(my.skill41);
my.skill42 = float(my.skill42);
my.skill43 = float(my.skill43);
my.skill44 = float(my.skill1/1000);
}
starter mtl_terrain_init
{
bmap_to_mipmap(terrain_detail);
bmap_to_mipmap(grass_detail);
bmap_to_mipmap(mud);
bmap_to_cubemap(bmap_to_mipmap(reflect));
effect_load(mtl_terrainmulti3,"terrain.fx");
}
here is the terrain.fx file:
Code:
//Ultimate terrain shader, based on work by Ventilator, SteemPipe, JCL and others
//by Matt Aufderheide Jan 2005
float4x4 matWorld;
float4x4 matWorldInv;
float4x4 matWorldView;
float4x4 matView;
float4x4 matWorldViewProj;
float4 vecSunDir;
float4 vecSunPos;
float4 vecSunDiffuse = float4(200.f/255.f, 200.f/255.f, 200.f/255.f, 1.f);
float4 vecFog;
float4 vecLight;
float4 vecViewPos;
Texture entSkin1; // Red/green for blending, blue for shadow
Texture entSkin2; // Basic tiled terrain texture
Texture entSkin3; // Red masked tiled texture
Texture entSkin4; // Green masked tiled texture
Texture mtlSkin1; // normalmap
Texture mtlSkin2; // detail
Texture mtlSkin3; // detail
Texture mtlSkin4; // detail
float4 vecSkill41;
float4 vecSkill1;//sun position
float4 vecLightPos[8];
float4 vecLightColor[8];
float3 vecFalloff = float3(0.f, 0.f, 1.5f);
sampler sMaskTex = sampler_state
{
Texture = <entSkin1>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
sampler sBaseTex = sampler_state
{
Texture = <entSkin2>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
sampler sRedTex = sampler_state
{
Texture = <entSkin3>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
sampler normal = sampler_state
{
Texture = <mtlSkin1>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
sampler detail = sampler_state
{
Texture = <mtlSkin2>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
sampler mud = sampler_state
{
Texture = <mtlSkin3>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
sampler reflect = sampler_state
{
Texture = (mtlSkin4);
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
AddressU = CLAMP;
AddressV = CLAMP;
AddressW = CLAMP;
};
sampler sGreenTex = sampler_state
{
Texture = <entSkin4>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
AddressU = Wrap;
Addressv = Wrap;
};
////////////////////////////////////////////////////////////////////////////
// normal map
struct VS_OUTPUT
{
float4 Pos : POSITION;
float2 Tex : TEXCOORD0;
float3 Light : TEXCOORD1;
float Fog: FOG;
};
VS_OUTPUT VS(float4 inPos : POSITION, float2 Tex : TEXCOORD, float3 Normal : NORMAL, float3 Tangent : TANGENT )
{
VS_OUTPUT Out = (VS_OUTPUT)0;
Out.Pos = mul(inPos, matWorldViewProj); // transform Position
// compute the 3x3 tranform matrix
// to transform from world space to tangent space
float3x3 worldToTangentSpace;
worldToTangentSpace[0] = mul(Tangent, matWorld);
worldToTangentSpace[1] = mul(cross(Tangent, Normal), matWorld);
worldToTangentSpace[2] = mul(Normal, matWorld);
Out.Tex = Tex.xy;
Out.Light.xyz = mul(worldToTangentSpace, vecSunPos);
float3 PosWorld = normalize(mul(inPos, matWorld));
// Add fog
float3 PositionWorld = mul(inPos, matWorld);
float ofog = 1 - (distance(PositionWorld, vecViewPos) - vecFog.x) * (vecFog.z*1.5);
Out.Fog = ofog;
return Out;
}
float4 PS(float2 Tex: TEXCOORD0, float3 Light : TEXCOORD1) : COLOR
{
float4 color = 1;// tex2D(sBaseTex, (Tex*8)); // fetch color map
float3 bumpNormal = 2 * (tex2D(normal, (Tex*24)) - 0.5); // bump map
float3 LightDir = normalize(Light); // L
float4 diff = saturate(dot(bumpNormal, LightDir)); // diffuse comp.
float shadow = saturate(4 * diff); // compute self-shadowing term
return 0.2 * color + shadow * (color * diff );
}
//////////////////////////////////////////////////////////////////////
// return the sun light on the surface
float4 DoSunLight(float3 N)
{
// modulate the sunlight by the surface angle
return vecSunDiffuse * dot(N,-vecSunDir);
}
// return the dynamic light on the surface
float4 DoPointLight(float3 P, float3 N, int i)
{
// calculate the light ray pointing from the light to the surface
float3 D = (float3)vecLightPos[i]-P;
// calculate the angle between surface and light ray
float NdotL = dot(N,normalize(D));
// modulate the light by the surface angle
float4 Color = vecLightColor[i] * NdotL;
// calculate the light attenuation factor
float fac = 0.f;
if (NdotL >= 0.f && vecLightPos[i].w > 0.f)
{
// get the distance factor
float LD = length(D)/vecLightPos[i].w;
if (LD < 1.f)
fac = 1.f - LD;
}
return (Color * fac);
}
//////////////////////////////////////////////////////////////////////
struct TMULTI_VS_OUT // Output to the pixelshader fragment
{
float4 Pos : POSITION;
float4 Color: COLOR0;
float Fog: FOG;
float2 MaskCoord : TEXCOORD0;
float2 BaseCoord : TEXCOORD1;
float2 RedCoord : TEXCOORD2;
float2 DetailCoord : TEXCOORD4;
float2 GreenCoord : TEXCOORD3;
};
TMULTI_VS_OUT TMulti_VS(
float4 inPos : POSITION,
float3 inNormal : NORMAL,
float2 inTexCoord0 : TEXCOORD0)
{
TMULTI_VS_OUT Out;
// transform the vector position to screen coordinates
Out.Pos = mul(inPos,matWorldViewProj);
// rotate and normalize the normal
float3 N = normalize(mul(inNormal,matWorldInv));
//float3 N = normalize(inNormal);
float3 P = mul(inPos,matWorld);
Out.Color=vecSkill41.w;
// Add 6 dynamic lights (maximum for vs 1.1)
for (int i=0; i<6; i++)
Out.Color += DoPointLight(P,N,i);
float3 PositionWorld = mul(inPos, matWorld);
float ofog = 1 - (distance(PositionWorld, vecViewPos) - vecFog.x) * vecFog.z;
Out.Fog = ofog;
// scale the texture coordinates for the masked textures
Out.MaskCoord = inTexCoord0.xy;
Out.DetailCoord = inTexCoord0.xy*200;
Out.BaseCoord = inTexCoord0.xy * vecSkill41.x;
Out.RedCoord = inTexCoord0.xy * vecSkill41.y;
Out.GreenCoord = inTexCoord0.xy * vecSkill41.z;
return Out;
}
float4 TMulti_PS(TMULTI_VS_OUT In): COLOR
{
// retrieve the pixels for the textures and the masks
float4 MaskColor = tex2D(sMaskTex,In.MaskCoord);
float4 BaseColor = tex2D(sBaseTex,In.BaseCoord);
float4 RedColor = tex2D(sRedTex,In.RedCoord);
float4 detail = tex2D(detail,In.DetailCoord);
float4 mudmap = tex2D(mud,In.BaseCoord);
float4 gloss = tex2D(mud,In.MaskCoord);
// blend the red and green textures over the base texture
float4 GreenColor = tex2D(sGreenTex,In.GreenCoord);
GreenColor.rgb *= detail.a;
float4 BaseRed = lerp(BaseColor,RedColor,MaskColor.r);
BaseRed.rgb *= detail.rgb;
float4 addmud = lerp(BaseRed,GreenColor,MaskColor.g);
float4 FinalColor=lerp(addmud,mudmap,MaskColor.w);
// Add the vertex light and shadow map, plus the entity ambient
FinalColor *= MaskColor.b + vecSkill41.w;
FinalColor *= (In.Color*2);
FinalColor.a = 1.0f; // prevent transparency
return FinalColor;
}
/////////////////////////////////////////////////////////////////////
struct VS_OUTPUT_2_0
{
float4 Pos : POSITION;
float2 Tex : TEXCOORD0;
float4 PPos : TEXCOORD1;
float3 PNorm : TEXCOORD2;
float Fog: FOG;
float3 att: TEXCOORD3;
float4 TanToCube0: TEXCOORD4;
float4 TanToCube1: TEXCOORD5;
float4 TanToCube2: TEXCOORD6;
};
VS_OUTPUT_2_0 VShader_2_0(float4 Pos : POSITION,
float3 Normal : NORMAL,
float3 Tex : TEXCOORD0,
float3 Tangent : TEXCOORD2)
{
VS_OUTPUT_2_0 Out = (VS_OUTPUT_2_0)0;
Out.PNorm = normalize(mul(Normal,matWorldView));
Out.PPos = mul(Pos, matWorldView);
Out.Pos = mul(Pos,matWorldViewProj);
Out.Tex = Tex;
Out.Fog = 10000;
float3 PosWorld = mul(Pos, matWorld);
float3 viewpos = PosWorld - vecViewPos;
Out.att = viewpos / 400;
return Out;
}
float4 PShader_2_0(VS_OUTPUT_2_0 In) : COLOR0
{
float3 P = normalize(In.PPos);
float3 N = normalize(tex2D(normal, In.Tex*100) * normalize(In.PNorm));
float3 V = -normalize(P);
float3 L = -normalize(vecSkill1);
float3 R = normalize(2 * dot(N, L) * N - L);
float3 G = normalize(2 * dot(N, V) * N - V);
float4 Diff = 0.2 * max(0, dot(N, L)) ;
float4 Spec = 0.8 * max(0, pow(dot(R, V),8));
float4 Glance = texCUBE(reflect, -G);
float4 Attenuation = saturate(dot(In.att, In.att));
float4 gloss = tex2D(sMaskTex, In.Tex);
return ((Glance + Spec + Diff)*(1 -Attenuation))*(gloss.w*0.15);
}
technique tmulti3
{
pass one
{
zenable=true;
zwriteenable=true;
sampler[0] = (sMaskTex);
sampler[1] = (sBaseTex);
sampler[2] = (sRedTex);
sampler[3] = (sGreenTex);
sampler[4] = (mud);
VertexShader = compile vs_1_1 TMulti_VS();
PixelShader = compile ps_2_0 TMulti_PS();
}
pass two
{
zenable=true;
zwriteenable=true;
alphablendenable=true;
srcblend=destcolor;
destblend=srccolor;
// compile shaders
VertexShader = compile vs_2_0 VS();
PixelShader = compile ps_2_0 PS();
}
pass three
{
zenable=true;
zwriteenable=true;
alphablendenable=true;
srcblend=one;
destblend=one;
VertexShader = compile vs_2_0 VShader_2_0();
PixelShader = compile ps_2_0 PShader_2_0();
}
}