http://forum.china.unity3d.com/thread-25738-1-10.html

上一篇对着色器系统的工作原理做了介绍,现在我们将继续深入,将目光聚焦在标准着色器的光照函数。

重新回到Standard.shader,这次在UnityStandardCoreForward.shader中,我们将选择另一个“不简单”的那个分支。它将我们引向UnityStandardCore.shader,而我们感兴趣的是fragForwardBaseInternal函数。

[C#] 纯文本查看 复制代码
?
half4
fragForwardBaseInternal (VertexOutputForwardBase i) 
{
    FRAGMENT_SETUP(s)
#if
UNITY_OPTIMIZE_TEXCUBELOD
    s.reflUVW      
= i.reflUVW;
#endif
 
    UnityLight
mainLight = MainLight (s.normalWorld);
    half
atten = SHADOW_ATTENUATION(i);
 
 
    half
occlusion = Occlusion(i.tex.xy);
    UnityGI
gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
 
    half4
c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
    c.rgb
+= UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);
    c.rgb
+= Emission(i.tex.xy);
 
    UNITY_APPLY_FOG(i.fogCoord,
c.rgb);
    return

OutputForward (c, s.alpha);
}

拿来做参考的简单版本:

[C#] 纯文本查看 复制代码
?
half3
c = BRDF3_Indirect(s.diffColor, s.specColor, gi.indirect, PerVertexGrazingTerm(i, s), PerVertexFresnelTerm(i)); 
    c
+= BRDF3DirectSimple(s.diffColor, s.specColor, s.oneMinusRoughness, rl) * attenuatedLightColor;
    c
+= UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);

与上一节中的版本不同,最终的颜色由对UNITY_BRDF_PBS、UNITY_BRDF_GI和Emission的调用结果相加得出。



Emission与简单版本中的相同。UNITY_BRDF_PBS和UNITY_BRDF_GI是包含文件中定义的函数别名。在下面这些包含文件中进行查找:

[C#] 纯文本查看 复制代码
?
#include
"UnityCG.cginc"
#include
"UnityShaderVariables.cginc"
#include
"UnityInstancing.cginc"
#include
"UnityStandardConfig.cginc"
#include
"UnityStandardInput.cginc"
#include
"UnityPBSLighting.cginc"
#include
"UnityStandardUtils.cginc"
#include
"UnityStandardBRDF.cginc"
 
#include
"AutoLight.cginc"

UnityStandardBRDF和UnityPBSLighting看起来最像,所以先查看它们。它们就在UnityPBSLighting.cginc中,不同的着色器目标会选择不同的函数。



选择BRDF1_Unity_PBS,它就在UnityStandardBRDF.cginc中,它看起来是最逼真的可用BRDF,而BRDF3_Unity_PBS则是消耗最低的版本。



如你所见,这是个大函数,因此跳过一些与优化相关的细节,依次逐块的进行讲解,首先从这个非常有用的注释开始:

[C#] 纯文本查看 复制代码
?
//
Main Physically Based BRDF
//
Derived from Disney work and based on Torrance-Sparrow micro-facet model
//
//  
BRDF = kD / pi + kS * (D * V * F) / 4
//  
I = BRDF * NdotL
//
//
* NDF (depending on UNITY_BRDF_GGX):
// 
a) Normalized BlinnPhong
// 
b) GGX
//
* Smith for Visiblity term
//
* Schlick approximation for Fresnel

注释给出了使用的公式,以及引用与作用。NDF(法线分布函数)有多个选择,但这里仅介绍GGX,因为我觉得它更好。



下面对注释中的公式进行简单的介绍:

  • kD: 漫反射率
  • pi: π常量
  • kS: 镜面反射率
  • D: 法线分布
  • V: 几何可见度系数
  • F: 菲涅尔反射率

自定义光照函数:

[C#] 纯文本查看 复制代码
?
half4
BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 
    half3
normal, half3 viewDir,
    UnityLight
light, UnityIndirect gi)
{
    half
roughness = 1-oneMinusRoughness;

将光滑度转换为粗糙度。

[C#] 纯文本查看 复制代码
?
half3
halfDir = Unity_SafeNormalize (light.dir + viewDir);

half向量。



正确处理NdotV(查看文件中的注释):

[C#] 纯文本查看 复制代码
?
half
nl = DotClamped(normal, light.dir);
 
half
nh = BlinnTerm (normal, halfDir);
half
nv = DotClamped(normal, viewDir);
 
half
lv = DotClamped (light.dir, viewDir);
half
lh = DotClamped (light.dir, halfDir);

计算 V 和 D:

[C#] 纯文本查看 复制代码
?
half
V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
   half
D = GGXTerm (nh, roughness);

根据Disney BRDF,计算漫反射项,以及镜面反射系数:

[C#] 纯文本查看 复制代码
?
half
disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5);
   half
specularTerm = (V * D) * (UNITY_PI/4);
//
Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
   //HACK
(see file for more comments)
   specularTerm
= max(0, specularTerm * nl);
   half
diffuseTerm = disneyDiffuse * nl;
 
   //
surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(realRoughness^2+1)
   half
realRoughness = roughness*roughness;      
//
need to square perceptual roughness
   half
surfaceReduction = 1.0 / (realRoughness*realRoughness + 1.0);         
//
fade \in [0.5;1]
 
   half
grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));

将所有的加总,包括全局光照贡献:

[C#] 纯文本查看 复制代码
?
  half3
color =    diffColor * (gi.diffuse + light.color * diffuseTerm)
                    +
specularTerm * light.color * FresnelTerm (specColor, lh)
                    +
surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
 
    return

half4(color, 1);
}

以上就是光照函数的全部。下面来深入介绍全局光照对最终结果的贡献。



本节我们将介绍全局光照贡献的计算方式。过程有些麻烦,因为进行关键计算的代码隐匿在质量选择层的层层定义之后。



所以让我们查看下所有与全局光照有关的函数和结构体,它们就位于我们前面三节提及的代码中。



在UnityStandardCore.cginc中, fragForwardBaseInternal:

[C#] 纯文本查看 复制代码
?
UnityGI
gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight); 
half4
c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); 
    c.rgb
+= UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);

在我们的片段前向基本函数中,FragmentGI被用于计算全局光照数据:“gi”,它被传递给UNITY_BRDF_PBS 和UNITY_BRDF_GI (它们的定义分别对应着不同的质量级别)。



在UnityStandardBRDF.cginc中, BRDF1_Unity_PBS:

[C#] 纯文本查看 复制代码
?
half4
BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 
    half3
normal, half3 viewDir,
    UnityLight
light, UnityIndirect gi)
{
[...]
    half3
color =    diffColor * (gi.diffuse + light.color * diffuseTerm)
                    +
specularTerm * light.color * FresnelTerm (specColor, lh)
                    +
surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
    return

half4(color, 1);
}

这是UNITY_BRDF_PBS部分,它接受gi,用它来计算着色像素的颜色。



以下两个定义至少需要定义一个:

  • LIGHTMAP_ON
  • DYNAMICLIGHTMAP_ON

还有一堆额外的定义,用来控制代码的跳转,或决定函数的选择:

  • DIRLIGHTMAP_SEPARATE
  • DIRLIGHTMAP_COMBINED
  • UNITY_BRDF_PBS_LIGHTMAP_INDIRECT
  • UNITY_BRDF_GI
  • UNITY_SHOULD_SAMPLE_SH
  • UNITY_SPECCUBE_BLENDING
  • UNITY_SPECCUBE_BOX_PROJECTION
  • _GLOSSYREFLECTIONS_OFF
  • UNITY_SPECCUBE_BOX_PROJECTION

全局光照数据的流转基本是这样的,从基本结构体流向与定义相关的函数:

  • 结构体UnityGI (在UnityLightingCommon.cginc中) 保存着多个UnityLight,取决于光照贴图的类型
  • 结构体UnityGIInput (在UnityLightingCommon.cginc中) 保存着计算GI所需的其他不同信息,被用于许多函数中
  • 函数UNITY_BRDF_GI (在UnityPBSLighting.cginc中) 在fragForwardBaseInternal 中用于计算对BRDF的间接贡献(通过调用BRDF_Unity_Indirect)
  • 函数BRDF_Unity_Indirect(在UnityPBSLighting.cginc中)将UNITY_BRDF_PBS_LIGHTMAP_INDIRECT 的结果与传入的colour相加
  • 函数UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (在UnityPBSLighting.cginc中) 被定义为BRDF2_Unity_PBS (但一条注释说也可以使用BRDF1_Unity_PBS ,以获得更佳质量)
  • 函数BRDF2_Unity_PBS 或BRDF1_Unity_PBS,我们在前面一节中见过。这里用于计算间接贡献
  • 函数FragmentGI (在UnityStandardCore.cginc中) 填充必要的数据,包括来自反射探针的数据,然后传递给UnityGlobalIllumination
  • 函数UnityGlobalIllumination:(4个版本,不同的签名)传递数据给UnityGI_Base 和UnityGI_IndirectSpecular
  • 函数UnityGI_Base(在UnityGlobalIllumination.cginc中)对光照贴图进行采样和解码,混合实时衰减和应用遮蔽
  • 函数UnityGI_IndirectSpecular(在UnityGlobalIllumination.cginc中),计算反射,对盒型投影进行矫正(如果已激活),应用遮蔽

对于了解全貌同样有用的东西:

  • 结构体UnityIndirect(在UnityLightingCommon.cginc中) 仅包含一个漫反射和一个镜面反射颜色
  • 结构体UnityLight(在UnityLightingCommon.cginc中)保存光源的颜色、方向和NdotL
  • 纹理立方体unity_SpecCube0 和unity_SpecCube1: 反射探针
  • 结构体Unity_GlossyEnvironmentData: 保存粗糙度和反射UV
  • 函数ResetUnityGI: 清空一个UnityGI结构体
  • 函数ResetUnityLight: 清空一个UnityLight 结构体
  • 函数ShadeSHPerPixel: 对每个像素进行Spherical Harmonics采样

这些知识应该已足以让你在修改标准着色器时,不会意外的将全局光照玩坏。

Unity 5着色器系统代码介绍(下)的更多相关文章

  1. Unity 5着色器系统代码介绍(上)

    http://forum.china.unity3d.com/thread-25724-1-10.html Unity 5着色器系统代码介绍(上) Unity在着色器开发方面提供了很大的灵活性.有些工 ...

  2. Unity Shader着色器优化

    https://mp.weixin.qq.com/s?__biz=MzU5MjQ1NTEwOA==&mid=2247493518&idx=1&sn=c51b92e9300bcf ...

  3. Unity 几何着色器

    Unity 几何着色器 shaderGeometry Shader几何着色器 Unity 几何着色器 如果学习不能带来价值,那将毫无意义 简介     在顶点和片段着色器之间有一个可选的着色器,叫做几 ...

  4. [Unity] Shader(着色器)输入输出和语义

    在Unity5.x后, 已经支持了基于物理的光照模型,也就是常说的次时代引擎所必须具备的功能. 如果在Properties使用2D,CG里要用sampler2D,代表使用的是2维纹理 如果在Prope ...

  5. [Unity] Shader(着色器)之纹理贴图

    在Shader中,我们除了可以设定各种光线处理外,还可以增加纹理贴图. 使用 settexture 命令可以为着色器指定纹理. 示例代码: Shader "Sbin/ff2" { ...

  6. Unity 光照着色器

    光照着色器需要考虑光照的分类,一般分为漫反射和镜面反射. 漫反射计算基本光照: float brightness=dot(normal,lightDir)    将法线和光的入射方向进行点积运算,求出 ...

  7. [Unity] Shader(着色器)之固定管线

    在Unity中,固定管线Shader的性能是最好的. 什么是固定管线呢? 固定渲染管线 —— 这是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界.视.投影变换及固定光照控制 ...

  8. Unity 渲染教程(二):着色器基础

    转载:https://www.jianshu.com/p/7db167704056 这是关于渲染基础的系列教程的第二部分.这个渲染基础的系列教程的第一部分是有关矩阵的内容.在这篇文章中我们将编写我们的 ...

  9. Unity 着色器基础知识

    一.着色器基础知识 着色器通过代码模拟物体表面发生的事情,其实就是GPU中运行的一段代码. 着色器的类型: 顶点着色器.片元着色器.无光照着色器.表面着色器.图像特效着色器.计算着色器. 坐标空间: ...

随机推荐

  1. 【Xcode学C-4】进制知识、位运算符、变量存储细节以及指针的知识点介绍

    一.进制知识 (1)默认是十进制.八进制前面加0.即int num1=015;是13.十六进制前面加0x/0X.即int num1=0xd.结果是13.二进制前面是0b/0B,即int num1=0b ...

  2. 采集练习(十) php 获得电视节目预告---数据来自搜视网

    前几天逛湖南卫视,偶然间发现它的网站上也有节目预告,一看源码,居然是来自搜视网的xml,于是就想获得它的数据(页面直接ajax加载估计会有跨域问题) 前阵子也写过另一个方法获得 节目预告(采集练习(七 ...

  3. 【html学习整理】常用标签

    什么是html 超文本标记语言 html语法规则       所有的命令放到<> 大部分成对存在,以<tag>开始,</tag>结束 网页的基本框架,常用的标记 & ...

  4. Contiki进程间的交互

    之前都是从各个模块开始看起,从底层开始看起.应该改变一下思路,从高往下看,站得高看得远. 一.Main函数 源码:contiki-release-2-7\platform\stm32test\cont ...

  5. JS获取首字母

    function pySegSort(arr, empty) { if (!String.prototype.localeCompare) return null; var letters = &qu ...

  6. BZOJ-4327:JSOI2012 玄武密码(AC自动机模板题)

    在美丽的玄武湖畔,鸡鸣寺边,鸡笼山前,有一块富饶而秀美的土地,人们唤作进香河.相传一日,一缕紫气从天而至,只一瞬间便消失在了进香河中.老人们说,这是玄武神灵将天书藏匿在此.  很多年后,人们终于在进香 ...

  7. ***静态成员的定义及初始化 for c++ for新用法

    静态成员的初始化要在类外不然报错error: ISO C++ forbids in-class initialization of non-const static member '***' 但是声明 ...

  8. bzoj3456城市规划 多项式取模

    題目大意 求出有n个点的有标号简单连通无向图的数目. 题解 什么破玩意,直接输出\(2^{C_n^2}\)走人 我们发现这张图要求连通,而上式肯定不能保证连通. 其实上式表示的是不保证连通的有标号简单 ...

  9. HTML Document 头

    1. 比较宽松的HTML格式,样式 <!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"> 如下 ...

  10. AtCoder Regular Contest 074 E:RGB Sequence

    题目传送门:https://arc074.contest.atcoder.jp/tasks/arc074_c 题目翻译 给你一行\(n\)个格子,你需要给每个格子填红绿蓝三色之一,并且同时满足\(m\ ...