本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/50812177
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
本文工程使用的Unity3D版本: 5.2.1
好久不见。
从这篇文章开始, Shader系列博文将继续开始更新。且在这次重启,这个系列文章会更多专注于实际Shader的书写,力求推出更多具有特色和实用性的Shader,概念性的东西在原则上上是不会再多讲的。
作为可编程Shader系列的第一篇文章,本文将从最简化的可编程Shader开始,逐步变换与实现一个漫反射(也就是实现Lambert光照模型)顶点&片段Shader,可以说,是在讲述一个可编程Shader的进化史。
依然是先放出游戏场景的exe和运行截图。
【可运行的本文配套exe游戏场景请点击这里下载】
我们知道,在Unity中,Shader可以分成如下三种基本类型:
1.固定功能着色器(FixedFunction Shader)
2.表面着色器(SurfaceShader)
3.顶点着色器&片段着色器(Vertex Shader & Fragment Shader)
我们也知道,可编程Shader是Unity Shader之中功能最强大、最自由的形态。就让我们开始一个最简单的单色可编程Shader的书写。
单色Shader算是比较精简的可编程Shader。直接上注释好的代码:
-
-
- Shader "浅墨Shader编程/Volume12/1.SimpleShader"
- {
-
- SubShader
- {
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
-
-
-
- float4 vert(float4 vertexPos : POSITION) : SV_POSITION
- {
-
-
- return mul(UNITY_MATRIX_MVP,vertexPos);
- }
-
-
-
-
-
- float4 frag(void): COLOR
- {
-
- return float4(0.0, 0.6, 0.8, 1.0);
- }
-
-
- ENDCG
- }
- }
- }
这个Shader也就是先指定一下顶点和片段着色器的名称,然后在顶点着色器中进行一下坐标空间的转换,在片段着色器中直接返回一个固定的蓝色,仅此而已。
将其施用于材质之上的效果如下:
仅仅Hard encoding硬编码颜色怎么可以,我们想要可调节,可玩的Shader。这不,来一个Properties属性块,里面定义一个Color属性,然后在片段着色器里替换一下float4型的固定颜色不就好了。所以,代码如下:
-
-
- Shader "浅墨Shader编程/Volume12/2.ColorChange"
- {
-
- Properties
- {
-
- _Color("Color", Color) = (1,1,1,1)
- }
-
-
- SubShader
- {
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
-
-
-
- float4 vert(float4 vertexPos : POSITION) : SV_POSITION
- {
-
-
- return mul(UNITY_MATRIX_MVP, vertexPos);
- }
- uniform float4 _Color;
-
-
-
-
-
- float4 frag(void) : COLOR
- {
-
- return _Color;
- }
-
-
- ENDCG
- }
- }
- }
将其施用于材质之上的效果如下:
于是,我们便可以用这边的调色板对此Shader进行颜色的调节。
接下来,看点更有意思的Shader。
这个是Unity旧版官方文档中出现的一个Shader示例,先上原版RGB Cube的Shader代码,稍后对其进行改造升级:
-
-
- Shader "浅墨Shader编程/Volume12/3.RGB cube"
- {
-
- SubShader
- {
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
- struct vertexOutput
- {
- float4 positon : SV_POSITION;
- float4 color : TEXCOORD0;
- };
-
-
-
-
-
- vertexOutput vert(float4 vertexPos : POSITION)
- {
-
- vertexOutput output;
-
-
- output.positon = mul(UNITY_MATRIX_MVP, vertexPos);
-
- output.color = vertexPos + float4(0.2, 0.2, 0.2, 0.0);
-
-
- return output;
- }
-
-
-
-
-
- float4 frag(vertexOutput input) : COLOR
- {
-
- return input.color;
- }
-
-
- ENDCG
- }
- }
- }
将其施用于材质之上的效果如下:
我们在上述Shader的基础上,加上一个可调节的属性,代码如下:
-
-
- Shader "浅墨Shader编程/Volume12/4.RGB cube v2"
- {
-
- Properties
- {
-
- _ColorValue("Color", Range(0.0, 1.0)) = 0.6
- }
-
-
- SubShader
- {
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
- struct vertexOutput
- {
- float4 positon : SV_POSITION;
- float4 color : TEXCOORD0;
- };
-
-
- uniform float _ColorValue;
-
-
-
-
-
- vertexOutput vert(float4 vertexPos : POSITION)
- {
-
- vertexOutput output;
-
-
- output.positon = mul(UNITY_MATRIX_MVP, vertexPos);
-
- output.color = vertexPos + float4(_ColorValue, _ColorValue, _ColorValue, 0.0);
-
-
- return output;
- }
-
-
-
-
-
- float4 frag(vertexOutput input) : COLOR
- {
-
- return input.color;
- }
-
-
- ENDCG
- }
- }
- }
将其施用于材质之上的效果如下:
在上述Shader的基础上,加上三个可调节的属性,分别代表RGB三色的分布情况。那么Shader代码就变成了:
-
-
- Shader "浅墨Shader编程/Volume12/5.RGB cube v3"
- {
-
- Properties
- {
-
- _ColorValueRed("ColorRed", Range(0.0, 1.0)) = 0.2
-
- _ColorValueGreen("ColorGreen", Range(0.0, 1.0)) = 0.5
-
- _ColorValueBlue("ColorBlue", Range(0.0, 1.0)) = 0.6
- }
-
-
- SubShader
- {
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
- struct vertexOutput
- {
- float4 positon : SV_POSITION;
- float4 color : TEXCOORD0;
- };
-
-
- uniform float _ColorValueRed;
- uniform float _ColorValueGreen;
- uniform float _ColorValueBlue;
-
-
-
-
-
- vertexOutput vert(float4 vertexPos : POSITION)
- {
-
- vertexOutput output;
-
-
- output.positon = mul(UNITY_MATRIX_MVP, vertexPos);
-
- output.color = vertexPos + float4(_ColorValueRed, _ColorValueGreen, _ColorValueBlue, 0.0);
-
-
- return output;
- }
-
-
-
-
-
- float4 frag(vertexOutput input) : COLOR
- {
-
- return input.color;
- }
-
-
- ENDCG
- }
- }
- }
将其施用于材质之上的效果如下:
OK,热身差不多就这么多,接下来看看漫反射的原理与Shader实现。
漫反射(Diffuse Reflection),又称Lambert反射,是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射。
在生活中,我们看到的月球的光近乎完全是漫反射。粉笔或者磨砂纸也是漫反射。事实上,任何表面的漫反射看起来都是类似于磨砂表面的效果。
而在完美的漫反射的情况下,所观察到的反射光的强度取决于在表面法线矢量和入射光的光线之间的角度的余弦。如图所示,经过归一化的物体表面法线向量N与物体表面正交,光源照射到物体表面的光线方向为L。
也就是说,我们可以使用表面的法线矢量N和入射光方向矢量L,计算漫反射。
想要计算出眼睛观察到的漫反射光,需要计算归一化的表面法向量N和归一化的方向光源向量L之间夹角的余弦值,即点积运算N·L。因为任何两个向量a和b的点积运算可以表示为:
而对于已经经过归一化的向量,|a| 和|b|都是1。
如果点积运算N·L为负,那么光源方向就是在表面内部照射过来的,这是错误的。这种情况下,我们就将反射光设为0即可。这可以通过代码max(0, N·L)来实现,这样就确保了在点积结果为负时,得到的结果为0。此外,漫反射光还取决于入射光light和材质的漫反射系数 。而对于一个黑色的表面,材质漫反射系数 为0,而对于白色的表面,材质的漫反射强度就为1.
根据如上的表述,漫反射强度的方程如下:
需注意,此公式适用于任何单一的颜色分量(如红、绿、蓝),也适用于颜色分量混合颜色的入射光。
OK,理论部分先说这么多。
实现Diffuse Reflection Shader
关于漫反射的可编程Shader实现,总结一下吧:
- 核心代码可以在顶点着色器中实现,也可以在片段着色函数中实现。
- 需在世界空间中进行实现,因为世界空间中Unity提供了光源方向。
- 关于如何获取参数,总结如下:
- 通过属性properties中指定后传进来。
- 世界空间的光源方向由unity内置变量_WorldSpaceLightPos0给出。
- 光源颜色由unity内置变量_LightColor0给出。
- 环境光颜色通过内置变量UNITY_LIGHTMODEL_AMBIENT给出。
- 用Tags {"LightMode" = "ForwardBase"}确保上述内置变量的值处于正确的状态。
- 世界空间下的物体表面的法线向量,可以通过输出参数语义NORMAL来获得物体空间下的表面的法线向量,然后将此向量从物体空间转到世界空间中获得。
OK,下面就开始Shader的书写吧。
6.单色可调的漫反射光照Shader书写
本节首先实现了一个单一光照条件下,可调颜色的漫反射可编程Shader。代码如下。
- Shader "浅墨Shader编程/Volume12/6.Diffuse(Lambert) Shader"
- {
-
- Properties
- {
-
- _Color("Main Color", Color) = (1, 1, 1, 1)
-
- }
-
-
- SubShader
- {
-
- Tags{ "RenderType" = "Opaque" }
-
- Tags{ "LightingMode" = "ForwardBase" }
-
- LOD 200
-
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
- #include "UnityCG.cginc"
-
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- };
-
-
- struct v2f
- {
- float4 position : SV_POSITION;
- float3 normal : NORMAL;
- };
-
-
- float4 _LightColor0;
- float4 _Color;
-
-
-
-
-
- v2f vert(appdata input)
- {
-
- v2f output;
-
-
-
- output.position = mul(UNITY_MATRIX_MVP, input.vertex);
-
- output.normal = mul(float4(input.normal, 0.0), _World2Object).xyz;
-
-
- return output;
- }
-
-
-
-
-
-
- fixed4 frag(v2f input) : COLOR
- {
-
-
- float3 normalDirection = normalize(input.normal);
-
- float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
-
-
- float3 diffuse = _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));
-
-
- float4 DiffuseAmbient = float4(diffuse, 1.0) + UNITY_LIGHTMODEL_AMBIENT;
-
-
- return DiffuseAmbient;
- }
-
-
- ENDCG
-
- }
- }
- }
将其施用于材质之上的效果如下:
7.可调颜色和自定义纹理的漫反射光照Shader
接下来,让我们进一步实现一个可以自定义纹理的漫反射光照的顶点&片段着色器。也就是在Properties属性中加上一项纹理,然后在最终的漫反射颜色计算完成之后,乘上纹理即可。
- Shader "浅墨Shader编程/Volume12/7.Diffuse(Lambert) Shader with Texture"
- {
-
- Properties
- {
-
- _MainTex("Texture", 2D) = "white"{}
-
- _Color("Main Color", Color) = (1, 1, 1, 1)
-
- }
-
-
- SubShader
- {
-
- Tags{ "RenderType" = "Opaque" }
-
- Tags{ "LightingMode" = "ForwardBase" }
-
- LOD 200
-
-
- Pass
- {
-
- CGPROGRAM
-
-
- #pragma vertex vert
- #pragma fragment frag
-
-
- #include "UnityCG.cginc"
-
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float2 texcoord : TEXCOORD0;
- };
-
-
- struct v2f
- {
- float4 positon : SV_POSITION;
- float3 normal : NORMAL;
- float2 texcoord : TEXCOORD0;
- };
-
-
- float4 _LightColor0;
- float4 _Color;
- sampler2D _MainTex;
-
-
-
-
-
-
- v2f vert(appdata input)
- {
-
- v2f output;
-
-
-
- output.positon = mul(UNITY_MATRIX_MVP, input.vertex);
-
- output.normal = mul(float4(input.normal, 0.0), _World2Object).xyz;
-
- output.texcoord = input.texcoord;
-
-
- return output;
- }
-
-
-
-
-
- fixed4 frag(v2f input) : COLOR
- {
-
-
- float4 texColor = tex2D(_MainTex, input.texcoord);
-
- float3 normalDirection = normalize(input.normal);
-
- float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
-
-
- float3 diffuse = _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));
-
-
- float4 DiffuseAmbient = float4(diffuse, 1.0) + UNITY_LIGHTMODEL_AMBIENT;
-
-
- return DiffuseAmbient* texColor;
- }
-
-
- ENDCG
- }
- }
- }
将其施用于材质之上的效果如下:
下图是此漫反射Shader使用到皮卡丘模型上的效果图。
今天写的Shader的全家福:
最后放几张加上镜头特效的场景效果图:
OK,本篇的内容大致如此,下次更新见。
附1: 本博文相关资源下载链接清单
【百度云】博文游戏场景exe下载
【百度云】博文示例场景资源和源码工程下载 ( PS:工程所用Unity版本为5.2.1)
【Github】本文全部Shader源码
附2:Reference
[1] http://docs.unity3d.com/Manual/SL-Reference.html
[2] https://en.wikibooks.org/wiki/Cg_Programming