纹理映射

纹理映射

纹理最初的目的就是使用一张图片来控制模型的外观。使用纹理映射(texture mapping)技术,我们可以把一张图“黏”在模型表面,逐纹素(texel)(纹素的名字是为了和像素进行区分)地控制模型的颜色。
在美术人员建模的时候,通常会在建模软件中利用纹理展开技术把纹理映射坐标(texture-mapping coordinates) 存储在每个顶点上。纹理映射坐标定义了该顶点在纹理中对应的2D 坐标。通常,这些坐标使用一个二维变量(u, v)来表示,其中u是横向坐标,而v是纵向坐标。因此,纹理映射坐标也被称为UV坐标。
尽管纹理的大小可以是多种多样的,例如可以是256×256或者1028×1028,但顶点UV坐标的范围通常都被归一化到[0,1]范围内。需要注意的是,纹理采样时使用的纹理坐标不一定是在[0,1]范围内。实际上,这种不在[0,1]范围内的纹理坐标有时会非常有用。与之关系紧密的是纹理的平铺模式,它将决定渲染引擎在遇到不在[0,1]范围内的纹理坐标时如何进行纹理采样。
纹理采样就是漫反射系数值设定为纹理

普通纹理映射

由像素的位置算出uv的值,得到对应的纹理值,纹理作为漫反射系数

纹理映射_第1张图片

纹理的像素为纹素

纹理映射_第2张图片
物体表面的每个点的k_d属性表示纹理颜色等等,是漫反射系数
三维物体的表面是2d的,所以物体的纹理可以由图片来表示
纹理映射_第3张图片
空间的三角形映射到纹理的三角形关系由Artist实现,或者自动化,我们不需要管
纹理映射_第4张图片
纹理用uv表示坐标系
纹理映射_第5张图片

Unity 单张纹理采样的实现

Shader "TurBoQShader/Chapter7/ST(单张纹理加BP模型)"
{
     
    Properties
    {
     
       _Color("Color Tint",Color)=(1,1,1,1)//控制纹理的色调
       _MainTex("Main Tex",2D)="white" {
     }//2D是纹理属性的声明方式
       _Specular("Specular",Color)=(1,1,1,1)//高光反射的颜色
       _Gloss("Gloss",Range(8.0,256))=20//高光区域的大小
    }
    SubShader
    {
     
    Pass{
     
    //指定该Pass的光照模式,LightMode属于Pass标签
        Tags {
      "LightMode"="ForwardBase" }
       
        CGPROGRAM//包裹CG代码片
        //顶点着色器名称
      #pragma vertex vert
      //片元着色器名称
      #pragma fragment frag
      //为了使用Unity内置的变量,如_LightColor0,引入内置文件
      #include "Lighting.cginc"
      //CG代码中需要和Unity面板对应的属性
      fixed4 _Color;
      sampler2D _MainTex;
      float4 _MainTex_ST;//纹理的缩放和平移
      fixed4 _Specular;
      float _Gloss;
      //顶点着色器的输入结构体a2v,顶点着色器的输出结构体和片元着色器的输入结构体v2f
      struct a2v{
     
   float4 vertex :POSITION;
   float3 normal:NORMAL;
   float4 texcoord:TEXCOORD0;//顶点纹理坐标
   };
   struct v2f{
     
   float4 pos:SV_POSITION;
   float3 worldNormal:TEXCOORD0;
   float3 worldPos:TEXCOORD1;
   float2 uv:TEXCOORD2;
   };
   v2f vert(a2v v)//顶点着色器代码,计算世界法线和世界坐标和转换世界坐标到模型坐标
   {
     
   v2f o;
   o.pos=UnityObjectToClipPos(v.vertex);
  
   o.worldNormal=UnityObjectToWorldNormal(v.normal);
   o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
   // o.uv=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
   //在顶点着色器中,我们使用纹理的属性值_MainTex_ST来对顶点纹理坐标进行变换,得到最终的纹理坐标。
   //计算过程是,首先使用缩放属性_MainTex_ST.xy对顶点纹理坐标进行缩放,然后再使用偏移属性_MainTex_ST.zw
   //对结果进行偏移。Unity 提供了一个内置宏TRANSFORM_TEX来帮我们计算上述过程。TRANSFORM_TEX是在 UnityCGcginc中定义的:
   //它接受两个参数,第一个参数是顶点纹理坐标,第二个参数是纹理名,在它的实现中,将利用纹理名_ST的方式来计算变换后的uv坐标。
    o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);//使用缩放和偏移计算纹理,v.texcoord是材质的顶点纹理坐标,_MainTex是2D图片的名称
   return o;
   }

    fixed4 frag(v2f i):SV_Target{
     //片元着色器代码,负责计算高光反射和漫反射,漫反射中加入纹理的uv坐标
    fixed3 worldNormal=normalize(i.worldNormal);//normalize归一化世界法线
    //fixed3 worldLigthDir=normalize(_WorldSpaceLightPos0.xyz);//normalize截取前三行三列光源方向
    fixed3 worldLigthDir=normalize(UnityWorldSpaceLightDir(i.worldPos));//世界光照方向
    //纹理进行采样。它的第一个参数是需要被采样的纹理,第二个参数是一个float2类型的纹理坐标,它将返回计算得到的纹素值。
    fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;//纹素计算
    fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;//世界光照乘纹素

   fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLigthDir));//漫反射=入射光颜色强度*材质的漫反射系数*dot(光源方向,表面法线),乘世界光照和纹素

  // fixed3 reflectDir=normalize(reflect(-worldLigthDir,worldNormal));
   //fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);
   fixed3 viewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));//视角方向
   fixed3 halfDir=normalize(worldLigthDir+viewDir);//normalize归一化
   fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Gloss);//高光反射=入射光颜色强度*材质的高光反射系数*dot(视角方向,
   //反射方向(由表面法线和光源方向求得))
   return fixed4(ambient+diffuse+specular,1.0);
   }
        ENDCG
        }
    }
    FallBack "Specular"
}

Unity中纹理的属性

纹理太小的解决方案

Unity的处理方式(最近邻插值,双线性插值,双三次插值)

纹理映射_第6张图片在内部实现上,Point模式使用了最近邻(nearest neighbor)滤波,在放大或缩小时,它的采样像素数目通常只有一个,因此图像会看起来有种像素风格的效果。而 Bilinear滤波则使用了线性滤波,对于每个目标像素,它会找到4个邻近像素,然后对它们进行线性插值混合后得到最终像素,因此图像看起来像被模糊了。而 Trilinear滤波几乎是和 Bilinear一样的,只是 Trilinear还会在多级渐远纹理之间进行混合。如果一张纹理没有使用多级渐远纹理技术,那么Trilinear得到的结果是和 Bilinear就一样的。通常,我们会选择Bilinear滤波模式。需要注意的是,有时我们不希望纹理看起来是模糊的,例如对于一些类似棋盘的纹理,我们希望它就是像素风的,这时我们可能会选择Point模式。

图形学的处理方式

纹理映射_第7张图片

双线性插值

纹理图太小,想要像素平滑过渡的解决方法

纹理映射_第8张图片邻近四个点做插值
纹理映射_第9张图片
水平插值结束后竖直插值
纹理映射_第10张图片

Bicubic双三次插值

函数f在点 (x,y) 的值可以通过矩形网格中最近的十六个采样点的[加权平均]得到,在这里需要使用两个多项式插值三次函数,每个方向使用一个。

纹理太大的解决方案

Unity的处理方式

纹理缩小的过程比放大更加复杂一些,此时原纹理中的多个像素将会对应一个目标像素。纹理缩放更加复杂的原因在于我们往往需要处理抗锯齿问题,一个最常使用的方法就是使用多级渐远纹理(mipmapping)技术。其中“mip”是拉丁文“multum in parvo”的缩写,它的意思是“在一个小空间中有许多东西”。如同它的名字,多级渐远纹理技术将原纹理提前用滤波处理来得到很多更小的图像,形成了一个图像金字塔,每一层都是对上一层图像降采样的结果。这样在实时运行时,就可以快速得到结果像素,例如当物体远离摄像机时,可以直接使用较小的纹理。但缺点是需要使用一定的空间用于存储这些多级渐远纹理,通常会多占用33%的内存空间。这是一种典型的用空间换取时间的方法。在Unity中,我们可以在纹理导入面板中,首先将纹理类型(TextureType)选择成Advanced,再勾选Generate Mip Maps 即可开启多级渐远纹理技术。同时,我们还可以选择生成多级渐远纹理时是否使用线性空间(用于伽玛校正,详见18.4.2.节)以及采用的滤波器等,如图7.8所示。
纹理映射_第11张图片

图形学的处理方式

离摄像机远的像素,像素里面覆盖(包含)的纹理太多,一个纹素代表不了纹理的结果

纹理映射_第12张图片

超采样(增加像素数量)

纹理映射_第13张图片

求范围的平均值,Mipmap

需要原纹理图的三分之一空间存储
纹理映射_第14张图片
点的右边和上边投影到纹理空间,求对应的长度最大值,作为纹理空间的边长,边长决定去第几层mipmap找像素,立即找到区域的平均值。
纹理映射_第15张图片不平滑用插值来解决,比如1.8层,在1和2层做双线性插值,再做插值,叫做三线性插值

各项异性过滤

解决三线性插值以及mipmap对于不是正方形的计算不准确的问题
纹理映射_第16张图片
mipmap是对角线
纹理映射_第17张图片解决长条和矩形区域问题
纹理映射_第18张图片

重心坐标(三角形顶点属性知道,其他通过插值得到)

三角形内的点三个系数都是非负的
纹理映射_第19张图片A点重心坐标
纹理映射_第20张图片重心坐标求法
纹理映射_第21张图片重心的重心坐标(0.3,0.3,0.3)
属性通过重心坐标插值
纹理映射_第22张图片

纹理模式

纹理映射_第23张图片

纹理应用

凹凸映射

纹理的另一种常见的应用就是凹凸映射(bump mapping)。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但可以从模型的轮廓处看出“破绽”。
有两种主要的方法可以用来进行凹凸映射:一种方法是使用一张==高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值,这种方法也被称为高度映射(heightmapping);另一种方法则是使用一张法线纹理(normal map)==来直接存储表面法线,这种方法又被称为法线映射(normal mapping)。尽管我们常常将凹凸映射和法线映射当成是相同的技术,但读者需要知道它们之间的不同。

高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息。也就是说,我们通常会使用法线映射来修改光照。

纹理定义点相对于自己模型的相对高度,应用导致法线变换,最后导致光照结果出现变换

橙色为凹凸贴图存的内容
纹理映射_第24张图片先算切线再逆时针旋转90度
纹理映射_第25张图片现代位移贴图,会按照橙色线变换三角形顶点的位置
纹理映射_第26张图片

纹理图记录其他计算结果

在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常,我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。例如,我们可以把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,最后把自发光强度存储在A通道。
在游戏《DOTA2》的开发中,开发人员为每个模型使用了4张纹理:一张用于定义模型颜色,一张用于定义表面法线,另外两张则都是遮罩纹理。这样,两张遮罩纹理提供了共8种额外的表面属性,这使得游戏中的人物材质自由度很强,可以支持很多高级的模型属性。读者可以在他们的官网上找到关于《DOTA2》的更加详细的制作资料,包括游戏中的人物模型、纹理以及制作手册等。这是非常好的学习资料。

光照纹理

环境光照可以用纹理保存
纹理映射_第27张图片

遮罩纹理

遮罩纹理(mask texture)是本章要介绍的最后一种纹理,它非常有用,在很多商业游戏中都可以见到它的身影。那么什么是遮罩呢?简单来讲,遮罩允许我们可以保护某些区域,使它们免于某些修改。例如,在之前的实现中,我们都是把高光反射应用到模型表面的所有地方,即所有的像素都使用同样大小的高光强度和高光指数。但有时,我们希望模型表面某些区域的反光强烈一些,而某些区域弱一些。为了得到更加细腻的效果,我们就可以使用一张遮罩纹理来控制光照。另一种常见的应用是在制作地形材质时需要混合多张图片,例如表现草地的纹理、表现石子的纹理、表现裸露土地的纹理等,使用遮罩纹理可以控制如何混合这些纹理。
使用遮罩纹理的流程一般是:通过采样得到遮罩纹理的纹素值,然后使用其中某个(或某几个)通道的值(例如 texel.r)来与某种表面属性进行相乘,这样,当该通道的值为1时,可以保护表面不受该属性的影响。总而言之,使用遮罩纹理可以让美术人员更加精准(像素级别)地控制模型表面的各种性质。

你可能感兴趣的:(计算机图形学,计算机图形学,unity)