OK... Im DONE!
So here it is, for anyone interested. Working as well as I had hoped!
In summary, this shader is the crucial part of a "terrain painter",
as shown (in total) by this example. It is aimed primarily for those of us
who generate our terrain DYNAMICALLY by taking a terrain (or terrain-like model)
and adjusting the z-heights of its vertices to create a terrain landscape.
Getting a skin on this entity that 'looks right' has always been a pain.
NOW, by using this system, you can run "RePaint_Skin" on the entity after you have
adjusted your heights, and the shader will 'bake' a multi-texture terrain BMAP
STRAIGHT INTO THE SKIN! The shader only needs one frame to do this, and because
it does not take lighting into account, it generates a true-color texture.
(in theory at least. It has yet to undergo FULL testing. Feel free to do so)So once the "RePaint_Skin" function has run(which takes ONE frame to complete),
the entity now has a fully painted skin, based on four external textures
(as stored in the "RePaintSkin_mat" material), and the sub-textures are blended
into place based on the ACTUAL height of the ACTUAL pixel each pixel location.
All the external textures and cutoff-heights are set in the material at startup,
included the 'width' to blend across transition-lines.
There is also a small amount of randomising of these heights so you dont end up
with super-flat divisions, and the transition-blending prevents any 'raggedness'
of these edges. The randomness is only pseudo, so it will always look the same if
the target model has exact same geometry. (This can only be tweaked IN the shader ATM)
FYI: These heights are taken from the MODELS origin point, not world-space!
FYI+: If you want, it would be easy for me to add an entity-based offset.
DISCLAIMER:: So far, this program/shader has only been tested to match MY needs.
If you want more, or find bugs, PLEASE let me know, here in this thread...
Requirements: For usage yourself, I dont know of any needs, I havent hit any.
But for using this example, the following files are needed:-
"Terr.mdl" was a custom MODEL, converted to model (in MED) from a 128*128 vertex terrain,
with a width and length of 4000quants, and 'warped' to a maximum height of 200 quants.
"sand.tga", "grass.tga", "rock.tga" and "smoke.tga" cann all be found in
the gamestudio8/templates/images folder.
"plane01.hmp" can be found in the gamestudio8/templates/levels folder.
(yes, I know it doesnt look good on this terrain, but its just too high/tall for
the numbers currently in the material.
So... enough of the text wall, heres the code, so feel free to
play, dissect, and comment! This is my first "real" shader...
Here's the lite-c portion of the example.
Note the code marked CORE-CODE. Thats all you NEED if you wanna steal my work!
#include <acknex.h>
#include <default.c>
//
//==========================================================================
//==========================================================================
//CORE CODE
//==========================================================================
MATERIAL *RePaintSkin_mat = NULL;
//--------------------------------------------------------------------------
function skinChangeEvent() { if(me!=render_view.genius) return(1); }
//--------------------------------------------------------------------------
void RePaintSkin_startup()
{ if(!RePaintSkin_mat) //CREATE material if not already existing
{ RePaintSkin_mat = mtl_create();
RePaintSkin_mat.effect= "TerrainPainter.fx";
set(RePaintSkin_mat, ENABLE_RENDER | AUTORELOAD);
RePaintSkin_mat.event = skinChangeEvent; }
//-----------------------------------------------------------------------
RePaintSkin_mat.skin1 = bmap_create("sand.tga"); // Sand/Dirt Texture
RePaintSkin_mat.skin2 = bmap_create("grass.tga"); // Grass Texture
RePaintSkin_mat.skin3 = bmap_create("rock.tga"); // Rock Texture
RePaintSkin_mat.skin4 = bmap_create("smoke.tga"); // Snow Texture
//-----------------------------------------------------------------------
RePaintSkin_mat.skill1 = floatv( 20); // Overlap Range (in quants)
//-----------------------------------------------------------------------
// Sand/Dirt is ALL below Grass
RePaintSkin_mat.skill2 = floatv( 37); // Grass Start Height (in quants)
RePaintSkin_mat.skill3 = floatv( 80); // Rock Start Height (in quants)
RePaintSkin_mat.skill4 = floatv(125); // Snow Start Height (in quants)
//-----------------------------------------------------------------------
RePaintSkin_mat.skill5 = floatv( 6); // Sand Texture Scale multiplier
RePaintSkin_mat.skill6 = floatv( 6); // Grass Texture Scale multiplier
RePaintSkin_mat.skill7 = floatv( 2); // Rock Texture Scale multiplier
RePaintSkin_mat.skill8 = floatv( 3); // Snow Texture Scale multiplier
}
//--------------------------------------------------------------------------
void RePaint_Skin(ENTITY* ME)
{ if(!ME) return; //invalid entity to capture.
if(!ent_getskin(ME,1)) return; //entity has no skin to capture into.
//-----------------------------------------------------------------------
VIEW *view = view_create(1000); set(view, SHOW);
view.material = RePaintSkin_mat; view.genius = ME;
view.x=ME.x-(ME.max_x+ME.min_x)/2; view.y=ME.y-(ME.max_y+ME.min_y)/2;
view.z = ME.z + maxv(ME.max_x-ME.min_x, ME.max_y-ME.min_y)*0.75;
vec_set(view.pan,vector(0,-90,0)); view.bmap = ent_getskin(ME,1);
view.size_x = view.bmap.width; view.size_y = view.bmap.height;
//-----------------------------------------------------------------------
wait(1); ptr_remove(view); beep(); }
//==========================================================================
//End of CORE CODE
//==========================================================================
//==========================================================================
//
ENTITY* Terr;
void updateView() { RePaint_Skin(Terr); }
function main()
{
wait(1); level_load(NULL); wait(1); diag("\n\n\n");
draw_textmode("Times",0,32,100); warn_level = 3; terrain_chunk=256;
//------------------------------------------------------------------------------------
//
Terr = ent_create("terr.MDL", nullvector, NULL);
vec_set(camera.x, vector(-300,150,220)); vec_set(camera.pan, vector(350,-25,0));
//
on_space = updateView;
//
while(!key_esc)
{
if(key_f1) DEBUG_BMAP(ent_getskin(Terr,1), 0, 1);
if(key_f2) //swap entities
{ if(!str_cmp(Terr.type, "PLANE01.HMP"))
{ ent_morph(Terr, "plane01.hmp"); vec_fill(Terr.scale_x, 0.1); }
else
{ ent_morph(Terr, "Terr.mdl"); vec_fill(Terr.scale_x, 1); }
while(key_f2) wait(1); }
wait(1);
}
}
Heres the shader code. My lite-c part is expecting it to be called "TerrainPainter.fx"
//===================================================================================
// ColorMap Samplers
texture mtlSkin1, mtlSkin2, mtlSkin3, mtlSkin4;
sampler SandSampler = sampler_state { Texture = <mtlSkin1>; };
sampler GrassSampler = sampler_state { Texture = <mtlSkin2>; };
sampler RockSampler = sampler_state { Texture = <mtlSkin3>; };
sampler SnowSampler = sampler_state { Texture = <mtlSkin4>; };
//
//===================================================================================
//Material Skill Parameters
float4 vecSkill1; // vecSkill1.x == Border Overlap Range (in quants)
// vecSkill1.y == base start height of GRASS texture
// vecSkill1.z == base start height of ROCK texture
// vecSkill1.w == base start height of SNOW texture
float4 vecSkill5; // vecSkill5.x == SAND/DIRT texture scale multiplier
// vecSkill5.y == GRASS texture scale multiplier
// vecSkill5.z == ROCK texture scale multiplier
// vecSkill5.w == SNOW texture scale multiplier
//
//===================================================================================
//Random Number Generator
#define cMult 0.0001002707309736288
#define aSubtract 0.2727272727272727
//
float random(float4 t)
{
float a, b, c, d;
a=t.x+t.z*cMult+aSubtract-floor(t.x);
a*=a; b=t.y+a; b-=floor(b);
c=t.z+b; c-=floor(c); d=c;
a+=c*cMult+aSubtract-floor(a);
a*=a; b+=a; b-=floor(b);
c+=b; c-=floor(c);
return ((a+b+c+d)/4);
}
//
//===================================================================================
//
void Painter_VS( in float4 inPos: POSITION, in float2 inTex: TEXCOORD0,
out float4 outPos: POSITION, out float outHgt: TEXCOORD0,
out float2 outTexD:TEXCOORD1, out float2 outTexG:TEXCOORD2,
out float2 outTexR:TEXCOORD3, out float2 outTexS:TEXCOORD4 )
{
outPos = float4(inTex.x*2-1, -inTex.y*2+1, 0,1); //output normalized XYpos
outHgt = inPos.y + (random(inPos)*20)-10; //output slightly-randomized height
outTexD = inTex * vecSkill5.x; // re-scale coords for SAND/DIRT texture lookup
outTexG = inTex * vecSkill5.y; // re-scale coords for GRASS texture lookup
outTexR = inTex * vecSkill5.z; // re-scale coords for ROCK texture lookup
outTexS = inTex * vecSkill5.w; // re-scale coords for SNOW texture lookup
}
//
//===================================================================================
//
float4 Painter_PS( in float inHgt: TEXCOORD0,
in float2 inTexD:TEXCOORD1, in float2 inTexG:TEXCOORD2,
in float2 inTexR:TEXCOORD3, in float2 inTexS:TEXCOORD4 ):COLOR0
{
float4 color = 0;
if(inHgt<(vecSkill1.y-vecSkill1.x)) color = tex2D(SandSampler, inTexD);
else if(inHgt<(vecSkill1.y+vecSkill1.x)) color = lerp(tex2D(SandSampler, inTexD),
tex2D(GrassSampler,inTexG),saturate((inHgt-vecSkill1.y)/vecSkill1.x));
else if(inHgt<(vecSkill1.z-vecSkill1.x)) color = tex2D(GrassSampler, inTexG);
else if(inHgt<(vecSkill1.z+vecSkill1.x)) color = lerp(tex2D(GrassSampler,inTexG),
tex2D(RockSampler, inTexR),saturate((inHgt-vecSkill1.z)/vecSkill1.x));
else if(inHgt<(vecSkill1.w-vecSkill1.x)) color = tex2D(RockSampler, inTexR);
else if(inHgt<(vecSkill1.w+vecSkill1.x)) color = lerp(tex2D(RockSampler, inTexR),
tex2D(SnowSampler, inTexS),saturate((inHgt-vecSkill1.w)/vecSkill1.x));
else color = tex2D(SnowSampler, inTexS);
return color;
}
//
//===================================================================================
//
technique terrain_paint
{
pass p0
{
CullMode = None;
VertexShader = compile vs_2_0 Painter_VS();
PixelShader = compile ps_2_0 Painter_PS();
}
}
//
//===================================================================================
//
Enjoy!