1.前言
所谓的光照效果,反映到屏幕上就是一个个像素问题,所以光照的计算公式就是计算的一个一个颜色值。这些公式基本属于经验公式范畴,只是让人看起来像真正的光效。
光分为环境光、自发光、漫反射以及高光反射,由于反映到像素上就是颜色的叠加。即最终在片元着色器中返回的颜色值=环境光颜色+自发光颜色+漫反射颜色+高光反射颜色。下面依次对这些光进行说明。
2.环境光Ambient
环境光在Unity中可以设置,即windows-lighting中即可看到,在2018中则是window-rendering-lightingsettings中设置。在shader中通过如下变量可以获取到:UNITY_LIGHTMODEL_AMBIENT。但是一般计算中我们只需要颜色的rgb值。
3.自发光Emissive
自发光是最简单的,直接简单粗暴的暴露一个颜色值,包此值当作自发光颜色。
4.漫反射diffuse
漫反射计算参考一下光照模型进行计算。
4.1 兰伯特定律LambertLaw
兰伯特定律认为反射光线跟光源方向与法线夹角有关系(夹角余弦值),所以漫反射计算公式如下:
漫反射颜色 = 光源颜色 x 材质的漫反射颜色 x Max(0,Dot(法线,指向光源的方向)
其中:
光源颜色:变量_LightColor0代表光源颜色。
材质漫反射颜色:自定义的颜色值。
法线:可以获取到每个顶点的法线,计算式采用单位向量。
光源方向:指从当前位置到光源的方向,计算式采用单位向量。
由于实际中当法线与光源方向夹角大于90°时,已经不会有反射效果,此时两者余弦值为负值,所以此时应该取0。
4.1.1 逐顶点计算
逐顶点计算漫反射时,在顶点着色器中计算,此时计算量小,但是效果差。在计算世界坐标系下的法线时,采用如下所示:
//获取法线(统一到世界坐标系下),不做归一化效果要优于归一化。
//fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
上述注释掉的方法也是UnityObjectToWorldNormal方法,当然UnityCG种方法比注释掉的方法复杂一点。完整代码如下:
Shader "LL/Light/DiffuseVertex_Lambert"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
float3 color : COLOR;
};
fixed4 _Diffuse;
v2f vert (a2v v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取法线(统一到世界坐标系下)。
//fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
//获取环境光方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//总强度叠加效果
o.color = ambient + diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
注:在计算光源方向时,应该是光源方向应该是光源位置减顶点位置,但是在真正计算时考虑不同情况,计算方式不同,UnityCG中也存在计算方法,直接用光源位置是一种情况,本文是示例,所以只采用最简单的方法。
4.1.2 逐片元计算
逐片元计算方法与顶点计算方法一致,只是在片元着色器中进行颜色计算,在顶点着色器中将法线传递过来即可,如下所示:
Shader "LL/Light/DiffuseFragment_Lambert"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
//float3 color : COLOR;
float3 worldNormal : NORMAL;
};
fixed4 _Diffuse;
v2f vert (a2v v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
//获取法线(统一到世界坐标系下)
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
//获取环境光方向
//fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射强度
//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//总强度叠加效果
//o.color = ambient + diffuse;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取归一化的法线
fixed3 worldNormal = normalize(i.worldNormal);
//fixed3 worldNormal = i.worldNormal;
//获取环境光的方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算光照强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
//叠加
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
4.2 半兰伯特光照
半兰伯特光照其实还是根据兰伯特定律进行的另外一种光照经验公式。
漫反射颜色 = 光源颜色 x 材质的漫反射颜色 x( A*Dot(法线,指向光源的方向) + B)
其他参数见4.1,A和B为两个参数值,当其均为0.5时,则相当于将法线和光源方向的点积结果映射到0-1范围内。
4.2.1 半兰伯特光照计算
此方法只给出在片元着色器中的计算,跟4.1差别在于计算漫反射颜色方式有差别。
Shader "LL/Light/DiffuseFragment_HalfLambert"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
float3 worldNormal : NORMAL;
};
fixed4 _Diffuse;
v2f vert (a2v v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
//获取法线(统一到世界坐标系下)
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取归一化的法线
fixed3 worldNormal = normalize(i.worldNormal);
//fixed3 worldNormal = i.worldNormal;
//获取环境光的方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算半lambert值
fixed halfLambert = dot(worldNormal, worldLight) * 0.5 + 0.5;
//计算光照强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
//叠加
fixed3 color = ambient + diffuse;
return fixed4(color,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
5.高光反射Specular
高光反射本质上还是计算一个颜色值,其计算公式如下所示:
高光反射颜色 = 入射光颜色 X 高光反射颜色 X max(0,Dot(视角方向,反射方向))^反射系数
其中:
视角方向可以通过摄像机与当前顶点位置计算,如下所示:
_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz
反射方向通过入射光方向与法线方向即可计算,如下所示:
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
//获取环境光方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
5.1 逐顶点计算
Shader "LL/Light/SpecularVertex"
{
Properties
{
_Diffuse ("Diffuse", Color) = (1,1,1,1)
_Specular ("Specular", Color) = (1,1,1,1)
_Gloss ("Gloss",Range(8.0,256)) = 20
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
float3 color : COLOR;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (a2v v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取法线(统一到世界坐标系下),不做归一化效果要优于归一化。
fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
//获取环境光方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//计算反射方向
fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
//获取视线方向
fixed3 view = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
//计算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
//总强度叠加效果
o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4(i.color,1);
}
ENDCG
}
}
Fallback "Specular"
}
5.2 逐像素计算
Shader "LL/Light/SpecularFragment"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1)
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 position : SV_POSITION;
float3 normal : NORMAL;
float3 worldPos : TEXCOORD0;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert(a2v v)
{
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.normal = mul(v.normal,unity_WorldToObject);
//获取环境光
//fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取法线(统一到世界坐标系下)
//fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
//fixed3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
//fixed3 worldNormal = mul((float3x3)unity_ObjectToWorld, v.normal);
//获取环境光方向
//fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射强度
//fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//计算反射方向
//fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
//获取视线方向
//fixed3 view = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
//计算高光反射
//fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
//总强度叠加效果
//o.color = ambient + diffuse + specular;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取法线(统一到世界坐标系下)
fixed3 worldNormal = normalize(i.normal);
//获取环境光方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射强度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));
//计算反射方向
fixed3 reflectDir = normalize(reflect(-worldLight,worldNormal));
//获取视线方向
fixed3 view = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
//计算高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,view)),_Gloss);
return fixed4(ambient + diffuse + specular,1);
}
ENDCG
}
}
Fallback "Specular"
}
5.3 Blinn-Phong光照模型
高光反射颜色值还可以采用如下方法计算,其计算公式如下所示:
高光反射颜色 = 入射光颜色 X 高光反射颜色 X max(0,Dot(法向量,视角光照向量))^反射系数
其中:
视角光照向量是视角方向与光照方向相加归一后的法向量。
//获取法线(统一到世界坐标系下)
fixed3 worldNormal = normalize(i.normal);
//获取环境光方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//获取视线方向
fixed3 view = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
fixed halfDir = normalize(worldLight + view);
//计算BlinnPhong高光反射
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal, halfDir)), _Gloss);
6.结语
以上为基本的光照模型,且计算均为单一情况测试,考虑不充分,只是用来学习。