文章目录

  • 1.两个结构体
    • 数据来源:Input结构体
  • 2.Unity背后使用了什么

1.两个结构体

上一节我们已经讲过,表面着色器支持最多自定义4种关键函数:表面函数(用于设置各种表面性质,如反射率、法线等),光照函数(定义表面使用的光照模型),顶点修改函数(修改或传递顶点属性),最后的颜色修改函数(对最后的颜色进行修改)。那么这些函数之间的信息传递是怎么实现的呢?例如,我们想把顶点颜色传递给表面函数,添加到顶点反射率的计算中,要怎么做呢?这就是两个结构体的工作。
一个表面着色器需要使用两个结构体:表面函数的输入结构体Input,以及存储了表面属性的结构体SurfaceOutput(Unity5引入了另外两个相同的结构体SurfaceOutputStandard和SurfaceOutputStandardSpecular)

数据来源:Input结构体

Input结构体包含了许多表面属性的数据来源,因此,它会作为表面函数的输入结构体(如果自定义了顶点修改函数,它还会是顶点修改函数的输出结构体)。Input支持很多内置的变量名,通过这些变量名,我们告诉Unity需要使用的数据信息。例如,Input结构体中包含了主纹理和法线纹理的采样坐标uv_MainTex和uv_BumpMap。这些采样坐标必须以“uv”为前缀(实际上也可以以“uv2”为前缀,表明使用次纹理坐标集合),后面紧跟纹理名称。以主纹理_MainTex为例,如果需要使用它的采样坐标,就需要在Input结构体中声明float2 uv_MainTex来对应它的采样坐标。下表列出了Input结构体中内置的其他变量:

变量 描述
float3 viewDir 包含了视角方向,可用于计算边缘光照等
使用COLOR语义定义的float4变量 包含了插值后的逐顶点颜色
float4 screenPos 包含了屏幕空间的坐标,可以用于反射或屏幕特效
float3 worldRefl 包含了世界空间下的反射方向。前提是没有修改表面法线o.Normal
float3 worldRefl;INTERNAL_DATA 如果修改了表面法线o.Normal,需要使用该变量告诉Unity要基于修改后的法线计算世界空间下的反射方向。在表面函数中,我们需要使用WorldReflectionVector(IN,o.Normal)来得到世界空间下的反射方向
float3 worldNormal 包含了世界空间的法线方向。前提是没有修改表面法线o.Normal
float3 worldPos 包含了世界空间下的位置
float3 worldNormal;INTERNAL_DATA 如果修改了表面发现o.Normal,需要使用该变量告诉Unity要基于修改后的法线计算世界空间下的法线方向。在表面函数中,我们需要使用WorldNormalVector(IN,o.Normal)来得到世界空间下的法线方向

需要注意的是,我们并不需要自己计算上述的各个变量,而只需要在Input结构体中按上述名称严格声明这些变量即可,Unity会在背后为我们准备好这些数据,而我们只需要在表面函数中直接使用它们即可。一个例外情况是,我们自定义了顶点修改函数,并需要向表面函数中传递一些自定义的数据。例如,为了自定义雾效,我们可能需要在顶点修改函数中根据顶点在视角空间下的位置信息计算雾效混合系数,这样我们就可以在Input结构体中定义一个名为half fog的变量,把计算结果存储在该变量后进行输出。 ### 表面属性:SurfaceOutput结构体 有了Input结构体提供所需要的数据后,我们可以据此计算各种表面属性。因此,另一个结构体就是用于存储这些表面属性的结构体,即SurfaceOutput、SurfaceOutputStandard和SurfaceOutputStandardSpecular,它会作为表面函数的输出,随后会作为光照函数的输入来进行各种光照计算。相比于Input结构体的自由性,这个结构体里面的变量是提前就声明好的,不可以增加也不会减少(如果没有对某些变量赋值,就会使用默认值)。SurfaceOutput的声明可以在Lighting.cginc文件中找到。

struct SurfaceOutput {fixed3 Albedo;fixed3 Normal;fixed3 Emission;half Specular;fixed Gloss;fixed Alpha;
};

而SurfaceOutputStandard和SurfaceOutputStandardSpecular的声明可以在UnityPBSLLighting.cginc中找到:

struct SurfaceOutputStandard
{fixed3 Albedo;      // base (diffuse or specular) colorfloat3 Normal;      // tangent space normal, if writtenhalf3 Emission;half Metallic;      // 0=non-metal, 1=metal// Smoothness is the user facing name, it should be perceptual smoothness but user should not have to deal with it.// Everywhere in the code you meet smoothness it is perceptual smoothnesshalf Smoothness;    // 0=rough, 1=smoothhalf Occlusion;     // occlusion (default 1)fixed Alpha;        // alpha for transparencies
};
struct SurfaceOutputStandardSpecular
{fixed3 Albedo;      // diffuse colorfixed3 Specular;    // specular colorfloat3 Normal;      // tangent space normal, if writtenhalf3 Emission;half Smoothness;    // 0=rough, 1=smoothhalf Occlusion;     // occlusion (default 1)fixed Alpha;        // alpha for transparencies
};

在一个表面着色器中,只需要选择上述三者中的其一即可,这取决于我们选择使用的光照模型。Unity内置的光照模型有两种,一种是Unity 5之前的、简单的、非基于物理的光照模型,包括了Lambert和BlinnPhong;另一种是Unity 5添加的、基于物理的光照模型包括Standard或StandardSpecular,我们会分别使用SurfaceOutputStandard或SurfaceOutputStandardSpecular结构体。其中,SurfaceOutputStandard结构体用于默认的金属工作流程(Metallic Workflow),对应了Standard的光照函数;而SurfaceOutputStandardSpecular结构体用于高光工作流程(Specular Workflow),对应了StandardSpecular光照函数。更多基于物理的渲染内容,我们会在后面讲到。
在本节,我们着重介绍一下SurfaceOutput结构体中的变量和含义。在表面函数中,我们需要根据Input结构体传递的各个变量计算表面属性。在SurfaceOutput结构体,这些表面属性包括了:
●fixed3 Albedo:对光源的反射率。通常由纹理采样和颜色属性的乘积计算而得。
●fixed3 Normal:表面法线方向。
●fixed3 Emission:自发光。Unity通常会在片元着色器最后输出前(并在最后的顶点函数被调用前,如果被定义的话),使用类似下面的语句进行简单的颜色叠加:

c.rgb+=o.Emission;

●half Specular:高光反射中的指数部分的系数,影响高光发射的计算。例如,如果使用了内置了BlinnPhong光照函数,它会使用如下语句计算高光反射的强度:

float spec=pow(nh,s.Specular*128.0)*s.Gloss;

●fixed Gloss:高光反射中的强度系数。和上面的Specular类似,计算公式见上面的代码。一般在包含了高光反射的光照模型里使用。
●fixed Alpha:透明通道。如果开启了透明度的话,会使用该值进行颜色混合。

2.Unity背后使用了什么

在前面的内容中,我们已经了解到如何利用编译指令、自定义函数(表面函数、光照函数,以及可选的顶点修改函数和最后的颜色修改函数)和两个结构体来实现一个表面着色器。我们一直强调,Unity实际会在背后为表面着色器生成真正的顶点/片元着色器。那么,表面着色器中的各个函数、编译指令和结构体与顶点/片元着色器之间有什么关系呢?这正是本节要学习的内容。
我们之前说过,Unity在背后会根据表面着色器生成一个包含了很多Pass的顶点/片元着色器。这些Pass有些是为了针对不同的渲染路径,例如,默然情况下Unity会为前向渲染路径生成LightMode为ForwardBase和ForwardAdd的Pass,为Unity 5之前的延迟渲染路径生成LightMode为PrePassBase和PrePassFinal的Pass,为Unity 5之后的渲染路径生成LightMode为Deferred的Pass。还有一些Pass是用于产生额外的信息,例如,为了给光照映射和动态全局光照提取表面信息,Unity会生成一个LightMode为Meta的Pass。有些表面着色器由于修改了顶点的位置,因此我们可以利用addshadow编译指令为它生成相应的LightMode为ShadowCaster的阴影投射Pass。这些Pass的生成都是基于我们在表面着色器中的编译指令和自定义的函数,这是有规律可循的。Unity提供了一个功能,让那些“好奇宝宝”可以对表面着色器自动生成的代码一探究竟:在每个编译完成的表面着色器面板上,都有一个“Show generated code”的按钮,如下图所示,我们只需要单击一下它就可以看到Unity为这个表面着色器生成的所有顶点/片元着色器。

通过查看这些代码,我们就可以了解到Unity到底是如何根据表面着色器生成各个Pass的。以Unity生成的LightMode为ForwardBase的Pass(用于前向渲染)为例,它的渲染计算流水线如下所示:

从上图我们可以看出,4个允许自定义的函数在流水线中的位置。
Unity对该Pass的自动生成过程大致如下。
(1)直接将表面着色器中CGPROGRAM和ENDCG之间的代码复制过来,这些代码包括了我们对Input结构体、表面函数、光照函数(如果自定了的话)等变量和函数的定义。这些函数和变量会在之后的处理过程中被当成正常的结构体和函数进行调用。
(2)Unity会分析上述代码,并据此生成顶点着色器的输出——v2f_surf结构体,用于在顶点着色器和片元着色器之间进行数据传递。Unity会分析我们在自定义函数中所使用的变量。而且,即便有时我们在Input中定义了某些变量(如某些纹理坐标),但Unity在分析后续代码时发现我们并没有使用这些变量,那么这些变量实际上是不会在v2f_surf中生成的。这也就是说,Unity做了一些优化。v2f_surf中还包含了一些其他需要的变量,例如阴影纹理坐标、光照纹理坐标、逐顶点光照等。
(3)接着,生成顶点着色器。
①如果我们自定义了顶点修改函数,Unity会首先调用顶点修改函数来修改顶点数据,或填充自定义的Input结构体中的变量。然后,Unity会分析顶点修改函数中修改的数据,在需要时通过Input结构体将修改结果存储到v2f_surf相应的变量中。
②计算v2f_surf中其它生成变量的值。这主要包括了顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理的采样坐标等。当然,我们可以通过编译指令来控制某些变量是否需要计算。
③最后,将v2f_surf传递给接下来的片元着色器
(4)生成片元着色器。
①使用v2f_surf的对应变量填充Input结构体,例如纹理坐标、视角方向等。
②调用我们自定义的表面函数填充SurfaceOutput结构体
③调用光照函数得到初始的颜色值。如果使用的是内置的lambert或BlinnPhong光照函数,Unity还会计算动态全局光照,并添加到光照模型的计算中。
④进行其它的颜色叠加。例如,如果没有使用光照烘焙,还会添加逐顶点光照的影响。
⑤最后,如果自定义了最后的颜色修改函数,Unity就会调用它进行最后的颜色修改。
其它Pass的生成过程和上面类似,这里不再赘述。

第十六章 Unity的表面着色器探秘(2)相关推荐

  1. 第十六章 Unity 预制件prefab(上)

    本章节我们介绍一下"预制件",也有人叫"预制体",也就是Prefab.在游戏世界中,那些自然环境的游戏对象,我们可以提前创建在场景中,这个大家能够理解.但是,有 ...

  2. 游戏感:虚拟感觉的游戏设计师指南——第十六章 Raptor Safari

    这是一本游戏设计方面的好书 转自天:天之虹的博客:http://blog.sina.com.cn/jackiechueng 感谢天之虹的无私奉献 Word版可到本人的资源中下载 第十六章 Raptor ...

  3. Ruby‘s Adventrue游戏制作笔记(十六)Unity子弹数量及其UI

    Ruby's Adventrue游戏制作笔记(十六)Unity子弹数量及其UI 前言 一.创建新的UI 二.编辑脚本 三.创建获得子弹的道具 系列链接 前言 本文章是我学习Unity官方项目项目所做笔 ...

  4. 鸟哥的Linux私房菜(基础篇)- 第二十六章、Linux 核心编译与管理

    第二十六章.Linux核心编译与管理 最近升级日期:2009/09/18 我们说的 Linux 其实指的就是核心 (kernel) 而已.这个核心控制你主机的所有硬件并提供系统所有的功能,所以说,他重 ...

  5. 嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十六章 UCOSII 实验 1任务调度...

    1)实验平台:alientek NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第三十六章 ...

  6. 【JAVA SE】第十六章 进程、线程、同步锁和线程锁的简介

    第十六章 进程.线程.同步锁和线程安全问题 文章目录 第十六章 进程.线程.同步锁和线程安全问题 一.进程 1.基本介绍 2.进程模型 二.线程 1.基本介绍 2.线程的生命周期 3.线程的优先级 4 ...

  7. 第十六章——处理锁、阻塞和死锁(3)——使用SQLServer Profiler侦测死锁

    原文: 第十六章--处理锁.阻塞和死锁(3)--使用SQLServer Profiler侦测死锁 前言: 作为DBA,可能经常会遇到有同事或者客户反映经常发生死锁,影响了系统的使用.此时,你需要尽快侦 ...

  8. 第十六章 - 垃圾回收相关概念

    第十六章 - 垃圾回收相关概念 文章目录 第十六章 - 垃圾回收相关概念 1.System.gc( )的理解 1.1 手动 GC 来理解不可达对象的回收 2.内存溢出与内存泄露 2.1 内存溢出(OO ...

  9. 第六十六章 Caché 函数大全 $TRANSLATE 函数

    文章目录 第六十六章 Caché 函数大全 $TRANSLATE 函数 大纲 参数 描述 `$TRANSLATE`和`$REPLACE` 示例 第六十六章 Caché 函数大全 $TRANSLATE ...

最新文章

  1. Jpa-操作mongodb
  2. 前端获取浏览器标识_浏览器缓存机制
  3. 报名开启 | 神策 2019 数据驱动大会「矩·变」等你!
  4. IIS 支持 php
  5. 贝塞尔曲线理解与应用
  6. java 统计单词个数和标点符号
  7. 创建链表和遍历链表算法演示
  8. 在程序员眼里,马云 1000 亿的大业败给了王者荣耀 100 个月工资的奖金
  9. ubuntu 搭建正版彩虹秒赞网
  10. openssl 加盐_nodejs-md5加盐到解密比对
  11. WPS中用mathtype插入公式的方法
  12. Hive - 内表和外表的区别
  13. 利用0day-java环境-宏感染-安卓客户端进行渗透
  14. 微信撤回消息在服务器可以看到吗,微信撤回消息可以查看了,对方撤回了什么一目了然...
  15. 使用微信小程序做一个简易的下拉框,无动画效果,纯原生写法(下拉列表框)
  16. Linux内核调试技术指南
  17. threejs examples 学习
  18. 矩池云 | Tony老师解读Kaggle Twitter情感分析案例
  19. Java程序朗读文字的实现,jacob.jar
  20. Python之print打印

热门文章

  1. 【音频处理】使用 PolyPhone 软件修正 SoundFont 音源中的不规范音符 ( 设置音符频率校正 )
  2. 三星SCH-I739官方原版ROM下载及刷机教程
  3. 添加mysql 函数库_mysql函数创建
  4. python采集天气数据 并做数据可视化 (含完整源代码)
  5. BZOJ3809: Gty的二逼妹子序列
  6. ctf实验室2020-11-27出题记录
  7. Html和Css中的空格
  8. 如何在windows xp[ 下使用自带的播放器播放 mp4、AVI视频
  9. 如何查看计算机用户的密码,电脑密码如何查看? 教您查看方法
  10. RMAN备份数据库_制作和更新RMAN增量备份(Incremental Backup)