Unity系列文章目录

文章目录

  • Unity系列文章目录
  • 前言
  • 一、pandas是什么?
  • 二、使用步骤
    • 2.Unity 中的法线纹理类型
  • 参考

前言

现在,我们来实现第二种方法,即在世界空间下计算光照模型。我们需要在片元着色器中把
法线方向从切线空间变换到世界空间下。这种方法的基本思想是:在顶点着色器中计算从切线空
间到世界空间的变换矩阵,并把它传递给片元着色器。变换矩阵的计算可以由顶点的切线、副切
线和法线在世界空间下的表示来得到。最后,我们只需要在片元着色器中把法线纹理中的法线方
向从切线空间变换到世界空间下即可。尽管这种方法需要更多的计算,但在需要使用Cubemap 进
行环境映射等情况下,我们就需要使用这种方法。

一、pandas是什么?

为此,我们进行如下准备工作。
(1)使用上一节中使用的场景。
(2)新建一个材质。在本书资源中,该材质名为NormalMapWorldSpaceMat。
(3)新建一个Unity Shader。在本书资源中,该Shader 名为Chapter7-NormalMapWorldSpace。
把新的Shader 赋给第2 步中创建的材质。
(4)把第2 步中创建的材质赋给胶囊体。
打开Chapter7-NormalMapWorldSpace,把上一节中的代码粘贴进去,并进行如下修改:
(1)我们需要修改顶点着色器的输出结构体v2f,使它包含从切线空间到世界空间的变换矩阵:
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
};
我们在3.3.2 节中讲到,一个插值寄存器最多只能存储float4 大小的变量,对于矩阵这样的变
量,我们可以把它们按行拆成多个变量再进行存储。上面代码中的TtoW0、TtoW1 和TtoW2 就依
次存储了从切线空间到世界空间的变换矩阵的每一行。实际上,对方向矢量的变换只需要使用3×3
大小的矩阵,也就是说,每一行只需要使用float3 类型的变量即可。但为了充分利用插值寄存器
的存储空间,我们把世界空间下的顶点位置存储在这些变量的w 分量中。

二、使用步骤

(2)修改顶点着色器,计算从切线空间到世界空间的变换矩阵:
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(_Object2World, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
// Compute the matrix that transform directions from tangent space to world space
// Put the world position in w component for optimization
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
在上面的代码中,我们计算了世界空间下的顶点切线、副切线和法线的矢量表示,并把它们
按列摆放得到从切线空间到世界空间的变换矩阵。我们把该矩阵的每一行分别存储在TtoW0、
TtoW1 和TtoW2 中,并把世界空间下的顶点位置的xyz 分量分别存储在了这些变量的w 分量中,
以便充分利用插值寄存器的存储空间。
(3)修改片元着色器,在世界空间下进行光照计算:
fixed4 frag(v2f i) : SV_Target {
// Get the position in world space
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
// Compute the light and view dir in world space
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
// Transform the normal from tangent space to world space
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump),
dot(i.TtoW2.xyz, bump)));

}
我们首先从TtoW0、TtoW1 和TtoW2 的w 分量中构建世界空间下的坐标。然后,使用内置
的UnityWorldSpaceLightDir 和UnityWorldSpaceViewDir 函数得到世界空间下的光照和视角方向。
接着,我们使用内置的UnpackNormal 函数对法线纹理进行采样和解码(需要把法线纹理的格式
标识成Normal map),并使用_BumpScale 对其进行缩放。最后,我们使用TtoW0、TtoW1 和TtoW2
存储的变换矩阵把法线变换到世界空间下。这是通过使用点乘操作来实现矩阵的每一行和法线相
乘来得到的。
从视觉表现上,在切线空间下和在世界空间下计算光照几乎没有任何差别。在Unity 4.x 版
本中,在不需要使用Cubemap 进行环境映射的情况下,内置的Unity Shader 使用的是切线空间
来进行法线映射和光照计算。而在Unity 5.x 中,所有内置的Unity Shader 都使用了世界空间来
进行光照计算。这也是为什么Unity 5.x 中表面着色器更容易报错,因为它们使用了更多的插值
寄存器来存储变换矩阵(还有一些额外的插值寄存器是用来辅助计算雾效的,更多内容可以参
见19.2 节)

2.Unity 中的法线纹理类型

上面我们提到了当把法线纹理的纹理类型标识成Normal map 时,可以使用Unity 的内置函
数UnpackNormal 来得到正确的法线方向,如图7.16
所示。
当我们需要使用那些包含了法线映射的内置的
Unity Shader 时,必须把使用的法线纹理按上面的方
式标识成Normal map 才能得到正确结果(即便你忘
了这么做,Unity 也会在材质面板中提醒你修正这个
问题),这是因为这些Unity Shader 都使用了内置的
UnpackNormal 函数来采样法线方向。那么,当我们
把纹理类型设置成Normal map 时到底发生了什么
呢?为什么要这么做呢?
简单来说,这么做可以让Unity 根据不同平台对
纹理进行压缩(例如使用DXT5nm 格式,具体的压
缩细节可以参考:http://tech-artists.org/wiki/Normal_map_compression),再通过UnpackNormal 函
数来针对不同的压缩格式对法线纹理进行正确的采样。我们可以在UnityCG.cginc 里找到
UnpackNormal 函数的内部实现:
inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
fixed3 normal;
normal.xy = packednormal.wy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalDXT5nm(packednormal);
#endif
}
从代码中可以看出,在某些平台上由于使用了DXT5nm 的压缩格式,因此需要针对这种格式
对法线进行解码。在DXT5nm 格式的法线纹理中,纹素的a 通道(即w 分量)对应了法线的x
分量,g 通道对应了法线的y 分量,而纹理的r 和b 通道则会被舍弃,法线的z 分量可以由xy 分
量推导而得。为什么之前的普通纹理不能按这种方式压缩,而法线就需要使用DXT5nm 格式来进
行压缩呢?这是因为,按我们之前的处理方式,法线纹理被当成一个和普通纹理无异的图,但实
际上,它只有两个通道是真正必不可少的,因为第三个通道的值可以用另外两个推导出来(法线
是单位向量,并且切线空间下的法线方向的z 分量始终为正)。使用这种压缩方法就可以减少法线
纹理占用的内存空间。
当我们把纹理类型设置成Normal map 后,还有一个复选框是Create from Grayscale,那么它
是做什么用的呢?读者应该还记得在本节开始我们提到过另一种凹凸映射的方法,即使用高度图,
而这个复选框就是用于从高度图中生成法线纹理的。高度图本身记录的是相对高度,是一张灰度
图,白色表示相对更高,黑色表示相对更低。当我们把一张高度图导入Unity 后,除了需要把它
的纹理类型设置成Normal map 外,还需要勾选Create from Grayscale,这样就可以得到类似图7.17中的结果。然后,我们就可以把它和切线空间下的法线纹理同等对待了。

当勾选了Create from Grayscale 后,还多出了两个选项—Bumpiness 和Filtering。其中
Bumpiness 用于控制凹凸程度,而Filtering 决定我们使用哪种方式来计算凹凸程度,它有两种选
项:一种是Smooth,这使得生成后的法线纹理会比较平滑;另一种是Sharp,它会使用Sobel 滤
波(一种边缘检测时使用的滤波器)来生成法线。Sobel 滤波的实现非常简单,我们只需要在一
个3×3 的滤波器中计算x 和y 方向上的导数,然后从中得到法线即可。具体方法是:对于高度图
中的每个像素,我们考虑它与水平方向和竖直方向上的像素差,把它们的差当成该点对应的法线
在x 和y 方向上的位移,然后使用之前提到的映射函数存储成到法线纹理的r 和g 分量即可。

参考

Unity Shader入门精要
冯乐乐

Unity Shader入门精要第七章 基础纹理 凹凸映射之在世界空间下计算相关推荐

  1. Unity Shader入门精要第七章 基础纹理之遮罩纹理

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.实践 参考 前言 遮罩纹理(mask texture)是本章要介绍的最后一种纹理,它非常有用,在很多商业游戏中 都可以见到它的身影. ...

  2. Unity Shader入门精要第七章 基础纹理渐变纹理

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.渐变纹理是什么 参考 前言 尽管在一开始,我们在渲染中使用纹理是为了定义一个物体的颜色,但后来人们发现,纹理 其实可以用于存储任何表 ...

  3. Unity Shader入门精要第四章:学习Shader 所需的数学基础--坐标空间

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.4.6.1 为什么要使用这么多不同的坐标空间 二.4.6.3 顶点的坐标空间变换过程 4.6.4 模型空间 4.6.6 观察空间 4 ...

  4. Unity Shader入门精要--第4 章 学习Shader 所需的数学基础

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 4.1 背景:农场游戏 4.2 笛卡儿坐标系 4.2.2 三维笛卡儿坐标系 4.2.3 左手坐标系和右手坐标系 4.2.4 Unity ...

  5. Unity Shader入门精要--第4 章 学习Shader 所需的数学基础:点和矢量

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 点和矢量 4.3.1 点和矢量的区别 参考 前言 点(point)是n 维空间(游戏中主要使用二维和三维空间)中的一个位置,它没有大小. ...

  6. Unity Shader入门精要——第3章 Unity Shader基础

    Unity Shader入门精要读书笔记系列 第1章 欢迎来到Shader的世界 第2章 渲染流水线 第3章 Unity Shader基础 文章目录 Unity Shader入门精要读书笔记系列 前言 ...

  7. Unity Shader入门精要第3 章 Unity Shader 基础

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一.Unity Shader 概述 二.使用步骤 1.3.1.2 Unity 中的材质 2.Unity 中的Shader 3.Unity ...

  8. shader入门精要第七章

    凹凸映射(包含基础纹理和法线纹理) 凹凸映射 凹凸映射的目的是使用一张纹理来修改模型表面的法线.这种方法不会真正该改变模型顶点位置,使模型看起来具有凹凸的效果,这点从模型的轮廓可以看出来. 凹凸映射的 ...

  9. Unity Shader入门精要 第4章 笛卡尔坐标系 读书笔记

    第4章 学习Shader所需的数学基础-笛卡尔坐标系 注意:图片的来源基本来自作者冯乐乐的GitHub,感谢作者分享 https://github.com/candycat1992/Unity_Sha ...

最新文章

  1. Design Pattern - Mediator(C#)
  2. 一个简单的blog系统(九) 增加标签和标签页面
  3. 如何在一分钟内搞定面试官
  4. Kafka 慌了!这个中间件,要火了?
  5. 【opencv】人脸检测(图片实时)
  6. elm具体实现过程_函数式编程中的战斗机(二)---elm语言MUV设计模式应用实例...
  7. MOOS学习笔记3——命令行
  8. 双点双向重分发中的次优路径和环路问题
  9. zabbix详解(十五)——zabbix proxy配置实战
  10. Macx OS下没有GCC问题
  11. 2021最新版谷歌浏览器百度网盘下载
  12. 最优秀最合理2019年移动便携图形工作站配置探讨
  13. 闲鱼商品选投实时性优化
  14. phpstorm 免费生成 激活码 保证有效
  15. [BZOJ3238] [AHOI2013] 差异 - 后缀自动机
  16. air flow空调上是什么意思_air flow空调滤芯上是什么意思
  17. 利用计算机管理档案,初探档案的计算机管理
  18. 关于在打败C魔王前经常被背刺这件事(C语言常见问题描述及解决方案和原因)
  19. 从几何角度全新理解线性代数
  20. 运维(24)-运维技能知识图谱

热门文章

  1. 一种基于LSTM的音频质量检测方案
  2. 半导体产业,城市,发展讨论
  3. 大脚插件技能栏美化_血条也性感:TidyPlates姓名板美化增强插件
  4. 《Linux/UNIX OpenLDAP实战指南》——2.7 OpenLDAP用户以及与用户组相关的配置
  5. linux uboot nfs启动,嵌入式uboot,内核启动通过nfs挂载根文件系统
  6. python常用 标准库介绍
  7. 171. Excel表列序号
  8. RSA算法理解与实现
  9. SlikSvn创建服务
  10. 弘辽科技:被降权过的宝贝还有必要做吗?如何确定商品降权了?