Awesomebump V1.0: 1 Height To Normal Conversion
Awesomebump V1.0: 1 Height To Normal Conversion
0
K. Kolasiski
January 18, 2015
Abstract
This paper explains most important methods and algorithms used in AwesomeBump (AB) tool in version v2.0.
I assumed that reader has some mathematical background about derivatives, dierential operators, numerical
methods and iterative solvers. Note that in v2.0 some of algorithms was modied.
This is the simplest problem which I will describe in this text. We can calculate normal vector of surface
at some point
r 0 = (x0 , y 0 )
z = f (r)
r0 .
n
b
r'
t
z=f(r)
z = f (r)
with normal
n,
tangent
and bitangent
vector at
r0 .
t = (1, 0,
f
),
x r=r0
numerically (using the nite dierence method wikipedia) this will have following form
t = (1, 0,
f (x + x, y) f (x, y)
).
x
In order to have control on the amplitude of the normal I introduced the depth parameter
t = (1, 0,
For
=0
f (x + x, y) f (x, y)
).
x
1
vector is normalized
t := normalize(t).
The process of calculation of the
n = t b = cross(t, b)
Here is the full code of the GLSL shader which converts the height map to normal texture.
1
2
3
4
5
6
7
8
9
10
11
12
13
s u b r o u t i n e ( f i l t e r M o d e T y p e ) v ec4 mode_height_to_normal ( ) {
c o n s t ve c2 s i z e = v ec2 ( 1 . 0 , 0 . 0 ) ;
c o n s t i v e c 3 o f f = i v e c 3 ( 1 ,0 ,1) ;
v ec 2 t e x _ c o o r d = v2QuadCoords . s t ;
v ec 4
hc = t e x t u r e ( l a y e r A , t e x _ c o o r d ) ;
f l o a t s 1 1 = hc . x ;
f l o a t s 2 1 = t e x t u r e O f f s e t ( l a y e r A , tex_coord , o f f . z y ) . x ;
f l o a t s 1 2 = t e x t u r e O f f s e t ( l a y e r A , tex_coord , o f f . y z ) . x ;
v ec 3 va
= n o r m a l i z e ( v ec3 ( s i z e . xy , 1 0 g u i _ h n _ c o n v e r s i o n _ d e p t h ( s21 s 1 1 ) ) ) ;
v ec 3 vb
= n o r m a l i z e ( v ec3 ( s i z e . yx , 1 0 g u i _ h n _ c o n v e r s i o n _ d e p t h ( s12 s 1 1 ) ) ) ;
v ec 3 bump = n o r m a l i z e ( c r o s s ( va , vb ) ) ;
r e t u r n v ec 4 ( clamp ( bump 0 . 5 + 0 . 5 , ve c3 ( 0 ) , v ec 3 ( 1 ) ) , 1 ) ;
}
= 10 gui_hn_conversion_depth,
and here
x = 1.
Additionally at the end of the shader the normal map is converted to standard textures values i.e. components have
values from 0 to 1. This approach of calculating the normal texture from height map can be found in many places
in the internet.
This problem is more complicated. As it was in previous section we dene a surface function of form
z = h(x, y),
where
is the height eld in our case described by the bump texture which we want to nd, and
(x, y) are UV
on a new
function
h
H = ( h
x , y , 1).
h h
, , 1) H,
x y
(1)
Now we act with the divergence operator on the Eq.(1), which gives following partial
dierential equation
n
ny
nx
+
x
y
From Eq. (1) we have
nz
z
= 0,
2
z
+ n
z = H = (
2h 2h
+ 2 + 0).
x2
y
nx
ny
+
=
x
y
2h 2h
+ 2
x2
y
.
(2)
In order to solve this equation numerically I used nite dierence approximation, which give following equation
(assuming that
x = y = 1,
nx (x + x, y) nx (x x, y) ny (x, y + x) ny (x, y x)
+
2x
2x
H(x + x, y) + H(x x, y) 2H(x, y) H(x, y + x) + H(x, y x) 2H(x, y)
+
x2
x2
2
=
.
n1 number
k = 1.
n6
H(x, y)
=
+
in which unknown is
H(x, y)
H
(3)
(4)
function.
H(x, y)
H(x, y)
which I assumed for simplicity to be periodic boundary conditions. This is quite natural because usually we want to
have seamless textures, and additionally periodicity is build-in feature of UV coordinates which simplify many things
in GLSL. If you know a little bit of numerical methods you should notice that scheme (3) is the simplest solution
in case of iterative solvers but the convergence of it is very poor. Strictly speaking you may need even thousand of
iterations to reach the convergence and proper solution thus proper form of
H(x, y)
improve the algorithm above using a simple trick which is called multi-grid iteration (MGI) approach. The MGI
method which I will describe in this text will be quite dierent than it is normally used in engineering problems.
We introduce a scale parameter
H(x, y)
=
+
(3):
(5)
I will not go into details of multi-grid medthods, but in AB the algorithm is following:
n1
then
k = 8,
then
restores the equation (3). One may check that this approach lead to convergence after around hundred of iterations.
Of course this will depend on the size of the input image. The number of iterations
n1 , n2 , ..., n6
scale parameter can be changed in program with horizontal sliders in normal to height conversion tool (see Fig. 3).
The shader which solves Eq. (2) using MGI method is following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
s u b r o u t i n e ( f i l t e r M o d e T y p e ) v ec4 mode_normal_to_height ( ) {
float scale
= hn_min_max_scale . z ; // s c a l e p a r a m e t e r
v ec 3 o f f
= s c a l e v ec3 ( ve c2 ( 1 ,1) dxy , 0 ) ;
v ec 2 t e x _ c o o r d = v2QuadCoords . s t ;
float
float
float
float
hxp
hxm
hyp
hym
=
=
=
=
texture ( layerA
texture ( layerA
texture ( layerA
texture ( layerA
float
float
float
float
nxp
nxm
nyp
nym
=
=
=
=
2( t e x t u r e ( layerB
2( t e x t u r e ( layerB
2( t e x t u r e ( layerB
2( t e x t u r e ( layerB
, tex_coord
, tex_coord
, tex_coord
, tex_coord
+
+
+
+
, tex_coord
, tex_coord
, tex_coord
, tex_coord
off
off
off
off
+
+
+
+
. yz ) . x ;
. xz ) . x ;
. zy ) . x ;
. zx ) . x ;
off
off
off
off
. yz
. xz
. zy
. zx
) . x 0.5) ;
) . x 0.5) ;
) . y 0.5) ;
) . y 0.5) ;
// Main e q u a t i o n
In above, the hn_min_max_scale is an uniform variable which in z-th component contains the value of
After the iterations for all values of
H(x, y) is calculated.
side:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
GLint textureWidth , t e x t u r e H e i g h t ;
g l G e t T e x L e v e l P a r a m e t e r i v (GL_TEXTURE_2D, 0 , GL_TEXTURE_WIDTH, &t e x t u r e W i d t h ) ;
g l G e t T e x L e v e l P a r a m e t e r i v (GL_TEXTURE_2D, 0 , GL_TEXTURE_HEIGHT, &t e x t u r e H e i g h t ) ;
f l o a t img = new f l o a t [ t e x t u r e W i d t h t e x t u r e H e i g h t 4 ] ;
g l G e t T e x I m a g e ( GL_TEXTURE_2D, 0 , GL_RGBA, GL_FLOAT, img ) ;
f l o a t min [ 3 ] = { img [ 0 ] , img [ 1 ] , img [ 2 ] } ;
f l o a t max [ 3 ] = { img [ 0 ] , img [ 1 ] , img [ 2 ] } ;
f o r ( i n t i = 0 ; i < t e x t u r e W i d t h t e x t u r e H e i g h t ; i ++){
f o r ( i n t c = 0 ; c < 3 ; c++){
i f ( max [ c ] < img [ 4 i+c ] ) max [ c ] = img [ 4 i+c ] ;
i f ( min [ c ] > img [ 4 i+c ] ) min [ c ] = img [ 4 i+c ] ;
}
}
...
program >s e t U n i f o r m V a l u e ( " m i n _ c o l o r " , QVector3D ( min [ 0 ] , min [ 1 ] , min [ 2 ] ) ) ;
program >s e t U n i f o r m V a l u e ( " max_color " , QVector3D ( max [ 0 ] , max [ 1 ] , max [ 2 ] ) ) ;
...
1
2
3
4
5
6
s u b r o u t i n e ( f i l t e r M o d e T y p e ) v ec4 m o d e _ n o r m a l i z e _ f i l t e r ( ) {
v ec 4 c o l o r = t e x t u r e ( l a y e r A , v2QuadCoords . xy ) ;
c o l o r . r g b = ( c o l o r . r g b m i n _ c o l o r ) / ( max_color m i n _ c o l o r ) ;
color . a = 1;
return color ;
}
k.
In order to calculate normal map from diuse texture the sobel lter is applied on gray scaled diuse image:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
c o n s t mat3 s o b e l _ k e r n e l =
mat3 ( 1.0 , 0 . 0 , +1.0 ,
2.0 , 0 . 0 , +2.0 ,
1.0 , 0 . 0 , +1.0) ;
s u b r o u t i n e ( f i l t e r M o d e T y p e ) v ec4 m o d e _ s o b e l _ f i l t e r ( ) {
f l o a t sobel_x = 0;
f l o a t sobel_y = 0;
v ec 4 o c o l o r= v ec4 ( 0 ) ;
f o r ( i n t i = 0 ; i < 3 ; i ++){
f o r ( i n t j = 0 ; j < 3 ; j ++){
sobel_x
+= t e x t u r e ( l a y e r A , v2QuadCoords . xy+v ec2 ( i 1, j 1) dxy ) . r s o b e l _ k e r n e l [ i ] [ j ] ;
sobel_y
= t e x t u r e ( l a y e r A , v2QuadCoords . xy+v ec 2 ( i 1, j 1) dxy ) . r s o b e l _ k e r n e l [ j ] [ i ] ;
}}
v ec 3 n = n o r m a l i z e ( v ec3 ( gui_basemap_amp v ec 2 ( sobel_y , s o b e l _ x ) , 1 ) ) ;
o c o l o r . r g b = clamp ( n 0 . 5 + 0 . 5 , v ec3 ( 0 ) , ve c3 ( 1 ) ) ; // c o n v e r t t o v a l u e s from 0 t o 1
o c o l o r . r g b = v ec 3 (1 o c o l o r . r , o c o l o r . gb ) ;
return ocolor ;
}
The parameter gui_basemap_amp is responsible for the amplitude of the calculated normals (Amplitude slider
in Fig. 4). Note that if gui_basemap_amp=0 then the image will be completely at. After this step image can be
presmoothed using the two-pass guassian blur lter (Presmooth slider, where the value of slider corresponds to the
width of the Gaussian mask i.e. standard deviation of the gaussian distribution function).
After this step normals are manipulated by normal expansion lter, which is executed n-times, where
controlled by the Iters slider:
is
Figure 5: Normal extraction lter example outputs. From left: a) input image, b) image obtained from sobel lter
(case when the parameter Iters is equal 0), c) the same but normal lter is applied (case when Iters
1).
d) Large
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
s u b r o u t i n e ( f i l t e r M o d e T y p e ) v ec4 m o d e _ n o r m a l _ e x p a n s i o n _ f i l t e r ( ) {
v ec 3 f i l t e r = v ec3 ( 0 ) ;
f l o a t wtotal = 0.0;
int radius
= gui_filter_radius ;
// n o r m a l e x p a n s i o n
i f ( gui_combine_normals == 0 ) {
f o r ( i n t i = r a d i u s ; i <= r a d i u s ; i ++){
f o r ( i n t j = r a d i u s ; j <= r a d i u s ; j ++){
v ec 2 c o o r d s = v ec2 ( v2QuadCoords . xy+v ec 2 ( i , j ) dxy ) ;
v ec3 n o r m a l = n o r m a l i z e (2 t e x t u r e ( l a y e r A , c o o r d s ) . xyz 1) ;
f l o a t w = mix ( l e n g t h ( n o r m a l . xy ) ,
1/(20 g a u s s i a n ( v ec 2 ( i , j ) , g u i _ f i l t e r _ r a d i u s ) l e n g t h ( n o r m a l . xy ) +1) ,
g u i _ n o r m a l _ f l a t t i n g +0.001) ;
w t o t a l += w ;
f i l t e r += n o r m a l w ;
}}
f i l t e r /= ( w t o t a l ) ; // n o r m a l i z a t i o n
r e t u r n v ec 4 ( 0 . 5 n o r m a l i z e ( f i l t e r ) + 0 . 5 , 1 ) ;
} else { . . .
...
}
Basically this code calculates the weighted arithmetic mean of normal vector where the weights are obtained
from the slope of the normal at given point. For example when the value of expression length(normal.xy) is big
then the slope is big. This lter makes that the edges become extruded along the edges. See example below:
The obtained image from the previous step is then mixed with the normal image obtained from sobel operator.
There are two type of blending: a) slope-based (Mixing slider) and b) standard blending (Blending). See the code
below:
1
2
3
4
5
6
7
8
9
10
11
s u b r o u t i n e ( f i l t e r M o d e T y p e ) v ec4 m o d e _ n o r m a l _ e x p a n s i o n _ f i l t e r ( ) {
i f ( gui_combine_normals == 0 ) {
....
} e l s e { // b l e n d i n g and s l o p e b a s e d m i x i n g
v ec 3 n
= n o r m a l i z e ( t e x t u r e ( l a y e r A , v2QuadCoords . xy ) . x y z 2 1) ; // g e t n o r m a l
f l o a t s l o p e = ( 1 . 0 / ( exp (+10 gui_mix_normals l e n g t h ( n . xy ) ) +1) ) ; // s l o p e b a s e d b l e n d i n g param
v ec 4 a = t e x t u r e ( l a y e r A , v2QuadCoords . xy ) ; // p r o c e s s e d n o r m a l image
v ec 4 b = t e x t u r e ( l a y e r B , v2QuadCoords . xy ) ; // n o r m a l image o b t a i n e d from s o b e l f i l t e r
r e t u r n mix ( mix ( a , b , s l o p e ) , a , g u i _ b l e n d _ n o r m a l s ) ;
}
}
Finally, when the Convert to NH button is pressed, the image visible in the right window is copied to the
Normal texture tab, then Height texture is calculated using the algorithm presented in (3).
In order to calculate specularity from the diuse texture the dierence of gaussian (DOG) lters is applied. For
more information see wikipedia and elsewhere in the internet.
In AB ambient occlusion is calculated using well known Screen Space Ambient Occlusion approximation. For more
information see wikipedia and elsewhere in the internet.