Shader入门精要项目链接:
我们是如何看到这个世界的
光源(太阳)发射光线,光线和物体相交,一部分光线会被吸收、一部分光线会散射到其他方向,最终摄像机吸收了一些光,产生了一张图像。
光源
在光学里,使用辐照度来量化光。对于一个空间仅有平行光来说,辐照度等于平行光垂直截面一个单位面积内穿过的能量。
在计算光照模型时,我们需要计算一个物体表面的辐照度,而物体表面往往是不与光线垂直的,可使用光线方向和表面法线之间的夹角的余弦值cos求得。
解释一下,为什么求法线和光线方向之间的夹角余弦值能得到表面的辐照度?(推荐看看)
(个人观念)表面的辐照度:表面单位面积上接收到的光线能量或可理解为光线数目;光线数目越多代表辐照度越高,表面越亮,反之辐照度越低,表面越暗。
上面两幅图(用Window8.1画图工具随便画的)左图是光线垂直于表面情况,右图是光线倾斜于表面情况。
其中,黑色箭头是光线,蓝色箭头是法线,θ是法线和光线的夹角,d是光线之间的距离,distance是表面接收光线点之间的距离;注意:distance不是光线之间的距离!这里要好好理解。
重点部分全在右图,左图画出来对比理解。
cosθ = 邻边 / 斜边
求distance,右图画出了辅助线和辅助角θ(红色部分),其中邻边长度(红色线)是d,斜边长度是distance;
那么distance = d / cosθ, 因为 cosθ = d / distance, 则 d / cosθ = d / (d / distance) = distance
为什么求distance? 因为我们可以用distance(表面接收光线点之间的距离) 来知道表面的辐照度。
当distance越小时(光线点之间距离越小),表明表面单位面积上接收到的光线数目越多,表面辐照度越大;
当distance越大时(光线点之间距离越大),表明表面单位面积上接收到的光线数目越少,表面辐照度越小。
因此,法线和光线之间的夹角的余弦值cosθ 和 表面辐照度成正比例关系。
(可能有些人反应不过来为什么正比,我再次解释)
distance = d / cosθ 已知, d可理解为常数, cosθ越大,distance越小,表面辐照度越大;反之亦然。
所以是正比关系!而这个正比关系就是我们要求得的一个很重要的线索!我们可以用这个法线和光线之间夹角的余弦值来进行模拟真实世界的光照,因为我们很难去获得真正的表面辐照度 ,可能因为光线还有复杂的强度变换等因素。
理解了这里的辐照度和法线与光线之间夹角余弦值的关系,将会有助于你理解光照模型的漫反射或其他反射。
着色指的是根据材质属性、光源信息(光源方向、辐照度),使用一个等式去计算沿某个观察方向的出射度的过程。这个等式就称之为光照模型。
其中,标准光照模型只关心直接光照,即直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。
- 自发光(emissive)部分,自发光是物体本身发射的光,使用全局光照技术后自发光才会影响到周围物体,否则只是本身更亮。
- 高光反射(specular)部分,描述当光线从光源照射到表面时,该表面会在完全镜面反射方向散射多少辐射量。
- 漫反射(diffuse):当光线从光源照射到模型表面时,该表面会向每个方向散射多少辐射量。
- 环境光:描述所有的间接光照。
其中漫反射符合兰伯特定律:反射光线的强度与表面法线和光源方向之间的夹角的余弦值成正比。
- 漫反射公式:c(diffuse) = (c(light) * m(diffuse)) * max(0, n · l)
c(diffuse)是漫反射颜色rbg,c(light)是光线颜色rgb, m(diffuse)是漫反射材质颜色rgb, n是归一化的法线xyz,l是归一化的光线方向xyz
- 半兰伯特的漫反射公式:c(diffuse) =(c(light) * m(diffuse)) * ((n · l) * 0.5 + 0.5)
- Phong模型 高光反射公式:c(specular) = (c(light) * m(specular)) * max(0, v · r)^m(gloss)
其中, c(specular)是高光反射颜色, m(specular)是高光反射材质颜色rgb, v是表面上的点到摄像机的向量(归一化),r是(以法线为平分线)的反射光向量(归一化),m(gloss)是光泽度。
- Blinn模型 高光反射公式:c(specular) = (c(light) * m(specular)) * max(0, n · h)^m(gloss)
h 是对v和l取平均后再归一化得到的,即 h = normalize(v + l);
漫反射详解
根据漫反射公式:我们需要知道光源颜色、漫反射材质颜色、法线向量、光源向量就能求得漫反射颜色。
为此我们需要在Pass{}中引入如下代码:
Shader "Shader"{ ... SubShader{ Pass{ CGPROGRAM Tags{ "LightModel" = "ForwardBase" } #include "Lighting.cginc" ... ENDCG } } ... }
只有在上面引入了这个标签和这个头文件,下面我说的才会生效。
_LightColor0是光源颜色(rbga),漫反射材质颜色是自定义的就是一个Color,法线向量从顶点着色器的输入结构体传入(由应用阶段自动传入,用NORMAL语义),光源向量是_WorldSpaceLightPos0.xyz;
由于漫反射公式使用到了点积,两个向量必须要在同一个空间,点积才有意义!(全部都转成世界空间)
因此要将模型空间的法线转世界空间的法线:mul(法线, (float3x3)_World2Object)),在Shader入门精要里有介绍,法线的转换是需要和其逆转置矩阵进行右乘才得到的,mul(矩阵,向量)是右乘,模型到世界的逆矩阵是_World2Object(由UnityShaderVariables.cginc提供)世界转模型矩阵,然后mul(向量, 逆矩阵)的结果就是 mul(逆转置矩阵, 向量)因此来得到世界空间下的法线!
但是,你会觉得很繁琐,要记忆太多东西,我们可以引入 "UnityCG.cginc" ,然后使用它里面的
float3 worldNormal = normalize(UnityObjectToWorldNormal(normal.xyz))来进行将法线从模型空间转到世界空间下。
世界空间下的 光源方向:float3 worldLightDir = _WorldSpaceLightPos0.xyz - float3(0,0,0) (后面的 - float3(0,0,0)可省略)
这个世界光源方向其实也有相应的方法直接获取:
UnityWorldSpaceLightDir(float4 v): v是世界空间下的坐标;即
float3 worldPos = UnityObjectToWorldDir(pos); //pos是模型空间下的顶点位置
float3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
那么漫反射公式变成:
兰伯特漫反射颜色 = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir))
半兰伯特漫反射颜色=_LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal, worldLightDir) * 0.5 + 0.5)
高光反射详解
根据Phong模型高光反射公式,我们需要知道光源颜色、高光反射材质颜色、视角向量、反射向量(一样是世界空间下且归一化)
worldViewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz) = normalize(UnityWorldSpaceViewDir(worldPos))
worldReflectDir = normalize(reflect(-worldLightDir, worldNormal) )
其中worldViewDir是世界视角向量,worldReflectDir是世界反射向量,其中reflect函数参数如下:(必须保持在同一空间下!且归一化)
参数1:入射方向(光源反向是朝着光源的,入射方向是反过来)
参数2::法线方向
可见,Phong模型为了求得反射向量,需要法线向量和光源向量,为了求得视角向量需要世界顶点位置。
Phong模型的高光反射颜色如下:
_LightColor0.rgb * _Specular.rgb * pow( saturate( dot(worldViewDir, worldReflectDir) ), gloss)
Blinn模型的高光反射,需要法线向量、v和l的平均向量(都要归一化和世界空间下)
其中,用世界顶点位置求出v(视角向量)和l(光源向量),然后根据normalize(v+l)求得h(v和l的平均向量),因此
Blinn模型的高光反射颜色如下:
_LightColor0.rgb * _Specular.rgb * pow( saturate(dot( worldNormal , h )), gloss)
总结:两种模型的高光反射都必须要在顶点着色器输入结构体中获取顶点位置、法线,以及材质颜色。
UnityCG.cginc头文件中的部分帮助函数
float3 WorldSpaceViewDir(float4 objectPos) | 输入模型空间下的顶点位置,返回世界空间下的视角方向(观察方向) 视角方向指从该顶点到摄像机位置的向量。(下同) | 常用于高光反射 |
float3 UnityWorldSpaceViewDir(float4 worldPos) | 输入世界空间下的顶点位置,返回世界空间下的视角方向(观察方向) | 常用于高光反射 |
float3 ObjectSpaceViewDir(float4 objectPos) | 输入模型空间下的顶点位置,返回模型空间下的视角方向(观察方向) | |
float3 WorldSpaceLightDir(float4 objectPos) | (仅可用于前向渲染中) 即 Pass的Tags{"LightModel" = "ForwardBase"} 输入模型空间下的顶点坐标,返回世界空间下的光源方向(从该点到光源的向量) | 常用于漫反射、高光反射 |
float3 UnityWorldSpaceLightDir(float4 worldPos) | (仅可用于前向渲染中)输入世界空间下的顶点坐标,返回世界空间下的光源方向 | 常用于漫反射、高光反射 |
float3 ObjectSpaceLightDir(float4 objectPos) | (仅可用于前向渲染中)输入模型空间下的顶点坐标,返回模型空间下的光源方向 | |
float3 UnityObjectToWorldNormal(float3 objectNormal) | 输入模型空间下的法线,返回世界空间下的法线 | 常用于漫反射、高光反射 |
float3 UnityObjectToWorldDir(float3 objectDir) | 输入一个模型空间下的三维矢量,返回世界空间下的三维矢量 | |
float3 UnityWorldToObjectDir(float3 worldDir) | 输入一个世界空间下的三维矢量,返回模型空间下的三维矢量 |
Lighting.cginc 头文件中上例介绍使用过的内置变量
_LightColor0 | 光源颜色 |
_WorldSpaceLightPos0 | 光源位置 |
_WorldSpaceCameraPos | 摄像机位置 |
UnitCG.cginc文件位置:
PC:/Unity安装目录/Data/CGIncludes/
Mac上:/Applications/Unity/Unity.app/Contents/CGIncludes:
如果还是找不到就直接全搜索这个文件: UnityCG.cginc