Shader学习笔记(二)

UnityShader 初级篇(二)

Unity中的基础纹理

学习Unity shader的第二天,紧接着了解一下shader的基础纹理。
建议先大致了解下UnityShader常用函数(UnityShader内置函数、CG和GLSL内置函数等)

单张纹理

通常会使用一张纹理来代替物体的漫反射颜色
在这里插入图片描述在这里插入图片描述
对于顶点着色器的输入和输出结构体,在a2v结构体中增加了texcoord。在v2f结构体中添加了用于存储纹理坐标的变量uv,以便在片元着色器中使用该坐标进行纹理采样
在这里插入图片描述
顶点着色器
在这里插入图片描述
对于片元着色器,在计算漫反射时使用纹理中的纹素值:
在这里插入图片描述
纹理的属性
这里有个属性非常重要——Wrap Mode决定了当纹理坐标超过[0,1]范围后将会如何被平铺。Wrap Mode 有两种模式:
一种是Repeat,在这种模式下,如果纹理坐标超过了1,那么它的整数部分会被舍弃,而直接使用小数部分进行采样,这样的结果是纹理将会不断重复
另一种是Clamp,在这种模式下,如果纹理坐标大于1,那么将会截取到1,如果小于0,那么将会截取到0
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述
纹理导入面板的下一个属性是Filter Mode 属性,它决定了当纹理由于变换而产生拉伸时将会采用哪种滤波模式。支持3种模式:PointBilinear以及Trilinear。它们得到的图片滤波效果依次提升,但需耗费的性能也依次增大
在这里插入图片描述
这里需处理抗锯齿问题,最常用的方法是使用多级渐远纹理(mipmapping)技术。首先将纹理类型(TextureType)选择成Advanced,再勾选 Generate Mip Maps 即可开启多级渐远纹理技术。
在这里插入图片描述
最后,来讲一下纹理的最大尺寸和纹理模式。若导入的纹理大小超过了
MaxTextureSize
中的设置值,那么Unity将会把纹理缩放为这个最大分辨率。理想情况下,导入的纹理可以是非正方形的,但长宽的大小应该是2的幂,若使用了非2的幂大小的纹理,这时Unity会缩放成最近的2的幂大小。
Format决定了Unity内部使用哪种格式来存储该纹理。对于一些不需要使用很高精度的纹理(例如用于漫反射颜色的纹理),应该尽量使用压缩格式。

凹凸映射

有两种主要方式来进行凹凸映射:一种方法是使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值,这种方法也被称为高度映射(height mapping);另一种方法则是使用一张法线纹理(normal map)来直接存储表面法线,这种方法又被称为法线映射(normal mapping)

  1. 高度纹理
    使用一张高度图来实现凹凸映射。高度图中存储的是强度值(intensity),它用于表示模型表面局部的海报高度。颜色越浅表示越向外凸起,越深表示越向里凹。但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是需要由像素的灰度值计算而得,因此需要消耗更高的性能。
    在这里插入图片描述

  2. 法线纹理
    而法线纹理中存储的就是表面的法线方向。由于法线方向的分量范围在[-1,1],而像素的分量范围为[0,1],因此需要做一个映射,通常使用的映射就是:
    在这里插入图片描述
    Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数:
    在这里插入图片描述
    一种直接的想法就是讲修改后的模型空间中的表面法线存储在一张纹理中,该纹理被称为模型空间的法线纹理(object-space normal map)。然而,在实际制作中,往往会采用另一种坐标空间,即模型顶点的切线空间(tangent space) 来存储法线。对于模型的每个顶点都有属于自己的切线空间,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为是副切线(bitangent)或副法线,如下图这种纹理被称为切线空间的法线纹理(tangent-space normal map)
    在这里插入图片描述
    总体来说,模型空间下的法线纹理更符合人类的直观认识,使用的优点如下:
    1> 实现简单,计算少,由于模型的切线一般是和UV方向相同,因此想要得到效果比较好的法线映射就要求纹理映射也是连续的。
    2> 在纹理坐标的缝合处和尖锐的边角部分,可见的突变较少,即可提供平滑的边界。模型空间下的法线纹理存储的是同一坐标系下的法线信息,因此在边界处通过插值得到的法线可平滑变换。而切线空间下的法线纹理中的法线信息是依靠纹理坐标的方向得到的,可能会在边缘处造成更多可见的缝合现象
    使用切线空间有更多优点。
    1> 自由度很高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型,而应用到其他模型上效果就完全错误了。而切线空间下的法线纹理记录的是相对法线信息,这意味着,即使把该纹理应用到一个完全不同的网格上,也可得到一个合理的结果。
    2> 可进行UV动画。比如,可移动一个纹理的UV坐标来实现一个凹凸移动的效果,但使用模型空间下的法线纹理会得到完全错误的结果。原因同上
    3> 可以重用法线纹理;比如,一个砖块,仅使用一张法线纹理就可用到所有的6个面上。原因同上。
    4> 可压缩。由于切线空间下的法线纹理中法线的Z方向总是正方向,因此可以仅存储XY方向,而推导得到Z方向,而推导得到Z方向。而模型空间下的法线纹理由于每个方向都是可能的,因此必须存储3个方向的值,不可压缩。
    由于法线纹理中存储的法线是切线空间下的方向,因此通常有两种选择:一种选择是在切线空间下进行光照计算,此时我们需要把光照方向、视角方向变换到切线空间下;另一种选择是在世界空间下进行光照计算,此时我们需要把采样得到的法线方向变换到世界空间下,再和世界空间下的光照方向和视角方向进行计算。
    从效率上来说,第一种方法往往优于第二种因为可以在顶点着色器中就完成对光照方向和视角方向得变换,而第二种方法由于要先对法线纹理进行采样,所以变换过程必须在片元着色器中实现,这意味着需要在片元着色器中进行一次矩阵操作。
    但从通用性角度来说,第二种优于第一种,因为有时需要使用世界空间下进行一些计算。例如在使用Cubemap进行环境映射时,就需要使用世界空间下的反射方向对Cubemap进行采样,若同时需要进行法线映射,就需要把法线方向变换到世界空间下。

在切线空间下计算

即在切线空间下计算光照模型。基本思路是:在片元着色器中通过纹理采样得到切线空间下的法线,然后再与切线空间下的视角方向、光照方向等进行计算,得到最终的光照结果。

  1. 已知切线空间由顶点法线和切线构建出的一个坐标空间。修改顶点着色器的输入结构体a2v:
    在这里插入图片描述
    使用TANGENT语义来描述float4类型的tangent变量,以告诉Unity把顶点的切线方向填充到tangent变量中,需要注意的是,和法线方向normal不同,tangent的类型是float4,而非float3,原因是使用tangent.w分量来决定切线空间中的第三个坐标轴——副切线的方向性
  2. 需要计算切线空间的光照和视角方向,因此在v2f结构体中添加了两个变量来存储变换后的光照和视角方向:
    在这里插入图片描述
  3. 定义顶点着色器,把模型空间下切线方向、副切线方向和法线方向按行排列来得到从模型空间到切线空间的变换矩阵rotation,需要注意的是,在计算副切线时使用v,tangent.w和叉积结果进行相乘。原因是切线和法线方向都垂直的方向有两个,而w决定了其中选择哪一个方向。Unity提供了TANGENT_SPACE_ROTATION来帮助直接计算得到rotation变换矩阵。得到模型空间下的光照和视角方向,再利用变换矩阵rotation把它们从模型空间变换到切线空间中。
    在这里插入图片描述
  4. 片元着色器,只需采样得到切线空间下的法线方向,再在切线空间下进行光照计算即可:
    在这里插入图片描述

在世界空间下计算

而世界空间下计算光照模型。基本思想:在顶点着色器中计算从切线空间到世界空间的变换矩阵,并把它传递给片元着色器。变换矩阵的计算可由顶点的切线、副切线和法线在世界空间下的表示得到。最后,只需在片元着色器中把法线纹理中的法线方向从切线空间变换到世界空间下即可。

  1. 修改顶点着色器的输入结构体v2f,使它包含从切线空间到世界空间的变换矩阵:
    在这里插入图片描述
  2. 修改顶点着色器。计算从切线到世界的变换矩阵:
    在这里插入图片描述
  3. 修改片元着色器,在世界空间下进行光照计算:
    在这里插入图片描述

最终效果

在这里插入图片描述

Unity中的法线纹理类型

必须标识成Normal map时,才能使用Unity的内置函数UnpackNormal来得到正确的法线方向,
在这里插入图片描述
还有一个复选框是Create from Grayscale,作用是从高度图中生成法线纹理,高度图本身是一张灰度图,白色表示相对更高,黑色表示相对更低。结果如下:
在这里插入图片描述
勾选后还多出两项——BumpinessFiltering。其中Bumpiness用于控制凹凸程度,而Filtering决定使用哪种方式来计算凹凸程度,它有两种:一种是Smooth,这使得生成后的法线纹理更平滑;另一种是Sharp,它会使Sobel滤波(一种边缘检测时使用的滤波器)来生成法线。Sobel滤波的实现非常简单,只需在一个3x3的滤波器中计算x和y方向上的导数,然后从中得到法线即可。具体方法是:对于高度图的每个像素,考虑它与水平和竖直方向上的像素差,把它的差当成该点对应的法线在x和y方向上的位移,然后使用之前得到的映射函数存储成法线纹理的r和g分量即可。

渐变纹理

一种常见的用法是使用渐变纹理来控制漫反射光照的结果。计算漫反射光照时,都使用表面法线和光照方向的点积结果与材质的反射率相乘来得到表面的漫反射光照。

  1. 声明一个纹理属性类存储渐变纹理
    在这里插入图片描述
  2. 为渐变纹理_RampTex定义了它的纹理属性变量_RampTex_ST
    在这里插入图片描述
  3. 顶点的输入和输出结构体:
    在这里插入图片描述
  4. 定义顶点着色器:
    在这里插入图片描述
  5. 片元着色器:
    在这里插入图片描述

最终效果

在这里插入图片描述

遮罩纹理(mask texture)

遮罩允许保护某些区域,使它们免于某些修改。之前高光反射应用到模型表面的所有地方,即所有的像素都使用同样大小的高光强度和高光指数。但有时,希望模型表面某些区域的反光强烈一些,某些区域弱一些。为了得到更细腻的效果,可使用一张遮罩纹理来控制光照。另一种常见应用是制作地形材质时需混合多张图片,使用遮罩纹理可控制如何混合这些纹理。
使用遮罩纹理的流程一般是:通过采样得到遮罩纹理的纹素值,然后使用其中某个通道的值来与某种表面属性进行相乘,这样,当通道的值为0时,可保护表面不受该属性的影响

  1. 声明更多变量来控制高光反射:
    在这里插入图片描述
  2. 定义与Properties各个属性相匹配的变量:
    在这里插入图片描述
  3. 顶点的输入和输出结构体:
    在这里插入图片描述
  4. 顶点着色器中,对光照和视角方向进行了从模型到切线空间的变换,以便在片元着色器中和法线进行光照运算:
    在这里插入图片描述
  5. 使用遮罩纹理的地方是片元着色器,用它来控制模型表面的高光反射强度:
    在这里插入图片描述
    在计算高光反射时,首先对遮罩纹理_SpecularMask进行采样。这里使用的遮罩纹理中的每个纹素的rgb分量其实都是一样的,表明了该点对应的高光反射强度,这里**选择r分量来计算掩码值。用得到的掩码值和_SpecularScale相乘,一起控制高光反射的强度。**需要说明是,使用的遮罩纹理其实有很多空间被浪费——它的rgb分量存储的都是同一个值。具体制作中,会充分利用遮罩纹理中的每一个颜色通道来存储不同的表面属性。

最终效果

在这里插入图片描述

其他遮罩纹理

会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,最后把自发光强度存储在A通道

猜你喜欢

转载自blog.csdn.net/weixin_42050609/article/details/124842297