Unity3D中NormalMap法线贴图介绍

您所在的位置:网站首页 unity2d法线贴图 Unity3D中NormalMap法线贴图介绍

Unity3D中NormalMap法线贴图介绍

2022-06-04 10:36| 来源: 网络整理| 查看: 265

本篇教程来学习法线贴图NormalMap。 什么是法线贴图? 在游戏中,如果角色或物体模型做的越精细(面数越多),那么渲染后效果也就越好,但很多时候处于对时间成本而通过其它的一些技术手 学设计来丝路教程网

学习要有三心,一信心,二决心,三恒心! —— 陈景润 开始学习

  本篇教程来学习法线贴图NormalMap。

  什么是法线贴图?

  在游戏中,如果角色或物体模型做的越精细(面数越多),那么渲染后效果也就越好,但很多时候处于对时间成本而通过其它的一些技术手段来达到相似的效果.而应用的最广泛的可能就要数法线贴图了,在有光照的环境下,如果物体表面是凹凸不平的,那么它在接受光照的时候在不同的区域就会呈现出不同的明暗效果来展现这种凹凸感。

  我们介绍过漫反射和镜面反射的计算中我们都用到了物体表面的法线,正因为物体表面法线的不同才导致了最终光照结果的不同,如果我们能够把整个模型表面各个位置的法线映射到一张二维贴图上,然后在这张贴图上存储上法线的信息,不就可以达到通过底模+二维贴图达到高模效果了么?而这里的二维贴图就是我们所说的法线贴图.

  为什么叫它法线贴图呢?

  它和我们之前一直使用的纹理贴图有何区别呢?纹理贴图中我们存储的是颜色值RGBA,而法线贴图里存储的是物体表面的法线,两类贴图的读取映射方式都是一致的,都是通过顶点自带信息里的texcoord里的uv坐标来读取,不过法线读取之后并不能直接使用,还要经过一些处理,我们会在后面说.

  下面在正式进入代码之前,我们先来了解几个知识点,很重要。

  1.切线空间

  这个概念并不是十分好理解,但只要仔细想想也是可以弄清楚的。我想大家对本地空间一定不陌生,一般美术做完的模型里面每个顶点的坐标都是本地坐标,也就是说对于模型上的各个部位共用一个一个统一的坐标原点,但有时候这样并不是很方便,比如建了一个人体模型,如果我们只是想以相对手为基准而进行一些动作,而不是坐标原点,这时候原本的本地坐标系便不再适应。

  我们可以以手臂为基准再建立一个坐标系。综上不难理解,之所以存在不同的坐标系,根本上是为了方便我们只考虑相关的因素,而排除不相关的因素。就像如果我想以手为基准进行一个弯手指的操作是不需要考虑我这个手指在模型空间的位置坐标的,这样有效的降低了问题的复杂度。而切线空间的概念提出也是为了方便使用法线贴图(当然了也许有其他用途)。下面结合图(图片来源CSDN作者BonChoix)来说一下:

  其实纹理坐标可以以一个三角面作为一个单位来分析,在一个三角面上的三个顶点他们都拥有自己的纹理坐标,而又因为三点确定一个平面(前提是三点不在一条直线啊),那么纹理坐标其实也就是个二维空间的坐标(一般横轴是u,纵轴是v)。

  那对于一个三角面来说,其上的三个顶点所拥有的切线和法线(图中T和N)其实是一样的,通过叉乘我们可以求得笛卡尔坐标系中的两一个坐标轴B(Binormal,一般称之为负法线).实际上纹理坐标上的u对应的就是切线方向,而V对应的就是负法线方向。法线贴图上存储的法线信息也就是对应的这个三维空间。这个地方比较绕,我说的也不是很清楚,大家结合图好好理解一下吧。

  2.DXT5nm压缩格式

  美术做出来的一张法线贴图一般来说使用的RGB三个通道的,分别用来存放法线的XYZ三个轴向的坐标。但是Unity在导入法线贴图的时候会自动将法线贴图压缩成DXT5nm格式,这个格式的好处是它只使用ag两个通道来存放两个轴向的坐标值而另外一个轴向的坐标值由于是单位坐标可以通过1减去另外前两个轴向坐标的平方和来得到,从而可以以同样的容量存放更大尺寸的法线贴图,我们都知道图片的颜色通道存的都是非负数(法线贴图生成的时候已经把[-1,1]压缩为[0-1]),而我们的三维空间是[-1,1],

  所以我们要把它解析放大一下,方法就是对对应的颜色通道值乘以2再减1,有一点要额外说明,如果你是针对移动平台来开发游戏那么Unity不会为你压缩法线贴图,这意味着你还要使用RGB通道来解析法线贴图。至于他为什么呈现蓝色,因为贴图本身还是用RGB来存的,而其中B通道对应的是z轴的方向,代表三角面的法线方向,一般来说一个片段对应的法线知识于平面法线方向有少许偏移,所以z还是接近于1的所以说会呈现出蓝色。

  下面我们进行实例代码的分析:

  Shader "Esfog/NormalMap"

  {

  Properties

  {

  _MainTex ("Base (RGB)", 2D) = "white" {}

  _NormalMap("NormalMap",2D) = "Bump"{}

  _SpecColor("SpecularColor",Color) = (1,1,1,1)

  _Shininess("Shininess",Float) = 10

  }

  SubShader

  {

  Pass

  {

  Tags { "LightMode"="ForwardBase" }

  CGPROGRAM

  #pragma vertex vert

  #pragma fragment frag

  #include "UnityCG.cginc"

  uniform sampler2D _MainTex;

  uniform sampler2D _NormalMap;

  uniform float4 _SpecColor;

  uniform float _Shininess;

  uniform float4 _LightColor0;

  struct VertexOutput

  {

  float4 pos:SV_POSITION;

  float2 uv:TEXCOORD0;

  float3 lightDir:TEXCOORD1;

  float3 viewDir:TEXCOORD2;

  };

  VertexOutput vert(appdata_tan v)

  {

  VertexOutput o;

  o.pos = mul(UNITY_MATRIX_MVP,v.vertex);

  o.uv = v.texcoord.xy;

  float3 normal = v.normal;

  float3 tangent = v.tangent;

  float3 binormal= cross(v.normal,v.tangent.xyz) * v.tangent.w;

  float3x3 Object2TangentMatrix = float3x3(tangent,binormal,normal);

  o.lightDir = mul(Object2TangentMatrix,ObjSpaceLightDir(v.vertex));

  o.viewDir = mul(Object2TangentMatrix,ObjSpaceViewDir(v.vertex));

  return o;

  }

  float4 frag(VertexOutput input):COLOR

  {

  float3 lightDir = normalize(input.lightDir);

  float3 viewDir = normalize(input.viewDir);

  float4 encodedNormal = tex2D(_NormalMap,input.uv);

  float3 normal = float3(2.0*encodedNormal.ag - 1,0.0);

  normal.z = sqrt(1 - dot(normal,normal));

  float4 texColor = tex2D(_MainTex,input.uv);

  float3 ambient = texColor.rgb * UNITY_LIGHTMODEL_AMBIENT.rgb;

  float3 diffuseReflection = texColor.rgb * _LightColor0.rgb * max(0,dot(normal,lightDir));

  float facing;

  if(dot(normal,lightDir)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3