Unity Shader学习日记 part6 基本光照模型
从这篇之后呢,就主要是具体的实现了,今天呢,先来看看基本的光照模型。
至于光照什么的就不赘述了,主要是学习以及实现一些基本的光照模型,如果有感兴趣的,可以自行寻找相关文档学习。
标准光照模型
光照模型的种类很多,不过在早期游戏开发者使用的基本上只有一个--标准光照模型,具体的概念我就不写了,实际上这个模型主要有4个部分,他们分别是自发光(emissve),高光反射(specular),漫反射(diffuse),环境光(ambient)。
下面我会给出几种光的计算公式,原理不会去说。
环境光:
自发光:
漫反射:
n是表面法线,l是光源的单位矢量,m(diffuse)是材质的漫反射颜色,c(light)光源颜色
注意:我们需要对n和l的点乘限制大于0,可以防止物体被后面的光源照亮
高光反射:
高光反射需要的信息比较多,需要知道表面法线,视角方向,光源方向,反射方向等等,下面的公式中,字母都表示向量
实际上,我们只需要知道这三个即可,第四个反射方向可以通过计算的到。
m(gloss)光泽度或者反光度(shinness),它可以控制反光区域的大小,这个值与高光区域大小成反比, m(specular) 高光反射颜色,c(light)光源颜色。
除了这个公式之外呢,还有另外一个公式(Blinn),可以避免计算反射方向r,通过引入h这个矢量,h可以通过v和l取平均后归一化得到。
所以他的公式是这样的。
那么在学习了公式之后,我们在哪里去使用他们,在开发中,代码主要在顶点函数以及片元函数中,因此我们也同样是在这两个中来计算。
不过,在不同的函数中去计算得到的效果是不一样的,这就分成了逐像素以及逐顶点
逐像素:在片元着色器中计算
逐顶点:在顶点着色器中计算
两个的具体区别我们会在实践中看到。
由于环境光以及自发光不需要编写,所以下面主要是对于漫反射和高光反射的实现。
注意,下面的实现只有代码部分,至于材质自己新建即可,然后将天空盒取消
漫反射的实现
回忆一下公式,我们需要知道4个参数,怎么得到这些参数,代码中会有具体的注释。我们先来看看逐顶点的方式实现
逐顶点
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "StanderLightMode/Diffuse Vertex-Level"
{
Properties
{
_Diffuse ("Diffuse",Color)=(1,1,1,1)//漫反射颜色
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}//光照模式
CGPROGRAM
#pragma vertex vertex
#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 vertex(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);//顶点转到裁剪空间中
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;//内置变量的环境光
fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));//法线方向
fixed3 worldLight=normalize(_WorldSpaceLightPos0.xyz);//光源方向
//漫反射计算
//saturate可以将值截取在【0,1】,你也可以使用max的方式实现
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
}
}
}
逐像素
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "StanderLightMode/Diffuse Pixe-Level"
{
Properties
{
_Diffuse ("Diffuse",Color)=(1,1,1,1)//漫反射颜色
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}//光照模式
CGPROGRAM
#pragma vertex vertex
#pragma fragment frag
#include "Lighting.cginc"//引入内置函数
fixed4 _Diffuse; //同名变量
struct a2v
{
float4 vertex:POSITION;//模型空间的顶点数据
float3 normal:NORMAL;//法线
};
struct v2f
{
float4 pos:SV_POSITION;//裁剪空间中的顶点坐标
fixed3 worldNormal:TEXCOORD0 ;
};
v2f vertex(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);//顶点转到裁剪空间中
o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
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
}
}
}
可以发现,逐顶点和逐像素的区别注意在于代码的计算位置,除此之外,两者的效果也不一样。
在逐顶点中,可以明显的看到阴影部分的锯齿,而逐像素中这种现象减少很多。
但是呢这两个方式,都会导致光照不到的地方全黑,这样会导致,无法看清模型细节,所以引入另外一种模型,半兰伯特光照模型。
半兰伯特光照模型
通常来说,a和b是0.5,所以
来看看他的实现
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "StanderLightMode/HalfLambert"
{
Properties
{
_Diffuse ("Diffuse",Color)=(1,1,1,1)//漫反射颜色
}
SubShader
{
Pass
{
Tags {"LightMode"="ForwardBase"}//光照模式
CGPROGRAM
#pragma vertex vertex
#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 vertex(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);//顶点转到裁剪空间中
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;//内置变量的环境光
fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
fixed3 worldLight=normalize(_WorldSpaceLightPos0.xyz);//光源方向
//漫反射计算
fixed3 halfLambert=dot(worldNormal,worldLight)*0.5+0.5;
fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*halfLambert;
o.color=ambient+diffuse;
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
return fixed4(i.color,1.0);
}
ENDCG
}
}
}
放一起看看
高光反射的实现
回忆一下公式
再结合代码来看
逐顶点
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "StanderLightMode/SpecularVertexLevelShader"
{
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
{
fixed4 vertex:POSITION;
fixed3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
fixed3 color:COLOR;
};
v2f vert(a2v v)
{
v2f o;
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*ambient*saturate(dot(worldNormal,worldLightDir));
fixed3 relfectDir=normalize(reflect(-worldLightDir,worldNormal));//反射方向
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-mul(unity_ObjectToWorld,v.vertex).xyz);//视角方向
fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(saturate(dot(relfectDir,viewDir)),_Gloss);
o.color=ambient+diffuse+specular;
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
return fixed4(i.color,1.0);
}
ENDCG
}
}
}
逐像素
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "StanderLightMode/SpecularPixeLevelShader"
{
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
{
fixed4 vertex:POSITION;
fixed3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOOD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
o.worldPos=mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
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*ambient*saturate(dot(worldNormal,worldLightDir));
fixed3 relfectDir=normalize(reflect(-worldLightDir,worldNormal));//反设方向
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);//视角方向
fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(saturate(dot(relfectDir,viewDir)),_Gloss);
return fixed4(ambient+diffuse+specular,1.0);
}
ENDCG
}
}
}
Blinn-Phong模型
回忆公式
具体实现
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "StanderLightMode/SpecularBlinnPhongShader"
{
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
{
fixed4 vertex:POSITION;
fixed3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOOD1;
};
v2f vert(a2v v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
//将法线法线从模型空间转到了世界空间
o.worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));
o.worldPos=mul(v.normal,(float3x3)unity_WorldToObject);
return o;
}
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*ambient*saturate(dot(worldNormal,worldLightDir));
fixed3 relfectDir=normalize(reflect(-worldLightDir,worldNormal));//反设方向
fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);//视角方向
fixed3 halfDir=normalize(worldLightDir+viewDir);
fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Gloss);
return fixed4(ambient+diffuse+specular,1.0);
}
ENDCG
}
}
}
放一起看看
实际上,代码方面有很多可以优化的地方,对于很多东西,我们采用的是自己计算的方式,不过呢,内置库中给我们提供了很多的方法可以避免这些计算,比如这些
不过,具体的替换之后的代码我就不放了。
目前学习参考《UnityShader入门精要》 ,只作为个人学习使用。
基本的光照就到这里来,接下来是材质相关的Shader学习。
原文地址:https://blog.csdn.net/weixin_70808146/article/details/145280851
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!