【Unity】Shader应用
实验性工具 Shader Forge
- 这里的连连看插件用的是Shader Forge,但是现在已经不更新了,最后更新是在2018的版本中,但是也可以用。
- Shader Forge最后生成的Shader代码没有Shader Graph那么多,稍微方便一点,所以先用这个。
漫反射模型(兰伯特光照)
兰伯特光照是最基础的漫反射模型,其效果如下
![]()
在兰伯特光照中,很明显,朝向光的点最亮(灰度为1),背向光的点最暗(灰度为0),中间则介于0~1之间。
通过光的反方向lDir和某个点的朝向(该点法向量)nDir的接近程度,就可以得知该点是否面朝光。
刚好,数学上的点乘Dot刚好可以用于表示两个向量方向的接近程度,数值越大,两者方向越接近。(两个向量前提都经过了归一化处理)
点乘的定义是:a·b = |a||b|cosθ
当某点面朝光时,θ = 0,那么cosθ = 1。最后点乘结果也是等于1。
对模型上的每个点都进行一次 灰度值 = a·b 的运算,就可以得到整个模型经过漫反射之后的外观。
![]()
- 用到的函数:
UnityObjectToClipPos(点的局部坐标)
UnityObjectToWorldNormal( 点的局部法向量 )
dot(向量,向量)
代码
Shader "Shader Forge/02" {
Properties {
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float3 normalWS : TEXCOORD;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.pos = UnityObjectToClipPos( v.vertex );
o.normalWS = UnityObjectToWorldNormal( v.normal );
return o;
}
float4 frag(VertexOutput i) : COLOR {
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float c = dot(i.normalWS,lightDir);
c = max(0,c);
return float4(c,c,c,1);
}
ENDCG
}
}
FallBack "Diffuse"
}
镜面反射模型(高光反射)
Phong 模型
思想:亮度 = Dot(光折射方向, 视方向)
- 用到的函数:reflect(入射光,法线) 、mul( 矩阵,矩阵(或向量) )
- 用到的矩阵:unity_ObjectToWorld
- 用到的全局变量:_WorldSpaceCameraPos、_WorldSpaceLightPos0
![](http://upload-images.jianshu.io/upload_images/22973577-1953752e1236d68a.png?imageMogr2/auto-orient/strip|imageView2/2/w/433/format/webp)
代码
Shader "Shader Forge/02" {
Properties {
_Color("颜色",Color) = (1.0,1.0,0.0,1.0)
_SpecPow("高光次幂",Range(1,50)) = 30
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _Color;
uniform float _SpecPow;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 posCS : SV_POSITION;
float4 posWS : TEXCOORD0;
float3 normalWS : TEXCOORD1;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.posCS = UnityObjectToClipPos( v.vertex );
o.posWS = mul(unity_ObjectToWorld, v.vertex);
o.normalWS = UnityObjectToWorldNormal( v.normal );
return o;
}
float4 frag(VertexOutput i) : COLOR {
//准备向量
float3 nDir = i.normalWS;
float3 vDir = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
float3 lDir = normalize(_WorldSpaceLightPos0.xyz);
float3 rDir = reflect(-lDir,nDir);
//准备点积结果
float nDotl = dot(nDir,lDir);
float vDotr = dot(vDir,rDir);
//光照模型
float lambert = max(0,nDotl);
//float phong = pow(max(0,vDotr),_SpecPow); 直接截断负值的方式
float phong = pow((vDotr+1)/2,_SpecPow); // 将[-1,1]的区间映射到[0,1]的方式
//输出结果
float3 finalRGB = _Color * lambert + phong;
return float4(finalRGB,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Blinn-Phong 模型
思想:亮度 = Dot(半角方向, 法线方向)
代码(除了frag其他和Phone一样)
float4 frag(VertexOutput i) : COLOR {
float3 vDir = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
float3 lDir = normalize(_WorldSpaceLightPos0.xyz);
float3 hDir = normalize(vDir+lDir);
float3 nDir = i.normalWS;
float nDotl = dot(nDir,lDir);
float nDoth = dot(nDir,hDir);
float lambert = max(0,nDotl);
float B_Phong = pow(max(0,nDoth),_SpecPow);
float3 finalRGB = _Color * lambert + B_Phong;
return float4(finalRGB,1.0);
}
将法线映射为颜色
![](http://upload-images.jianshu.io/upload_images/22973577-e832772505f7ef85.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
float4 frag(VertexOutput i) : COLOR {
return float4(i.normalWS,1.0);
}
三色环境光
思路:
- 不同侧面的环境光:利用法线方向来确定朝向。
- 模型对环境光的遮挡导致的亮暗区别(例如耳朵朝内凹,因此内部容易被遮挡,会较暗):利用一张AO图(需要烘焙),来指明被遮挡的区域。
![](http://upload-images.jianshu.io/upload_images/22973577-2ae3e1551c755019.png?imageMogr2/auto-orient/strip|imageView2/2/w/402/format/webp)
![](http://upload-images.jianshu.io/upload_images/22973577-bdd864bdb9be01f3.png?imageMogr2/auto-orient/strip|imageView2/2/w/831/format/webp)
代码 (最后随意地结合了一下Lambert和Phong)
Shader "Shader Forge/03" {
Properties {
_Color("颜色",Color) = (1.0,1.0,0.0,1.0)
_SpecPow("高光次幂",Range(1,90)) = 30
_EnvUpCol("上部环境光色",Color) = (1,1,1,1)
_EnvDownCol("下部环境光色",Color) = (0.2,0.2,0.2,1)
_EnvSideCol("侧边环境光色",Color) = (0.1,0.8,0.1,1)
_Occlusion("环境遮罩图AO",2d) = "white"{}
_EnvIntensity("环境光强度",Range(0,1)) = 0.3
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform float4 _Color;
uniform float _SpecPow;
uniform float4 _EnvUpCol;
uniform float4 _EnvDownCol;
uniform float4 _EnvSideCol;
uniform float _EnvIntensity;
uniform sampler2D _Occlusion;
struct VertexInput {
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct VertexOutput {
float4 posCS : SV_POSITION;
float4 posWS : TEXCOORD0;
float3 normalWS : TEXCOORD1;
float2 uv : TEXCOORD2;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.posCS = UnityObjectToClipPos( v.vertex );
o.posWS = mul(unity_ObjectToWorld, v.vertex);
o.normalWS = UnityObjectToWorldNormal( v.normal );
o.uv = v.uv;
return o;
}
float4 frag(VertexOutput i) : COLOR {
//准备向量
float3 nDir = i.normalWS;
float3 vDir = normalize(_WorldSpaceCameraPos.xyz - i.posWS);
float3 lDir = normalize(_WorldSpaceLightPos0.xyz);
float3 rDir = reflect(-lDir,nDir);
//准备点积结果
float nDotl = dot(nDir,lDir);
float vDotr = dot(vDir,rDir);
//光照模型
//漫反射 + 镜面反射
float lambert = max(0,nDotl);
float phong = pow((vDotr+1)/2,_SpecPow);
//环境光
float upMask = max(0,i.normalWS.y);
float downMask = max(0,-i.normalWS.y);
float sideMask = 1 - upMask - downMask;
float occlusion = tex2D(_Occlusion,i.uv);
float3 envCol = _EnvUpCol*upMask + _EnvSideCol*sideMask + _EnvDownCol*downMask;
//输出结果
float3 finalRGB = (_Color * lambert *(1-_EnvIntensity) + envCol * _EnvIntensity * occlusion + phong ); //这个暂时先用加法简单相加了,实际不知道是什么样的
return float4(finalRGB,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}