Vertex/image shading (in 2d, based on faked 3d..)

BlitzMax Forums/BlitzMax Programming/Vertex/image shading (in 2d, based on faked 3d..)

plash(Posted 2009) [#1]
This screenshot is from my tilemap engine, and, as you can see, has no sort of shading to give the viewer any sort of depth perception (except for the lines, obviously).


This is what I would like to achieve (this is from a game, it uses the same basic 2d rendering technique I use).


I render tiles by the vertex, so I think I could actually determine whether a vertex is on a down slope away from or toward the global lighting (South-West) then raise/lower the color values.

Any ideas?


Arowx(Posted 2009) [#2]
This looks like you could use a psudo 3d coordinate system projected onto 2d like those used by isometric engines, although it's probably easier to pre-render tiles in 3d then just draw them in place with lighting?

Of course the next step would be to use 3D.


Warpy(Posted 2009) [#3]
Drawing an actual shaded tile can maybe be achieved simply by using SetColor, though I don't know how you're drawing them.

Now, to calculate how much to shade each tile, I can help you.
Define a light source vector coming down at a slight angle, like (1/sqrt(3))*(1,1,1). (The 1/sqrt(3) is to make the vector have length 1).

Now, for each tile, consider it as a quadrilateral defined by points ABCD. It doesn't matter which way round you label the points.
Find the normal of the tile by finding the cross product of the vectors A->B and A->D. Make the normal a unit vector by dividing each component by the length of the vector.

Next, take the dot product of the light vector and the normal. If the tile is directly facing the light source, the dot product will be 1. If it is exactly at right angles the dot product will be 0, and if it is facing away from the light source the dot product will be negative, which you can take to mean there is no light on the tile. You might want to add an ambient amount of light though, so every tile can be seen, even if it's facing away from the light.

for example, skipping out most of the vector operations:
dp#=dotproduct(normal,light)
if dp<0 then dp=0   'can't subtract light!
light#=dp*.6+.4   'unlit tiles still have some ambient light on them
setcolor light*255,light*255,light*255



If you need help with vector operations, I've written a big explanation somewhere on the forum in the past, but I can't remember where. I'll do it again if you ask nicely.


MGE(Posted 2009) [#4]
Warpy's right on the money. Each tile's rgb should be manipulated in realtime based on it's height value. Should be a piece of cake. ;)


plash(Posted 2009) [#5]
This looks like you could use a psudo 3d coordinate system projected onto 2d like those used by isometric engines, although it's probably easier to pre-render tiles in 3d then just draw them in place with lighting?
That is entirely unfeasible. My maps can have a huge range of different heights, from zone to zone. Also, being able to change the height of any tile in realtime is a big plus.

Great, Warpy, I will try this soon.

Like I said, I have all four positions of the quad for a tile, and I render them using the image frame in a TImage (same code from the TImageFrame.Draw method, but I needed to define the points exactly without transformation):


Since I have access to each vertex of the quad, would it look best to give color to each vertex instead of the whole tile? (somehow I think it wouldn't)

Will try! Thanks.


plash(Posted 2009) [#6]
Are you speaking of 3d vectors? (x,y,z)

Currently I have the tile quad completely pre-calculated into a 2d vector (one for each corner of the quad). So far it doesn't seem to be drawing correct.. I'm going to try using 3d vectors where each corner of the quad also has the height.

I'll post the code/technique if it doesn't work.


Warpy(Posted 2009) [#7]
Yes, 3d vectors. The shading won't depend on where the tile is projected, but on its coordinates in 3d space.


plash(Posted 2009) [#8]
I've got somewhere!
The tiles are shaded now, but they always have the same shading, regardless of slope.

http://img413.imageshack.us/img413/4995/mapeditorshading1fh7.png

Determining color:
Local normal:TVec3, lvec:TVec3, light:Float, dp:Float
lvec = New TVec3.Create(1 / Sqr(3), 1 / Sqr(3), 1 / Sqr(3))

normal = _quad.topright.Copy()
normal.MultiplyVec(_quad.topleft)
normal.MultiplyVec(_quad.bottomright)
normal.Normalize()

dp = normal.DotProduct(lvec)

If dp < 0 Then dp = 0
light = dp * .6 + .4
SetColor(light * 255, light * 144, light * 144)


Vector type (mostly Yahfree's vector module, but with changes from around the forums - and obviously in 3 dimensions):



Warpy(Posted 2009) [#9]
Unless I'm missing something, you're doing the cross product wrong. You want to take the cross product (topleft - topright) x (bottomright - topright)

And MultiplyVec in Yahfree's code isn't a cross product operation. In fact, I can't think what it could be for. Wikipedia has the proper definition for you, and here's a function you can add to your code:

Function CrossProduct:TVec3( vec1:TVec3, vec2:TVec3 )
	Return TVec3.Create( vec1.y*vec2.z - vec1.z*vec2.y, vec1.z*vec2.x - vec1.x*vec2.z, vec1.x*vec2.y - vec1.y*vec2.x )
End Function


So:
Local normal1:TVec3, normal2:TVec3, normal:TVec3
normal1 = _quad.topleft.copy()
normal2 = _quad.bottomright.copy()
normal1.SubtractVec(_quad.topright)
normal2.SubtractVec(_quad.topright)

normal = TVec3.CrossProduct( normal1, normal2 )


By the way, I think that in defining vector types, the operators should return new vector instances, not modify existing ones. Maybe it would be slow if you're doing loads of vector work, but in that case you should probably think about representing them with float triples.


plash(Posted 2009) [#10]
Thanks Warpy!

This code works perfect (light vector and colors will be initiated once in the near future):
Local lvec:TVec3, light:Float, dp:Float
lvec = New TVec3.Create(1 / Sqr(3), - 1 / Sqr(3), 1.0)

Local normal1:TVec3, normal2:TVec3, normal:TVec3
normal1 = _quad.topright.Copy()
normal2 = _quad.bottomright.Copy()
normal1.SubtractVec(_quad.topleft)
normal2.SubtractVec(_quad.topleft)

normal = TVec3.CrossProduct(normal1, normal2)
normal.Normalize()
dp = normal.DotProduct(lvec)

If dp < 0 Then dp = 0
light = dp * 0.4 + 0.6
SetColor(light * 255, light * 255, light * 255)


I also changed the quad positions used because they were slightly wrong (topleft = A, topright = B, bottomleft = C, bottomright = D).
I still haven't found the best light position, some seem to make the color change too intense, some not enough.
http://img252.imageshack.us/img252/2800/earlywithshadingmoew5.png
http://img252.imageshack.us/img252/7167/earlywithshadingdebuglizx0.png


Warpy(Posted 2009) [#11]
Glad it works!

As you're doing this in realtime, why not add a little control to change the angle of the light vector until you get something you like?


plash(Posted 2009) [#12]
Good idea!


plash(Posted 2009) [#13]
Is the Normalize method correct in my vector type?:
		Method Normalize() 
			Local magn:Float = GetMagnitude()
			
			If magn <> 0
				
				x:/magn
				y:/magn
				z:/magn
				
			End If
			
		End Method


Curious because of the method minib3d uses (klepto's version):
	Method Normalize()
	
		Local d#=1/Sqr(x*x+y*y+z*z)
		x:*d
		y:*d
		z:*d
		
	End Method


Also, what would I have to do to smooth the light coloring over a neighboring tiles?


Warpy(Posted 2009) [#14]
Yep, same thing. sqr(x*x+y*y+z*z) is the magnitude, and you're dividing by it and klepto is multiplying by (1 dividided by the magnitude). 6 and two 3s.

To smooth the light colouring, you'll have to look into vertex colouring. I don't know OpenGL or Direct3d, so you're on your own on that one.


plash(Posted 2009) [#15]
Ah, righto.. Thanks.