《Unity Shader入门精要》读书笔记06

铁名_IronName Lv2

第 7 章 基础纹理

纹理映射(texture mapping):把一张图“黏”在模型表面,逐 纹素(texel) 地控制模型的颜色。
纹理映射坐标(texture-mapping coordinates) 存储在每个顶点上,定义了该顶点在纹理中对应的坐标。通常用一个二维变量 (u,v)(u,v) 表示,u是横向坐标,而v是纵向坐标。纹理映射坐标也被称为UV坐标。顶点UV坐标的范围通常都被归一化到[0, 1]。

本章着重讲述纹理采样的原理,因此实现的Shader不能直接应用到实际项目,直接使用会缺少阴影、光照衰减等效果。

单张纹理

实践

在 Unity Shader 中使用单张纹理作为模拟的颜色。

  • 其中,_MainTex_ST的名字不是任意起的。在Unity中,我们需要使用纹理名_ST的方式来声明某个纹理的属性。其中,ST是缩放(scale)和平移(translation)的缩写。xy存储缩放值,zw存储偏移值。
  • 滤波模式:Point,Bilinear,Trilinear。Point 模式使用了最近邻(nearest neighbor)滤波,看起来有像素风格;Bilinear 滤波是对临近 4 个限速的线性插值混合;Trilinear 和 Blinear 几乎一样,只是 Trilinear 还会在多级渐远纹理之间混合(如果用了mipmap)。
  • 多级渐远纹理(mipmapping)技术将原纹理提前用滤波处理来得到很多更小的图像,形成一个图像金字塔,每一层都是对上一层图像降采样的结果。

你知道吗?

mip 是拉丁文 multum in parvo 的缩写,意思是“在一个小空间里有许多东西”。

  • 导入的纹理长宽应该是 2 的幂。 非 2 的幂(Non Power of Two,NPOT) 大小的纹理会占用更多的内存空间,GPU读取速度下降,有些平台甚至不支持这种纹理。Unity 在内部会将NPOT纹理缩放为最近的2的幂大小。出于性能和空间的考虑,我们应该尽量使用 2 的幂大小的纹理。

凹凸映射

凹凸映射(bump mapping):使用一张纹理来修改模型表面的法线,为模型提供更多的细节。

高度纹理

高度图中存储的是强度值(intensity),用于表示模型表面局部的海拔高度。在实时计算的时候不能直接得到表面法线,而是需要由像素的灰度值计算。通常和法线映射一起使用,用于给出表面凹凸的额外信息。

法线纹理

法线纹理中存储的是表面的法线方向。由于像素的分量范围是 [0,1][0,1] ,而法线方向的分量范围是 [1,1][-1,1] ,所以在存储和读取过程中需要转化。

pixel=normal+12pixel=\frac{normal+1}{2} normal=pixel×21normal=pixel\times2-1

话说我之前在 Godot 里鼓捣流体模拟的时候,就用到了这个互相转化。(略微得意)不过没成功。

模型空间的法线纹理(object-space normal map):在模型空间下,存储着模型顶点自带的法线。
切线空间(tangent space):模型的每个顶点都有一个切线空间,这个切线空间的原点是该顶点本身,z 轴是顶点的法线方向(n),x 轴是顶点的切线方向(t),而 y 轴是副切线(bitangent,b)或副法线,由法线和切线叉积而得。
切线空间的法线纹理(tangent-space normal map):在切线空间下,存储着模型顶点自带的法线。”法线扰动方向“

模型空间下的法线纹理看起来是“五颜六色”的;而切线空间下的法线纹理看起来几乎全是浅蓝色。

模型空间存储法线的特点:

  1. 实现简单,直观。
  2. 边界平滑。因为法线都处于同一空间,可以用插值平滑变换。
  3. 绝对法线信息,只能用于创建它时的那个模型

切线空间存储法线的特点:

  1. 是相对法线信息,用到其他模型也可以得到合理的结果。
  2. 可进行UV动画。
  3. 可以重用法线纹理。
  4. 可压缩。由于切线空间下,法线的Z方向总是正的,因此可以仅存储XY方向,使用时再推导出Z方向。

实践

使用切线空间的法线纹理计算光照有两种方法:一、在切线空间下计算;二、在世界空间下计算。(其他空间也行,就是没这两种常用)
前者可以在顶点着色器中计算,效率更高;后者的通用性优于前者。

为什么说前者可以在顶点着色器中变换光照矢量,而后者必须在片元着色器中变换法线?

首先,法线和光照计算相关矢量在计算时必须在同一坐标空间。那些光照矢量原本处于世界空间,而法线处于切线空间。那么为了计算,两者需要变换到同一空间,”要么你过来,要么我过去“。
法线纹理的数据的绝大部分需要靠采样从纹理中获得,所以必须在片元着色器中,法线的变换不能提前。而变换矩阵和光照矢量的变换可以在顶点着色器中计算,然后再插值后拿来计算,有误差但不大。

实践

在切线空间下计算光照模型。

  • 和法线方向normal不同,tangent的类型是float4,而非float3,这是因为我们需要使用tangent.w分量来决定切线空间中的第三个坐标轴——副切线的方向性。
  • 由于我们使用了两张纹理,因此需要存储两个纹理坐标。代码中使用float4的 xy 和 zw 分别存储两个纹理坐标。
  • 在检查器(Inspector)中将纹理类型(Texture Type)设为 Normal Map,就不需要再在代码中做数值范围转化。而是使用UnpackNormal(packedNormal);

实践

在世界空间下计算光照模型。

  • 一个插值寄存器最多只能存储float4大小的变量,对于矩阵这样的变量,我们可以把它们按行拆成多个变量再进行存储。

Normal map 类型和 UnpackNormal()

Unity可以根据不同平台对纹理进行压缩,并对不同压缩格式进行正确的采样。比如在DXT5nm的压缩格式解码时,只用到wy两个分量作为xy,其他通道会被舍弃。
将高度图设置为Normal map的纹理类型,然后勾选 Create from Grayscale 就能把它当成切线空间下的法线纹理使用。

渐变纹理

这种技术最初由 Gooch 等人在1998年他们发表的一篇著名的论文《A Non-Photorealistic Lighting Model For Automatic Technical Illustration》中被提出,在这篇论文中,作者提出了一种基于 冷到暖色调(cool-to-warm tones) 的着色技术,用来得到一种插画风格的渲染效果。使用这种技术,可以保证物体的轮廓线相比于之前使用的传统漫反射光照更加明显,而且能够提供多种色调变化。而现在,很多卡通风格的渲染中都使用了这种技术。

实践

用一张渐变纹理控制漫反射光照。

  • Ctrl+9 可以快速叫出 Lighting 选项卡,方便修改天空盒。
  • 渐变纹理的 Wrap Mode 要设为 Clamp 模式,以防对纹理采样时由于浮点数精度问题造成高光区域出现黑点。

你发现了吗?

书中的代码虽然把纹理坐标变换后存储到uv中,但是在读取纹理的时候是和uv无关的,所以缩放和平移都是没用的。fixed2(halfLambert * _RampTex_ST.x + _RampTex_ST.z, 0.5) 这样_RampTex_ST才能发挥作用。

遮罩纹理

遮罩纹理(mask texture):遮罩允许我们保护某些区域,使它们免于某些修改;或控制不同区域的反光度;或混合多张图片。

实践

用一张高光遮罩纹理,逐像素地控制模型表面的高光反射强度。

在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常,我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。例如,我们可以把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,最后把自发光强度存储在A通道。

  • 标题: 《Unity Shader入门精要》读书笔记06
  • 作者: 铁名_IronName
  • 创建于 : 2026-01-17 09:18:00
  • 更新于 : 2026-01-17 20:23:41
  • 链接: https://blog.ironname.top/2026/01/17/《Unity-Shader入门精要》读书笔记06/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论