【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图


转载请注明出处:http://www.cnblogs.com/-867259206/p/5627565.html

写作本系列文章时使用的是Unity5.3。
写代码之前:

  1. 当然啦,如果Unity都没安装的话肯定不会来学Unity Shaders吧?

  2. 阅读本系列文章之前你需要有一些编程的概念。

  3. 在VS里面,Unity Shaders是没有语法高亮显示和智能提示的,VS党可以参考一下这篇文章使代码高亮显示,也可以下载shaderlabvsNShader之类的插件使代码高亮显示。

  4. 这是针对小白的Unity Shaders的基础知识,如果你已经有了基础或者你是大神,那么这些文章不适合你。

  5. 由于作者水平的局限,文中或许会有谬误之处,恳请指出。


法线贴图是一种在低模上模拟高模的效果的技术。这是维基对它的介绍
法线贴图类似凹凸贴图的升级版,凹凸贴图记录了物体表面凹凸的情况,法线贴图记录了物体表面凹凸的光照信息。光照信息即是入射光与法线的夹角信息。
为了提高性能,模型的面数越少越好,很多细节的东西都是用贴图去弥补。但是光照是基于顶点去计算的,这样高光阴影等光照的表现就不够真实。于是前辈们发明了法线贴图这个办法,用贴图记录表面的光照信息,也就是用RGB值存储法线坐标的XYZ值,使低模也能够有高模的光照信息,从而表现出高模的光照效果。这是一种存储空间换计算时间的方法。这篇文章介绍了3DMax制作法线贴图的方法,看完应该会更理解法线贴图的作用。
下面这张图便说明了法线贴图的原理:

接下来就来学习如何编写有法线贴图的Shader吧。


首先准备一张NormalMap,要将它的Texture Type改为Normal Map:

先定义几个Properties:

    Properties
{
//Add these Properties
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_NormalTex ("Normal Map", 2D) = "bump" {}
_NormalIntensity ("Normal Map Intensity", Range(0,2)) = 1
}

_NormalTex就是法线贴图,_NormalIntensity是法线的强度,也就是控制凹凸的强度。
在SubShader里定义同名的变量:

        //Link the property to the CG program
sampler2D _NormalTex;
float4 _MainTint;
float _NormalIntensity;

在Input结构里定义法线贴图的纹理坐标:

        //Make sure you get the uvs for the texture in the Struct
struct Input
{
float2 uv_NormalTex;
};

编写surf函数:

void surf (Input IN, inout SurfaceOutput o)
{
//Get the normal Data out of the normal map textures
//using the UnpackNormal() function.
float3 normalMap = UnpackNormal(tex2D(_NormalTex, IN.uv_NormalTex));
normalMap = float3(normalMap.x * _NormalIntensity,
normalMap.y * _NormalIntensity, normalMap.z);
//Apply the new normals to the lighting model
o.Normal = normalMap.rgb;
o.Albedo = _MainTint.rgb;
o.Alpha = _MainTint.a;
}

解释

法线贴图中记录了高模的法线信息。法线的值是[-1,1],RGB的值是[0,1],所以将法线信息存储在RGB中需要转换。方法是((x,y,z)+(1,1,1))*0.5。也就是原先(-1,0,1)坐标变成了(0,0.5,1)。UnpackNormal函数的作用就是转换坐标点。
在Unity安装目录(/Editor/Data/CGIncludes/)下的Unity.cginc文件里有UnpackNormal函数的定义。

inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
fixed3 normal;
normal.xy = packednormal.wy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normal;
} inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalDXT5nm(packednormal);
#endif
}

我们看到,有两种转换法线的函数,这两种函数是针对不同法线贴图格式的。
如果是未压缩的格式,就将法线贴图里存储的xyz值乘2减1,变回原来的坐标。
如果是DXT5nm压缩格式(可以推测这应该是Unity使用的压缩格式),则要用另外一种转换方法。(这也是为什么要将纹理类型设置为Normal Map的原因,因为不同类型的图片要用不同的方法转换)
DXT5nm压缩格式,只有G和A通道。因为法线是单位向量,所以只要知道任意两个坐标值,就能求出另一个坐标值(z2=1-x2-y2),只要两个通道存储两个坐标值即可。因为这样的计算并不复杂,所以可以节省空间以使用更多的贴图。DXT5nm将R通道的值移到了Alpha通道,保留G通道的值,R和B通道则以某种颜色填充。
所以转换DXT5nm的法线贴图,要先将A通道和G通道的值(wy)转换为xy,再计算z值。
后面的代码就好解释了。转换了法线坐标以后将法线赋值给输出结构体的Normal变量。因为法线(0,0,1)就表明这点的法线和高模的法线相同,所以Z坐标没有必要乘_NormalIntensity。

法线贴图原理

法线贴图的基本原理前面已经讲过了,就是将高模的法线信息xyz存储在rgb中,应用法线贴图的时候将rgb值转换为xyz,在计算光照的时候,使用高模的法线进行计算,从而得到高模的光影效果,造成低模呈现高模较为平滑的表面的假象,这是一种视觉欺骗。
这个小节里要再详细谈谈法线的坐标。
要表示一个向量在三维空间的位置需要一个三维坐标系作参考。要表示法线的方位可以用世界坐标(World Space),也可以用模型坐标(Object Space)。这两个坐标系大家应该听说过。
如果用世界坐标,那么模型就不能旋转和移动了。因为模型的位置改变了,它在世界坐标系里的坐标也改变了,但法线的坐标是基于世界坐标系的,这样法线的位置就不对了。不然读取了法线信息还要再进行世界坐标的转换。
如果用模型坐标,就没有世界坐标不能旋转和移动的局限了。不管模型怎么旋转移动,法线的位置是相对于模型的,所以法线的坐标不会变。相比于世界坐标,使用模型坐标还要进行模型坐标到世界坐标的转换,效率低点,但灵活性更好了。
但是使用模型坐标还有局限,就是模型不能变形。因为模型变形了,法线相对模型的坐标也变了,一些会有形变的物体(比如有骨骼蒙皮动画的模型)就不适用基于模型坐标的法线贴图。
所以还需要另外一种坐标系,使法线贴图不仅仅适用于某个模型,还可以用在其他模型上。
这种坐标系就是切线坐标(Tangent Space)。切线坐标就以表面上某一点的切线作为XY轴,法线作为Z轴(即垂至表面的轴)。这样的话,符合要求的坐标系有无数种。我们可以直接将纹理的UV坐标当作XY轴,这样就有一个现成的坐标系可以使用。
可以这样理解切线坐标,它表示的是高模上的法线相对对应点上的低模的法线的扰动程度。当法线坐标是(0,0,1)的时候,说明高模法线和低模法线一致,相当于切线坐标表示的是高模法线相对低模法线在XY方向上的偏移程度。这也解释了上文代码中为什么Z轴不用乘_NormalIntensity。
法线贴图是用于表现表面微小的凹凸的,比如皮肤皱纹、鱼鳞等,所以法线的扰动值不会太大,也就是Z坐标的值是比较大,所以B通道的值比较大,所以法线贴图通常都是蓝盈盈的。
使用切线坐标也就是说每个面都有一个自己的坐标系。在Vertex Shader里进行光照计算的时候要将光线向量的坐标从World Space转换到Tangent Space。所以我们可以在Surface Shader里使用lightDir变量和normal变量进行计算,因为Unity帮我们做了很多工作。
相比模型坐标,切线坐标更加灵活,可以应用在与原模型形状不同的模型上,比如可以把花岗岩的法线贴图应用在一个圆柱体表面,使圆柱体表面也具有花岗岩的凹凸效果。
法线贴图通常有两种,一种是Object Space Normal Map,一种是Tangent Space Normal Map。如果模型没有动画,那么可以使用Object Space Normal Map,如果模型有动画,或者会变形(比如流动的水、火焰)那就要使用Tangent Space Normal Map。因为Tangent Space Normal Map要灵活的多,可以应用于不同的物体,所以Tangent Space Normal Map使用得更多一些。

【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图的更多相关文章

  1. 【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型

    [Unity Shaders]学习笔记——SurfaceShader(十一)光照模型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5664792.html ...

  2. 【Unity Shaders】学习笔记——SurfaceShader(十)镜面反射

    [Unity Shaders]学习笔记——SurfaceShader(十)镜面反射 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以. 水 ...

  3. 【Unity Shaders】学习笔记——SurfaceShader(九)Cubemap

    [Unity Shaders]学习笔记——SurfaceShader(九)Cubemap 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以 ...

  4. 【Unity Shaders】学习笔记——SurfaceShader(四)用纹理改善漫反射

    [Unity Shaders]学习笔记——SurfaceShader(四)用纹理改善漫反射 转载请注明出处:http://www.cnblogs.com/-867259206/p/5603368.ht ...

  5. 【Unity Shaders】学习笔记——SurfaceShader(二)两个结构体和CG类型

    [Unity Shaders]学习笔记——SurfaceShader(二)两个结构体和CG类型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5596698. ...

  6. 【Unity Shaders】学习笔记——SurfaceShader(八)生成立方图

    [Unity Shaders]学习笔记——SurfaceShader(八)生成立方图 转载请注明出处:http://www.cnblogs.com/-867259206/p/5630261.html ...

  7. 【Unity Shaders】学习笔记——SurfaceShader(六)混合纹理

    [Unity Shaders]学习笔记——SurfaceShader(六)混合纹理 转载请注明出处:http://www.cnblogs.com/-867259206/p/5619810.html 写 ...

  8. 【Unity Shaders】学习笔记——SurfaceShader(五)让纹理动起来

    [Unity Shaders]学习笔记——SurfaceShader(五)让纹理动起来 转载请注明出处:http://www.cnblogs.com/-867259206/p/5611222.html ...

  9. 【Unity Shaders】学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert

    [Unity Shaders]学习笔记——SurfaceShader(三)BasicDiffuse和HalfLambert 转载请注明出处:http://www.cnblogs.com/-867259 ...

随机推荐

  1. android学习笔记38——样式和主题

    Style.Theme 样式和主题资源都是用于android应用的美化操作. 样式:一组格式的集合,可重复使用. android的样式资源存放与res/values文件夹下,其根元素为<reso ...

  2. ASP.NET Eval四种绑定方式

    1.1.x中的数据绑定语法 <asp:Literal id="litEval2" runat="server" Text='<%#DataBinde ...

  3. u-boot启动流程分析(1)_平台相关部分

    转自:http://www.wowotech.net/u-boot/boot_flow_1.html 1. 前言 本文将结合u-boot的“board—>machine—>arch—> ...

  4. TCP/IP四层模型和OSI七层模型的概念

    转:http://blog.csdn.net/superjunjin/article/details/7841099/ TCP/IP四层模型 TCP/IP是一组协议的代名词,它还包括许多协议,组成了T ...

  5. SpringJDBC

    <!-- JdbcTemplate:最基础的springJDBC模板,这个模板支持最简单的jdbc数据库访问功能以及简单的索引参数查询 NamedParameterJdbcTemplate:使用 ...

  6. POJ 2411 Mondriaan'sDream(状压DP)

    题目大意:一个矩阵,只能放1*2的木块,问将这个矩阵完全覆盖的不同放法有多少种. 解析:如果是横着的就定义11,如果竖着的定义为竖着的01,这样按行dp只需要考虑两件事儿,当前行&上一行,是不 ...

  7. MYSQL 安装更新,使用,管理,备份和安全等

    如何安装更新,使用,管理,备份和安全,维护优化一个MYSQL系统. 一.MYSQL发展历史,特点.对SQL语法进行介绍 二.如何安装一个MYSQL系统 三四.如何利用SQL语言以及其他的客户工具对MY ...

  8. 在sql脚本中将查询结果集拼接成字符串

  9. 转-安卓中实现两端对齐,中间fill_parent的方法

    安卓中实现两端对齐,中间fill_parent的方法 Java代码:   <?xml version="1.0″ encoding="utf-8″?> <Line ...

  10. ASCII码排序(未完)

    用指针数组 存放多个字符串 描述输入三个字符(可以重复)后,按各字符的ASCII码从小到大的顺序输出这三个字符.   输入 第一行输入一个数N,表示有N组测试数据.后面的N行输入多组数据,每组输入数据 ...