Shader 笔记三 SubShader 的一些配置

Tags

表面着色器可以被若干的标签(tags)所修饰,硬件将通过判定这些标签来决定什么时候调用该着色器。例如Tags { "RenderType"="Opaque" },告诉了系统应该在渲染非透明物体时调用我们。Unity定义了一些列这样的渲染过程,与RenderType是Opaque相对应的显而易见的是"RenderType" = "Transparent",表示渲染含有透明效果的物体时调用。在这里Tags其实暗示了你的Shader输出的是什么,如果输出中都是非透明物体,那写在Opaque里;如果想渲染透明或者半透明的像素,那应该写在Transparent中。

另外比较有用的标签还有"IgnoreProjector"="True"(不被投影(Projectors)影响),"ForceNoShadowCasting"="True"(从不产生阴影)以及"Queue"="xxx"(指定渲染顺序队列),如

Tags
{
    "Queue" = "Transparent"
    "IgnoreProjector" = "True"
    "RenderType" = "Transparent"
}

这里想要着重说一下的是Queue这个标签,如果你使用Unity做过一些透明和不透明物体的混合的话,很可能已经遇到过不透明物体无法呈现在透明物体之后的情况。这种情况很可能是由于Shader的渲染顺序不正确导致的。Queue指定了物体的渲染顺序(数值越小,渲染的越早),预定义的Queue有:

渲染队列渲染队列描述渲染队列值
Background这个队列被最先渲染。它被用于skyboxes等。1000
Geometry这是默认的渲染队列。它被用于绝大多数对象。不透明几何体使用该队列。2000
AlphaTest通道检查的几何体使用该队列。它和Geometry队列不同,对于在所有立体物体绘制后渲染的通道检查的对象,它更有效。2450
Transparent该渲染队列在Geometry和AlphaTest队列后被渲染。任何通道混合的(也就是说,那些不写入深度缓存的Shaders)对象使用该队列,例如玻璃和粒子效果。3000
Overlay该渲染队列是为覆盖物效果服务的。任何最后被渲染的对象使用该队列,例如镜头光晕。4000

在我们实际设置Queue值时,不仅能使用上面的几个预定义值,我们也可以指定自己的Queue值,写成类似这样:"Queue"="Transparent+100",表示一个在Transparent之后100的Queue上进行调用。通过调整Queue值,我们可以确保某些物体一定在另一些物体之前或者之后渲染,这个技巧有时候很有用处。

 

LOD

LOD(Level of Detail),如LOD 200,即指定了其为200(其实这是Unity的内建Diffuse着色器的设定值)。这个数值决定了我们能用什么样的Shader。在Unity的Quality Settings中我们可以设定允许的最大LOD,当设定的LOD小于SubShader所指定的LOD时,这个SubShader将不可用。Unity内建Shader定义了一组LOD的数值,我们在实现自己的Shader的时候可以将其作为参考来设定自己的LOD数值,这样在之后调整根据设备图形性能来调整画质时可以进行比较精确的控制。

Unity中内建的着色器的LOD设置参数
名称
VertexLit及其系列100
Decal, Reflective VertexLit150
Diffuse200
Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit250
Bumped, Specular300
Bumped Specular400
Parallax500
Parallax Specular600

 

Cull(剔除)

通过这个设置,我们可以剔除用户看不见的面,用在不同的情况中,以优化性能。有如下三个属性

Cull Back(默认)不渲染背面
Cull Front不渲染正面
Cull Off关闭剔除效果,两面都渲染

例如在场景中创建一个Plane(默认的Cull Back),可以在Scene视图中正常看见该Plane,但是当你将Scene中的视角移到Plane的背面的时候,会发现该Plane消失了,这就是Cull Back的效果。由此可推出,Cull Front则反之,Cull off则两面都可以看见(大家可多动手试试加深印象)

 

ColorMask

通过这个设置可以设置输出的颜色通道,(R,G,B,A四个参数可以任意搭配)

RGBA四个通道的颜色都输出
RG只输出R,G两个通道的颜色
R只输出R通道的颜色
...RGBA搭配组合
0不输出颜色

我们还可以在Properties中设置参数,类型为float,然后通过参数给ColorMask赋值,RGBA用四位的二进制表示,二进制1111即为15,表示RGBA全部输出,二进制1010即为10,表示只输出RB两个通道。

Properties {
	_ColorMask("Color Mask", Float) = 14
}

SubShader {
    ColorMask [_ColorMask]
}

注:若直接填写 ColorMask 15 是会报错的。

 

ZWrite(深度写入) / ZTest(深度测试)

ZWrite决定片元的深度值是否写入深度缓冲,可配置(ZWrite On(默认) / ZWrite Off)

ZTest开启后测试结果决定片元是否被舍弃,有如下配置

ZTest Less深度小于当前缓存则通过
ZTest Greater深度大于当前缓存则通过
ZTest LEqual(默认)深度小于等于当前缓存则通过
ZTest GEqual深度大于等于当前缓存则通过
ZTest Equal深度等于当前缓存则通过
ZTest NotEqual深度不等于当前缓存则通过
ZTest Always不论如何都通过(ZTest Off 等同于 ZTest Always)

系统中存在一个颜色缓冲区和一个深度缓冲区,分别存储颜色值和深度值,来决定画面上应该显示什么颜色。深度值是物体在世界空间中距离摄像机的远近。距离越近,深度值越小;距离越远,深度值越大。

主要流程如下:首先判断是否开启了深度测试,若开启了,则比较该片元的深度值和已经存在于深度缓冲区的深度值,根据配置得到结果。如果通过了深度测试,则再去判断是否开启深度写入,若开启了深度写入则将深度值写入深度缓冲区(未开启即不写入),最后结束流程,写入颜色缓冲区,若没有通过深度测试则直接舍弃该片元,结束流程。若没有开启深度测试,则直接去判断是否开启了深度写入,并执行后面的相关流程。

可以归纳为

1.深度测试通过,深度写入开启:写入深度缓冲区,写入颜色缓冲区 

2.深度测试通过,深度写入关闭:不写深度缓冲区,写入颜色缓冲区 

3.深度测试失败,深度写入开启或关闭:不写深度缓冲区,不写颜色缓冲区

 

blend(混合)

Blend 主要作用是颜色混合,颜色混合是在所有渲染完成图像都成型后进行的,这时像素都已经被绘制好了,并且屏幕上也已经有成像的图形,但还没有完全完成,因为是物体各自画各自的已经画完了放入了缓存中,但alpha还没有发挥出效果,这时就需要混合来做补充,这之前的屏幕上所有的图像都是实体的,没有半透明和透明部分。总之Blend最重要的作用就是让alpha起到半透明的作用,当然也可以做到颜色加强,改变,混合,削弱等作用。

详见:《Unity3D高级编程之进阶主程》第七章,Shader(六) - Pass(三)Blend

我们将片段着色器中计算出来的颜色称之为 “源颜色”,帧缓存中对应的像素已经存在的颜色叫做“目标颜色”(背景色)。混合操作就是将源颜色与目标颜色以一些选项进行结合。格式如下:

Blend SrcFactor DstFactor

我们选择混合的选项的过程是通过以下面的等式来进行RGBA颜色的计算的:

float4 result = SrcFactor * fragment_output + DstFactor * pixel_color;

其中SrcFactor和DstFactor可以取下面这些值(假设原颜色为A, 原通道为A_alpha,目标颜色为B,目标通道为B_alpha)

One

值为1,使用此因子来让帧缓冲区源颜色或是目标颜色完全的通过。* 1
Zero值为0,使用此因子来删除帧缓冲区源颜色或目标颜色的值。 * 0
SrcColor使用此因子为将当前值乘以帧缓冲区源颜色的值  * A
SrcAlpha使用此因子为将当前值乘以帧缓冲区源颜色Alpha的值。 * A_alpha
DstColor使用此因子为将当前值乘以帧缓冲区目标颜色的值。* B
DstAlpha使用此因子为将当前值乘以帧缓冲区目标颜色Alpha分量的值。 * B_alpha
OneMinusSrcColor使用此因子为将当前值乘以(1 -帧缓冲区源颜色值)  * (1 - A)
OneMinusSrcAlpha使用此因子为将当前值乘以(1 -帧缓冲区源颜色Alpha分量的值)  * (1 - A_alpha)
OneMinusDstColor使用此因子为将当前值乘以(1 –目标颜色值)  * (1 - B)
OneMinusDstAlpha使用此因子为将当前值乘以(1 –目标Alpha分量的值)  * (1 - B_alpha)

例如:

Blend SrcAlpha OneMinusSrcAlpha源颜色 x 源通道  +  目标颜色 x(1 - 源通道) A * A_alpha + B * (1 - A_alpha)
Blend One One源颜色 + 目标颜色(叠加模式)A + B
Blend SrcAlpha One源颜色 x 源通道 + 目标颜色(带通道的叠加模式)A * A_alpha + B
Blend DstColor SrcColor源颜色 x 目标颜色 + 目标颜色 x 源颜色(正片叠底的效果)A * B + A * B
Blend DstColor Zero源颜色 x 目标颜色 + 0(目标颜色 x 0)A * B + 0

 

颜色的相加与相乘:

1,颜色乘法,可以视为颜色(或者说图像)的叠加

为什么乘法颜色节点能够让颜色叠加。 我们把两个RGBA值转换成为0-1之间的范围就可以明白了。 比如白色(1,1,1,1),灰色(0.5,0.5,0.5,1),两个颜色相乘得到(0.5,0.5,0.5,1) 还是灰色,也就是说在白色的底板上,画上灰色的颜色,颜色就叠加了,这就是颜色的叠加方法。

颜色相乘的公式是 Color a, Color b, a*b = new Color(a.r * b.r, a.g * b.g, a.b * b.b, a.a * b.a); 从此公式可以看出,因为所有数字都小于等于1,所以相乘只会让颜色更加深,也就是更加接近黑色,不断的叠加各种各样的颜色,最后都会无限接近黑色(0,0,0,1)或者无限接近黑色透明(0,0,0,0)

2,颜色加法,可以视为颜色(或者说图像)加白(或者说亮)。

为什么说加法可以视为颜色的加白操作。因为颜色相加后,不能超过最大值1(或者另一种表达方式的数字255),超过部分都会被截断。加法的公式是:Color a, Color b, a+b = new Color(a.r + b.r, a.g + b.g, a.b + b.b, a.a + b.a); 从此公式可以看出,所有的数字都小于等于1,所以r,g,b,a,随着不断加各种颜色,最终会到达最大值1。也就是不透明白色(1,1,1,1)

 

实例演示

举几个简单的例子来对上面的一些知识点进行深入理解,小伙伴们自己也可以动手试试举一反三。

首先我们先创建两个最简单的shader,Custom/ShaderDemo1和Custom/ShaderDemo2,内容相同如下(名称不一样),并分别挂在两个新的material下。同时光源的颜色设置为白色,去除光源摄像机的阴影效果。

Shader "Custom/ShaderDemo1" {
	
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
	}
	
	SubShader {
		Tags { "RenderType"="Opaque"}
		
		CGPROGRAM
		#pragma surface surf Lambert//使用Lambert光照色差较小

		fixed4 _Color;

		struct Input {
			float3 viewDir;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			o.Albedo = _Color.rgb;
			o.Alpha = _Color.a;
		}
		ENDCG
	}
}

然后我们在场景中创建两个Cube,挂上新的material,color值自行设置(例子中为蓝色对应shader1和绿色对应shader2)。将摄像机的Clear Flags设置为Solid Color,Background自行设置即可(例子中为红色,方便看效果)。如下。

在这里我们做个假设,由于蓝色方块离摄像机较近,我们暂定为蓝色方块的深度值为0.3,绿色方块的深度值为0.6,摄像机的深度值为1。

打开Window->Frame Debugger可以查看渲染的顺序,我们可以看到系统先渲染了蓝色方块,然后渲染绿色方块(修改Queue值可以修改渲染顺序,例子中Queue值相同),由于默认的Zwrite On与ZTest LEqual(深度小于等于当前缓存则通过)。所以系统会先渲染蓝色方块,现在缓存中对比屏幕的深度值1。0.3<1即全部通过。显示蓝色方块并将0.3的深度值写入缓存中。接着渲染绿色方块的时候。被遮挡的部分深度值0.6与缓存中的0.3对比,0.6>0.3不通过则不渲染,未遮挡的部分与1比较,0.6<1,渲染。得到上面的画面。

这个时候如果我们将蓝色方块的shader1添加一句ZWrite Off,即渲染完蓝色方块后不会将蓝色方块的深度值写入深度缓存。所以再之后渲染绿色方块的时候,将和深度1相比较,全部通过。

假设我们shader1继续为ZWrite Off,但是将绿色的shader2修改为ZTest GEqual(深度大于等于当前缓存则通过)。即渲染绿色的时候被遮挡部分0.6>0.3,通过渲染,未被遮挡部分0.6<1,未通过不渲染,如下:

有关ZWrite 和 ZTest 的暂时就举例这么多,小伙伴们可以自己试试其他参数其他情况。

假如现在我们要让蓝色的小方块变为透明,但是只修改蓝色material的color的布尔值并不会起效果,根据第一篇的内容,我们需要将fullforwardshadows改为alpha。效果如下

可以发现,虽然蓝色方块确实变透明了,但是绿色方块的层级并不对。但是在shader中我们并没有关闭ZWrite和修改ZTest。通过Frame Debug我们可以发现,渲染顺序依旧是先蓝色后绿色,但是蓝色的ZWrite却被关闭了(透明物体不写入深度缓存)。根据上面的知识,我们应该将蓝色方块的material的Render Queue值设为Transparent(先渲染绿色方块再渲染蓝色方块),即可得到正确的效果

 

接下来我们试试Blend。新建一个Shader如下:

Shader "Custom/BlendDemo" {
	Properties {
		_Color("Color", Color) = (1,1,1,1)
	}

	SubShader {
		Tags{ "RenderType" = "Transparent"  "Quene" = "Transparent" }
		
		Pass {
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata {
				float4 vertex : POSITION;
			};

			struct v2f {
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v) {
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				return o;
			}
			
			fixed4 _Color;

			fixed4 frag (v2f i) : SV_Target {
				return _Color;
			}
			ENDCG
		}
	}
}

同样新建一个material关联此shader并绑定到cube_blue上,cube_green不变,此时不管怎么改变新的shader上颜色的透明度,发现都不会有效果。

此时假设我们蓝色方块的RGB值为(0,255,255)即(0,1,1),透明度为0.7。绿色方块的RGB值为(0,255,0)即(0,1,0),透明度为1,背景红色RGB值为(255,0,0)即(1,0,0),透明度为1。

想要实现透明,我们可以加一句Blend SrcAlpha OneMinusSrcAlpha即可。效果如下:

通过上面的知识,我们知道SrcAlpha是自己的透明通道即值为0.7,OneMinusSrcAlpha即为1 - 0.7 = 0.3。

蓝色和红色的部分,原颜色即为蓝色(0,1,1),目标颜色即为红色(1,0,0)得

最终颜色 = (0,1,1) * 0.7 + (1,0,0) * 0.3 = (0,0.7,0.7) + (0.3,0,0) = (0.3,0.7,0.7) = (77,179,179)

蓝色和绿色的部分,原颜色即为蓝色(0,1,1),目标颜色即为绿色(0,1,0)得

最终颜色 = (0,1,1) * 0.7 + (0,1,0) * 0.3 = (0,0.7,0.7) + (0,0.3,0) = (0,1,0.7) = (0,255,179)

我们通过颜色选择工具获取透明蓝色方块的两种颜色可以发现正确无误。

 

 

 

 

 

 

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,Shader中的SubShader是一个用于定义渲染器如何处理特定平台和功能的代码块。每个SubShader都包含了一组渲染通道(Pass),用于指定渲染的过程和效果。以下是SubShader的基本结构: ```shader SubShader { // Tags 和 LOD 等属性设置 // 渲染状态设置 // 渲染通道定义 } ``` 在SubShader中,你可以设置一些标签(Tags)和LOD(Level of Detail)属性来控制渲染器的行为。这些属性可以影响Shader在不同平台和质量设置下的表现。 接下来,你可以设置渲染状态(Render States),包括剔除模式、混合模式、深度测试等。这些设置会影响渲染过程中的渲染状态。 最后,在SubShader中定义一个或多个渲染通道(Pass)。渲染通道描述了一个渲染步骤,包括顶点和片元着色器以及其他必要的渲染设置。你可以为不同的渲染效果定义多个渲染通道。 下面是一个示例,展示了SubShader的结构: ```shader SubShader { Tags { "RenderType" = "Opaque" } LOD 100 CGPROGRAM // 顶点着色器和片元着色器定义 Pass { // 渲染通道的属性设置和渲染状态 CGPROGRAM // 渲染通道的顶点着色器和片元着色器代码 ENDCG } Pass { // 另一个渲染通道的属性设置和渲染状态 CGPROGRAM // 另一个渲染通道的顶点着色器和片元着色器代码 ENDCG } } ``` 在这个例子中,SubShader包含了两个Pass,每个Pass都有自己的渲染设置和着色器代码。 通过这种方式,你可以在Shader中定义不同的SubShader和渲染通道,以满足不同平台、质量等要求,并实现各种渲染效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值