UnityShader学习笔记(一) 漫反射着色
发表于2017-01-22
大家好,我是Zander。今天我们来学习一下漫反射着色器。任何优秀的着色器开始总会依赖于一个基础的漫反射组件或者光照模型。因此从漫反射部分开始着色器的编写是非常有意义的。
Baidu百科
漫反射(diffuse)是指光线被粗糙表面无规则地向各个方向反射的现象。当一束平行光触及光滑物体表面时,光线则发生规律性反射,反射后的光线也相互平行,这种规律性反射称为光的单向反射或镜面反射。但物体的光滑程度是相对的,而一般物体的表面多粗糙不平,入射线虽然为平行光线,但反射后的光线则向各个方向分散,此种现象为光的漫反射。
实现:
添加属性:
1 2 3 4 5 | Properties { _EmissiveColor( "Emissive Color" ,Color) = (1,1,1,1) _AmbientColor( "Ambient Color" ,Color) = (1,1,1,1) _MySliderValue( "This is a Slider" ,Rang(0,10)) = 2.5 } |
Unity提供了很多类型让我们在着色器中使用,下面描述了这些类型:
表面着色器的属性类型
Rang(min,max) 创建Float属性、以滑动条的形式便于在最大值和最小值之间进行调节
Color 创建色块,可以在Inspector面板上通过拾色器获取颜色值=(float,float,float,float)
2D 创建纹理属性,允许直接拖拽一个纹理
Rect 创建一个非2次方的纹理属性,作为2DGUI元素
Cube 在Inspector面板上创建一个立方贴图属性,允许用户直接拖拽立方贴图作为着色器属性
Float 在Inspector面板上创建一个非滑动条的float属性
Vector 创建一个拥有4个Float值的属性,可以用以标记方向或颜色
参考:这些属性都记录在Unity手册里 ,网址 :https://docs.unity3d.com/Manual/SL-Properties.html
现在我们创建了一些属性 ,我们可以在材质的Inspector面板上调整属性,如果我们想要在着色器代码里通过变量名来获得它的属性值,则必须在SubShader里面创建与之对应的另一个变量
1 2 3 | float4 _EmissiveColor; float4 _AmbientColor; float _MySliderValue; |
现在让我们将_EmissiveColor的属性值加到_AmbientColor属性值上,然后把计算结果赋给o.Albedo
1 2 3 4 5 6 7 8 | void surf (Input IN, inout SurfaceOutput o) { float4 c; c = pow((_EmissiveColor + _AmbientColor), _MySliderValue); o.Albedo = c.rgb; o.Alpha = c.a; } |
上面使用了一个Pow(arg1,arg2)函数, 大家可以在http://http.developer.nvidia.com/CgTutorial/cg_tutorial_appendix_e.html查阅各种着色器的CG函数
这样一个简单的漫反射shader就完成了 ,全部代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | Shader "Custom/BasicDiffuse" { 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 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert float4 _EmissiveColor; float4 _AmbientColor; float _MySliderValue; 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; } ENDCG } FallBack "Diffuse" } |
从实际的经验来看,我们永远不会认为一个只使用自带光照模型的项目是个好项目。我们希望创建自定义光照模型来实现各种效果,比如实现边缘高亮的效果、或者实现更多基于立方贴图的光照,接下来我们将介绍如何创建自定义光照模型。
将#pragma标记修改为如下代码:
1 | #pragma surface surf BasicDiffuse |
1 2 3 4 5 6 7 8 9 | inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) { float difLight = max(0, dot (s.Normal, lightDir)); float4 col; col.rgb = s.Albedo * _LightColor0.rgb * (difLight * atten * 2); col.a = s.Alpha; return col; } |
保存后,和之前的对比如图:
实现原理:
#pragma surface指令告诉着色器将使用哪个光照模型来计算。在我们创建的第一个着色器代码中,默认使用Lighting.cgnic文件里包含的Lambert光照模型,所以可以使用这种光照模型来计算。现在我们告诉着色器将使用名叫BasicDiffuse的光照模型;
通过声明一个新的光照模型函数我们就能创建一个新的光照模型了,当完成了这个步骤后,便可以将函数名替换成你想要的任何名字。函数名的格式是 : Linghting<任意名字>。 你可以使用三种格式的光照模型函数:
一:该函数用于不需要视角方向的前向着色。
half LightingName(SurfaceOutput s,half3 lightDir, half atten){}
二:该函数用于需要视角方向的前向着色
half LightingName(SurfaceOutput s,half3 lightDir,half3 viewDir ,half atten){}
三:该函数用于需要使用延迟着色的项目。
half LightingName_PrePass(SurfaceOutput s,half4 light){}
点积函数是CG语言的另一个内置数学函数,我们可以用它来比较两个向量在空间里的方向。点积函数会检查两个向量是相互平行还是相互垂直。任意两个向量都可以通过点积函数获得-1 ~1的夹角范围,-1表示平行向量并背离你的方向,1也表示平行向量,不过是朝向你的方向,0表示和你垂直的方向向量
矢量点积(或内积)的归一化向量N和L是测量两个向量之间夹角的方法。两个向量之间的夹角越小,点积越大,表面可以接受的入射光也越多
为了完成漫反射计算,我们需要将Unity和SurfaceOutput结构体提供给我们的数据做乘法运算。为此我们需要乘上s.Albedo(来自Sur的函数)和_LightColor0.rgb(来自Unity) 然后再将结果与(difLight * atten)相乘, 最后,返回这个值作为颜色值。
接下来我们创建一个HalfLambert光照模型
Half Lambert是由Valve公司提出的技术,是一种用于在低光照区域照亮物体的技术。它基本上提高了材质和物体表面周围的漫反射光照。
Half lambert光照模型最早用于《半条命》游戏的技术,它用来防止某个物体的背光面丢失形状并且显得太过平面化。这种技术是完全没有基于任何物理原理的,而仅仅是一种感性的视觉增强。
我们将上面自定义的光照模型的漫反射计算结果乘以0.5,然后讲下面的代码加入至光照函数里:
1 2 3 4 5 6 7 8 9 10 | inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) { float difLight = max(0, dot (s.Normal, lightDir)); float hLambert = difLight*0.5 +0.5; float4 col; col.rgb = s.Albedo * _LightColor0.rgb * (hLambert * atten * 2); col.a = s.Alpha; return col; } |
三种漫反射对比如图:
在UnityShader中,我们还可以通过渐变纹理(ramp texture)来控制漫反射光照的颜色,这允许你突出表面的颜色,来模拟更多的反射光照或者或者其他高级的灯光设置。可以在很多卡通风格的游戏中看到这种技术,通常在你想要更加艺术的画面效果,并且不需要很多真实物理模拟的光照模型时可以使用渐变纹理。
准备一张渐变纹理图片:
修改光照函数:
1 2 3 4 5 6 7 8 9 10 11 | inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) { float difLight = dot (s.Normal, lightDir); float hLambert = difLight * 0.5 + 0.5; float3 ramp = tex2D(_RampTex, float2(hLambert)).rgb; float4 col; col.rgb = s.Albedo * _LightColor0.rgb * (ramp); col.a = s.Alpha; return col; } |
为了了解更多关于Unity内置的大量的Cg功能,我们可以访问Unity安装目录下的.4EditorDataCGIncludes。不同的版本在相应不同的安装目录下,里面有三个文件需要特别注意,UnityCG.cginc 、Lighting.cginc和UnityShaderVariables.cginc。目前我们的着色器使用的正是这三个文件里的功能。