热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Shader笔记三SubShader的一些配置

Tags表面着色器可以被若干的标签(tags)所修饰,硬件将通过判定这些标签来决定什么时候调用该着色器。例如Tags{RenderTy

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

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

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

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

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

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

 

接下来我们试试Blend。新建一个Shader如下&#xff1a;

Shader "Custom/BlendDemo" {Properties {_Color("Color", Color) &#61; (1,1,1,1)}SubShader {Tags{ "RenderType" &#61; "Transparent" "Quene" &#61; "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 &#61; UnityObjectToClipPos(v.vertex);return o;}fixed4 _Color;fixed4 frag (v2f i) : SV_Target {return _Color;}ENDCG}}
}

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

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

想要实现透明&#xff0c;我们可以加一句Blend SrcAlpha OneMinusSrcAlpha即可。效果如下&#xff1a;

通过上面的知识&#xff0c;我们知道SrcAlpha是自己的透明通道即值为0.7&#xff0c;OneMinusSrcAlpha即为1 - 0.7 &#61; 0.3。

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

最终颜色 &#61; (0,1,1) * 0.7 &#43; (1,0,0) * 0.3 &#61; (0,0.7,0.7) &#43; (0.3,0,0) &#61; (0.3,0.7,0.7) &#61; (77,179,179)

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

最终颜色 &#61; (0,1,1) * 0.7 &#43; (0,1,0) * 0.3 &#61; (0,0.7,0.7) &#43; (0,0.3,0) &#61; (0,1,0.7) &#61; (0,255,179)

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

 

 

 

 

 

 


推荐阅读
  • vue使用
    关键词: ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文介绍了Composer依赖管理的重要性及使用方法。对于现代语言而言,包管理器是标配,而Composer作为PHP的包管理器,解决了PEAR的问题,并且使用简单,方便提交自己的包。文章还提到了使用Composer能够避免各种include的问题,避免命名空间冲突,并且能够方便地安装升级扩展包。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • Todayatworksomeonetriedtoconvincemethat:今天在工作中有人试图说服我:{$obj->getTableInfo()}isfine ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
author-avatar
小攸罗曼史
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有