MakeDot3

BlitzMax Forums/BlitzMax Programming/MakeDot3

JoshK(Posted 2007) [#1]
Filter to convert a heightmap to normal map. It's similar to a Sobel filter, but more accurate:
Strict

Import brl.pixmap


Function PixmapFilterDot3:TPixmap(pixmap:TPixmap,height#=1.0,parallax=0)
	Local lp,rp,tp,bp
	Local bumpmap:TPixmap
	Local vx#,vy#,vz#,m#
	Local tl#,bl#,ml#,tr#,mr#,br#,mm#,bm#,tm#
	Local isq2#,sum#,al#,ar#,at#,ab#,r,g,b,a,x,y
	Local format
	
	If parallax
		bumpmap=CreatePixmap(pixmap.width,pixmap.height,PF_RGBA8888)
	Else
		bumpmap=CreatePixmap(pixmap.width,pixmap.height,PF_RGB888)	
	EndIf
	
	For x=0 To pixmap.width-1
		For y=0 To pixmap.height-1
			
			lp=x-1
			rp=x+1
			tp=y-1
			bp=y+1
			If lp<0 lp=pixmap.width-1
			If lp>pixmap.width-1 lp=0
			If rp<0 rp=pixmap.width-1
			If rp>pixmap.width-1 rp=0
			If tp<0 tp=pixmap.width-1
			If tp>pixmap.height-1 tp=0
			If bp<0 bp=pixmap.width-1
			If bp>pixmap.height-1 bp=0
			
			tl#=ReadHeight(pixmap,x-1,y-1)
			tm#=ReadHeight(pixmap,x,y-1)
			tr#=ReadHeight(pixmap,x+1,y-1)
			ml#=ReadHeight(pixmap,x-1,y)
			mm#=ReadHeight(pixmap,x,y)
			mr#=ReadHeight(pixmap,x+1,y)
			bl#=ReadHeight(pixmap,x-1,y+1)
			bm#=ReadHeight(pixmap,x,y+1)
			br#=ReadHeight(pixmap,x+1,y+1)
			
			vx#=0.0
			vy#=0.0
			vz#=1.0
			
			isq2#=1.0/Sqr(2.0)
			sum#=1.0+isq2+isq2
			
			al#=(tl*isq2+ml+bl*isq2)/sum
			ar#=(tr*isq2+mr+br*isq2)/sum
			at#=(tl*isq2+tm+tr*isq2)/sum
			ab#=(bl*isq2+bm+br*isq2)/sum			

			vx#=(al-ar)/255.0
			vy#=(at-ab)/255.0
			m=Max(0,vx*vx+vy*vy)
			m=Min(m,1.0)

			vz=Sqr(1.0-m) 
			
			If height<>0.0
				vz:/height
				m#=Sqr(vx*vx+vy*vy+vz*vz)
				vx:/m
				vy:/m
				vz:/m
			EndIf
			
			r=vx*127.5+127.5+0.5'0.5 added for rounding
			g=vy*127.5+127.5+0.5
			b=vz*127.5+127.5+0.5
			a=mm'store height value in alpha channel
			
			r=Min(r,255)
			r=Max(r,0)
			g=Min(g,255)
			g=Max(g,0)
			b=Min(b,255)
			b=Max(b,0)
			
			WritePixel bumpmap,x,y,b+(g Shl 8)+(r Shl 16)+(a Shl 24)
		Next
	Next
	Return bumpmap
EndFunction

Private

Function ReadHeight:Float(pixmap:TPixmap,x,y)
	Local hue,r,g,b
	While x<0
		x:+pixmap.width
	Wend
	While x>pixmap.width-1
		x:-pixmap.width
	Wend
	While y<0
		y:+pixmap.height
	Wend
	While y>pixmap.height-1
		y:-pixmap.height
	Wend
	hue=ReadPixel(pixmap,x,y)
	r=(hue & $00FF0000) Shr 16
	g=(hue & $0000FF00) Shr 8
	b=(hue & $000000FF)
	Return Float(r)*0.3+Float(g)*0.59+Float(b)*0.11
EndFunction

Public



plash(Posted 2007) [#2]
Sweet.


Chroma(Posted 2007) [#3]
Josh that's awesome. You never cease to amaze that's for sure.

What I'm wondering is how this will look using the dot3 flag in Blitz3D. But I'm at work atm...


marksibly(Posted 2007) [#4]
Heya,

I'm interested in where you got the 1/sqr(2) bit from?!?

This appears to be the equivalent of a 'fudge factor' I use in my own routines for specifying texture 'depth' - by modifying this fudge factor, you can make surfaces appear more/less bumpy.

However, if there's another/better way to do this I'm all ears...


JoshK(Posted 2007) [#5]
ISq2 is inverse square root of 2. It is used to give the diagonal pixels slightly less influence over the averages than the pixels that are directly touching the pixel we are working on. Without this, bumpmaps come out looking very pixelated.


First it samples all pixels around the center; top-left, top-middle, top-right, etc. MM is the center pixel itself. It will wrap around from the other side of the texture if you are on an edge.

Then it computes an average for the four pixels directly around the center...average left, average right, avg. top, avg. bottom.

The horizontal normal of the pixel is determined by comparing the average left and right pixels. If left=right the X normal is 0.0. If left-right=255 the horizontal normal is 1.0.

The vertical component is calculated the same way.

After the X and Z component are calculated, that leaves the Z component, which is calculated like this:

1.0 = Sqr( x*x + y*y + z*z )
1.0 = x*x + y*y + z*z
1.0 - ( x*x +y*y ) = z*z
Sqr( 1.0 - ( x*x +y*y ) ) = z

The results seem to always come out with a magnitude of 1.0, which is good.

ATI's tool seems to do some scaling for low-contrast images, and this just performs the math on the true pixels. If it is correct, then I think this is better because it lets you work on low-bumpiness textures and you can keep things more uniform.


marksibly(Posted 2007) [#6]
Hey cool - never really considered the diagonals. My dot3 generators to date have been pretty primitive compared to this!

Another thing: shouldn't 'x*128.5+128.5' etc be 'x*127.5+127.5' etc? Or is there something else cute going on there?


JoshK(Posted 2007) [#7]
127.5 is correct. Please let me know if you find anything else off.


z4g0(Posted 2007) [#8]
I use in my own routines for specifying texture 'depth'

so this is an evidence of the 3d module existence! :D


Chroma(Posted 2007) [#9]
So this:
r=vx*128.5+128.5
g=vy*128.5+128.5
b=vz*128.5+128.5

should be:
r=vx*127.5+127.5
g=vy*127.5+127.5
b=vz*127.5+127.5
?

If so can you change the original post Josh.