“Without purpose, the days would have ended, as such days always end, in disintegration.”
– , Explorer
step1
经世界坐标空间位置的切片
Shader"Example / AutisticPatient SurfaceShader8"
{
Properties
{
_MainTex("MainTex",2D) = "white"{}
_Bump("Bump",2D) = "Bump"{}
}
SubShader
{
Tags{"RenderType" = "Opaque"}
CGPROGRAM
#pragma surface surf Lambert
//cull off是关闭阴影面剔除
//这里说一下Culling指令:culling是阴影面剔除的一种优化技术
//通常情况下,所有的多边形都有正反两面,而背面往往是摄像机看不见的,所以会将看不见的那面剔除掉
//cull back : 剔除背面
//cull front : 剔除正面
Cull off
struct Input
{
float2 uv_MainTex;
float2 uv_Bump;
//世界空间位置,是个三维向量
float3 worldPos;
}
sampler2D _MainTex;
sampler2D _Bump;
void surf(Input IN,inout SurfaceOutput o)
{
//clip(x) : 如果输入向量中的任何元素小于0,则丢弃当前像素。
//frac() 得作用很简单 ,举个例子:在frac函数内计算出的值若为1.5,frac函数将会取出0.5作为最终值输出
//也就是说frac里的计算结果如果小于0.5,那么这个像素点就被丢弃了
clip(frac( (IN.worldPos.y + IN.worldPos.z * 0.1) * 5) - 0.5);
//着色器会找到贴图上对应的UV坐标点,直接使用这个点的颜色信息rgb来进行着色.
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
//这里放弃以前的通俗解释
//UnpackNormal是标准法线解压函数,直接看源码
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}
//这里打开UnityCG.cginc,找到这个函数
//下面这些解释都是风宇冲前辈的经验之谈,想看更详细的内容百度:风宇冲
inline fixed3 UnpackNormal(fixed4 packednormal)
{
//该函数进行了预定义,如果是移动平台或者OpenGL ES,那么断定使用的是RGB法线贴图,否则则为DXT5nm贴图。
//但实际上移动平台也可以用压缩格式的法线贴图,而Windows也能使用RGB法线贴图。
#if defined(SHADER_API_GLES) && defined(SHADER_API_MOBILE)
return packednormal.xyz * 2 - 1;
#else
fixed3 normal;
normal.xy = packednormal.wy * 2 - 1;
normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
return normal;
#endif
}
ENDCG
}
}
最后一段对UnpackNormal的解释大家不理解也没关系,不影响接下来的学习,循序渐进嘛。
还是上一个官方的效果图吧
step2
使用顶点修改器进行法线挤压
沿法线移动顶点使游戏物体变得臃肿
沿着法线的方向移动顶点的位置,可以想象,其实就相当于把整个模型表面的顶点沿着法线方向“拉长了一截”,,,,而法线是垂直于各自的三角面的,,,所以,,,,它就“胖了”
Shader"Example / AutisticPatient SurfaceShader9"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//range是在Inspector面板中定义了一个滑动条,用于面板控制,不需要对代码进行修改
_Amount ("Extrusion Amount", Range(-1,1)) = 0.5
}
SubShader
{
//子着色器标签,渲染非透明物体
Tags { "RenderType" = "Opaque" }
CGPROGRAM
//定义表面着色器surf,光照模型Lambert , 顶点着色器vert
#pragma surface surf Lambert vertex:vert
struct Input
{
float2 uv_MainTex;
};
float _Amount;
//顶点着色器,vertex shader的函数名必须和#pragma 编译指令下的名称一致,否则shader找不到入口
void vert (inout appdata_full v)
{
//appdata_full 输入了一个顶点,并将顶点和当前的法线进行重叠,最后用float值进行倍增。
//还不理解的话建议拆开来看,当然要遵循运算符优先法则
//物体的法线 * float值 进行扩大
//然后顶点的xyz坐标与扩大后的法线进行叠加,顶点的xyz就会在法线的方向基础上进行不断的++
v.vertex.xyz += v.normal * _Amount;
}
sampler2D _MainTex;
//表面着色器
void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
step3
逐顶点计算的自定义数据
Shader"Example / AutisticPatient SurfaceShader10"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert vertex:vert
struct Input
{
float2 uv_MainTex;
float3 customColor;
};
//在这顶点函数内,Input结构做了输出,在表面着色器内做了输入
//以本函数为例,o.customColor输入进来并做了修改,然后作为输入传进了surf函数
void vert (inout appdata_full v, out Input o)
{
//用Unity内置的宏初始化参数
UNITY_INITIALIZE_OUTPUT(Input,o);
//将法线的绝对值赋值给输出结构体下的customColor
o.customColor = abs(v.normal);
}
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o)
{
///获取纹理的UV坐标值,并将颜色值作为最终输出赋给标准输出结构体的Albedo颜色属性
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
//这里Albedo颜色属性进行了倍增
//现在的Input结构体力的customColor不在是最初的那个啥都没有的float3了
//这里的customColor是经过顶点函数处理过的,它现在是abs(v.Normal),这么说理解了吧
o.Albedo *= IN.customColor;
}
ENDCG
}
Fallback "Diffuse"
}
step4
最终颜色修改器 (Final Color Modifier)
这是一个将色调应用于最终颜色的简单着色器。这与仅将色调应用于表面反射率 (Albedo) 颜色不同:此色调也会影响来自光照贴图、光探头和类似额外来源的任何颜色。好厉害的赶脚?
Shader"Example / AutisticPatient SurfaceShader11"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ColorTint ("Tint", Color) = (1.0, 0.6, 0.6, 1.0)
}
SubShader
{
Tags { "RenderType" = "Opaque" }
CGPROGRAM
//最终颜色修改器(Final Color ) 函数,该函数将修改由着色器计算的最终颜色。使用着色器编译指令
#pragma surface surf Lambert finalcolor:mycolor
struct Input
{
float2 uv_MainTex;
};
fixed4 _ColorTint;
//与编译指令对应,名字必须一致
void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
{
//color值会因受到_ColorTint值的改变而改变
//也就是说我们可以在Inspector面板中进行调试
color *= _ColorTint;
}
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
其实我们还可以在mycolor函数中大做文章,让效果更绚
step5
使用最终颜色修改器自定义雾
最终颜色修改器 (Final Color Modifier)(见上文)的常见应用为实现完全自定义的雾 (Fog)。雾 (Fog) 需要影响最终计算出的像素着色器颜色,这恰恰是(Final Color Modifier)修改器的用处所在。
Shader"Example / AutisticPatient SurfaceShader12"
{
//定义需要的属性
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_FogColor ("Fog Color", Color) = (0.3, 0.4, 0.7, 1.0)
}
SubShader
{
//渲染非透明物体,最近这些小的shader对标签的要求没那么严苛,以后会接触到更多的标签
Tags { "RenderType" = "Opaque" }
CGPROGRAM
//编译指令,指定表面着色器,光照模型,最终颜色修改器,和顶点着色器
#pragma surface surf Lambert finalcolor:mycolor vertex:myvert
struct Input
{
float2 uv_MainTex;
half fog;
};
void myvert (inout appdata_full v, out Input data)
{
//主要是将叫[data]的变量清空改成Input类型。
//initialize的中文含义便是初始化,试着理解
UNITY_INITIALIZE_OUTPUT(Input,data);
//mul,矩阵相乘运算函数,不需要开发者自己手动去进行矩阵相乘,有兴趣的同学可以自己实现一下这个功能
//UNITY_MATRIX_MVP是model、view、projection三个矩阵相乘出来的4x4的矩阵。
//v.vertex是一个float4的变量,可理解成4x1的矩阵(其实可以称作向量),两者相乘,则得出一个float4,这个值就是视角窗口的坐标值了
float4 hpos = mul (UNITY_MATRIX_MVP, v.vertex);
//这个fog的浮点值就是其强度,值越小就fog就越黑
//再看后面这个点积,点积运算公式:|x||y|cos(夹角)
//这个仔细一想,不难理解,其实就是为了达到一种扩散的效果,因此两个一样的向量相乘(因为cos值为1),其实就是直接对坐标做平方扩展,这样fog就更有雾的感觉。
data.fog = min (1, dot (hpos.xy, hpos.xy) * 0.1);
}
fixed4 _FogColor;
void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
{
fixed3 fogColor = _FogColor.rgb;
//官方的宏定义:正向渲染时的额外通道
//不太好理解,做个小改变将fogColor改成红色(1,0,0),外面的fog颜色改成白色,在试试效果,就会明白了
#ifdef UNITY_PASS_FORWARDADD
fogColor = (1,0,0);
#endif
//lerp函数的作用,即是在 第一个元素 与 第二个元素 之间利用 第三个元素 进行线性插值
//举例来说,当 IN.fog = 0 时,插值结果为color.rgb ,IN.fog = 1 时, 插值结果为fogColor。
//这样我们就可以通过设定IN.fog的值来控制雾化的百分比
//详细点,当IN.fog更接近0的时候,整个模型会显示大部分color.rgb,和小部分fogColor,反之同理
color.rgb = lerp (color.rgb, fogColor, IN.fog);
}
sampler2D _MainTex;
void surf (Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
}
ENDCG
}
Fallback "Diffuse"
}
step6
线性雾 (Linear Fog)
Shader"Example / AutisticPatient SurfaceShader13"
{
Properties
{
_MainTex("MainTex",2D) = "white"{}
}
SubShader
{
Tags { "RenderType"="Opaque" }
//细节层次设为:200
//它是Level of Detail的缩写,在这里例子里我们指定了其为200
LOD 200
CGPROGRAM
#pragma surface surf Lambert finalcolor:mycolor vertex:myvert
sampler2D _MainTex;
//变量名顾名思义
//uniform是常量,也就是在shader代码中不可被改变的量
uniform half4 unity_FogColor;
uniform half4 unity_FogStart;
uniform half4 unity_FogEnd;
struct Input
{
float2 uv_MainTex;
half fog;
};
void myvert (inout appdata_full v, out Input data)
{
//主要是将叫[data]的变量清空改成Input类型。
//initialize的中文含义便是初始化,试着理解
UNITY_INITIALIZE_OUTPUT(Input,data);
//mul,矩阵相乘运算函数,不需要开发者自己手动去进行矩阵相乘
//mul计算结束后得到的是一个float4,然后取出其xyz
//length函数用于取一个向量的长度,如果是float3则采取如下形式:
//float length(float3 v)
//{
// return sqrt(dot(v,v));
//}
float pos = length(mul (UNITY_MATRIX_MV, v.vertex).xyz);
float diff = unity_FogEnd.x - unity_FogStart.x;
float invDiff = 1.0f / diff;
//clamp(x,a,b) 如果x 值小于 a,则返回a;如果 x 值大于 b,返回b;否则,返回 x
data.fog = clamp ((unity_FogEnd.x - pos) * invDiff, 0.0, 1.0);
}
//跟上一篇一样,不赘述了
void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
{
fixed3 fogColor = unity_FogColor.rgb;
//正向渲染时的额外通道
#ifdef UNITY_PASS_FORWARDADD
fogColor = 0;
#endif
color.rgb = lerp (fogColor, color.rgb, IN.fog);
}
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
}
}
}
从这几篇开始,我已经开始慢慢的放弃一些代码的注释了,原因呢(实在是直白的就略过了)
本系列结束了,都是些简单到不能再简单的东西,感觉就像是复习了一遍3D数学基础,和API
接下来会进行官网的其他系列翻译,我在发表之前会吸收很多前辈的经验,当然肯定有理解的不够深刻的地方,欢迎指正,共同学习。
所有的我参考过的博客都会放第二个文章里