Terrain Shadowmapping
From GameStudio Wiki
This simple script generates shadow maps for terrains.
in the terrain action you have to specify which texture of the terrain is the shadow map. the shadow map has to be a completely white 24 bit tga texture.
the generate_shadows function uses c_trace to draw the shadows of level blocks and entities (according to the sun direction) into the shadow map. depending on the resolution of your shadow map this can be quite slow. with the WriteTga script you could save the final shadow map and avoid calculating the shadows anew each time the level starts.
to get rid of shadow jaggies the shadows get blurred after they are done. probably a better quality could be achieved by doing multi sampling while drawing the shadows but the blur was simpler to do and i think it is faster.
Known issues: it would be better to trace from the pixel to the sun and check if the obstacle was the terrain itself. with low sun elevations and steep hills it could happen that the shadow of an object gets cast behind a hill.
bmap* canvas;
entity* p_terrain;
var canvas_size[2];
var shadow_brightness=112;
//-----------------------------------------------------------------------------blur
function blur(passes)
{
var i;
var px;
var py;
var format;
var pixel;
var pixelcolor[3];
var pixelalpha;
var sample1[3];
var sample2[3];
var sample3[3];
var sample4[3];
var sample5[3];
var sample6[3];
var sample7[3];
var sample8[3];
var sample9[3];
i=0;
while(i<passes)
{
py=0;
while(py<canvas_size.y)
{
px=0;
while(px<canvas_size.x)
{
format=bmap_lock(canvas,0);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px,0,canvas_size.x-1),clamp(py,0,canvas_size.x-1));
pixel_to_vec(sample1,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px-1,0,canvas_size.x-1),clamp(py,0,canvas_size.x-1));
pixel_to_vec(sample2,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px-1,0,canvas_size.x-1),clamp(py+1,0,canvas_size.x-1));
pixel_to_vec(sample3,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px,0,canvas_size.x-1),clamp(py+1,0,canvas_size.x-1));
pixel_to_vec(sample4,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px+1,0,canvas_size.x-1),clamp(py+1,0,canvas_size.x-1));
pixel_to_vec(sample5,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px+1,0,canvas_size.x-1),clamp(py,0,canvas_size.x-1));
pixel_to_vec(sample6,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px+1,0,canvas_size.x-1),clamp(py-1,0,canvas_size.x-1));
pixel_to_vec(sample7,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px,0,canvas_size.x-1),clamp(py-1,0,canvas_size.x-1));
pixel_to_vec(sample8,pixelalpha,format,pixel);
pixel=pixel_for_bmap(canvas,clamp((canvas_size.x-1)-px-1,0,canvas_size.x-1),clamp(py-1,0,canvas_size.x-1));
pixel_to_vec(sample9,pixelalpha,format,pixel);
pixelcolor.x=int((sample1.x*7+sample2.x*2+sample3.x+sample4.x*2+sample5.x+sample6.x*2+sample7.x+sample8.x*2+sample9.x)/19);
pixelcolor.y=int((sample1.y*7+sample2.y*2+sample3.y+sample4.y*2+sample5.y+sample6.y*2+sample7.y+sample8.y*2+sample9.y)/19);
pixelcolor.z=int((sample1.z*7+sample2.z*2+sample3.z+sample4.z*2+sample5.z+sample6.z*2+sample7.z+sample8.z*2+sample9.z)/19);
pixel=pixel_for_vec(pixelcolor,100,format);
pixel_to_bmap(canvas,(canvas_size.x-1)-px,py,pixel);
bmap_unlock(canvas);
px+=1;
}
py+=1;
wait(1); // without a wait after each line the loop could get too big if the shadow map is huge
}
i+=1;
}
}
//-----------------------------------------------------------------------------generate_shadows
function getxyz(px,py)
{
var pixel_size[2];
pixel_size.x=(p_terrain.max_x-p_terrain.min_x)/canvas_size.x;
pixel_size.y=(p_terrain.max_y-p_terrain.min_y)/canvas_size.y;
temp.x=((p_terrain.x-p_terrain.min_x)-(pixel_size.x*px))-pixel_size.x/2;
temp.y=((p_terrain.y-p_terrain.min_y)-(pixel_size.y*py))-pixel_size.y/2;
c_trace(vector(temp.x,temp.y,p_terrain.z+50000),vector(temp.x,temp.y,p_terrain.z-50000),ignore_models+ignore_sprites+ignore_maps);
temp.z=target.z;
}
function generate_shadows()
{
var px;
var py;
var format;
var pixel;
py=0;
while(py<canvas_size.y)
{
px=0;
while(px<canvas_size.x)
{
getxyz(px,py); // get the world coordinates of the pixel
c_trace(sun_pos.x,vector(temp.x,temp.y,temp.z+1),ignore_me+ignore_sprites); // trace from the sun to the pixel
if(trace_hit) // draw shadow pixel if there is no obstacle
{
format=bmap_lock(canvas,0);
pixel=pixel_for_vec(vector(shadow_brightness,shadow_brightness,shadow_brightness),100,format);
pixel_to_bmap(canvas,(canvas_size.x-1)-px,py,pixel);
bmap_unlock(canvas);
}
px+=1;
}
py+=1;
wait(1); // without a wait after each line the loop could get too big if the shadow map is huge
}
blur(1); // do 1 blur pass
}
//-----------------------------------------------------------------------------terrain
action terrain
{
p_terrain=my;
my.material=mtl_terrainshadowmap;
canvas=bmap_for_entity(my,2); // the second texture of the terrain is the shadowmap
canvas_size.x=bmap_width(canvas);
canvas_size.y=bmap_height(canvas);
wait(1);
generate_shadows();
}
here is a simple example material. the first texture of the terrain has to be the color map (the tiling can be specified with the texture transformation matrix). the second texture of the terrain has to be the shadow map.
//-----------------------------------------------------------------------------materials
material mtl_terrainshadowmap
{
effect=
"
texture entSkin1;
texture entSkin2;
technique one_pass_shadow
{
pass p0
{
Texture[0]=<entSkin1>;
Texture[1]=<entSkin2>;
ColorArg1[0]=Texture;
ColorOp[0]=Modulate2x;
ColorArg2[0]=Diffuse;
TexCoordIndex[0]=0;
TextureTransformFlags[0]=Count2;
TextureTransform[0]={8.0,0.0,0.0,0.0, // color map u scale
0.0,8.0,0.0,0.0, // color map v scale
0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0};
ColorArg1[1]=Texture;
ColorOp[1]=Modulate;
ColorArg2[1]=Current;
TexCoordIndex[1]=0;
TextureTransformFlags[1]=Count2;
TextureTransform[1]={1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0};
}
}
";
}

