在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果

目录

概述

一般来说, 法线贴图是用高模的法线图, 低模的纹理图, 来生成较好的渲染效果. 而法线图通常是通过图像处理软件来生成的, 这里我们准备尝试用程序来生成法线图, 输入只有纹理图, 没有高度图.

那么这种方法究竟可行不可行? 答案是可行, 例如比较受欢迎的 CrazyBump 就是直接通过算法以纹理贴图未输入生成法线图, 不过具体算法就不清楚了, 接下来我们会进行各种试验, 来看看如何用算法生成能达到专业图像处理软件生成水准的法线图.

第一次脑洞

在对法线图有一点印象后, 我们打算先把纹理贴图转换为灰度图, 可以很明确地表示贴图表面的明暗程度, 再把灰度值转换为高度值--也就是z值(相当于高度图), 再利用原来的 x,y 坐标一起生成空间的向量坐标, 再求出该向量坐标对应的法线向量, 然后利用该法线向量生成法线图.

目前的问题是 z 值相对于 xy值有些太小, 所以把 xy 值缩小 2000 倍, 可以得到一张类似于法线图的蓝紫色图片, 但是跟实际的法线图对比, 还是不够好.

这个算法用 OpenGL ES Shader 实现, 主要是为了充分利用 GPU 来提高绘图速度, 代码如下:

-- 用来生成法线图的 shader
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/10000., vPosition.y/10000., 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;
}
]]
}

这个算法纯粹是偷懒(开始时不想认真研究法线图)+自由脑洞的产物, 效果奇差, 就不贴图了.

  • 本次试验得到的经验:

    以上代码是在对法线图一知半解的情况下脑洞大开写出来的, 在深入了解法线图的原理后, 就会发现这么去写纯粹是瞎搞, 而且居然一开始就作死直接用 shader 写, 虽然最终跑起来了, 但是调试过程简直是欲仙欲死.
  • 所以结论:

    1.先好好研究明白法线图的概念原理, 再继续试验;

    2.为了提高调试效率, 后续代码一律先在 CPU 上跑, 跑通了再改写成 shader 代码.

用索贝尔算子计算xy法矢量

经过一番研究, 本次试验用 sobel operator 来实现

LUA算法实现

吸取上次的教训, 先写 CPU 代码如下:

-- 法线图生成函数:从高度图(灰度图)生成
-- 确保像素点坐标落在纹理贴图内: (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.0,1.0,1.0)) * (255.0 / 2.0)
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 or 1)
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())
temp:set(x,y,color(newCol.x, newCol.y, newCol.z, 255))
end
end
return temp
end

生成效果如下:

左边是本算法生成的法线图, 右边是使用图像处理软件得到的法线图:



光照效果, 使用算法生成的法线图



光照效果, 使用图像处理软件生成的法线图:



跟图像处理软件生成的法线图比较, 发现虽然用上述算法生成的法线图更真实地反映了纹理的表面细节情况, 但是在做法线映射时, 出现的立体效果反而不如经过图像处理软件修饰的法线图, 它把边缘扩散了,边缘部分看起来更光滑, 光线照射起来凸凹感更强.

不过还是希望能达到图像处理软件那种效果, 为此还专门发了一个求助帖根据纹理贴图生成法线图的算法, 如何改进? 感觉更像凹凸图, 结果还没等回应就被N多的新问题被冲得没影了...

shader算法实现

上述算法生成的法线图凑乎还能看, 不过就是生成速度太慢了, 一张 300*225 大小的纹理, 在我的 iPad2 上要跑10秒左右.

既然算法确定了, 那我们就可以放心地把它改写到 GPU 上去了, 结果半小时不到就搞定移植, 看来只要方法对头了, 效率自然就高, shader 代码如下:

-- 用 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); }
]]
}

写了才发现, 原来 OpenGL ES 2.0 里自带了一个 clamp 函数, 不过需要输入3个参数:pX, pMin, pMax, 跟我们的 clamp 差不多, 为了避免报错, 把我们的 clamp 改为 clamp1.

跑一遍, 速度提升了10倍以上, 不到1秒就生成法线图了. 看来以后写图像处理算法一定要充分发掘 GPU 的潜力.

OK, 第二个试验成功了, 接下来的目标是在这个基础上对边缘做进一步处理, 目测貌似灰度值膨胀算法可以试验一下, 先研究研究再说.

下面是完整源代码:

-- normal mapping
--[[
The Normal Mapping code from Author: @1980geeksquad
Link: https://codea.io/talk/discussion/4674/example-of-normal-mapping
法线贴图光照效果的代码来自 @1980geeksquad
--]] function setup()
displayMode(OVERLAY) -- 用 shader 生成法线图
m1=mesh()
m1Tex = readImage("Dropbox:n1")
m1.shader=shader(shaders["genNormal1"].vs,shaders["genNormal1"].fs)
m1.shader.texture = m1Tex
m1.shader.w, m1.shader.h = m1Tex.width, m1Tex.height
-- 单独显示由 shader 生成的法线图
m1:addRect(m1Tex.width/2,m1Tex.height/2, m1Tex.width, m1Tex.height)
-- 用 nMap 保存由 shader 生成的法线图
nMap = image(m1Tex.width, m1Tex.height )
setContext(nMap)
m1:draw()
setContext() -- 用 shader 演示法线贴图-法线映射
m = mesh()
img = readImage("Dropbox:n1")
m.shader=shader(shaders["NormalMapping"].vs,shaders["NormalMapping"].fs)
local w,h = img.width, img.height
local ws,hs=WIDTH/w, HEIGHT/h
--ws,hs = 1,1 m.shader.Resolution = vec2(ws,hs)
m.shader.LightPos = vec3(0,0,0.075)
m.shader.LightColor = vec4(0.94,0.77,0.17,0.9)
m.shader.AmbientColor = vec4(0.6,0.6,1.0,0.5)
m.shader.Falloff = vec3(0.4,2.0,10.0)
m:setRectTex(m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT),0,0,ws,hs)
-- 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)}
m.shader.tex = "Dropbox:n1"
m.shader.s_multitex = "Dropbox:n2"
m.shader.s_multitex = nMap --[[
bumpImg = genBumpMap(img,0.007)
greyImg = genGreyMap(img,1)
--nMap = genNormalMap(img, 0.8)
gnMap = greyNormal(greyImg,2)
-- m.shader.tex = img
m.shader.s_multitex = gnMap
--saveImage("Dropbox:grey1",greyImg)
--saveImage("Dropbox:nmap1",gnMap)
--]] tchx=0
tchy=0
end function draw()
background(0) -- 设置光源位置坐标
m.shader.LightPos = vec3(tchx/WIDTH,tchy/HEIGHT,0.015)
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
shaders = {
--normal mapping
NormalMapping = { vs=[[
//--------vertex shader---------
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=[[
//---------Fragment shader------------
//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);
}
]]}, -- 旧的用来生成法向图的 shader
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); }
]]
}
}

Xcode 项目链接(等有空了再输出, 先占个位)

更新补充

上面的代码 dx 搞错方向了, 应该修改为:

float dx = (-(c[3]+2.*c[6]+c[9])+(c[1]+2.*c[4]+c[7]) + 1.0) / 2.0;

这样才会保持右侧边缘为紫色

正确的生成图:

错用梯度矩阵后的生成图:

用图像处理软件生成的:

参考

example-of-normal-mapping

ShaderLesson6

can-normal-maps-be-generated-from-a-texture

在 iPad 上试验从用算法生成法线贴图-到法线映射光照效果的更多相关文章

  1. java自动生成略缩图

    当你要做一个图库的项目时,对图片大小.像素的控制是首先需要解决的难题. 本篇文章,在前辈的经验基础上,分别对单图生成略缩图和批量生成略缩图做个小结. 一.单图生成略缩图 单图经过重新绘制,生成新的图片 ...

  2. PHP HMAC_SHA1 算法 生成算法签名

    HMAC_SHA1(Hashed Message Authentication Code, Secure Hash Algorithm)是一种安全的基于加密hash函数和共享密钥的消息认证协议. 它可 ...

  3. 根据twitter的snowflake算法生成唯一ID

    C#版本 /// <summary> /// 根据twitter的snowflake算法生成唯一ID /// snowflake算法 64 位 /// 0---0000000000 000 ...

  4. 算法生成N芒星

    前面两个图像生成算法是:道教的太极八卦图和佛教的卐和卍字图.这一节整个洋气的图像:芒星.但愿我别召唤出什么恐怖的禁忌,尤其今晚还是万圣节之夜.平时看玄幻小说,经常读到有关六芒星,七芒星,九芒星的技法. ...

  5. C# 根据twitter的snowflake算法生成唯一ID

    C# 版算法: using System; using System.Collections.Generic; using System.Linq; using System.Text; using ...

  6. ZeroMQ接口函数之 :zmq_z85_decode – 从一个用Z85算法生成的文本中解析出二进制密码

    ZeroMQ 官方地址 :http://api.zeromq.org/4-0:zmq_z85_decode zmq_z85_decode(3)         ØMQ Manual - ØMQ/4.1 ...

  7. 问题解决(一)在ipad上通过safari浏览文档

    项目背景 针对用Sencha touch 1.1开发的一个用于通过ipad浏览的网站(其实是对PC端一个网站的映射)中的一个模块的开发,这个模块的主要功能就是用户浏览各种‘报告’,这些被阅览的‘报告’ ...

  8. 通过Mac远程调试iPhone/iPad上的网页(转)

    我们知道在 Mac/PC 上的浏览器都有 Web 检查器这类的工具(如最著名的 Firebug)对前端开发进行调试,而在 iPhone/iPad 由于限于屏幕的大小和触摸屏的使用习惯,直接对网页调试非 ...

  9. 深入了解iPad上的MouseEvent【转】

    iPad上没有鼠标,所以手指在触发触摸事件(TouchEvent)的时候,系统也会产生出模拟的鼠标事件(MouseEvent).     这对于普通网页的浏览需求而言,基本可以做到与PC端浏览器无明显 ...

随机推荐

  1. 啊啊啊 草蛋啊 之前努力一天搞出来的时间算法 被一句pk掉 给我砖头

    package yun3; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Cale ...

  2. mac上查找nginx安装位置

    在终端输入: nginx -V 查看nginx版本及安装的本地位置 ngxin -v 查看nginx版本(此方法依然可以检测是否安装某一软件,如git,hg等)

  3. 『编程题全队』Alpha 阶段冲刺博客Day3

    1.每日站立式会议 1.会议照片 2.昨天已完成的工作统计 孙志威: 1.添加团队模块的标题栏 2.测试客户端和服务器之间的通讯基本连通性 3.完成团队模块的燃尽图模块 孙慧君: 1.完成了水印的设计 ...

  4. cxVerticalGrid

    cxVerticalGrid can't get values procedure TForm1.Button1Click(Sender: TObject); var i: Integer; lvNa ...

  5. Ubuntu安装使用中的一些注意事项

    在win7上安装VMware workstations10.0 ,在VMware workstations10.0上安装Ubuntu14.04 64位时,关于网络的连接注意: win7 网络连接里上的 ...

  6. C++的继承与多态

    ◆ 概念介绍 继承:为了代码的重用,保留基类的原本结构,并新增派生类的部分,同时可能覆盖(overide)基类的某些成员. 多态:一种将不同的特殊行为和单个泛化记号相关联的能力,分为静态多态和动态多态 ...

  7. Java容器深入浅出之PriorityQueue、ArrayDeque和LinkedList

    Queue用于模拟一种FIFO(first in first out)的队列结构.一般来说,典型的队列结构不允许随机访问队列中的元素.队列包含的方法为: 1. 入队 void add(Object o ...

  8. 谁能告诉delphi7 的updatebatch使用属性说明?

    谁能告诉delphi7 的updatebatch使用属性说明? ADODataSet1.UpdateBatch(arAll); 就是提交你的数据集到数据库 arCurrentOnly the upda ...

  9. [洛谷P4819][中山市选]杀人游戏

    题目大意:有一张$n$个点$m$条边的有向图,有一个关键点,如果你访问一个点,你会知道它连出的边中有没有关键点,以及若有的话是哪个.问最优策略下不访问关键点而知道关键点的概率 题解:发现若一个点不是关 ...

  10. 遇到问题----java----myeclipse或者eclipse发布的项目时配置文件不更新或者无配置文件

    myeclipse或者eclipse发布的项目时配置文件不更新或者无配置文件. 正常的web项目有目录 src/main/resources 和 src/main/java 这两个目录默认在编译发布时 ...