shader光照和阴影
一.标准光照模型
OpenGL与Direct3D提供了几乎相同的固定功能光照模型。什么是固定功能光照模型?在过去只有固定绘制流水线的时候,该流水线被限制只能使用一个光照模型,也即是固定功能光照模型。该模型基于phong光照模型。在下面的这个例子里,我们使用一个“基本”模型对固定功能光照模型提供了简化版本。这个基本模型的数学描述为高级公式为:
surfaceColor = emissive + ambient + diffuse + specular
从式子可以看出:物体表面的颜色是自发光(放射 emissive)、环境反射(ambient)、漫反射(diffuse)和镜面反射(specular)等光照作用的总和。每种光照作用取决于表面材质性质(例如亮度和材质颜色)和光源的性质(例如光的位置和颜色)。
下面对这个基本模型的各个部分进行讲解,最后我们使用CG语言写出该基本模型。
1.)自发光(emissive)
自发光光照作用独立于所有的光源。物体的自发光并不能照亮场景中的其他物体。换句话说,物体自发光不能照亮其他物体或者投下阴影。因此,一个放射性物体本身并不是一个光源。
另一个解释放射项的方法:它是一种在计算完其他所有光照项后添加的颜色。
自发光的数学公式:emissive = Ke
其中Ke代表材质的放射光颜色
2.)环境光(ambient)
环境光来自于四面八方,故环境放射光照项并不依赖于光源的位置。
1.环境放射项依赖:1.材质的反射能力 2.照射到材质上的环境光的颜色。
2.用于环境放射项的数学公式:
ambient = Ka * globalAmbient
其中ka是材质的环境反射系数,globalAmbient是入射环境光的颜色。
ka= tex2D(_MainTex, i.uv.xy).rgb
globalAmbient=UNITY_LIGHTMODEL_AMBIENT.xyz
3.)漫反射项(diffuse)
图片2.png兰伯特(Lambert )光照模型是目前最简单通用的模拟漫反射的光照模型,定义如下:模型表面的明亮度直接取决于光线向量(light vector)和表面法线(normal)两个向量夹角的余弦值。光线向量是指这个点到光从哪个方向射入,表面法线则定义了这个表面的朝向。
diffuse = kd * lightColor * max ( NL(点积) , 0 )
max ( NL(点积) , 0 )
规范化的向量N和L的点积是两个向量之间夹角的一个度量,夹角越小,P点受到更多的入射光照。而背向光源的表面将产生负数点积值,因此,公式*max ( NL(点积) , 0 )使得背向光源的表面的漫反射光为0,确保这些表面不会显示漫反射光照。
_WorldSpaceLightPos0这是一个表示世界做表中的光源的位置矢量或者方向矢量;
如果_WorldSpaceLightPos0.w为0,则表示该光源为平行光,_WorldSpaceLightPos0.xyz为此平行光的方向向量;
反之_WorldSpaceLightPos0.w不为0,则表示光源为点光源,_WorldSpaceLightPos0.xyz为此点光源.
半兰伯特光照模型
半兰伯特光照模型,解了决光照无法到达的区域, 背光面更亮一些
diffuse = kd * lightColor * (N * L *0.5 +0.5)
cos的值是-1到1之间,乘上0.5再加0.5后,值域是(0,1)
4.)镜面反射项(specular)
5366d0160924ab18b03b605431fae6cd7b890ba7.jpgPhong光照模型:
图片3.pngKs:材质的镜面反射颜色
facing的取值为0或1:当N* L大于0时为1,当N *L小于0时为0
R = reflect(-worldLightDir, worldNormal) ;
BlinnPhong光照模型
BlinnPhong光照模型混合和了Lambert的漫反射和标准的高光,渲染有时比Phong高光更柔和、更平滑,此外它的处理速度相当快,因此成为许多CG软件中默认的光照渲染方法。
specular = KslightColor pow(( dot(N,H), shininess )
其中H = (L + V) / | L+V |,实际就是normalize(L + V) , 计算H比计算反射向量R更快速。
4.)UnityCG.cginc常用内置函数
float3 WorldSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。
float3 UnityWorldSpaceViewDir(float4 v) 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向。
float3 ObjSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向。
float3 WorldSpaceLightDir(float4 v) 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。
float3 UnityWorldSpaceLightDir(float4 v) 仅可用于前向渲染中。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。
float3 ObjSpaceLightDir(float4 v) 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化。
float3 UnityObjectToWorldNormal(float3 norm) 把法线方向从模型空间转换到世界空间中。
float3 UnityObjectToWorldDir(flaot3 dir) 把方向矢量从模型空间变换到世界空间中。
float3 UnityWorldToObjectDir(float dir) 把方向矢量从世界空间变换到模型空间中。
二.法线贴图
逐像素计算光照时,我们每一个像素都会根据该点的法向量来计算最终该点的光照结果,那么,我们如果能够改变这个法线的方向,不是就可以改变这个点的光照结果了呢!那么,把纹理采样的思想用在这里,我们直接用一张图来存储法线(或者法线偏移值,见下文),逐像素计算时,在采样diffuse贴图的时候,再采样一张法线的贴图,就可以修改法线了,进而修改最终的效果。
什么是切线空间?
切线空间就是,基于模型上的一个顶点建立的坐标空间,它的X轴是这个顶点在模型中的切线分量,Z轴是改点在模型上的法线,Y轴就是这个顶点的副切线,因为坐标空间XYZ三个面都是互相垂直的嘛,所以这个副切线我们是可以求出来的(以为同样是与一个平面垂直的单位向量是有两方向的,在Unity中模型会提供一个副切线的方向的数据)。
来上一个图,让我们更好的理解这个所谓的切线空间。
我们的表面法线是单位向量, 位于范围 -1.0 到 1.0 之间. 而RGB的值域是(0,1) , 我们可以通过把法线范围转换为 0.0 到 1.0之间来把法线向量(x, y, z)存储到一个 RGB 纹理贴图中.
Color.rgb = Normal.xyz / 2.0 + 0.5;
现在我们可以分析为什么tangent space法线贴图是偏蓝色了.因为这个面渲染时计算机认为这个面的"弯曲"程度很小,即面上各个点插值得来的法线相互间偏差很小.基本跟整个面的垂直方向不会差太多.因此在tangent space里,这些法线都跟z轴偏差较小.而z轴是被保存在贴图里的b字节处(蓝色通道)里.所以贴图显示出来的颜色就偏蓝了.
http://blog.csdn.net/candycat1992/article/details/41605257
三.(渲染路径)Rendering Paths
1.Deferred Lighting,延迟光照路径。3者中最高质量地还原光照阴影。光照性能只与最终像素数目有关,光源数量再多都不会影响性能。
2.Forward Rendering,前向渲染路径。能发挥出Shader全部特性的渲染路径,当然也就支持像素级光照。最常用、功能最自由,性能与光源数目*受光照物体数目有关,具体性能视乎其具体使用到的Shader的复杂度。
3.Vertex Lit,顶点光照路径。顶点级光照。性能最高、兼容性最强、支持特性最少、品质最差。
1.)前向渲染
根据影响物体的光源的不同,正向渲染路径用单个或多个通道来渲染物体。在正向渲染中,光源本身也会根据他们的设置和强度受到不同的对待。
在正向渲染中,影响物体的最亮的几个光源使用逐像素光照模式。接下来,最多有4个点光源会以逐顶点渲染的方式被计算。其他光源将以球面调和(Spherical Harmonics)的方式进行计算,球面调和技术计算很快但只能得到近似值。根据以下的规则判断一个光源是否为逐像素光源:
渲染模式被设置为不重要(Not Important)的光源以逐顶点或球面调和的方式进行计算
最亮的方向光源为像素光源
渲染模式被设置重要(Important)的光源为像素光源
如根据以上规则得到的像素光源数量小于质量设置中的像素光源数量(Pixel Light Count),为了减少亮度,会有更多的光源以逐像素的方式进行渲染
Base Pass 基本通道
Base pass renders object with one per-pixel directional light and all SH/vertex lights. This pass also adds any lightmaps, ambient and emissive lighting from the shader. Directional light rendered in this pass can have Shadows. Note that Lightmapped objects do not get illumination from SH lights.
Note that when “OnlyDirectional” pass flag is used in the shader, then the forward base pass only renders main directional light, ambient/lightprobe and lightmaps (SH and vertex lights are not included into pass data).
需要在设置Pass的 Tags { "LightMode"="ForwardBase" }
定义宏#pragma multi_compile_fwdbase
Additional Passes附加通道
Additional passes are rendered for each additional per-pixel light that affect this object. Lights in these passes by default do not have shadows (so in result, Forward Rendering supports one directional light with shadows), unless multi_compile_fwdadd_fullshadows variant shortcut is used.
需要在设置Pass的 Tags { "LightMode"="ForwardAdd" }
定义宏#pragma multi_compile_fwdadd
https://docs.unity3d.com/Manual/RenderTech-ForwardRendering.html
四.投射阴影和接收阴影
1.)投射阴影
需要有平行光,且平行光开启阴影
物体的renderer组件需要开启Cast Shadow
物体的shader需要一个tags为”ShadowCaster”的pass来让物体加入阴影贴图的渲染内.可用FallBack "Specular"来让Unity自动查询可用的shader,注意透明物体需要特别指定.另外,如果有使用顶点动画,则需要自己定义可用的shader
2.)接收阴影
Unity中,对于衰减,主要是通过一张光照衰减纹理(_LightTexture0)的采样得到,其采样操作主要是在光照空间中进行。对于阴影,一般不会采用实时阴影的方式(性能耗费太高),但是现在随着技术的发展,越来越多的游戏采用的实时光照,当然这是对整体的光照的优化有一个极其高的情况下。一般情况下都是将场景烘培,对模型自身添加相应的光照设置。
在unity中,主要是通过一个shadowCaster的shader来实现对阴影的计算,对于物体是否投射阴影可以在其自身的组件上设置。而unity将阴影和衰减的计算合并在一起,主要通过三个基本的内部操作来实现:在a2v的结构体中设置SHADOW_COORDS(n),在vert shader中进行TRANSFER_SHADOW(o),最后在frag shader中进行 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos),此时得到的atten就是综合了衰减和阴影的结果,可以用其来作为衰减和阴影的因子与最终的光照颜色相乘。