shader复习与深入:Normal Map(法线贴图)Ⅱ(转)

24 篇文章 0 订阅
21 篇文章 1 订阅

本文写得很好,转自 http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html

1. 怎样获得顶点的TBN

其实我觉得这个是实践部分最麻烦的地方。OpenGL提供了诸如glNormal、normal-vbo之类的接口设置顶点的法线,然后在shader中以gl_Normal等方式取得顶点法线数据,但是没有提供切线和副法线的。当然两者只要其一就足够了(另一者可通过叉乘和左/右手定则获得)。因为要把TBN导入shader,干脆就设置attribute变量,记录每个顶点的切线。切线一般就是相邻顶点的差向量了(其实这有时候是非常繁重的工作)。

如果是通常的3DS模型的话,顶点法线是共顶点的面的面法线的加权,这样法线就不一定垂直于某个面,即与切线不垂直。但只要它们还是近似垂直的,上篇提及的Gram-Schmidt 算法应该可以处理。或者在shader中,把法线与切线叉乘出副法线,再用法线与副法线叉乘得新的切线,也能确保两两垂直。这样之前的TBN矩阵的转置矩阵就能直接作为其逆矩阵,完成向量从模型坐标系往切线空间坐标系的变换了。

问题不只这样。对于一些模型,共享顶点的三角面片面法线差角太大,这时候计算出的该顶点法线和切线就可能带来麻烦。在橙书(OpenGL Shading Language)中,谈及了切线必须是一致的(consistently),面片相邻的顶点切线不应该差距太大。但若相邻面片夹角太大,得到的该顶点法线就可能与“共享该顶点的面片”上的其他顶点的法线差异很大,从而切线也会相差很大,直接导致光向量等在这两顶点的切线空间差异很大,插值的各个针对像素的光向量方向差异很大,与像素法线点乘的cos也会差异得很明显(而现实中一般的凹凸面漫反射光线不会有太大方向差异)。解决方法是把该出了问题的顶点拆成两个(原地拷贝,3DS模型就不用了- -),一个面片用一个,其法线只受所属的面片的面法线决定(这样最后会形成突出的边缘,但夹角大的面片之间实际上就应该会是有这样的效果吧)。

另一个问题,我们向shader传入顶点法线切线,希望副法线由两者叉乘得出。但既然叉乘就有个方向问题(结果可以有两个方向,AXB与BXA是不一样的,我以前弄shadow volume就曾被它这种特性作弄过)。AXB改成BXA实际上会导致凹凸感反向,原来凹的变凸了,原来凸的变凹了(要仔细比对,不然会有首因效应)。一般就用N X T吧,因为基本上都是这个顺序的,结果也符合原Normal Map。

2. GLSL 1.2 Shader实现代码

没什么好说的,就是前面算法翻译成GLSL。

Vertex Shader:

  1. // vertex shader
  2. uniform vec3 lightpos; //传入光源的模型坐标吧
  3. uniform vec4 eyepos;
  4.  
  5. varying vec3 lightdir;
  6. varying vec3 halfvec;
  7. varying vec3 norm;
  8. varying vec3 eyedir;
  9.  
  10. attribute vec3 rm_Tangent;
  11.  
  12. void main(void)
  13. {
  14.    vec4 pos = gl_ModelViewMatrix * gl_Vertex;
  15.    pos = pos / pos.w;
  16.    
  17. //把光源和眼睛从模型空间转换到视图空间
  18.    vec4 vlightPos = (gl_ModelViewMatrix * vec4(lightpos, 1.0));
  19.    vec4 veyePos   = (gl_ModelViewMatrix * eyepos);
  20.    
  21.    lightdir = normalize(vlightPos.xyz - pos.xyz);
  22.    vec3 eyedir = normalize(veyePos.xyz - pos.xyz);
  23.    
  24.   //模型空间下的TBN
  25.    norm = normalize(gl_NormalMatrix * gl_Normal);
  26.  
  27.    vec3 vtangent  = normalize(gl_NormalMatrix * rm_Tangent);
  28.  
  29.    vec3 vbinormal = cross(norm,vtangent);
  30.    
  31.    //将光源向量和视线向量转换到TBN切线空间
  32.    lightdir.x = dot(vtangent,  lightdir);
  33.    lightdir.y = dot(vbinormal, lightdir); 
  34.    lightdir.z = dot(norm     , lightdir);
  35.    lightdir = normalize(lightdir);
  36.    
  37.    eyedir.x = dot(vtangent,  eyedir);
  38.    eyedir.y = dot(vbinormal, eyedir);
  39.    eyedir.z = dot(norm     , eyedir);
  40.    eyedir = normalize(eyedir);
  41.    
  42.    halfvec = normalize(lightdir + eyedir);
  43.  
  44.    gl_FrontColor = gl_Color;
  45.    
  46.    gl_TexCoord[0] = gl_MultiTexCoord0;
  47.    
  48.    gl_Position = ftransform();
  49. }

传入的lightPos,eyePos,gl_Vertex,gl_Normal,rm_Tangent是其模型坐标系下的坐标、向量,乘以ModelView矩阵(法线切线乘以ModelView矩阵的转置逆矩阵)到了视图空间(vlightPos,veyePos,pos,norm, vtangent);在视图空间它们已经有了“世界”的概念了,因此可以平等地相互影响(在各自封闭的模型空间是享受不了的),可以作各种点乘叉乘加减乘除计算。

注意,lightPos,eyePos虽说是在其各自模型坐标系下定义的,但不对它们弄什么平移旋转缩放操作的话,其模型矩阵就是一单位阵,此时其“世界坐标 == 模型坐标”。所以这时我可以当它是在世界空间定义的坐标(实际上一般我们都会在世界空间定义这两个点)。(注意,前提是不对它们做模型变换。)

从以上量得到光源向量、视线向量后(它们在视图空间),N、T叉乘得B(注意它们现在都在视图空间),通过TBN矩阵逆矩阵把两向量变换到当前顶点的切线空间,交给光栅去插值。 

对以上有不理解的朋友,可能是没看上篇:[shader复习与深入:Normal Map(法线贴图)Ⅰ]

fragment shader:

  1. //fragment shader
  2. uniform float shiness;
  3. uniform vec4 ambient, diffuse, specular;
  4.  
  5. uniform sampler2D bumptex;
  6. uniform sampler2D basetex;
  7.  
  8. float amb = 0.2;
  9. float diff = 0.2;
  10. float spec = 0.6;
  11.  
  12. varying vec3 lightdir;
  13. varying vec3 halfvec;
  14. varying vec3 norm;
  15. varying vec3 eyedir;
  16.  
  17. void main(void)
  18. {
  19.    vec3 vlightdir = normalize(lightdir);
  20.    vec3 veyedir = normalize(eyedir);
  21.  
  22.    vec3 vnorm =   normalize(norm);
  23.    vec3 vhalfvec =  normalize(halfvec);  
  24.    
  25.    vec4 baseCol = texture2D(basetex, gl_TexCoord[0].xy); 
  26.    
  27.    //Normal Map里的像素normal定义于该像素的切线空间
  28.    vec3 tbnnorm = texture2D(bumptex, gl_TexCoord[0].xy).xyz;
  29.    
  30.    tbnnorm = normalize((tbnnorm  - vec3(0.5))* 2.0); 
  31.    
  32.    float diffusefract =  max( dot(lightdir,tbnnorm) , 0.0); 
  33.    float specularfract = max( dot(vhalfvec,tbnnorm) , 0.0);
  34.    
  35.    if(specularfract > 0.0){
  36.    specularfract = pow(specularfract, shiness);
  37.    }
  38.    
  39.    gl_FragColor = vec4(amb * ambient.xyz * baseCol.xyz
  40.                  + diff * diffuse.xyz * diffusefract * baseCol.xyz
  41.                  + spec * specular.xyz * specularfract ,1.0);
  42. }

注意把normal map里的normal由(0,1)映射回(-1,1)。baseCol得到的是基底纹理的像素颜色。其余部分就是per pixel lighting的东西了。[Shader快速复习:Per Pixel Lighting(逐像素光照)]


(上为底纹理和法线纹理,下为它们与某破壁模型合作的效果,纹理from planetpixelemporium.com)
 Normal Map   www.zwqxin.com

Normal Map   www.zwqxin.com
(我想是游戏最常用的用途:砖墙。我想是最常用的NormalMap,from NEHE)
Normal Map   www.zwqxin.com
Normal Map   www.zwqxin.com
(自己把墙壁BaseMap放入Photoshop的normalMapFilter里弄的NormalMap,呃.....)

参考资料:N多网上资料+OpenGL Shading Language 2nd Edition

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现方法: 1. 法线贴图Normal Map):通过改变网格表面的法线来实现凹凸不平的效果。首先,需要将草地的几何体模型导入到three.js中。然后,使用一张法线贴图,通过ShaderMaterial来渲染草地模型。在渲染的过程中,将法线贴图传给ShaderMaterial,并使用shader进行法线贴图的计算,最终渲染出凹凸不平的草地效果。 2. 路径贴图(Path Map):将草地的材质按照路径进行划分,每个路径之间使用不同的材质进行渲染。这种方法可以轻松实现草地中不同方向的草叶生长方向不同的效果。 3. 颜色贴图(Color Map):使用一张纹理贴图来实现草地的颜色变化。首先,需要将草地的几何体模型导入到three.js中。然后,使用一张颜色贴图,通过ShaderMaterial来渲染草地模型。在渲染的过程中,将颜色贴图传给ShaderMaterial,并使用shader进行颜色的计算,最终渲染出颜色变化的草地效果。 技术: 1. ShaderMaterial:在three.js中,ShaderMaterial是一种可以自定义渲染方式的材质,可以使用它来实现复杂的渲染效果。通过ShaderMaterial,可以使用自定义的着色器程序进行渲染,从而实现凹凸质感的草地效果。 2. 纹理贴图:纹理贴图是一种将图片映射到三维模型表面的技术。在three.js中,可以使用TextureLoader加载纹理贴图,并将其应用到几何体模型的材质中,从而实现贴图效果。 简单实现步骤: 1. 导入草地几何体模型到three.js中。 2. 加载法线贴图、路径贴图、颜色贴图,并应用到草地模型的材质中。 3. 编写着色器程序,使用法线贴图、路径贴图、颜色贴图进行凹凸质感的计算。 4. 将着色器程序应用到草地模型的ShaderMaterial中。 5. 渲染草地模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值