Unity-Shader 01 光照模型
资源与声明:1-本文章图片来源 UCSB 闫令琪 老师《GAMES101 现代计算机图形学入门》
https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html
2-本文章代码来源 冯乐乐老师 《Unity Shader入门精要》
https://github.com/candycat1992/Unity_Shaders_Book/tree/unity_2017_1
<hr/>1-着色方式 Shading
1-1 平坦着色 Flat shading
逐顶点计算颜色,用三角形中心点的颜色代表整个三角形面的颜色
1-2 逐顶点着色Gouraud shading
逐顶点计算颜色,然后中间的就插值渐变过度一下;
1-3逐像素着色 Phong(冯) Shading
就是在屏幕的裁剪空间的没一点都去计算漫反射和高光颜色;
三种不同渲染方式的对比
2-漫反射光照模型实现
2-1漫反射光照模型
代码里是这样的
// 用漫反射光照公式计算
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
能通过正比算出传递到这个点的能量,又根据夹角 算出接受了多少能量
后面的max(0,n·1)就是防止点乘=负数的情况(背面传来的)
Ld= diffusely reflected light表示的是该点反射的光(包括光强+颜色)
kd = diffuse coefficient color 表示漫反射系数(包含反射率和颜色)
I/r = energy arrived 入射光强;用光源板强度和距离定义
max(0,n·1)max是防止点乘=负数的情况(背面传来的)入射光和发现的点乘是计算cos值来判断接受面吸收了多少光;
求的是接受面的单位面积能收到的能量?
比如说 夏季 冬季,就是地球单位面积,接受到的光照就少
2-2 逐顶点光照
// Upgrade NOTE: replaced &#39;_World2Object&#39; with &#39;unity_WorldToObject&#39;
// Upgrade NOTE: replaced &#39;mul(UNITY_MATRIX_MVP,*)&#39; with &#39;UnityObjectToClipPos(*)&#39;
// 这个是逐顶点光照
Shader &#34;Unity Shaders Book/Chapter 6/Diffuse Vertex-Level&#34; {
Properties {
_Diffuse (&#34;Diffuse&#34;, Color) = (1, 1, 1, 1)// 这是漫反射的颜色 1111 表示纯白 RGB+透明度
}
SubShader {
Pass {
Tags { &#34;LightMode&#34;=&#34;ForwardBase&#34; }
CGPROGRAM
#pragma vertex vert //定义顶点着色器的名称
#pragma fragment frag//定义片元着色器的名称
#include &#34;Lighting.cginc&#34; //要内置一个Unity Shader的光照库;
fixed4 _Diffuse; //这里要声明一下;以使用Properties中定义的变量 fixed 指的是数据类型
//Part1 Application to Vertex 结构体用于顶点着色器的输入
struct a2v {
float4 vertex : POSITION;//a2v 语义 表示顶点位置
float3 normal : NORMAL; //a2v 语义 表示顶点法线
};
//Part2 Vertex to Fragment 结构体,用于顶点着色器的输出,和片元着色器的输入
struct v2f {
float4 pos : SV_POSITION;//v2f 语义 表示裁剪空间的顶点坐标(projection space)
fixed3 color : COLOR;//v2f 语义 表示顶点颜色
};
//Part3 顶点着色器 输入顶点位置+顶点法线, 返回顶点坐标+颜色
v2f vert(a2v v) {
v2f o;//定义了v2f 变量o
// 模型空间顶点 做个 Projection 变到裁剪空间里 一个Unity函数搞掂
o.pos = UnityObjectToClipPos(v.vertex);
// 从Unity项目里得到当前的环境光信息
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 把法线坐标转换到世界坐标
fixed3 worldNormal = normalize(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 {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack &#34;Diffuse&#34; //如果subshader无法使用 就用默认的光照模式;
}
左 :逐顶点漫反射中:逐像素漫反射 右:半兰伯特模型
2-3逐像素光照
// Upgrade NOTE: replaced &#39;_World2Object&#39; with &#39;unity_WorldToObject&#39;
// Upgrade NOTE: replaced &#39;mul(UNITY_MATRIX_MVP,*)&#39; with &#39;UnityObjectToClipPos(*)&#39;
Shader &#34;Unity Shaders Book/Chapter 6/Diffuse Pixel-Level&#34; {
Properties {
_Diffuse (&#34;Diffuse&#34;, Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { &#34;LightMode&#34;=&#34;ForwardBase&#34; }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
fixed4 _Diffuse;
// 结构体a2v
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
//结构体v2f
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0; //返回的是世界坐标下的法线
};
//顶点着色器
v2f vert(a2v v) {
v2f o;
// 从三维空间到Project空间
o.pos = UnityObjectToClipPos(v.vertex);
// 顶点法线到世界坐标下的法线
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
// 片元着色器 直接返回的就是Diffuse的颜色
fixed4 frag(v2f i) : SV_Target {
// 获取全局光
fixed3 ambient = UNITY_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 = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack &#34;Diffuse&#34;
}
逐像素光照和逐顶点的差不太多;只是把一些语句用逐像素的方式执行了
左1是逐顶点,左二是逐像素;
逐像素要更丝滑一点;
2-4半兰伯特模型
2-2/2-3都叫做兰伯特,都会出现背部过暗
半兰伯特模型改进的是全局光的部分(多次反射)
将兰伯特的cos夹角改成cos夹角*0.5 + 0.5,而且不用做和0求max保证最暗的面也有亮度,看起来会亮一点;
代码和逐像素最大的不同就是 漫反射公式变成有个0.5系数
// Upgrade NOTE: replaced &#39;_World2Object&#39; with &#39;unity_WorldToObject&#39;
// Upgrade NOTE: replaced &#39;mul(UNITY_MATRIX_MVP,*)&#39; with &#39;UnityObjectToClipPos(*)&#39;
Shader &#34;Unity Shaders Book/Chapter 6/Half Lambert&#34; {
Properties {
_Diffuse (&#34;Diffuse&#34;, Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { &#34;LightMode&#34;=&#34;ForwardBase&#34; }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 和逐片元的最大的不同就是漫反射公式
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack &#34;Diffuse&#34;
}
半兰伯特模型 要亮一点
3-高光反射模型实现
3-1高光反射模型
高光模型就是在出射角附近的视线能看到高光
上图展示的是布林-冯模型的计算方法
Ls= specularly reflected light表示的是该点反射的光(包括光强+颜色)
ks = specular coefficient color 表示漫反射系数(包含反射率和颜色)
指数p=就是去降低cos的容忍度,cos的容忍度太高了,以至于我们在45度以上都能看到很好的高光,所以就降低高光的区域,正常用的是100-200,基本3°以外就看不到高光了
I/r = energy arrived 入射光强;用光源板强度和距离定义
max(0,n·h)=其实就是表示出射光和视线的偏差值,越接近说明越大的高光、冯模型里面是直接用出射角和视线角,布林冯模型用半程向量和法线比较,差不多的;
纵轴,Ks反射率增大,模型会越来越亮,横轴,随着指数p的增大,能看见高光的区域会越来越小。
http://pic1.zhimg.com/v2-ef8a25a032b32228ad471717d17f924_r.jpg
3-2逐顶点光照
// Upgrade NOTE: replaced &#39;_Object2World&#39; with &#39;unity_ObjectToWorld&#39;
// Upgrade NOTE: replaced &#39;_World2Object&#39; with &#39;unity_WorldToObject&#39;
// Upgrade NOTE: replaced &#39;mul(UNITY_MATRIX_MVP,*)&#39; with &#39;UnityObjectToClipPos(*)&#39;
Shader &#34;Unity Shaders Book/Chapter 6/Specular Vertex-Level&#34; {
Properties {
_Diffuse (&#34;Diffuse&#34;, Color) = (1, 1, 1, 1) //漫反射颜色
_Specular (&#34;Specular&#34;, Color) = (1, 1, 1, 1) //高光反射颜色
_Gloss (&#34;Gloss&#34;, Range(8.0, 256)) = 20 //就是公式里的指数p;用来控制高光区的大小;
}
SubShader {
Pass {
Tags { &#34;LightMode&#34;=&#34;ForwardBase&#34; }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
//这样定义一下才能定义Properties里的属性
fixed4 _Diffuse; //输出的漫反射
fixed4 _Specular; //输出的高光
float _Gloss; //指数p
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR; //这个是是顶点着色器 就只要输出color
};
v2f vert(a2v v) {
v2f o;
//Part1 计算漫反射
// 顶点从三维坐标到世界坐标
o.pos = UnityObjectToClipPos(v.vertex);
// 得到全局光照
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 法线从物体坐标到世界坐标
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 光源方向也到世界坐标
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 计算漫反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//Part2 计算高光
// 计算反射方向,也是一个Unity内置的函数
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 得到视线方向
// 漫反射是不需要考虑视线方向的,但是高光需要
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
// 计算高光的量
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
//总的color = 环境光+高光+漫反射
o.color = ambient + diffuse + specular;
return o;
}
//片元着色器就直接输出color就好了
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack &#34;Specular&#34;
}
我们还能调节gloss
来控制高光的区域
3-3逐像素光照
可能看起来会更柔和一点
// Upgrade NOTE: replaced &#39;_Object2World&#39; with &#39;unity_ObjectToWorld&#39;
// Upgrade NOTE: replaced &#39;_World2Object&#39; with &#39;unity_WorldToObject&#39;
// Upgrade NOTE: replaced &#39;mul(UNITY_MATRIX_MVP,*)&#39; with &#39;UnityObjectToClipPos(*)&#39;
Shader &#34;Unity Shaders Book/Chapter 6/Specular Pixel-Level&#34; {
Properties {
_Diffuse (&#34;Diffuse&#34;, Color) = (1, 1, 1, 1)
_Specular (&#34;Specular&#34;, Color) = (1, 1, 1, 1)
_Gloss (&#34;Gloss&#34;, Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { &#34;LightMode&#34;=&#34;ForwardBase&#34; }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// Transform the vertex from object spacet to world space
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
//片元着色器
//和顶点着色器是完全一样的计算过程
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 漫反射
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// 高光反射
// Get the reflect direction in world space
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack &#34;Specular&#34;
}
3-4布林-冯模型
我们要记得 对比一下;
不管冯模型 还是布林冯模型,求的都是出射角与视线角的差值;
但是冯模型直接求的是初射角和视线角,布林冯模型是法线和半程向量;
冯模型
布林冯模型
布林-冯 会看起来更大更亮一点,所以实际中我们一般都选用布林冯光照模型;
// Upgrade NOTE: replaced &#39;_Object2World&#39; with &#39;unity_ObjectToWorld&#39;
// Upgrade NOTE: replaced &#39;_World2Object&#39; with &#39;unity_WorldToObject&#39;
// Upgrade NOTE: replaced &#39;mul(UNITY_MATRIX_MVP,*)&#39; with &#39;UnityObjectToClipPos(*)&#39;
Shader &#34;Unity Shaders Book/Chapter 6/Blinn-Phong&#34; {
Properties {
_Diffuse (&#34;Diffuse&#34;, Color) = (1, 1, 1, 1)
_Specular (&#34;Specular&#34;, Color) = (1, 1, 1, 1)
_Gloss (&#34;Gloss&#34;, Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { &#34;LightMode&#34;=&#34;ForwardBase&#34; }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include &#34;Lighting.cginc&#34;
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;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// Transform the vertex from object spacet to world space
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Get the half direction in world space
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack &#34;Specular&#34;
}
Thanks!
页:
[1]