ShaderForge学习笔记:Normal(法线贴图)通道

发表于2018-07-17
评论0 8k浏览
ShaderForge是一款为Unity所用的、基于节点操作的Shader插件。所以这个ShaderForge学习笔记系列希望可以帮到大家。这一篇就来介绍下Normal(法线贴图)通道的使用。

ShaderForge Normal通道

一、官方介绍

normal通道输入切线空间中的法线方向,你可以连接法线贴图或者法向量到该通道。
用于使用光照的明暗模拟凹凸效果。

二、通道的输入

Normal通道输入的数据为存储着法线信息的法线贴图

三、 介绍法线贴图技术

想要模拟物体表面的凹凸效果,我们可以在建模时增加面,但这样一来渲染效率会下降。法线贴图技术就是一种不增加模型面数,而是靠光照的明暗来表现凹凸感的方法。

法线贴图技术原理

怎样利用法线贴图记录一个平面或者说是一个贴图的凹凸情况呢?
我们知道,物体表面产生明暗变化的直接原因,就是光线照射角度的不同,光线垂直于平面的地方就亮,光线斜射到平面的地方就暗,光线照不到的地方就更暗(应该是黑色,但是由于环境光照所以不会有阴影是真正的黑色)。

表示光线射向平面的角度时通常使用光线和该点法线角度来表示。这也就意味着,如果我们将一个贴图上所有点的法线记录起来的话,就不难再利用这些信息实现后期的假的凹凸效果了。
记录这些法线的载体就被我们称为法线贴图。
光线方向越靠近法线,越亮,反之则越暗。(用N点乘L来展现法线与灯光的朝向关系)

四、介绍法线贴图

一条法线是一个三维向量,一个三维向量由X、Y、Z等3个分量组成,于是人们想出了一个聪明的方法,就是以这3个分量当作红绿蓝3个颜色的值存储,这样的话就生成一张新的贴图了,这就是法线贴图的来历。

线贴图其实并不是真正的贴图,所以也不会直接贴到物体的表面,它所起的作用就是记录每个点上的法线的方向。所以这个贴图如果看起来也会比较诡异,经常呈现一种偏蓝紫色的样子。
法线贴图偏蓝紫色:因为法线经常是朝着Z轴的,Z映射到贴图的B(Blue)通道,因此发现贴图偏蓝。

1. 法线信息存入正常贴图
正常贴图: 存储颜色信息,有四个通道(R,G,B,A) ,每一个通道值得范围是[0,1]。
向量的取值范围是[-1,1],颜色的取值范围是[0,1],因此我们需要做一个从[1,1]到[0,1]的映射,将向量信息存入正常的贴图把它变成法线贴图。
pixel= \frac{normal+1}{2}
这样我们就获取到了法线贴图: 存储法线向量信息,有四个通道(x,y,z,w)

2. 从法线贴图获取法线向量
从法线贴图获取法线向量的方法正好是将法线信息存入贴图的逆过程:
使用Normalmap的时候
normal = pixel\times 2-1

3. 模型空间法线贴图和切线空间法线贴图
法线贴图通常有两种,一是模型空间法线贴图(object-space normalmap),一种是切线空间法线贴图(tangent-space normal map)
如果法线处于物体本身局部坐标中的,那称为object space normal。存储object space normal的贴图称为object-space normalmap。
如果法线处于切线空间坐标中的(tangent space),称为tangent space normal。存储tangent space normal的贴图称为tangent-space normalmap。

为什么法线贴图大多使用切线空间
很容易想象,world space normal一旦从贴图里解压出来后,就可以直接用了,效率很高。但是有个缺点,这个world space normal是固定了的,如果物体没有保持原来的方向和位置,那原来生成的normal map就作废了。
因此又有人保存了object space normal。它从贴图里解压,还需要乘以model-view矩阵转换到世界坐标,或者转换到其他坐标取决于计算过程及需求。object space normal生成的贴图,物体可以被旋转和位移.基本让人满意。但仍有一个缺点。就是一张贴图只能对应特定的一个模型,模型不能有变形(deform)。
变形时,顶点关系改变了,即面的形状,方向改变了。如果面上存在一个固定的坐标系,那当物体变形、移动、旋转时,这个坐标系必定跟着面一起运动,那么在这个坐标系里的某个点或向量,不需要变动。当整个面发生变化时,我们只需要计算面上的坐标系到世界坐标系的转换矩阵,那么定义在这个面上的点或坐标(固定的),乘以这个矩阵即可得到在世界中的坐标。这个坐标系术语里称为tangent space。
因此,我们大多将法线信息放到切线空间中,然后存储到贴图中。
引自法线贴图原理

4. 法线贴图的来源

  • 美术制作好的
如何在photoshop中制作normal(法线)贴图

  • 从U3D中制作
Unity3D 制作法线贴图

五、简单介绍TBN切线空间

这种空间叫做TBN切线空间,TBN这三个字母分别代表tangent、bitangent和normal向量。
tangent是顶点的切线,normal是顶点的法线,bitangent由tangent和normal叉乘(注意进行叉乘的N和T的先后顺序!绝对不能是T x N!)得到,是一个垂直于tangent和normal的向量。这样一来我们就有了三个相互垂直的向量,借此构筑一个TBN切线空间,TBN向量沿一个表面的法线贴图对齐于:上、右、前。

切线坐标系转换到世界坐标系

切线空间到世界空间的转换矩阵T2W:
\begin{bmatrix}
T_{x} & T_{y} & T_{z} \\ 
B_{x} & B_{y} & B_{z} \\ 
N_{x} & N_{y} & N_{z} 
\end{bmatrix}

切线空间下的法线向量乘以转换矩阵,得到世界坐标系下的切线向量
normalWorld= normalize(mul(normalTangent,T2W));

六、自定义Unity Shader实现法线贴图

写法
Shader "Hidden/normal"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _NormalTex("Normal Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normalDir : TEXCOORD1;
                float3 tangentDir : TEXCOORD2;
                float3 bitangentDir : TEXCOORD3;
            };
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.tangentDir = normalize(mul(unity_ObjectToWorld,v.tangent.xyz));
                o.normalDir = normalize(UnityObjectToWorldNormal(v.vertex));
                o.bitangentDir = normalize(cross(o.normalDir,o.tangentDir));
                return o;
            }
            sampler2D _MainTex;
            sampler2D _NormalTex;
            fixed4 frag (v2f i) : SV_Target
            {
                // 从法线贴图中获取切线向量,并将之从切线空间转换到世界空间
                float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);
                float3 normalFromTex = UnpackNormal(tex2D(_NormalTex,i.uv)).rgb;
                float3 normalDirection = normalize(mul(normalFromTex,tangentTransform));
                // 获取世界空间的灯光向量
                float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                // N 点乘 L
                float NdotL = max(0,dot(normalDirection,lightDirection));
                // 计算方向光
                float attenuation = LIGHT_ATTENUATION(i);
                float3 attenColor = attenuation * _LightColor0.xyz;
                float3 directDiffuse = NdotL * attenColor;
                // 计算环境光
                float3 inDirectDiffuse = UNITY_LIGHTMODEL_AMBIENT.rgb;
                fixed4 col = tex2D(_MainTex, i.uv);
                col.rgb = col.rgb*(inDirectDiffuse+directDiffuse);
                col.a = 1;
                return col;
            }
            ENDCG
        }
    }
}
效果展示
来自:https://blog.csdn.net/v_xchen_v/article/details/79074039

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引