There is a lot of light/shadow map issues reported in the forum, so i think i will share a little trick , perhaps someone can use it
I have been trying to make a lightmapper ( shadow ) that is fast enoff to run on mobile devices, what this mean is the “quality” (Lumel) is worse then anything
but i found some tricks that REALLY improve the quality of light shadows.
In my light mapper a 2048x2048 textures is covering the complete GG Level ( everything ), it need to be this way to run on mobiles, so each pixel must represent the light/shadow the best it can.
Perhaps this could be used to improve light/shadows in GG , its a simple halftone trick that take zero extra time to calculate in the lightmapper, no extra rays.
I know that GG use a UV type light mapper with a mush better lumel , but it could be used the same way.
First the section / object that need light/shadow is scanned for low X,Z high X,Z coordinates that actually have light or shadow. then the light mapper ONLY use this area in the light/shadow texture, so if a level only use half of the available level size the 2048 texture will only be used on this area, if the level only have 1 object all of the 2048 texture will be used on this single object.
The shader then scale the UV to fit this “improved” lumel/texture:
if( pos.x > lowlightxz.x && pos.z > lowlightxz.y && pos.z < lowlightxz.w && pos.x < lowlightxz.z ) {
vec2 modmyxz = vec2( -mod (lowlightxz.x-pos.x+0.5, (lowlightxz.z-lowlightxz.x) ), mod (lowlightxz.y-pos.z-0.5, (lowlightxz.w-lowlightxz.y)));
uvVaryingLight = modmyxz / vec2 ((lowlightxz.z-lowlightxz.x), (lowlightxz.w-lowlightxz.y));
…
( lowlightxz.w and lowlightxz.z are high values ).
( UV is part of the vertex shader so the branch (if) don’t really have a speed impact. )
This way you get the best lumel possible using the lowest possible textures.
The trick is like an antialias, you just assign a halftone shadow to areas around the real shadow. This gives a smooth shadow falloff, the shadow pixels kind of fade away. The shadow is first assigned a halftone pixel , and only if the halftone pixel is reach again it will get the real shadow color.
It would allow the light mapper to go closer to the ray hit point to decide if its a false positive shadow ( you nearly can’t see false positive halftone ). So there just under the roof where the ray hit the roof from the top ( so close to the ray target ) it will get a halftone shadow, fading out the “light” line of false positive shadow you normally have in that area.
Sorry this post got a bit to code’ish so i will just include the AGK code i use , and hope somebody can use it for something
Lee: Its a must that the normal map is included in the light mapper calculation, i can understand that you need to replace the normal map with the light map to improve speed and memory, but if you make the normal map texture part of the light map texture ( as normal light/shadow pixels ) you will still have this cool effect, without the speed drop. Or perhaps its just a bug that normal maps is not there when you have a baked light map ?
Screenshots of a GG level using a single scaleable 2048 texture for the hole level, one using normal shadow and one with the halftone shadow. All objects will share this texture so dynamic objects also get the light/shadow effect.
Code:
//if GetFileExists("lightmap.png") = 1 then exitfunction "lightmap.png"
if lightmapsize = 4096 // dont work max agk image size is 2048.
lname$="lightmap4096.png"
texi = LoadImage("dummy4096.png")
elseif lightmapsize = 2048
lname$="lightmap2048.png"
texi = LoadImage("dummy2048.png")
elseif lightmapsize = 1024
lname$="lightmap1024.png"
texi = LoadImage("dummy1024.png")
else
lname$="lightmap512.png"
texi = LoadImage("dummy512.png")
endif
memb2 = CreateMemblockFromImage(texi)
width = GetMemblockInt(memb2, 0)
height = GetMemblockInt(memb2, 4)
size = GetMemblockSize(memb2)
lightrange# = (4000/gameguruscale#)
// enable collision
for ml = 1 to objnumbers
if GetObjectExists( objectid[ml] ) = 1 and GetObjectX(objectid[ml]) > 0
if GetObjectX(objectid[ml]) < lowlightx# then lowlightx# = GetObjectX(objectid[ml])
if GetObjectX(objectid[ml]) > hlightx# then hlightx# = GetObjectX(objectid[ml])
if GetObjectZ(objectid[ml]) < lowlightz# then lowlightz# = GetObjectZ(objectid[ml])
if GetObjectZ(objectid[ml]) > hlightz# then hlightz# = GetObjectZ(objectid[ml])
SetObjectCollisionMode(objectid[ml],1)
endif
next ml
SetObjectCollisionMode(1,1)
for x = 0 to width-1
for y = 0 to width-1
p3dx# = (x) * (51200/gameguruscale#/width)
p3dz# = (y) * (51200/gameguruscale#/width)
h# = GetObjectHeightMapHeight(1,p3dx#,p3dz#)
if h# <> 0.0
if h# > 700.0/gameguruscale#
if p3dx# < lowlightx# then lowlightx# = p3dx#
if p3dx# > hlightx# then hlightx# = p3dx#
if p3dz# < lowlightz# then lowlightz# = p3dz#
if p3dz# > hlightz# then hlightz# = p3dz#
endif
endif
next y
next x
lowlightx# = lowlightx# - (lightrange#/2.0)
hlightx# = hlightx# + (lightrange#/2.0)
lowlightz# = lowlightz# - (lightrange#/2.0)
hlightz# = hlightz# + (lightrange#/2.0)
lightscalex# = (hlightx# - lowlightx#) / (51200/gameguruscale#)
lightscalez# = (hlightz# - lowlightz#) / (51200/gameguruscale#)
//disdebug(str(lightscalex#))
//disdebug(str(lightscalez#))
// we need to run the lowest,z scan, so when we use the saved image the scale is correct.
if GetFileExists(lname$) = 1
DeleteImage(texi)
DeleteMemblock(memb2)
exitfunction lname$
endif
for x = 0 to width-1
for y = 0 to width-1
idxfrom = 12+(x*4)+( (width-y) * width)*4
useshadowcolor = halfshadowcolor
// send the ray.
if getshadows(x,y,width) = 1
if x > 0 and y > 0 and y < (width-1) and x < (width-1)
// half tone.
hidxto = 12+((x-1)*4)+( (width-(y) ) * width)*4
if round(GetMemblockByte(memb2,hidxto)) <> round(shadowcolor)
SetMemblockByte(memb2, hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,1+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,2+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,3+hidxto , round(255))
endif
hidxto = 12+((x+1)*4)+( (width-(y) ) * width)*4
if round(GetMemblockByte(memb2,hidxto)) <> round(shadowcolor)
SetMemblockByte(memb2, hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,1+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,2+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,3+hidxto , round(255))
endif
hidxto = 12+((x)*4)+( (width-(y+1) ) * width)*4
if round(GetMemblockByte(memb2,hidxto)) <> round(shadowcolor)
SetMemblockByte(memb2, hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,1+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,2+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,3+hidxto , round(255))
endif
hidxto = 12+((x)*4)+( (width-(y-1) ) * width)*4
if round(GetMemblockByte(memb2,hidxto)) <> round(shadowcolor)
SetMemblockByte(memb2, hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,1+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,2+hidxto , round(halfshadowcolor))
SetMemblockByte(memb2,3+hidxto , round(255))
endif
endif
if round(GetMemblockByte(memb2,idxfrom)) = round(halfshadowcolor)
useshadowcolor = shadowcolor
endif
SetMemblockByte(memb2, idxfrom , round(useshadowcolor))
SetMemblockByte(memb2,1+idxfrom , round(useshadowcolor))
SetMemblockByte(memb2,2+idxfrom , round(useshadowcolor))
SetMemblockByte(memb2,3+idxfrom , round(255))
else
SetMemblockByte(memb2, idxfrom , round(lightcolor))
SetMemblockByte(memb2,1+idxfrom , round(lightcolor))
SetMemblockByte(memb2,2+idxfrom , round(lightcolor))
SetMemblockByte(memb2,3+idxfrom , round(255))
endif
next y
//print("Liphgmapx: " + str(x) + " y: " + str(y) )
//sync()
next x
bakedimage = CreateImageFromMemblock(memb2)
SaveImage(bakedimage,lname$)
DeleteImage(texi)
DeleteMemblock(memb2)
// disable collision
for ml = 1 to objnumbers
SetObjectCollisionMode(objectid[ml],0)
next ml
SetObjectCollisionMode(1,0)
best regards Preben Eriksen,