您的位置:首页 > 移动开发 > Unity3D

Unity_Shader学习(四)基础光照Shader

2019-01-21 22:26 387 查看

一.基本概念

首先,对于基础光照Shader我们需要先学习光照相关的知识。

我们使用辐照度来量化光,假设顶点法线为normal,入射光线向量为light,二者夹角为a,辐照量与cos(a)的值成正比。

着色指的就是根据材质属性,光照信息等根据一个公式计算辐照度的过程。我们把计算辐照度的这个公式称作光照模型。

在我们的光照模型中,需要考虑如下四种光类型:

自发光:一个物体本身发光的辐射量。

高光反射:完全镜面反射散射的辐射量。

漫反射:不规则反射散射的辐射量。

环境光:描述其他所有的间接光照。

 

对于环境光与自发光的计算,我们均采用简单的等式计算(a为已知量):

x(ambient)= a(ambient)

x(emissive)= a(emissive)

 

对于漫反射的计算,我们采用兰伯特定律来计算,即反射光线的强度与  表面法线和光源方向之间夹角的余弦值  成正比。

x(diffuse)=a(light) * a(material) * max(0, n*l)

a(light)是光照信息,a(material)是材质信息。n*l是法线与光照方向点乘计算得到的,其中n与l都是单位矢量,这样就得到了余弦值。max(0, n*l)是因为辐照度不能为负,我们的计算中不存在负强度的光照。

 

对于高光反射的计算,我们有两种策略:

1.x(specular)=a(light) * a(specular) *( max(0, v * r) ^ gloss )

gloss是光泽度,反光度。gloss值越高,高光区域的亮点就越小。a(specular)是材质信息,a(light)是光照信息。v是视角方向,r是反射向量。

2.Blin-Phong模型:

x(specular)=a(light) * a(specular) *( max(0, n * h)^gloss)

其中n为法线方向单位矢量,h为((v+l)/ | v+l |  )即v+l之后截取为单位矢量。

 

二.Shader实现

在下面的Shader中我们先不考虑自发光emissive,只考虑环境光ambient与漫反射光diffuse与高光反射specular。

1.漫反射光照模型:

下面是逐顶点光照的Shader。

[code]Shader "Custom/DiffuseVertex" {
Properties {
_Diffuse("Diffuse",Color) = (1,1,1,1)
}
SubShader {
Pass{
Tags{"LightMode"="ForwardBase"}

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

fixed4 _Diffuse;

struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
};

struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR;
};

v2f vert(a2v v){
v2f o;

//Pos Calculate
o.pos = UnityObjectToClipPos(v.vertex);

//Light Calculate
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 worldNormal = normalize(mul( v.normal, (float3x3)unity_WorldToObject ));

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldLight,worldNormal));
o.color = diffuse + UNITY_LIGHTMODEL_AMBIENT.xyz;
return o;
}

fixed4 frag(v2f i):SV_TARGET{
return fixed4(i.color, 1);
}

ENDCG
}
}
FallBack "Diffuse"
}

在逐顶点光照Shader中,可以看到我们所有的处理都在vert函数中完成。worldLight是光照方向单位向量,worldNormal是法线单位向量。saturate(x)函数实现了max(0,x)操作。最后将计算的diffuse与环境光ambient相加即可。

下面为逐像素光照:

[code]Shader "Custom/DiffuseFrag" {
Properties {
_Diffuse("Diffuse",Color)=(1,1,1,1)
}
SubShader {
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "Lighting.cginc"

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 = UnityObjectToClipPos(v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
return o;
}

fixed4 frag(v2f i):SV_TARGET{
fixed3 diffuse = _Diffuse.rgb * _LightColor0.rgb * saturate(dot(i.worldNormal, normalize(_WorldSpaceLightPos0.xyz)));
fixed3 light = diffuse + UNITY_LIGHTMODEL_AMBIENT.xyz;
return fixed4(light, 1.0);
}

ENDCG
}
}
FallBack "Diffuse"
}

在这个Shader中,我们的光照计算放到了frag函数中进行。

下面为对比图,左逐顶点,右逐像素。

在这里我们会发现一个问题,那就是在背光处计算的值均为0,从而导致全为暗色而无法分辨棱角:

这时我们可以采用半兰伯特模型(Half Lambert)来解决这个问题:

x(diffuse)=a(light) * a(material) * (b*(n * l) + c)

其中b和c为常数,我们常将b和c取为0.5,这样背光区域仍然可以区分出来。

[code]			fixed4 frag(v2f i):SV_Target{
float halfLambert = 0.5*dot(_WorldSpaceLightPos0.xyz, i.worldNormal)+0.5;
fixed3 light=_LightColor0.rgb * _Diffuse.rgb * halfLambert + UNITY_LIGHTMODEL_AMBIENT.rgb;
return fixed4(light, 1.0);
}

效果如下:

 

 

2.高光反射

相比较于漫反射Shader,我们可以这样描述高光反射:

漫反射光照模型=漫反射+环境光

高光反射模型=漫反射+环境光+高光反射

即高光反射模型在漫反射计算的基础上加上了高光反射计算出的辐照度。

 

普通高光Shader实现如下:

[code]Shader "Custom/SpecularFrag" {
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 = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
o.worldPos = mul(unity_ObjectToWorld, v.vertex);

return o;

}

fixed4 frag(v2f i):SV_Target{
float3 worldNormal = i.worldNormal;
float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

float3 view = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
float3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));

fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, view)),_Gloss);

fixed3 color = diffuse + specular + UNITY_LIGHTMODEL_AMBIENT;

return fixed4(color, 1);
}

ENDCG
}
}
FallBack "Diffuse"
}

相较于漫反射Shader我们定义了Specular描述材质高光属性,Gloss描述反射度。在frag中我们计算了specular,然后将diffuse,specular,ambient相加,得到最终辐照度。

 

Blin-Phong光照Shader实现如下:

[code]Shader "Custom/BlinPhong" {
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 = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));
o.worldPos = mul(_Object2World, v.vertex);
return o;
}

fixed4 frag(v2f i):SV_TARGET{
float3 worldNormal = i.worldNormal;
float3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 worldViewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));

float3 h = normalize(worldViewDir + worldLightDir);
float3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal,h)),_Gloss);

return fixed4(UNITY_LIGHTMODEL_AMBIENT+diffuse+specular, 1.0);
}

ENDCG
}
}

FallBack "Diffuse"
}

我们计算了一个新的单位向量h,并用h*n来计算高光值。

二者效果对比如下:

 

 

这样我们就学习了简单的基础光照Shader,其实我们计算过程中很多空间变换都可以用Unity提供的函数来实现,这些函数可以在UnityCG.cginc中找到。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: