zoukankan      html  css  js  c++  java
  • 【Unity Shaders】学习笔记——SurfaceShader(七)法线贴图

    【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制作法线贴图的方法,看完应该会更理解法线贴图的作用。
    下面这张图便说明了法线贴图的原理:
    img
    接下来就来学习如何编写有法线贴图的Shader吧。


    首先准备一张NormalMap,要将它的Texture Type改为Normal Map:
    img
    先定义几个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使用得更多一些。

  • 相关阅读:
    C/S和B/S结构区别整理
    JavaScript特点、优缺点及常用框架
    ExtJs特点、优缺点及注意事项
    Oracle SQL 脚本跟踪
    解决 C#中 SQL脚本执行超时 问题
    oracle 时间转化函数及常见函数 .
    Spring.NET 1.3.2 集成 NHibernate 3.2 1 下载软件
    SQL Server 跨服务器 不同数据库之间复制表的数据的方法:
    spring.net nhibernate 分布布式事务(上)
    set xact_abort ON 你懂的, 在分布式数据库事务中,用到. 在事务中,若出现错误,系统即默认回滚事务
  • 原文地址:https://www.cnblogs.com/-867259206/p/5627565.html
Copyright © 2011-2022 走看看