一、环境光和自发光
控制环境光:
Window->Lighting->Ambient Source/Ambient Color/Ambient Intensity
在Shader中,可以通过UNITY_LIGHTMODEL_AMBIENT得到环境光信息。
自发光:
大多数物体没有自发光特性,Shader中很少计算。
如果非要计算,只需在片元着色器输出最后的颜色之前,把材质的自发光添加到输出颜色上即可。
二、实现漫反射光照模型
函数:saturate(x) ------ x可以是标量或矢量,float、float2、float3。
作用:把x截取在[0,1]范围内,如果x是一个矢量,那么他会对每一个分量进行操作。
函数:reflect(i,n) ----- i是入射方向(光源指向交点,注意Unity中提供的worldLightDir是反向的,需要取负数),n是法线方向。返回反射方向
作用:求入射方向的反射方向。
1.实现逐顶点的漫反射效果:
代码:
Shader "Unity Shaders Book /Chapter 6/Diffuse Vertex-Level" //名字
{
Properties { _Diffuse("Diffuse",Color)=(1,1,1,1) } //设置材质属性-漫反射颜色
SubShader
{
Pass{
Tags{ "LightMode"="ForwardBase" } //Pass标签,设置正确的LightMode,才可以得到Unity的内置光照变量,例如(下文_LightColor0)
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //引入Unity内置文件,才可以使用内置变量(如_LightColor0)
fixed4 _Diffuse; //引用属性中的变量
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
flxed3 color : COLOR;
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul( UNITY_MATRIX_MVP, v.vertex ); //模型空间转换到裁剪空间
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; //获取环境光部分
fixed3 worldNormal = normalize( mul(v.normal,(float3×3)_World2Object) ); //获取法线:将模型空间的法线转换到世界空间中,并变换为单位矢量
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.0); //片元着色器直接返回顶点颜色
}
ENDCG
}
}
Fallback "Diffuse"
}
2.实现逐像素的漫反射效果
逐像素光照可以获得更平滑的光照效果。
Shader "Unity Shaders Book /Chapter 6/Diffuse Pixel-Level"
{
Properties { _Diffuse("Diffuse",Color)=(1,1,1,1) } //设置材质属性-漫反射颜色
SubShader
{
Pass
{
Tags{ "LightMode"="ForwardBase" } //Pass标签,设置正确的LightMode,才可以得到Unity的内置光照变量,例如(下文_LightColor0)
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" //引入Unity内置文件,才可以使用内置变量(如_LightColor0)
fixed4 _Diffuse; //引用属性中的变量
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0; //这里对比逐顶点 是将颜色传递改为纹理坐标传递(传递法线)。
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal = mul(v.normal,(float3×3)_World2Object); //法线还是要在顶点着色器中获取,并转换到世界坐标。
return o;
}
fixed4 frag(v2f i):SV_Target
{
fixed3 ambient = UNTIY_LIGHTMODEL_AMBIENT.xyz; //环境光
fixed3 worldNormal = normalize(i.worldNormal); //将法线转换为单位矢量
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); //将光源方向单位矢量
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir)); //计算漫反射。
fixed3 color = diffuse+ambient;
return fixed4(color,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
3.半兰伯特光照模型实现
我们前面使用漫反射光照模型也被称为兰伯特模型,
兰伯特定律:在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。
而 半兰伯特模型可以改进以上两种反射时背光面明暗一样的缺点。
半兰伯特光照模型是Valve公司开发《半条命》时提出的一种技术,无物理依据,只是进行视觉增强。
光照模型公式: c(diffuse)=(c(light)·m(diffuse))(a(n·l)+b),一般情况下a、b都取值0.5。
将逐像素漫反射光照模型的片元着色器更改。
fixed4 frag(v2f i):SV_Target
{
fixed3 ambient = UNTIY_LIGHTMODEL_AMBIENT.xyz; //环境光
fixed3 worldNormal = normalize(i.worldNormal); //将法线转换为单位矢量
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); //将光源方向单位矢量
fixed3 halfLambert = dot(worldNormal,worldLightDir)*0.5+0.5; //计算halfLambert,其实就是将(n·l)结果从[-1,1]映射到[0,1]。
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*halfLambert; //计算半兰伯特光照模型光照效果。
fixed3 color = diffuse+ambient;
return fixed4(color,1.0);
}
4.实现逐顶点光照的高光反射模型
代码:
Shader "Untiy Shaders Book/Chapter 6/Specular Vertex-Level"
{
Properties
{
_Diffuse ("Diffuse",Color)=(1,1,1,1) //漫反射属性-颜色
_Specular ("Specular",Color)=(1,1,1,1) //控制高光反射属性
_Gloss ("Gloss",Range(8.0,256))=20 //控制高光区域大小
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}//Pass标签,设置正确的LightMode,才可以得到Unity的内置光照变量,例如(下文_LightColor0)
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"//引入Unity内置文件,才可以使用内置变量(如_LightColor0)
fixed4 _Diffuse;
fixed4 _Specular;
fixed4 _Gloss;//以上三个属性引入
struct a2v
{
float4 vertex: POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);//将模型空间转换到裁剪空间
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//获取环境光
fixed3 worldNormal = normalize(mul(v.normal,(float3×float3)_World2Object));//获取法线,并转换为世界坐标
fixed3 worldLightDir = normallize(_WorldSpaceLightPos0.xyz);//获取入射光线放下给
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir));//计算漫反射
fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));//计算反射光方向,进行取反
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-mul(_Object2World,v.vertex).xyz);获取视角方向
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(reflectDir,viewDir)),_Gloss);//计算高光反射部分
o.color=ambient+diffuse+specular;//进行加和
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return fixed4(i.color,1.0);
}
}
}
Fallback "Specular"
}
逐顶点方法得到的高光反射效果有比较大的问题,不平滑。
因为高光反射计算是非线性的,而在顶点着色器中计算是线性的,就会有比较大的视觉问题。
因此我们需要使用逐像素的方法计算高光反射。
5.实现逐像素的高光反射模型
代码:
Shader "Untiy Shaders Book/Chapter 6/Specular Pixel-Level"
{
Properties
{
_Diffuse ("Diffuse",Color)=(1,1,1,1) //漫反射属性-颜色
_Specular ("Specular",Color)=(1,1,1,1) //控制高光反射属性
_Gloss ("Gloss",Range(8.0,256))=20 //控制高光区域大小
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}//Pass标签,设置正确的LightMode,才可以得到Unity的内置光照变量,例如(下文_LightColor0)
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"//引入Unity内置文件,才可以使用内置变量(如_LightColor0)
fixed4 _Diffuse;
fixed4 _Specular;
fixed4 _Gloss;//以上三个属性引入
struct a2v
{
float4 vertex: POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);//将模型空间转换到裁剪空间
o.worldNormal = mul(v.normal,(float3×3)_World2Object);//将法线传递到片元
o.worldPos = mul(_Object2World,v.vertex).xyz;//将顶点坐标传递到片元
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//获取环境光
fixed3 worldNormal = normalize(i.worldNormal);//获取法线,单位矢量化
fixed3 worldLightDir = normallize(_WorldSpaceLightPos0.xyz);//获取入射光线,单位矢量化
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir));//计算漫反射
fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));//计算反射光方向,进行取反
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);获取视角方向
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(reflectDir,viewDir)),_Gloss);//计算高光反射部分
return fixed4(ambiet+diffuse+specular,1.0);计算和
}
}
}
Fallback "Specular"
}
按照以上这种逐像素方式可以得到更加平滑的效果,这就是实现的完整的Phong光照模型。
6.实现Blinn-Phong模型
将片元着色器中代码更改:
fixed4 frag(v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;//获取环境光
fixed3 worldNormal = normalize(i.worldNormal);//获取法线,单位矢量化
fixed3 worldLightDir = normallize(_WorldSpaceLightPos0.xyz);//获取入射光线,单位矢量化
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir));//计算漫反射
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);获取视角方向
fixed3 halfDir = normalize(worldLightDir+viewDir);//计算Blinn-Phong中的h。
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Gloss);//计算高光反射部分(Blinn-Phong)
return fixed4(ambiet+diffuse+specular,1.0);计算和
}
Blinn-Phong光照模型的高光反射部分看起来更大,更亮。
在很多实际渲染中,我们都会选择Blinn-Phong模型。
这两种光照模型都是经验模型,Blinn-Phong不是对Phong模型的近似,因为有时Blinn-Phong更符合结果。
三、使用Unity中的内置函数
以上计算光源方向、视角方向等等方式,仅仅适用于在平行光内。
而当我们计算点光源、聚光灯等等,此时上述方法就不适用。
注意:以上函数返回值不是单位矢量,使用前记得变成单位矢量。
计算光源方向需要注意 前向渲染时才可使用。前向渲染中,其内部内置变量_WorldSpaceLightPos()等才会被正确赋值。
前向渲染是什么,第9章。写到第9章给 ———链接。