unity实现基础光照模型
unity shader的结构
Shader "MyShader" { // 名字
Properties {
//所需的各种属性
}
SubShader { // 显卡A用的子着色器
[Tags] //可选的,告诉Unity的渲染引擎:希望怎样以及何时渲染这个对象
[RenderSetup] //可选的,状态设置
Pass { // 每个 Pass定义了一次完整的渲染流程
[Name]
[Tags]
[RenderSetup]
// other code
}
}
SubShader { // 显卡B用的子着色器
//和上一个SubShader类似
}
FallBack "Diffuse" // 如果上面的SubShader都不能在这块显卡上运行,就使用这个最低级的Shader
}
【Name】
通过在字符串中添加斜杠("/") 可以控制 nity Shader 在材质面板中出现的位置
【Properties】
这些属性将会出现在材质面板中
【SubShader】
SubShader { // 显卡A用的子着色器
[Tags] // /可选的,告诉Unity的渲染引擎:希望怎样以及何时渲染这个对象
[RenderSetup] //可选的,状态设置
Pass { // 每个 Pass定义了一次完整的渲染流程
}
}
1 SubShader标签Tags:
2 状态设置:设置显卡的各种状态
3 Pass语义块
Pass { // 每个 Pass定义了一次完整的渲染流程
[Name]
[Tags]
[RenderSetup]
// other code
}
Pass 中定义该 Pass 名称,通过这个名称,我们可以使用 ShaderLab UsePass 令来直接使用其他 Unity Shader 中的Pass,注意在使用 sePass 命令时必须使用大写形式的名字。
Pass 同样可以设置标签,是用千告诉渲染引擎我们希望怎样来渲染该物体
4、Fallback
自动返回一个通用的最低级的Pass
unity shader的形式
1、顶点/片元着色器
定义在Pass 语义块内的 CGPROGRAM ENDCG 之间
需要自已定义每个 Pass 需要使用的 Shader 代码,更复杂但灵活性高
2、表面着色器
是 Unity 对顶点/片元着色器的更高一层的抽象,Unity 为我们处理了很多光照细节, 在背后仍旧把它转换成对应的顶点/片元着色器
表面着色器被定义在 SubShader 语义块内的 CGPROGRAM ENDCG 之间,不需要开发者关心使用多少个 Pass 、每个Pass 如何渲染等问题,
3、固定着色器
用在一些旧的设备上 其 GPU 仅支持 DirectX 7.0 OpenGL 1.5 OpenGL ES 1. 1), 它们不支持可编程管线着色器,这些着色器往往只可以完 一些非常简单效果
总结
【固定函数着色器】需要在非常旧的设备上运行
【表面着色器】 有各种光源打,但需要小心在移动平台上的性能表现。
【顶点/片元着色器】需要使用的光照数目非常少(例如只有个平行光);或者需要很多自定义的渲染效果
基础光照模型
顶点着色模型
在VS计算颜色,然后直接传到FS输出即可
Shader "Custom/VertexLevel"
{
Properties
{
_Diffuse("Diffuse", Color) = (1,1,1,1) // 得到并且控制材质的漫反射颜色
// _Diffuse("Diffuse", Color) = (0, 0, 0, 0) // 得到并且控制材质的漫反射颜色
_Specular("Specular", Color) = (1, 1, 1, 1) // 材质的高光颜色
_Gloss("Gloss", Range(8.0, 256)) = 20 // 高光范围
}
SubShader{
Pass {
Tags { "LightMode" = "ForwardBase" } // 该Pass的光照模式,只有定义了正确的 LightMode,才能得到一些 Unity 的内置光照变蜇
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc" // 使用 Unity 内置变量
fixed4 _Diffuse; // 要加Properties 语义块中声明的属性
fixed4 _Specular; // 颜色属性的范围在0~1,用fixed
float _Gloss; // 范围很大,用float
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR; // 为了VS中计算得到的光照颜色传递给FS
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 顶点位置转到clip
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject)); // unity_WorldToObject为模型到世界的逆矩阵,调换Mul中位置得到转置,法线是3维的,取前三行前三列
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); // 光源方向由_WorldSpaceLightPos0得到
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal)); // 注意是入射方向
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz); // 世界空间中的摄像机位置 - 世界空间中的顶点位置
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; // 内置变量得到环境光
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir)); // saturate限制在 [0,1];点积时向量要出在同一坐标系下,这里选择世界坐标系,记得归一化
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);
}
ENDCG
}
}
FallBack "Diffuse"
}
phong模型
【理论】
漫反射:
参数:入射光线的颜色和强度+材质的漫反射系数+表面法线+光源方向
HalfLambert 半兰伯特漫反射:max(0, nl)改成了 nl*0.5 + 0.5, 背面也有明暗变化
高光反射:
参数:入射光线的颜色和强度+材质高光反射颜色+观察方向+反射方向+材质反光度(反光区域大小)
【源代码】各项同性,逐像素
Shader "Custom/Phong"
{
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" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _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 = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
【具体解释】
Shader "Custom/C6_PixelLevel"
{
Properties
{
// 得到并且控制材质的漫反射颜色
_Diffuse("Diffuse", Color) = (1,1,1,1)
// 材质的高光颜色
_Specular("Specular", Color) = (1, 1, 1, 1)
// 高光范围
_Gloss("Gloss", Range(8.0, 256)) = 20
}
SubShader{
Pass {
// 有定义了正确的 LightMode,我们才能得到 Unity 的内置光照变量
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
// 表明该函数包含了顶点着色器的代码,一般用vert
#pragma vertex vert
// 该函数包含了片元着色器的代码, 一般用frag
#pragma fragment frag
// 使用 Unity 内置的一些变量
#include "Lighting.cginc"
//在CG代码中, 定义一个与属性名称和类型都匹配的变量,也就是说Properties有啥这里也要有啥
fixed4 _Diffuse;
// 颜色属性的范围在0~1,用fixed
fixed4 _Specular;
// 范围很大,用float
float _Gloss;
//使用一个结构体来定义项点着色器的输入
struct a2v {
// POSITION 语义告诉Unity,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
// NORMAL语义告诉Unity,用模型空间的法线方向填充normal变量
float3 normal : NORMAL;
};
//使用一个结构体来定义项点着色器的输出
struct v2f {
// SV_POSITION语义告诉Unity, pos里包含了顶点在裁剪空间中的位置信息
float4 pos : SV_POSITION;
// TEXCOORDO语义告诉Unity,用模型的第一套纹理坐标填充texcoord变量
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
// 声明输出结构
v2f o;
// 顶点位置从模型空间变换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
// 将表面法线从模型空间变换到世界空间
// 用顶点变换矩阵的逆转置矩阵:WorldToObject逆,放在mul右侧代表转置,因为法线是三维矢量,截取前三行前三列
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// 顶点位置从模型空间变换到世界空间,用于FS计算光照
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target{
// SV_Target 也是 HLSL 中的一个系统语义,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标(render target) 中,这里将输出到默认的帧缓存中
// 法线方向,世界坐标下
fixed3 worldNormal = normalize(i.worldNormal);
// 光源方向,世界坐标下
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 反射方向,世界坐标下:reflect(i,n),注意入射方向
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 观察方向:= 世界坐标下的相机位置-世界坐标下的物体位置
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// 环境光:利用内置变量得到
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 漫反射:saturate(x):把x截取在[0,1]范围内
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// 高光
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Blinn-Phong模型
【理论】
这两种光照模型都是经验模型
【代码】
Shader "Custom/C6_Blinn-Phong"
{
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" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular; // 颜色属性的范围在0~1,用fixed
float _Gloss; // 范围很大,用float
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 = UnityObjectToClipPos(v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz); // 在世界坐标下计算
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal, worldLightDir)*0.5 + 0.5); // HalfLambert 半兰伯特漫反射,背面也有明暗变化
// fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
效果
Phong和Blinn-Phong 对比:
半兰伯特漫反射和Blinn-Phong对比: