【Unity Shaders】学习笔记——SurfaceShader(四)用纹理改善漫反射
转载请注明出处:http://www.cnblogs.com/-867259206/p/5603368.html
写作本系列文章时使用的是Unity5.3。
写代码之前:
- 当然啦,如果Unity都没安装的话肯定不会来学Unity Shaders吧?
- 阅读本系列文章之前你需要有一些编程的概念。
- 在VS里面,Unity Shaders是没有语法高亮显示和智能提示的,VS党可以参考一下这篇文章使代码高亮显示,也可以下载
shaderlabvs
或NShader
之类的插件使代码高亮显示。 - 这是针对小白的Unity Shaders的基础知识,如果你已经有了基础或者你是大神,那么这些文章不适合你。
- 由于作者水平的局限,文中或许会有谬误之处,恳请指出。
上一篇里我们做的HalfLambert的效果是这样的:
我们看到,亮部到暗部没有自然的过渡,暗部直接涂成深灰。就像初学者画的素描,直接将暗部涂黑,没有变化。
这是因为这种光照计算太简单了,现实生活中,暗部因为漫反射的存在不会像画面上那么暗。但如果要引入光照计算的话,那太复杂了,我们可以用纹理来改善它。
因为我们需要一张纹理贴图,先在Properties里声明纹理:
_RampTex ("Ramp Texture", 2D) = "white"{}
然后再CGPROGRAM里声明一个同名变量:
sampler2D _RampTex;
最后只要稍微改改HalfLambert光照模型:
// add this line
float3 ramp = tex2D(_RampTex, float2(hLambert,hLambert)).rgb;
......
// modify this line
col.rgb = s.Albedo * _LightColor0.rgb * ramp;
将修改后的材质赋予小球,贴上这张贴图:
看看效果:
光影效果是不是真实了许多?
这是因为我们将这张贴图根据反射光强映射到小球上,改变了小球反射的颜色。
现在我们来做一个实验,跟着我来:
修改这句:
// float hLambert = 0.5 * difLight + 0.5;
float hLambert = difLight;
也就是不要使用半兰伯特光照。看看结果:
变成了这样子。再继续修改这句:
float hLambert = 0.9 * difLight;
映射在球上的贴图移动了一点点对不对?
然后继续修改,把0.9改为0.8,观察效果,把0.8改为0.7,观察效果,把0.7改为0.6,观察效果……
我们看到,贴图一点点地往上移,你们发现了什么吗?在我们把数值一点点改小的过程中,小球光照最亮的点的值由1慢慢变小,对应地,最亮的点由白色渐渐变成了蓝灰色,当把数值调为0.1的时候,变成了橙黄色。而那张贴图,最左侧是黄色,中间是蓝灰色,右侧是蓝白色,如果用坐标表示纹理的话,最左侧应该是0,中间是0.5,右侧是1.所以纹理的映射关系你清楚了吗?当小球最亮点的值是0.5的时候,它映射的就是贴图中央的部分,也就呈现蓝灰色。所以你们应该清楚这张贴图是如何映射在小球身上从而改善光影效果的了吧。经过半兰伯特光照的改善,原来光照为0的地方变成了0.5,所以映射的是纹理的中间部分,呈渐变的蓝灰色,原来光照为-1的地方变成了0,映射的是纹理左侧的橙色部分,如果你仔细观察,可以看到在小球接近地面的地方颜色有些泛黄,也就是纹理左侧的颜色。
学过素描的人知道,明暗交界线的地方是最暗的,背光面因为反光的原因,会亮一点,这样画出来的阴影就不会一片死黑,而是通透,接近真实。看下图:
所以贴图的中部颜色最深,是暗灰,模拟的是明暗交界处的颜色(也就是原先光照为0的地方经过改善变成了0.5),右侧的渐变模拟的是亮部的颜色,左侧的渐变模拟的是暗部因为反光而变亮一些的颜色。
这样的效果还是不能使我们满意,阴影的效果还是不够柔和。
假的BRDF
BRDF是bidirectional reflectance distribution function的简写,意思是双向反射分布函数。
之前我们都只考虑了入射光的方向,这样我们只能得到一个线性的结果。实际上,一个表面上的一点,由于观察方向不同,看到的效果是不同的。因为不同的角度,光线反射进眼里的强弱不同,颜色也不同。所以我们要引进一个新的变量,viewDir,它是人眼看向物体的方向。也就是说对于表面上某个点的颜色的计算,我们要考虑两个方向,光照方向和观察方向。可以简单地理解为入射光在不透明的物体表面同时反射到观察方向和出射光两个方向。
好了,直接上代码,不一句句地修改了:
inline float4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten){
float difLight = dot (s.Normal, lightDir);
float rimLight = dot (s.Normal, viewDir);
float dif_hLambert = 0.5 * difLight + 0.5;
float rim_hLambert = 0.5 * rimLight + 0.5;
float3 ramp = tex2D(_RampTex, float2(dif_hLambert, rim_hLambert)).rgb;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * ramp;
col.a = s.Albedo;
return col;
}
在光照模型函数里我们新增了一个变量half3 viewDir
,它就是观察方向,在函数里面,我们定义了一个新的变量float rimLight
,同样对它与法线方向点积,然后用HalfLambert优化,最后将dif_hLambert
和rim_hLambert
作为uv坐标传入tex2D
函数。
将新的材质赋予小球,贴上这张贴图:
我们来看看效果:
你们可以将Slider的数值调大一点,然后给球一个颜色,看看效果,再和原先的材质比较一下。
看看对比,左边是上一个只考虑光照方向的材质,右边是BRDF材质。左边很简单地区分了亮面和暗面,好像直接画了条圆弧区分了明暗交界线,而右侧的明暗过渡更为柔和和富于变化。
我们先来分析一下纹理的映射关系:
纹理的左下角坐标是(0,0),右上角坐标是(1,1),左上角坐标是(0,1),右下角坐标是(1,0)。
左下角对应着光照最弱且离人视线最远的地方,也就是小球接近地面的那个区域,对应纹理的黑色;右上角对应着光照最强且离人视线最近的地方,就是大概在小球高光附近的区域,对应纹理颜色最白的地方;左上角对应着光照最弱但离人视线最近的地方,也就是没上色的效果图里有点泛红的区域;右下角对应着光照最强但离人视线最远的地方,也就是小球靠近光源的边缘区域,我们看到没上色的图里它有些泛蓝。
实际上,图的右上角到左下角对应的就是光照方向,左上角到右下角对应的就是观察方向:
观察方向和平面法向量点积的结果如图:
意思就是说,离人视线越近的地方越亮,越远的地方越暗。
这和素描关系也是一样的,只考虑光照方向画出来的阴影就像上一个shader那样,不够立体,这是因为只考虑光照方向的体积,同时考虑的视线方向,小球才会更有立体感。
之所以用小球来演示shader是因为小球更容易看出光影的关系,素描第一课都是画球嘛。
其实shader和画画确实是一样的,只不过一个是用笔在纸上画出想要的结果,一个是用代码“画出”想要的结果~
附:代码清单
Shader "Custom/RampDiffuse" {
Properties {
_EmissiveColor ("Emissive Color", Color) = (1,1,1,1)
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
_MySliderValue ("This is a Slider", Range(0,10)) = 2.5
_RampTex ("Ramp Texture", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf HalfLambert
float4 _EmissiveColor;
float4 _AmbientColor;
float _MySliderValue;
sampler2D _RampTex;
struct Input {
float2 uv_MainTex;
};
void surf (Input IN, inout SurfaceOutput o){
float4 c;
c = pow((_EmissiveColor + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
inline float4 LightingHalfLambert (SurfaceOutput s, fixed3 lightDir, fixed atten){
float difLight = dot (s.Normal, lightDir);
float hLambert = 0.5 * difLight + 0.5;
float3 ramp = tex2D(_RampTex, float2(hLambert,hLambert)).rgb;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * ramp;
col.a = s.Alpha;
return col;
}
ENDCG
}
FallBack "Diffuse"
}