【Unity Shader】(七) ------ 复杂的光照(下)

时间:2023-01-09 17:07:15

笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题。

             【Unity Shader】(三)------ 光照模型原理及漫反射和高光反射的实现
        【Unity Shader】(四)------ 纹理之法线纹理、单张纹理和遮罩纹理的实现
             【Unity Shader】(五) ------ 透明效果之半透明效果的原理及实现
                    【Unity Shader】(六)------ 复杂的光照(上)

目录

前言

一. 光照衰减

1.1 使用 LUT

1.2 关于光照衰减纹理

1.3 关于光照衰减的总结

二. 阴影

2.1 阴影是如何实现出来的

Algorithm overview

2.2  普通非透明物体阴影的实现

2.2.1 准备工作

2.2.2 接收阴影

2.2.3 完善的的光照衰减和阴影管理

2.3 普通透明物体的阴影

三. 完整的光照 shader

四. 总结

前言

本文承接上文【Unity Shader】(六) ------ 复杂的光照(上),介绍剩下的光照衰减和阴影部分,最后实现包含了对不同光照类型进行光照计算,光照衰减,阴影产生等部分的真正意义上的标准光照 shader 。因为本文会上文有所联系,所以个人建议读者阅读上文,以免在本文某些地方出现思路上的突兀。

一. 光照衰减

1.1 使用 LUT

前面说过,我们使用 LUT 来计算衰减,这种做法的优劣点如下:

  • 优点:因为直接计算光照衰减会涉及大量且复杂的数学运算,使用 LUT 可以不依赖数学表达式的复杂性,只需一个参数去采样即可。
  • 缺点 : ① 需要预处理得到纹理,纹理大小影响衰减的精度。② 不直观,且使用 LUT 后就无法使用其它数学公式来计算。

当然,Unity 默认这种方法也是因为其在一定程度上提升了性能且大部分情况下,得到的效果是良好的。

1.2 关于光照衰减纹理

Unity 内使用 _LightTexture0 的纹理来计算光照衰减,在之前的代码中,我们已经使用过了。通常情况下,我们只关心 _LightTexture0 对角线上的纹理颜色值,其代表了在光源空间下不同位置的点的衰减值。(0,0)表示与光源重合的点的衰减值,(1,1)表示距离最远的点的光照衰减值。

上面说过,需要用一个点对纹理采样,那么就要先知道该点在光源空间下位置信息。同样是空间转换,我们在这里需要用到的转换矩阵为 _LightMatrix0 。在 Unity 5.4 之后,这个矩阵更换为 unity_WorldToLight 了。

【Unity Shader】(七) ------ 复杂的光照(下)

所以这里转换语句应该为

1 float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;

然后使用这个坐标的摸的平方进行采样。当然,如果用距离值来计算就需要开方操作了,为了,避免这个繁琐的步骤,我们使用顶点距离的平方来采样

1 fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

其中宏 UNITY_ATTEN_CHANNEL 可以得到衰减值所在的分量。

1.3 关于光照衰减的总结

上述所说的知识足够读者应付大部分的光照计算中的光照衰减部分,如果读者着实不希望采用 LUT 的方法来计算衰减,也可以使用数学公式,只是这样需要对公式有更深入的理解。很遗憾的是,笔者并没有找到关于计算衰减的公式的资料,对于衰减方面的资料,着实所寻不多,日后如果我能找到相关知识,我会补充到这篇文章中。

二. 阴影

在许多游戏制作中,为了追求真实,光影效果是必不可少的。光我们之前介绍了,现在来介绍阴影。

2.1 阴影是如何实现出来的

想象一下,一条光线从远方射过来,当它遇到了第一个不透明的物体时,那么理所当然的是,这条射线就无法再去照亮别的物体了。同时,挡住这条光线的物体会向附近的物体投射阴影。也就是说,阴影是光线无法到达的区域

在 Unity 的实时渲染中,我们采用的是 Shadow Map 技术。关于 Shadow Map,我们可以 WIKI 上看到它的解释。

【Unity Shader】(七) ------ 复杂的光照(下)

大意为:Shadow Map 是 Lance Williams 先生在 1978 年提出的技术,从此之后,它常用于预渲染和实时场景中。在 Unity 中,就是先把摄像机的位置与光源重合,然后摄像机看不到的区域就是阴影。这样理解是不是很简单?

我们先来看看 Shadow Map 是如何定义其工作原理的:

Algorithm overview

Rendering a shadowed scene involves two major drawing steps. The first produces the shadow map itself, and the second applies it to the scene. Depending on the implementation (and number of lights), this may require two or more drawing passes.

简单地说就是 : ① 生成阴影纹理  ② 在场景中使用阴影纹理

比如:在前向渲染中,如果平行光开启了阴影(要注意需要手动开启,创建了一个新光源,默认是没有阴影的)

【Unity Shader】(七) ------ 复杂的光照(下)

Unity 就会为这个平行光计算阴影映射纹理。这张阴影映射纹理实质就是一张深度纹理,记录着从光源出发,距离光源最近的表面信息

通常情况下,是通过调用 Base Pass 和 Additional Pass 来更新深度信息,但我们之前也说过,这两个 Pass 中包含了各种光照计算。为了避免多余的光照计算所造成的性能损耗,Unity 选择使用另外一个特别的 Pass 来管理光源的映射纹理。这个 Pass 就是 LightMode 标签中设置为 Shadow Caster 的那个 Pass。这个 Pass 的渲染目标是深度纹理。所以当一个光源开启了阴影效果之后,引擎就会在当前渲染物体的 shader 中寻找这个 Pass ,如果找不到,就去 Fallback 里面找;还找不到,就去 Fallback 的 Fallback 里面找。如果这样都找不到,那么该物体就无法向其它物体投射阴影,但是可以接收来自其它物体的阴影。

【Unity Shader】(七) ------ 复杂的光照(下) 

【Unity Shader】(七) ------ 复杂的光照(下)

文字有点多,总结一下:

  • 如果想要一个物体接收其它的物体的阴影,就要在 shader 中对阴影映射纹理进行采样,把采样结果和光照结果相乘得到阴影效果。
  • 如果想要一个物体向其它物体投射阴影,就要把该物体加入到阴影映射纹理之中,这一步骤是在 Shadow Pass 中实现的。
  • 如果想要一个光源产生阴影效果,则需要手动选择阴影类型:No Shadows , Hard Shadows , Soft Shadows。Hard Shadows 相对于 Soft Shadows 计算量少一些,能满足大部分场景,边缘不平滑,锯齿明显。
     

2.2  普通非透明物体阴影的实现

这一节我们来实现对一个不透明的物体的阴影处理,包括让它投射阴影和接收阴影。

2.2.1 准备工作

创建场景,去掉默认的天空盒子;新建一个 Material 和 一个 shader,命名为 Shadow;创建一个 Cube 和两个 plane,位置摆放如下;开启平行光的阴影;新建的 shader 中使用我上一篇最后给出的前向渲染的代码。

【Unity Shader】(七) ------ 复杂的光照(下)

看到上面的图,不知道读者有没有一种细思极恐的感觉,因为上图有两处诡异的地方。

  • 前文说过,需要一个 ShadowCaster 的 Pass 来处理阴影,但是在上一篇中实现的前向渲染的代码中,我们并没有定义这样的一个 Pass,也没有做出对阴影处理的操作,那么为什么正方体会有阴影呢?
  • 可以确定的是两个 plane 都开启了投射阴影和接收阴影,图中就可以看到地面上的 plane 接收了正方体的阴影,那么,为什么右边的 plane 没有投影呢?

其实这是两个需要注意的地方

  • 前文说过,当 shader 中没有 ShadowCaster 的
    Pass 时会去它的 Fallback 里面找,我们之前的 Fallback 为 Specular,Specular 中也没有这个
    Pass,最后在某个角落找到了它。想看源码的读者,可以在 Unity 官方下载 内置着色器 ,解压之后,在 DefaultResourcesExtra 文件夹中的 Normal-VertexLit 这个 shader 中找到以下代码。
     1 // Pass to render object as a shadow caster
    2 Pass
    3 {
    4 Name "ShadowCaster"
    5 Tags { "LightMode" = "ShadowCaster" }
    6
    7 CGPROGRAM
    8 #pragma vertex vert
    9 #pragma fragment frag
    10 #pragma target 2.0
    11 #pragma multi_compile_shadowcaster
    12 #pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
    13 #include "UnityCG.cginc"
    14
    15 struct v2f {
    16 V2F_SHADOW_CASTER;
    17 UNITY_VERTEX_OUTPUT_STEREO
    18 };
    19
    20 v2f vert( appdata_base v )
    21 {
    22 v2f o;
    23 UNITY_SETUP_INSTANCE_ID(v);
    24 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    25 TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    26 return o;
    27 }
    28
    29 float4 frag( v2f i ) : SV_Target
    30 {
    31 SHADOW_CASTER_FRAGMENT(i)
    32 }
    33 ENDCG
    34
    35 }

    所以事实上,我们之前实现的 ForwardRendering 也是可以产生阴影效果的。

  • 右边的 plane 确定是打开了 CasterShadow 的,而且 shader 为 Unity 内置标准shader。而它却无法投射阴影是因为在默认情况下,在计算光源的阴影映射纹理时剔除物体背面。而内置的平面其实只有一个面。不过我们可以设置为 Two Sided,就可以令物体的所有面都计算光照信息了。【Unity Shader】(七) ------ 复杂的光照(下)【Unity Shader】(七) ------ 复杂的光照(下)

这样就看到阴影了。不过还是有一个奇怪的地方,那就是正方体为什么没有接收右边平面的阴影?因为 ForwardRendering 里面没有对接收的阴影做出处理。我们接下来就要完善这一步了。

2.2.2 接收阴影

我们开始对 Shadow 改造

I. 添加一个头文件,我们计算阴影所需要的宏都是在这个文件中声明的

【Unity Shader】(七) ------ 复杂的光照(下)

II. 在输出结构体添加一个内置宏

【Unity Shader】(七) ------ 复杂的光照(下)

这个宏用于声明对阴影纹理采样的坐标,参数为下一个可用的插值寄存器的索引,在上面,我们在 worldNormal 和 worldPos 都使用了一个,所以此时这个宏的参数应该为2

III. 在顶点着色器中添加一个宏

【Unity Shader】(七) ------ 复杂的光照(下)

TRANSFER_SHADOW 这个宏会计算上一步定义的阴影纹理坐标。我们可以在 AutoLight 中看到它的定义

【Unity Shader】(七) ------ 复杂的光照(下)

IV. 在片元着色器中计算阴影,同样使用一个内置宏

【Unity Shader】(七) ------ 复杂的光照(下)

V. 将得到的阴影值与漫反射颜色,高光反射颜色相乘

【Unity Shader】(七) ------ 复杂的光照(下)

VI. 保存,查看效果

【Unity Shader】(七) ------ 复杂的光照(下)

可以看到,正方体已经接收到了右边平面的阴影。

此时,读者可能会有疑惑,上面步骤中那些代码应该添加在哪里,因为前向渲染中我们定义了两个 Pass,Base Pass 和 Additional Pass。事实上,两个 Pass 对阴影处理的原理是一样的,上面的步骤,我只对 Base Pass 做了修改,但这是不够完善的,所以接下来,我们来介绍完整的阴影管理。

在这里,还需注意的是 SHADOW_COORDS,TRANSFER_SHADOW,SHADOW_ATTENUATION 这三个宏会根据不同的情况有不同的定义

 1 // ---- Screen space direction light shadows helpers (any version)
2 #if defined (SHADOWS_SCREEN)
3
4 #if defined(UNITY_NO_SCREENSPACE_SHADOWS)
5 UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
6 #define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
7 inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
8 {
9 #if defined(SHADOWS_NATIVE)
10 fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
11 shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
12 return shadow;
13 #else
14 unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
15 // tegra is confused if we use _LightShadowData.x directly
16 // with "ambiguous overloaded function reference max(mediump float, float)"
17 unityShadowCoord lightShadowDataX = _LightShadowData.x;
18 unityShadowCoord threshold = shadowCoord.z;
19 return max(dist > threshold, lightShadowDataX);
20 #endif
21 }
22
23 #else // UNITY_NO_SCREENSPACE_SHADOWS
24 UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
25 #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
26 inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
27 {
28 fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
29 return shadow;
30 }
31
32 #endif
33
34 #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
35 #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
36 #endif

当然,你可以不必过于深究,但需要注意的是,这三个宏是天生的三个好基友,如果关闭了阴影,那么 SHADOW_COORDS,TRANSFER_SHADOW 会不起作用,而 SHADOW_ATTENUATION 的值为 1 。那么漫反射颜色和高光反射颜色不受 shadow 影响。而且这些宏会使用 v.vertex 和 a.pos 等变量来计算,所以 a2v 顶点坐标变量必须为 vertex,输入结构体 a2v 必须命名为 v ,且 v2f 中顶点位置坐标为 pos。

2.2.3 完善的的光照衰减和阴影管理

在之前实现前向渲染的时候,我们为了得到光照衰减值,对不同光源做了判断,然后将得到的结果与反射颜色相乘,在这一点上,阴影的计算过程类似。而幸运的是,Unity 为我们提供了一个内置宏 UNITY_LIGHT_ATTENUATION 来同时得到光照衰减因子和阴影值。我们可以在 AutoLight 中找到它的定义

1 #define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
2 unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
3 fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
4 fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
5 #endif

接下来,我们用它替换 Base Pass 和 Additional Pass 中对光照衰减和阴影的计算。代码和之前大部分都是一样的,所以这里就不分步讲解,给出完整代码。读者可以自行实现一下。

  1 Shader "Unity/01-Shadow"
2 {
3 Properties
4 {
5 _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
6 _Specular ("Specular", Color) = (1, 1, 1, 1)
7 _Gloss ("Gloss", Range(8.0, 256)) = 20
8 }
9 SubShader
10 {
11 Tags { "RenderType"="Opaque" }
12
13 Pass {
14
15 Tags { "LightMode"="ForwardBase" }
16
17 CGPROGRAM
18
19 #pragma multi_compile_fwdbase
20
21 #pragma vertex vert
22 #pragma fragment frag
23
24 #include "Lighting.cginc"
25 #include "AutoLight.cginc"
26
27 fixed4 _Diffuse;
28 fixed4 _Specular;
29 float _Gloss;
30
31 struct a2v {
32 float4 vertex : POSITION;
33 float3 normal : NORMAL;
34 };
35
36 struct v2f {
37 float4 pos : SV_POSITION;
38 float3 worldNormal : TEXCOORD0;
39 float3 worldPos : TEXCOORD1;
40 SHADOW_COORDS(2)
41 };
42
43 v2f vert(a2v v) {
44 v2f o;
45 o.pos = UnityObjectToClipPos(v.vertex);
46
47 o.worldNormal = UnityObjectToWorldNormal(v.normal);
48
49 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
50
51 TRANSFER_SHADOW(o);
52 return o;
53 }
54
55 fixed4 frag(v2f i) : SV_Target {
56 fixed3 worldNormal = normalize(i.worldNormal);
57 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
58
59 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
60
61 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
62
63 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
64
65 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
66 fixed3 halfDir = normalize(worldLightDir + viewDir);
67 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
68
69
70
71 return fixed4(ambient + (diffuse + specular) * atten, 1.0);
72 }
73
74 ENDCG
75 }
76
77 Pass {
78
79 Tags { "LightMode"="ForwardAdd" }
80
81 Blend One One
82
83 CGPROGRAM
84
85 #pragma multi_compile_fwdadd
86
87 #pragma vertex vert
88 #pragma fragment frag
89
90 #include "Lighting.cginc"
91 #include "AutoLight.cginc"
92
93 fixed4 _Diffuse;
94 fixed4 _Specular;
95 float _Gloss;
96
97 struct a2v {
98 float4 vertex : POSITION;
99 float3 normal : NORMAL;
100 };
101
102 struct v2f {
103 float4 pos : SV_POSITION;
104 float3 worldNormal : TEXCOORD0;
105 float3 worldPos : TEXCOORD1;
106 SHADOW_COORDS(2)
107
108 };
109
110 v2f vert(a2v v) {
111 v2f o;
112 o.pos = UnityObjectToClipPos(v.vertex);
113
114 o.worldNormal = UnityObjectToWorldNormal(v.normal);
115
116 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
117 TRANSFER_SHADOW(o);
118 return o;
119 }
120
121 fixed4 frag(v2f i) : SV_Target {
122 fixed3 worldNormal = normalize(i.worldNormal);
123 #ifdef USING_DIRECTIONAL_LIGHT
124 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
125 #else
126 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
127 #endif
128
129 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
130
131 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
132 fixed3 halfDir = normalize(worldLightDir + viewDir);
133 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
134
135 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
136
137
138 return fixed4((diffuse + specular) * atten, 1.0);
139 }
140
141 ENDCG
142 }
143 }
144 FallBack "Specular"
145 }

2.3 普通透明物体的阴影

光源效果体现于实物,如果一个物体是透明的,那么它肯定是没有影子,那在 shader 中,这是如何实现呢? 用回我们在 【Unity Shader】(五) ------ 透明效果之半透明效果的实现及原理 实现的透明度测试并结合上面的代码,并把 Fallback 改为 VertexLit。看一下效果

【Unity Shader】(七) ------ 复杂的光照(下)

可以看到阴影部分其实是相当于整个正方体的阴影,但事实上,镂空区域不应该有阴影。这是因为
VertexLit 中处理阴影的 Pass 并没有做透明度测试的计算。所以为了提供这样的一个 Pass ,我们可以更改 Fallback 为
"Transparent/Cutout/VertexLit" 。要注意的是,需要提供一个 _CutOff 的属性和 SHADOW_COORDS 的索引值。现在看一下效果:

【Unity Shader】(七) ------ 复杂的光照(下)

这是属于正常的结果。另外关于透明度混合的物体添加阴影会更加的复杂麻烦,有兴趣的读者可以自行思考与实现一下,这里就不再赘述了。

三. 完整的光照 shader

emmm,在之前就提到过会给出完整的一个光照 shader ,现在我们给出一个包含了纹理计算,光照计算,阴影计算,基于 Blinn-Phong 的高光发射 shader。

  1 Shader "Unity/BumpedSpecular" {
2 Properties {
3 _Color ("Color Tint", Color) = (1, 1, 1, 1)
4 _MainTex ("Main Tex", 2D) = "white" {}
5 _BumpMap ("Normal Map", 2D) = "bump" {}
6 _Specular ("Specular Color", Color) = (1, 1, 1, 1)
7 _Gloss ("Gloss", Range(8.0, 256)) = 20
8 }
9 SubShader {
10 Tags { "RenderType"="Opaque" "Queue"="Geometry"}
11
12 Pass {
13 Tags { "LightMode"="ForwardBase" }
14
15 CGPROGRAM
16
17 #pragma multi_compile_fwdbase
18
19 #pragma vertex vert
20 #pragma fragment frag
21
22 #include "UnityCG.cginc"
23 #include "Lighting.cginc"
24 #include "AutoLight.cginc"
25
26 fixed4 _Color;
27 sampler2D _MainTex;
28 float4 _MainTex_ST;
29 sampler2D _BumpMap;
30 float4 _BumpMap_ST;
31 fixed4 _Specular;
32 float _Gloss;
33
34 struct a2v {
35 float4 vertex : POSITION;
36 float3 normal : NORMAL;
37 float4 tangent : TANGENT;
38 float4 texcoord : TEXCOORD0;
39 };
40
41 struct v2f {
42 float4 pos : SV_POSITION;
43 float4 uv : TEXCOORD0;
44 float4 TtoW0 : TEXCOORD1;
45 float4 TtoW1 : TEXCOORD2;
46 float4 TtoW2 : TEXCOORD3;
47 SHADOW_COORDS(4)
48 };
49
50 v2f vert(a2v v) {
51 v2f o;
52 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
53
54 o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
55 o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
56
57 TANGENT_SPACE_ROTATION;
58
59 float3 worldPos = mul(_Object2World, v.vertex).xyz;
60 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
61 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
62 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
63
64 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
65 o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
66 o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
67
68 TRANSFER_SHADOW(o);
69
70 return o;
71 }
72
73 fixed4 frag(v2f i) : SV_Target {
74 float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
75 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
76 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
77
78 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
79 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
80
81 fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
82
83 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
84
85 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
86
87 fixed3 halfDir = normalize(lightDir + viewDir);
88 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
89
90 UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
91
92 return fixed4(ambient + (diffuse + specular) * atten, 1.0);
93 }
94
95 ENDCG
96 }
97
98 Pass {
99 Tags { "LightMode"="ForwardAdd" }
100
101 Blend One One
102
103 CGPROGRAM
104
105 #pragma multi_compile_fwdadd
106 // Use the line below to add shadows for point and spot lights
107 // #pragma multi_compile_fwdadd_fullshadows
108
109 #pragma vertex vert
110 #pragma fragment frag
111
112 #include "Lighting.cginc"
113 #include "AutoLight.cginc"
114
115 fixed4 _Color;
116 sampler2D _MainTex;
117 float4 _MainTex_ST;
118 sampler2D _BumpMap;
119 float4 _BumpMap_ST;
120 float _BumpScale;
121 fixed4 _Specular;
122 float _Gloss;
123
124 struct a2v {
125 float4 vertex : POSITION;
126 float3 normal : NORMAL;
127 float4 tangent : TANGENT;
128 float4 texcoord : TEXCOORD0;
129 };
130
131 struct v2f {
132 float4 pos : SV_POSITION;
133 float4 uv : TEXCOORD0;
134 float4 TtoW0 : TEXCOORD1;
135 float4 TtoW1 : TEXCOORD2;
136 float4 TtoW2 : TEXCOORD3;
137 SHADOW_COORDS(4)
138 };
139
140 v2f vert(a2v v) {
141 v2f o;
142 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
143
144 o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
145 o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
146
147 float3 worldPos = mul(_Object2World, v.vertex).xyz;
148 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
149 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
150 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
151
152 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
153 o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
154 o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
155
156 TRANSFER_SHADOW(o);
157
158 return o;
159 }
160
161 fixed4 frag(v2f i) : SV_Target {
162 float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
163 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
164 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
165
166 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
167 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
168
169 fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
170
171 fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
172
173 fixed3 halfDir = normalize(lightDir + viewDir);
174 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
175
176 UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
177
178 return fixed4((diffuse + specular) * atten, 1.0);
179 }
180
181 ENDCG
182 }
183 }
184 FallBack "Specular"
185 }

四. 总结

关于光照衰减我们通常是使用 LUT 来得到衰减值,这样在大部分情况下都能得到良好的效果且能提升性能。当然,如果是为了保持灵活性,我们也可以使用最原始的数学公式。

关于阴影,我们需要知道的是,光源需要开启阴影效果;物体的要开启 CasterShadow,让自身加入阴影映射纹理的计算,这样周围的物体才可以接收到自身投射的阴影

阴影映射纹理本质上是一张深度图,这个技术原本是延迟渲染产生阴影的方法,也正好解释了我们之前所说的延迟渲染不依赖于场景的复杂度,而依赖于屏幕空间大小。需要注意的是这个技术是在 Unity 5.0 之后才有的,它要求显卡支持 MRT,即表明这个技术对硬件有要求

最后,本文和上一篇 【Unity Shader】(六) ------ 复杂的光照(上)都介绍了许多理论知识,看起来十分的枯燥,但我还是衷心地希望本文能对你有所帮助。

【Unity Shader】(七) ------ 复杂的光照(下)

【Unity Shader】(七) ------ 复杂的光照(下)的更多相关文章

  1. Unity shader学习之切线空间下计算凹凸映射

    切线空间,即使用顶点的切线作为x轴,法线作为z轴,法线与切线的叉积作为y轴. 使用切线空间存储法线,使得法线纹理可以复用,很好. 在切线空间中计算光照,比在世界空间中计算光照少了很多计算量.在切线空间 ...

  2. 【Unity Shader】(六) ------ 复杂的光照(上)

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题.              [Unity Sha ...

  3. Unity Shader入门精要学习笔记 - 第9章 更复杂的光照

    转载自 冯乐乐的<Unity Shader入门精要> Unity 的渲染路径 在Unity里,渲染路径决定了光照是如何应该到Unity Shader 中的.因此,如果要和光源打交道,我们需 ...

  4. Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

    转自冯乐乐的<Unity Shader入门精要> 通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象. 首先,光线从光源中被发射出来. 然后,光线和场景中的一些物体相交 ...

  5. unity shader入门(二)语义,结构体,逐顶点光照

    下为一个逐顶点漫反射光照shader Shader "study/Chapter6/vertexShader"{ Properties{_Diffuse("Diffuse ...

  6. Unity3D学习(六):《Unity Shader入门精要》——Unity的基础光照

    前言 光学中,我们是用辐射度来量化光. 光照按照不同的散射方向分为:漫反射(diffuse)和高光反射(specular).高光反射描述物体是如何反射光线的,漫反射则表示有多少光线会被折射.吸收和散射 ...

  7. Unity Shader入门精要读书笔记(一)序章

    本系列的博文是笔者读<Unity Shader入门精要>的读书笔记,这本书的章节框架是: 第一章:着手准备. 第二章:GPU流水线. 第三章:Shader基本语法. 第四章:Shader数 ...

  8. 【Unity Shader】(十) ------ UV动画原理及简易实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...

  9. 【Unity Shader】(九) ------ 高级纹理之渲染纹理及镜子与玻璃效果的实现

    笔者使用的是 Unity 2018.2.0f2 + VS2017,建议读者使用与 Unity 2018 相近的版本,避免一些因为版本不一致而出现的问题. [Unity Shader](三) ----- ...

随机推荐

  1. 连载《一个程序猿的生命周期》-《发展篇》 - 3&period;农民与软件工程师,农业与IT业

    相关文章:随笔<一个程序猿的生命周期>- 逆潮流而动的“叛逆者”        15年前,依稀记得走出大山,进城求学的场景.尽管一路有父亲的陪伴,但是内心仍然畏惧.当父亲转身离去.准备回到 ...

  2. MYSQL远程登录权限设置 ,可以让Navicat远程连接服务器的数据库

    Mysql默认关闭远程登录权限,如下操作允许用户在任意地点登录: 1. 进入mysql,GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY ...

  3. android studio 安装总结

    Android Studio 的安装和配置篇(Windows篇<转> http://www.jianshu.com/p/fc03942548cc# 中间gradle下载比较慢:解决方法 需 ...

  4. Docker搭建ElasticSearch&plus;Redis&plus;Logstash&plus;Filebeat日志分析系统

    一.系统的基本架构 在以前的博客中有介绍过在物理机上搭建ELK日志分析系统,有兴趣的朋友可以看一看-------------->>链接戳我<<.这篇博客将介绍如何使用Docke ...

  5. Azure CosmosDB &lpar;11&rpar; MongoDB概念

    <Windows Azure Platform 系列文章目录> Azure Cosmos DB兼容MongoDB的API,下表将帮助我们更容易理解MongoDB中的一些概念: SQL概念 ...

  6. Debian 9 美化界面

    Debian 桌面美化 安装 gnome-tweak-tool aptitude install gnome-tweak-tool 登陆gnome-look下载主题包 gnome-look上有很多主题 ...

  7. PyQt4网格布局

    最通用的布局类别是网格布局(QGridLayout).该布局方式将窗口空间划分为许多行和列.要创建该布局方式,我们需要使用QGridLayout类. #!/usr/bin/python # -*- c ...

  8. 链表推导式 【list comprehension】

    x for x in x 链表推导式 [list comprehension]链表推导式提供了一个创建链表的简单途径,无需使用 map(), filter() 以及 lambda.返回链表的定义通常要 ...

  9. 『ACM C&plus;&plus;』 PTA 天梯赛练习集L1 &vert; 021-024

    忙疯警告,这两天可能进度很慢,下午打了一下午训练赛,训练赛的题我就不拿过来的,pta就做了一点点,明天又是满课的一天,所以进度很慢啦~ -------------------------------- ...

  10. CodeForces - 321E:Ciel and Gondolas (四边形不等式优化DP)

    题意:N个人排成一行,分成K组,要求每组的不和谐值之和最小. 思路:开始以为是斜率优化DP,但是每个区间的值其实已经知道了,即是没有和下标有关的未知数了,所以没必要用斜率. 四边形优化. dp[i][ ...