# Generate normal map from texture: I wrote some functions, please help me to improve it! thanks!

Generally we have 2 ways to get the normal map: for the 3D model, use the high poly 3D model to get it; for the plane objects, wall, ground etc. use the PS or GIMP or CrazyBump to get it from texture directly. Now, I want to get normal map from the texture with code, without height map.

below is the function:

``````-- ????????????(???)??
-- ??????????????: (1, pMax)
function clamp(pX, pMax)
if (pX > pMax) then
return pMax;
elseif (pX < 1) then
return 1;
else
return pX;
end
end

-- ??????: ??
function intensity(col)
-- ?????????
--local average = (col.r + col.g + col.b)/3;
local average = 0.3*col.r + 0.59*col.g + 0.11*col.b
return average
end

-- ?????? [-1.0, 1.0] ????? [0,255]
function normal2Color(p)
return (p + vec3(1,1,1)) * (255 / 2)
end

-- ???????
function genGreyMap(img, s)
local t = image(img.width, img.height)
for y =1,img.height do
for x = 1,img.width do
local r,g,b,a = img:get(x,y)
local g = (0.3*r+0.59*g+0.11*b)*s
t:set(x,y,color(g,g,g,255))
end
end
return t
end

function greyNormal(img,s)
local w,h = img.width,img.height
local temp = image(w,h)
for y =1,img.height do
for x = 1,img.width do
-- ????????? rgba?(???)
topLeft  = color(img:get(clamp( x - 1, w), clamp(y + 1, h)))
top      = color(img:get(clamp( x , w), clamp(y + 1, h)))
topRight = color(img:get(clamp( x + 1, w), clamp(y + 1, h)))
right    = color(img:get(clamp( x - 1, w), clamp(y , h)))
bottomRight    = color(img:get(clamp( x + 1, w), clamp(y - 1 , h)))
bottom    = color(img:get(clamp( x , w), clamp(y - 1 , h)))
bottomLeft    = color(img:get(clamp( x - 1, w), clamp(y - 1 , h)))
left    = color(img:get(clamp( x - 1, w), clamp(y , h)))

--?? sobel filter
dX = (topRight.r + 2.0 * right.r + bottomRight.r) - (topLeft.r + 2.0 * left.r + bottomLeft.r);
dY = (bottomLeft.r + 2.0 * bottom.r + bottomRight.r) - (bottomLeft.r + 2.0 * top.r + topRight.r);
dZ = 255 / s;

newCol = normal2Color(vec3(dX,dY,dZ):normalize())
--print(newCol)
temp:set(x,y,color(newCol.x, newCol.y, newCol.z, 255))
end
end
return temp
end
``````

Usage:
1 Generate grey map from texture:
2 Generate normal map from grey map: greyNormal(greyImg, 1)

The texture:

Generated with the function:

Generated with the software(PS, GIMP or others)

If you can not see the image, please see here: http://www.oschina.net/question/219279_2170736

The function can get the normal map, but it looks not so good, it is more similar as a bump map, so I need your help to improve it, please give me some idea, thanks!

https://codea.io/talk/discussion/4674/example-of-normal-mapping

I don’t think you’ll get any ideas. Nobody has worked on that stuff for a long time, and I never saw anything that was any better than what you have.

@Ignatz Thank you for your praise! These days, I am studying the bump-mapping, normal-mapping, parallax-mapping and displacement-mapping and want to try it on Codea(the last one Codea can not support because of OpenGL ES can not support), I think in our forum there maybe someone who is interested in the it also, so I post this. When we finish it, we can share it to everyone

This is great work, thanks for sharing! A couple of years back someone did post some very nice normal-mapping examples with a neat top-down 3D maze demo.

Yes, please share if you don’t mind!

It was @spacemonkey 's “lit mesh” class I was thinking of, he calls it bump mapping, but I think it’s the same as normal mapping. See the gist linked towards the bottom of this thread: https://codea.io/talk/discussion/2282/adding-light-sources-to-a-3d-scene

Or maybe it was this gist. Not sure which version I grabbed:

Spacemonkey always said it was very simple and he’d like to improve it, which is why I said that I doubted it was better than the approach above. To my knowledge, nobody else has tried to do it.

@Ignatz yes, I`d like to share to others, because I have benefitted so many from others` share in our forum.

This is the whole code, I wrote the functions in shader because of the slow speed(on my iPad 2 need 10s, with shader only 1s). I will study more about how to improve it.

``````-- normal maping
--[[
The Normal Mapping code is from: @1980geeksquad
--]]

function setup()
displayMode(OVERLAY)

m1=mesh()
-- ? nMap ??? shader ??????
nMap = image(m1Tex.width, m1Tex.height )
setContext(nMap)
m1:draw()
setContext()

m = mesh()
local w,h = img.width, img.height
local ws,hs=WIDTH/w, HEIGHT/h
--ws,hs = 1,1

-- the same as above line ??????
-- m.vertices={vec2(0,0),vec2(WIDTH,0),vec2(0,HEIGHT),vec2(WIDTH,0),vec2(0,HEIGHT),vec2(WIDTH,HEIGHT) }
-- m.texCoords = {vec2(0,0),vec2(ws,0),vec2(0,hs),vec2(ws,0),vec2(0,hs),vec2(ws,hs)}

--[[
bumpImg = genBumpMap(img,0.007)
greyImg = genGreyMap(img,1)
--nMap = genNormalMap(img, 0.8)
gnMap = greyNormal(greyImg,2)
--saveImage("Dropbox:grey1",greyImg)
--saveImage("Dropbox:nmap1",gnMap)
--]]

tchx=0
tchy=0
end

function draw()
background(0)

-- ????????
m:draw()

--[[
m1:draw()
sprite(bumpImg,WIDTH/2+300,400)
sprite(greyImg,WIDTH/2+300,150)
--sprite(nMap,WIDTH/2,450)
sprite(gnMap,WIDTH/2+350,650)
sprite("Dropbox:n2",WIDTH/2,650)
--]]
sprite(nMap,WIDTH/2,650)
end

function touched(touch)
if touch.state == BEGAN or touch.state == MOVING then
tchx=touch.x+50
tchy=touch.y+50
end
end

-- ????????????(???)??
-- ??????????????: (1, pMax)
function clamp(pX, pMax)
if (pX > pMax) then
return pMax;
elseif (pX < 1) then
return 1;
else
return pX;
end
end

-- ??????: ??
function intensity(col)
-- ?????????
--local average = (col.r + col.g + col.b)/3;
return 0.3*col.r + 0.59*col.g + 0.11*col.b
end

-- ?????? [-1.0, 1.0] ????? [0,255]
function normal2Color(p)
return (p + vec3(1,1,1)) * (255 / 2)
end

-- ????????????
function genNormalMap(img,s)
local w,h = img.width,img.height
local temp = image(w,h)
for y =1,img.height do
for x = 1,img.width do
-- ????????? rgba?(???-??)
topLeft  = color(img:get(clamp( x - 1, w), clamp(y + 1, h)))
top      = color(img:get(clamp( x , w), clamp(y + 1, h)))
topRight = color(img:get(clamp( x + 1, w), clamp(y + 1, h)))
right    = color(img:get(clamp( x + 1, w), clamp(y , h)))
bottomRight    = color(img:get(clamp( x + 1, w), clamp(y - 1 , h)))
bottom    = color(img:get(clamp( x , w), clamp(y - 1 , h)))
bottomLeft    = color(img:get(clamp( x - 1, w), clamp(y - 1 , h)))
left    = color(img:get(clamp( x - 1, w), clamp(y , h)))

-- ???????
tl = intensity(topLeft)
t = intensity(top);
tr = intensity(topRight);
r = intensity(right);
br = intensity(bottomRight);
b = intensity(bottom);
bl = intensity(bottomLeft);
l = intensity(left);

--?? sobel filter
dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
dZ = 255 / s;

newCol = normal2Color(vec3(dX,dY,dZ))
newCol = normal2Color(vec3(dX,dY,dZ):normalize())
--print(newCol)
temp:set(x,y,color(newCol.x, newCol.y, newCol.z, 255))
end
end

return temp

end

-- ?????????
function greyNormal(img,s)
local w,h = img.width,img.height
local temp = image(w,h)
local sobel = math.sqrt(2.0)
local sobel = 1.0
for y =1,img.height do
for x = 1,img.width do
-- ?? me ??????? rgba?(???)
tl  = color(img:get(clamp( x - 1, w), clamp(y + 1, h)))
t   = color(img:get(clamp( x , w), clamp(y + 1, h)))
tr  = color(img:get(clamp( x + 1, w), clamp(y + 1, h)))
r   = color(img:get(clamp( x + 1, w), clamp(y , h)))
br  = color(img:get(clamp( x + 1, w), clamp(y - 1 , h)))
b   = color(img:get(clamp( x , w), clamp(y - 1 , h)))
bl  = color(img:get(clamp( x - 1, w), clamp(y - 1 , h)))
l   = color(img:get(clamp( x - 1, w), clamp(y , h)))
me  = color(img:get(clamp( x , w), clamp(y , h)))

--?? sobel filter
dX = (tr.r + sobel * r.r + br.r) - (tl.r + sobel * l.r + bl.r)
dY = (bl.r + sobel * b.r + br.r) - (tl.r + sobel * t.r + tr.r)
dZ = 255 / s

newCol = normal2Color(vec3(dX,dY,dZ):normalize())
--print(newCol)
temp:set(x,y,color(newCol.x, newCol.y, newCol.z, 255))
end
end
return temp
end

-- ???????
function genGreyMap(img, s)
local t = image(img.width, img.height)
for y =1,img.height do
for x = 1,img.width do
local r,g,b,a = img:get(x,y)
local g = (0.3*r+0.59*g+0.11*b)*s
t:set(x,y,color(g,g,g,255))
end
end
return t
end

``````
``````-- ?????? shader
--normal mapping
NormalMapping = { vs=[[
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying vec2 vTexCoord;
varying vec4 vColor;

uniform mat4 modelViewProjection;

void main()
{
vColor = color;
vTexCoord = texCoord;
gl_Position = modelViewProjection * position;
}
]],
fs=[[
//Default precision qualifier
precision highp float;

varying vec2 vTexCoord;
varying vec4 vColor;

// ????
uniform sampler2D tex;
// ??????
uniform lowp sampler2D s_multitex;

uniform vec2 Resolution;
uniform vec3 LightPos;
uniform vec4 LightColor;
uniform vec4 AmbientColor;
uniform vec3 Falloff;

void main()
{
//vec4 DiffuseColor = texture2D(tex,vTexCoord);
//vec3 NormalMap = texture2D(s_multitex,vTexCoord).rgb;
vec4 DiffuseColor = texture2D(tex,vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0)));
vec3 NormalMap = texture2D(s_multitex,vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))).rgb;
//NormalMap.g = 1.0 - NormalMap.g;
vec3 LightDir = vec3(LightPos.xy-(vTexCoord.xy/Resolution.xy),LightPos.z);
LightDir.x *= Resolution.x/Resolution.y;
float D = length(LightDir);
vec3 N = normalize(NormalMap*2.0-1.0);
vec3 L = normalize(LightDir);
vec3 Diffuse = (LightColor.rgb * LightColor.a) * max(dot(N,L), 0.0);
vec3 Ambient = AmbientColor.rgb * AmbientColor.a;
float Attenuation = 1.0 / (Falloff.x + (Falloff.y*D) + (Falloff.z*D*D));
// ?? = ??? + ?? * ??
vec3 Intensity = Ambient + Diffuse * Attenuation;
vec3 FinalColor = DiffuseColor.rgb * Intensity;
gl_FragColor = vColor * vec4(FinalColor,DiffuseColor.a);
}
]]},

genNormal = {
vs = [[
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying vec2 vTexCoord;
varying vec4 vColor;
varying vec4 vPosition;

uniform mat4 modelViewProjection;

void main()
{
vColor = color;
vTexCoord = texCoord;
vPosition = position;
gl_Position = modelViewProjection * position;
}
]],
fs = [[
precision highp float;

varying vec2 vTexCoord;
varying vec4 vColor;
varying vec4 vPosition;

uniform sampler2D texture;

// ????
uniform sampler2D tex;
void main()
{
//Sample the texture at the interpolated coordinate
lowp vec4 col = texture2D( texture, vTexCoord );

// vec4 normal = normalize(col.rgba);
//??????. ???
// gl_FragColor = vec4(fmod( (normal.xyz+1.)/2.,1.0), 1.);

// ?????????
float grey = 0.2126*col.r + 0.7152* col.g + 0.0722*col.b;
//???? [0, 1.0] ?????????? [-1.0, 1.0] ????

vec3 pos3D = vec3(vPosition.x, vPosition.y, grey);
vec3 greyCol = vec3(grey,grey,grey);

// ?????? z ???, ???????? vec3 ??
//vec3 normal = normalize(vec3(vPosition.x*2.-1., vPosition.y*2.-1., grey*2.-1.));
vec3 normal = normalize(vec3(vPosition.x/1000., vPosition.y/1000., grey*2.-1.));

// ???????
gl_FragColor = vec4(greyCol, col.a);

gl_FragColor = vec4((normal.xyz+1.)/2., col.a);

//Set the output color to the texture color
// gl_FragColor = col;
}
]]
},

-- ? sobel ???????    generate normal map with sobel operator
genNormal1 = {
vs = [[
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying vec2 vTexCoord;
varying vec4 vColor;
varying vec4 vPosition;

uniform mat4 modelViewProjection;

void main()
{
vColor = color;
vTexCoord = texCoord;
vPosition = position;
gl_Position = modelViewProjection * position;
}
]],
fs = [[
precision highp float;

varying vec2 vTexCoord;
varying vec4 vColor;
varying vec4 vPosition;

// ????
uniform sampler2D tex;
uniform sampler2D texture;

//??????-??, ??????-??
uniform float w;
uniform float h;

float clamp1(float, float);
float intensity(vec4);

float clamp1(float pX, float pMax) {
if (pX > pMax)
return pMax;
else if (pX < 0.0)
return 0.0;
else
return pX;
}

float intensity(vec4 col) {
// ?????????
return 0.3*col.x + 0.59*col.y + 0.11*col.z;
}

void main() {
// ????-???????????-??????
float ws = 1.0/w ;
float hs = 1.0/h ;
float c[10];
vec2 p = vTexCoord;
lowp vec4 col = texture2D( texture, p );

// sobel operator
// position.      Gx.            Gy
// 1 2 3     |-1. 0. 1.|   |-1. -2. -1.|
// 4 5 6     |-2. 0. 2.|   | 0.  0.  0.|
// 7 8 9     |-1. 0. 1.|   | 1.  2.  1.|
// ?????????
c[3] = intensity(texture2D( texture, vec2(clamp(p.x+ws,0.,w), clamp(p.y+hs,0.,h) )));
c[6] = intensity(texture2D( texture, vec2(clamp1(p.x+ws,w), clamp1(p.y,h))));
c[9] = intensity(texture2D( texture, vec2(clamp1(p.x+ws,w), clamp1(p.y-hs,h))));

// ?, ?
c[2] = intensity(texture2D( texture, vec2(clamp1(p.x,w), clamp1(p.y+hs,h))));
c[8] = intensity(texture2D( texture, vec2(clamp1(p.x,w), clamp1(p.y-hs,h))));

// ???, ?, ???
c[1] = intensity(texture2D( texture, vec2(clamp1(p.x-ws,w), clamp1(p.y+hs,h))));
c[4] = intensity(texture2D( texture, vec2(clamp1(p.x-ws,w), clamp1(p.y,h))));
c[7] = intensity(texture2D( texture, vec2(clamp1(p.x-ws,w), clamp1(p.y-hs,h))));

// ??? sobel ??, ????? [-1,1] ??? [0,1]
float dx = (c[3]+2.*c[6]+c[9]-(c[1]+2.*c[4]+c[7]) + 1.0) / 2.0;
float dy = (c[7]+2.*c[8]+c[9]-(c[1]+2.*c[2]+c[3]) + 1.0) / 2.0;
float dz = (1.0 + 1.0) / 2.0;

gl_FragColor = vec4(vec3(dx,dy,dz), col.a);

}
]]
}
}
``````

Is it possible to make a simple example that uses this shader, so we can see how you set the inputs?

@Ignatz the first block about mesh-m1 in the setup above is how to use the genNormal1 shader, the input is texture image, the output is its normal map. The second block about mesh-m in the setup is how to use the normal map in a normal-mapping scence.

A simple demo to use the genNormal1 shader to generate a normal map is below:

``````function setup()
displayMode(OVERLAY)

-- Generate normal map of  texture with shader, input is texture image
m1=mesh()