下面是一些
表面着色器(Surface Shaders)
的示例。下面的示例都是使用的内置光照模式(lighting models),关于如何实现自定义光照模式可以参考
表面着色器光照范例(Surface Shader Lighting Examples)。
Simple 简单的示例
我们从分析和建立一个简单的着色器开始。下面是这个着色器仅仅设置了表面颜色( surface color)为"白色"。它使用了内置的 Lambert (diffuse)光照模式(lighting model)。
Shader
"Example/Diffuse Simple"
{
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float4
color
:
COLOR
;
}
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
1
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解:
struct
SurfaceOutput
{
half3
Albedo
;
//漫反射的颜色值
half3
Normal
;
//法线坐标
half3
Emission
;
//自发光颜色
half
Specular
;
//镜面反射系数
half
Gloss
;
//光泽系数
half
Alpha
;
//透明度系数
}
;
pragma surface surfaceFunction lightModel [optionalparams] surfaceFunction,没什么好说,肯定是函数名了。 lightModel是所采用的光照模型。可以自己写也可使用内置如Lambert和BlinnPhong. optionalparams:可选参数,一堆可选包括透明度,顶点与颜色函数,投射贴花shader等等。具体用到可以细选。
o.Albedo=1;漫反射的颜色是一个rgb值,如果给一个1,其实就是 float3(1,1,1),就是反射出来的颜色为白色, 如果为100,则是加强反射强度,并不会改变其颜色。为0或为负数时道理类似。 Fallback "Diffuse",Diffuse是自带的shader,可以用自己自定义好的。这里这句的意思是, 如果所有subshader在当前显卡都不支持,则默认返回自带的Diffuse。
下面是效果。它看起来像是在模型上设置了两个
光照(lights
)。
Texture 纹理
看一个白模是相当枯燥的。所以我们来给它添加纹理(texture)。我们将在着色器的
属性(Properties
)块中添加。在材质球(Material)中我们选择一个纹理(texture)。有变化的地方在下面用粗体字表示出来了。
Shader
"Example/Diffuse Texture"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
}
;
sampler2D
_MainTex
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: Tex2D,这个方法是根据UV上的点找指定 2DSample上的Texture信息,此处需要其RGB信息, 就打出来赋给了漫反射值。所以对有材质图的情况下,要显示出图,还是要相应的反射其原图的rgb值。
Normal mapping 法线贴图
我们来添加一些法线贴图(normal map)。
Shader
"Example/Diffuse Bump"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_BumpMap
(
"Bumpmap"
,
2D
)
=
"bump"
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float2
uv_BumpMap
;
}
;
sampler2D
_MainTex
;
sampler2D
_BumpMap
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
o
.
Normal
=
UnpackNormal
(
tex2D
(
_BumpMap
,
IN
.
uv_BumpMap
)
)
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: 这个UnpackNormal是unity自带的标准解压法线用的,所谓解压,就是将法线的区间进行变换。由于 tex2D(_BumpMap, IN.uv_BumpMap)取出的是带压缩的[0,1]之间,需要转成[-1,1]。这个函数会针对移动平台或OPENGL ES平台采用 RGB法线贴图,其他采用DXT5nm贴图。因此也可自己写。
Rim Lighting 边缘光照
现在我们试着添加边缘光照(Rim Lighting),在对象的边缘部分增加亮度。我们要在表面法线(surface normal)和视图方向(view direction)的基础上添加散射光照(emissive light)。为了实现它,我们要使用 viewDir,这是表面着色器(surface shader)内置的一个变量。
Shader
"Example/Rim"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_BumpMap
(
"Bumpmap"
,
2D
)
=
"bump"
{
}
_RimColor
(
"Rim Color"
,
Color
)
=
(
0
.
26
,
0
.
19
,
0
.
16
,
0
.
0
)
_RimPower
(
"Rim Power"
,
Range
(
0.5
,
8.0
)
)
=
3.0
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float2
uv_BumpMap
;
float3
viewDir
;
}
;
sampler2D
_MainTex
;
sampler2D
_BumpMap
;
float4
_RimColor
;
float
_RimPower
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
o
.
Normal
=
UnpackNormal
(
tex2D
(
_BumpMap
,
IN
.
uv_BumpMap
)
)
;
half
rim
=
1.0
-
saturate
(
dot
(
normalize
(
IN
.
viewDir
)
,
o
.
Normal
)
)
;
o
.
Emission
=
_RimColor
.
rgb
*
pow
(
rim
,
_RimPower
)
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: viewDir 意为World Space View Direction。就是当前坐标的视角方向。
最里层是Normalize函数,用于获取到的viewDir坐标转成一个单位向量且方向不变,外面再与点的法线做点积。最外层再用 saturate算出一[0,1]之间的最靠近(最小值但大于所指的值)的值。这样算出一个rim边界。为什么这么做。原理以下解释: 1.这里o.Normal就是单位向量。外加Normalize了viewDir。因此求得的点积就是夹角的cos值。 2.因为cos值越大,夹角越小,所以,这时取反来。这样,夹角越大,所反射上的颜色就越多。于是就得到的两边发光的效果。哈哈这样明了吧。 half:CG里还有类似的float和fixed。half是一种低精度的float,但有时也会被选择成与float一样的精度。 fragment是一定会支持fixed类型,同时也会有可能将其精度设成与float一样,这个比较复杂,后面篇章学到fragment时再深入探讨。
Detail Texture 细节纹理
为了实现不同的效果。让我们来添加细节纹理(detail)。它是与基础纹理(base texture)的结合。细节纹理(detail texture)与基础纹理(base texture)使用相同的UV。但是在材质球(Material)中平铺(Tiling)值通常是不同的。所以我们必须输入结构(input structure)中使用不同的UV坐标。
Shader
"Example/Detail"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_BumpMap
(
"Bumpmap"
,
2D
)
=
"bump"
{
}
_Detail
(
"Detail"
,
2D
)
=
"gray"
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float2
uv_BumpMap
;
float2
uv_Detail
;
}
;
sampler2D
_MainTex
;
sampler2D
_BumpMap
;
sampler2D
_Detail
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
o
.
Albedo
*=
tex2D
(
_Detail
,
IN
.
uv_Detail
)
.
rgb
*
2
;
o
.
Normal
=
UnpackNormal
(
tex2D
(
_BumpMap
,
IN
.
uv_BumpMap
)
)
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: 在原先的反射基础上,在加一层,Texture的反射。
使用一个方格纹理(chercker texture)并没有实际的意义。但至少让我明白了会产生什么效果:
Detail Texture in Screen Space 在屏幕空间使用细节纹理
怎样在屏幕空间(screen space)使用细节纹理(detail texture)?它对一个士兵的头部模型没有多大意义。但它说明了在输入结构(input structure)中如何使用内置的screenPos。
Shader
"Example/ScreenPos"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_Detail
(
"Detail"
,
2D
)
=
"gray"
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float4
screenPos
;
}
;
sampler2D
_MainTex
;
sampler2D
_Detail
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
float2
screenUV
=
IN
.
screenPos
.
xy
/
IN
.
screenPos
.
w
;
screenUV
*=
float2
(
8
,
6
)
;
o
.
Albedo
*=
tex2D
(
_Detail
,
screenUV
)
.
rgb
*
2
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: 是从上个例子的基础上将第二层叠加上的2D Texture根据当前屏幕的UV进行叠加,而不是根据自身的UV。这样带有含此shader材质的物体的贴图就会跟着移动到的位置而变换图片。 这里只需要说三点: 1.关于screenPos:screenPos是一个三维点,但是用齐次坐标的形式表示出来就是(x,y,z,w),根据齐次坐标的性质。 (x,y,z,w)的齐次坐标对应三维点(x/w,y/w,z/w)。因此把w值除掉可以看来是一种Normalize的作法,这样就取出了实际的屏幕 xy的UV值。 2.对screenUV进行倍剩:此处剩float2(8,6)意为将原获取到屏幕尺寸进行拉大的倍数。即x轴拉大8倍,y轴拉大6倍。 3.如何就平铺了刚好一行8个,一列6个了呢? 原因我觉得是在于2d Texture自己是按Normalize后进行铺的,因此在//2(刚转完标准的)screenPos后,将其剩多少即便将原图铺多少张。 这个东西可以拿来做放大镜的应用。
从着色器中删除法线贴图(normal mapping)只是为了试着色器代码简短一点。
Cubemap Reflection 立方图反射
在这里要在输入结构(input structure)中使用内置的worldRefl 来做立方图反射(cubemap reflection)。它实际是与内置的 Reflective/Diffuse着色器非常类似。
Shader
"Example/WorldRefl"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_Cube
(
"Cubemap"
,
CUBE
)
=
""
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float3
worldRefl
;
}
;
sampler2D
_MainTex
;
samplerCUBE
_Cube
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
*
0.5
;
o
.
Emission
=
texCUBE
(
_Cube
,
IN
.
worldRefl
)
.
rgb
;
}
ENDCG
}
Fallback
"Diffuse"
}
因为它指定了和自发光(Emission)一样的反射颜色(reflection color),所以我们得到了一个非常有光泽的士兵。
如果你想在这基础上做反射效果,它是要受到法线贴图(normal map)的影响的。这需要在输入结构(input structure)中加入一个稍微复杂的 INTERNAL_DATA。在世界反射向量(WorldReflectionVector)函数中计算每个像素(per-pixel)反射向量(reflection vector),然后你要在输出结构(output structure)中写入法线(normal)。
Shader
"Example/WorldRefl Normalmap"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_BumpMap
(
"Bumpmap"
,
2D
)
=
"bump"
{
}
_Cube
(
"Cubemap"
,
CUBE
)
=
""
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float2
uv_BumpMap
;
float3
worldRefl
;
INTERNAL_DATA
}
;
sampler2D
_MainTex
;
sampler2D
_BumpMap
;
samplerCUBE
_Cube
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
*
0.5
;
o
.
Normal
=
UnpackNormal
(
tex2D
(
_BumpMap
,
IN
.
uv_BumpMap
)
)
;
o
.
Emission
=
texCUBE
(
_Cube
,
WorldReflectionVector
(
IN
,
o
.
Normal
)
)
.
rgb
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: 这两段都是加一个cubemap的反射。第二段相比之下是在有normal反射的基础上加的。Cubemap这东西,可设置几种面的不能渲染图,这方面可用于做天空盒。因为这样可以从各个角度看过去以显示不同的渲染效果。 以下说明:
-
worldRefl:即为世界空间的反射向量。
-
texCUBE:将反射向量一个个的往_Cube反射盒上找出然后做为Emission反射出来。
-
第二个例子只是将其用在Normal反射后,这样一定要多添加一个INTERNAL_DATA的属性,另外也需用到WorldReflectionVectore方法取其利用Normal后的反射向量值。 类似于的效果,可见官网中的。我这也有一个,有点像打了光的样子。
这是一个贴了法线贴图(normal map)的有光泽的士兵。
Slices via World Space Position 通过世界空间位置进行切割
在这个着色器里,被"切割" 所抛弃的像素(pixels)形状是几乎与水平位置平行的环状。它是基于世界位置的像索(pixel)通过使用Cg/HLSL语言的 clip()函数实现。我们将使用表面着色器(surface shader)内置的worldPos变量。
Shader
"Example/Slices"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_BumpMap
(
"Bumpmap"
,
2D
)
=
"bump"
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
Cull Off
CGPROGRAM
#
pragma
surface surf Lambert
struct
Input
{
float2
uv_MainTex
;
float2
uv_BumpMap
;
float3
worldPos
;
}
;
sampler2D
_MainTex
;
sampler2D
_BumpMap
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
clip
(
frac
(
(
IN
.
worldPos
.
y
+
IN
.
worldPos
.
z
*
0.1
)
*
5
)
-
0.5
)
;
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
o
.
Normal
=
UnpackNormal
(
tex2D
(
_BumpMap
,
IN
.
uv_BumpMap
)
)
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: frac是取小数的函数,如1.23 取出来是 0.23。clip函数用于清Pixel的,负值情况下才进行清pixel。且越小,即绝对值越大则清越多。 这里注意那个 5,仔细一想,如果frac出来的值越大,-0.5值就越大,绝对值就越小,因此这样清掉的pixel越少,所以就可以间接的增加分段的次数。那为什么要+IN.worldPos.z 0.1呢,主要原因就是空开的断添加一个倾斜角度,可以用空间思想想下。
Normal Extrusion with Vertex Modifier 法线挤压与顶点修改
它可以在顶点着色器(vertex shader)中使用"顶点修改(vertex modifier)"函数修改传入的顶点(vertex)数据。它能作用在程序动画上。比如顺着法线挤压等等。表面着色器(surface shader)是通过编译vertex:functionName函数指令来使用它。这个函数传入的参数是 inout appdata_full 。
这个着色器它在材质(material)里面顶点是随着法线变化的:
Shader
"Example/Normal Extrusion"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_Amount
(
"Extrusion Amount"
,
Range
(
-
1
,
1
)
)
=
0.5
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert vertex:vert
struct
Input
{
float2
uv_MainTex
;
}
;
float
_Amount
;
void
vert
(
inout
appdata_full
v
)
{
UNITY_INITIALIZE_OUTPUT
(
Input
,
data
)
;
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"
}
注解: 这是个自定义vertex的例子,效果可以实现点坐标的放大缩小,以形成肥仔与瘦棍的效果,哈哈。 添加一个可选参数为vertex,主要是为了给其添加一个函数vert。 _Amount对应开头的那个属性_Amount。具体是个Range值,可在shader界面外通过滑动条改变这个值。默认为0.5。 v.vertex.xyz += v.normal * _Amount;就是为个点,换当前法线向量的指定倍数进行扩展。 这里除了之前学过的东西外,多了个appdata_full的结构体。这里面的结构(载自UNITY官方论坛)如下:
struct
appdata_full
{
float4
vertex
:
POSITION
;
float4
tangent
:
TANGENT
;
float3
normal
:
NORMAL
;
float4
texcoord
:
TEXCOORD0
;
float4
texcoord1
:
TEXCOORD1
;
fixed4
color
:
COLOR
;
}
;
顶点随着它的法线(normal)变化后生成一个浮肿的士兵:
Custom data computed per-vertex 用自定义数据计算每个顶点变化
在一个顶点着色器(vertex shader)中通过计算自定义数据也可以实现上面的顶点修改函数(vertex modifier function)的效果。我们这就通过表面着色器函数(surface shader function)来计算每个顶点(per-vertex)变化。同样需要使用编译vertex:functionName这个函数指令。但是这次我们传入两个参数: inout appdata_full 和 out Input o 。在这里你能传入任何一个属于输入结构(input structure)的成员,而不是内置的值。
下面的例子定义了一个自定义的float3 customColor成员,它将在顶点函数(vertex function)中运算:
Shader
"Example/Custom Vertex Data"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert vertex:vert
struct
Input
{
float2
uv_MainTex
;
float3
customColor
;
}
;
void
vert
(
inout
appdata_full
v
,
out
Input
o
)
{
UNITY_INITIALIZE_OUTPUT
(
Input
,
data
)
;
o
.
customColor
=
abs
(
v
.
normal
)
;
}
sampler2D
_MainTex
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
o
.
Albedo
*=
IN
.
customColor
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: 这个例子是用来渲染颜色的: 取一个颜色值,float3,对应RGB。 较前个例子,多一个Input类型的参数,只为输出使用。 RGB颜色值当然只能为正值,所以使用绝对值去取normal的值。 在原先已经渲染上texture颜色值的基础上,加上这层自定义的颜色值。
在这个例子里customColor值设置的是法线(normal)的绝对值。
计算所有每个顶点(per-vertex)数据可以有更多的用途。它不是提供内置的输入结构(input structure)内的变量,或者优化着色器计算。例如:它可以计算在对象的顶点((vertex)上计算边缘光照(Rim lighting),而不是在表面着色器(surface shader)的每个像索(per-pixel)内做。
Final Color Modifier 最终颜色修改
这可以使用了一个"最终颜色修改(final color modifier)"函数,这个函数将通过着色器计算变化的最终颜色。为了实现它要使用finalcolor:functionName这个表面着色器(surface shader)编译命令。这个函数传入的参数是Input IN, SurfaceOutput o, inout fixed4 color 。
下面是一个简单的着色器(shader),它适用于给最终颜色着色。这是一个特别的仅适用于给表面反射率的颜色(surface Albedo color)着色。 这个色调也会影响任何颜色的光照贴图(lightmap), 光照探测(light probes)和类似的特别资源。
Shader
"Example/Tint Final Color"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_ColorTint
(
"Tint"
,
Color
)
=
(
1.0
,
0.6
,
0.6
,
1.0
)
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert finalcolor:mycolor
struct
Input
{
float2
uv_MainTex
;
}
;
fixed4
_ColorTint
;
void
mycolor
(
Input
IN
,
SurfaceOutput
o
,
inout
fixed4
color
)
{
color
*=
_ColorTint
;
}
sampler2D
_MainTex
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: 这个例子是跟上面例子的对比,前种使用普通反射进行叠加上颜色,此处则是直接使用finalcolor对其颜色进行处理,这种可以处理整个模型的固定颜色值的渲染。以下做简要的分析: 1.finalcolor:mycolor :这个是另一种可选参数,就是用户自定义的颜色处理函数。函数名为mycolor. 2.mycolor函数:注意到函数除了有surf的两个参数外,还多了个颜色参数,这个颜色参数就是当前模型上颜色对象,对他的更改将直接影响全部来自于lightmap,light probe和一些相关资源的颜色值。
Custom Fog with Final Color Modifier 自定义雾效与最终颜色修改
在共用最终颜色修改(final color modifier)的情况下将完全实现自定义雾效。雾效需要受到最终计算像索的着色器颜色的影响。这正是finalcolor做的。
下面这个着色器适用于给基于屏幕中心距离远近的雾效来着色。这结合了顶点修改(vertex modifier)与自定义顶点数据(custom vertex data)(雾效)还有最终颜色修改(final color modifier)。当附加到正向渲染(forward render)通道(pass)中使用时,雾效(Fog)需要淡入淡出黑色。在这个例子中控制它还不如检查 UNITY_PASS_FORWARDADD。
Shader
"Example/Fog via Final Color"
{
Properties
{
_MainTex
(
"Texture"
,
2D
)
=
"white"
{
}
_FogColor
(
"Fog Color"
,
Color
)
=
(
0.3
,
0.4
,
0.7
,
1.0
)
}
SubShader
{
Tags
{
"RenderType"
=
"Opaque"
}
CGPROGRAM
#
pragma
surface surf Lambert finalcolor:final vertex:vert
struct
Input
{
float2
uv_MainTex
;
half
fog
;
}
;
void
vert
(
inout
appdata_full
v
,
out
Input
data
)
{
UNITY_INITIALIZE_OUTPUT
(
Input
,
data
)
;
float4
hpos
=
mul
(
UNITY_MATRIX_MVP
,
v
.
vertex
)
;
data
.
fog
=
min
(
1
,
dot
(
hpos
.
xy
,
hpos
.
xy
)
*
0.1
)
;
}
sampler2D
_MainTex
;
void
surf
(
Input
IN
,
inout
SurfaceOutput
o
)
{
o
.
Albedo
=
tex2D
(
_MainTex
,
IN
.
uv_MainTex
)
.
rgb
;
}
fixed4
_FogColor
;
void
final
(
Input
IN
,
SurfaceOutput
o
,
inout
fixed4
color
)
{
fixed3
fogColor
=
_FogColor
.
rgb
;
#ifdef UNITY_PASS_FORWARDADD
fogColor
=
0
;
#
endif
color
.
rgb
=
lerp
(
color
.
rgb
,
fogColor
,
IN
.
fog
)
;
}
ENDCG
}
Fallback
"Diffuse"
}
注解: mul是矩阵相乘的函数。UNITY_MATRIX_MVP是model、view、projection三个矩阵相乘出来的4x4的 矩阵。v.vertex是一个float4的变量,可理解成4x1的矩阵,两者相乘,则得出一个float4,这个值就是视角窗口的坐标值,这个坐标就跟 camera的关联了。 这个fog的浮点值就是其强度,范围一般在-1到1之间,说一般,只是我个人建议的值,设成其他也行,只是没多大意义。越负就越黑。再 看后面这个点积,这个仔细一想,不难理解,其实就是为了达到一种扩散的效果,因此两个一样的向量相乘,其实就是直接对坐标做平方扩展,这样fog就更有雾 的感觉。 这个宏不好找,就看官方对这个例子的解释为正向渲染时的额外通道。字面不好理解,多多尝试过可以有所发现,其实就是在雾气渐渐消失处那 块额外的渲染区。可以将fogColor = 0; 改成fogColor = fixed3(1,0,0)。外面雾气颜色再选成白色,雾气改成绿色后,效果则如下. lerp函数是个有趣的函数。第一个参数是左边界,第二个参数是右边界,第三个相当于一个值介于0到1之间的游标。游标为0,则为左边 界,为1为右边界,取中间值则是以此类推,取插值。其实也可以把它看成百分比。这里的fog则可以看来那个游标,值越大,则越接近fogColor,越小 越接近原色。