这一篇仍是一些基本的shader操做:裁剪、透明和法向量的应用
(纠结了好久写不写这些,由于代码很简单,主要是些概念上的东西)
先来看下大概的效果图:(从左到右依次是裁剪,透明,加了法向量的透明)
(好奇怪,为啥我字那么多,提示我少于150字)
算法
Shader "LT/Lesson3_Cull" { Properties { _Color ("Color", Color) = (1, 1, 1, 1) } SubShader { Pass { Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _Color; appdata_base vert ( appdata_base input) { input.texcoord = input.vertex ; input.vertex = mul(UNITY_MATRIX_MVP, input.vertex ); return input; } fixed4 frag (appdata_base input) : COLOR { if(input.texcoord.y < 0) { discard; } return _Color; } ENDCG } } }
代码的基本格式我就不介绍了,不熟的请看教程一
这里引入了一个新的东西: Cull Off缓存
裁剪简单的能够分为三种裁剪:擦除裁剪(discard),正面裁剪(Front)和背面裁剪(Back)
这里的Cull Off表明关闭unity自带的背面裁剪(由于若是不写的话默认为Cull Back)app
Cull Off 关闭裁剪 Cull Back 背面裁剪(默认) Cull Front 正面裁剪
而后关于这个面的正反的界定,是经过法线来完成的,法线向量>0则为正
Cull Back 和 Cull Front Unity替咱们完成了,若是须要简单的裁掉正面和反面的话直接使用预制的就好了
(Q:若是我想同时裁正面和反面怎么处理? A:若是你要同时裁掉正面和反面,你的模型就不用显示了好嘛)
这里我想经过模型的坐标位置来裁面,因此关闭了Unity的自动裁剪
而后就是把顶点信息缓存下来:函数
input.texcoord = input.vertex ;
为了方便(同时也能提升性能,因为GPU是频繁调用这些函数,因此无论什么空间啊,计算啊都能省就省),我直接将数据存在了input对象中,而后又将input返回给frag使用(这里随便选一个不用的字段用来缓存咱们的位置信息就好了,注意vertex是要使用的,因此vertex不能用来缓存这个信息)
而后在frag中判断咱们缓存的坐标信息,知足条件就discard掉性能
if(input.texcoord.y < 0) { discard; }
这里的discard至关于强行中断该次渲染,也能够说成取消渲染,这样就完成了咱们的裁剪功能了
顺带一提,discard很消耗性能的,因此能不用仍是就不用了(况且这里还多了一步if判断语句),须要指定裁剪仍是改模型来的高效,固然若是须要动态裁剪就须要这样的代码了测试
仍是先来代码
Shader "LT/Lesson3_Transparent" { Properties { _Color ("Color", Color) = (1, 1, 1, 0.5) } SubShader { Tags { "Queue" = "Transparent" } Pass { ZWrite Off Blend One One CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _Color; appdata_base vert ( appdata_base input) { input.vertex = mul(UNITY_MATRIX_MVP, input.vertex ); return input; } fixed4 frag (appdata_base input) : COLOR { return _Color; } ENDCG } } }
恩...这里呢代码的内容比较少,就是直接把外面设置的颜色应用到物体上就完了
可是多了优化
Tags { "Queue" = "Transparent" } ZWrite Off Blend One One
这三个东西
Tags { "Queue" = "Transparent" }
先来第一个Tags { "Queue" = "Transparent" }
你们可能看到有个Transparent会想要写透明是否是必需要这句话,其实否则,这行代码只是指定一个渲染顺序而已,通常状况下是具体状况具体分析,好比你但愿这个shader是渲染背景物体的时候,你能够设置为
"Queue" = "Background",须要时物体的时候设置为“Geometry”,其实对应下来的就是一个数值,这个数值越小,越先渲染,而后从咱们的渲染顺序能够理解,先渲染的天然在背后了
下面列举下经常使用的值对应的名字网站
"Background"。值为1000。好比用于天空盒。 "Geometry"。值为2000。大部分物体在这个队列。不透明的物体也在这里。这个队列内部的物体的渲染顺序会有进一步的优化(应该是从近到远,early-z test能够剔除不需通过FS处理的片元)。其余队列的物体都是按空间位置的从远到近进行渲染。 "AlphaTest"。值为2450。已进行AlphaTest的物体在这个队列。 "Transparent"。值为3000。透明物体。 "Overlay"。值为4000。好比镜头光晕。
还有就是,用户能够定义任意值,好比"Queue"="Geometry+10"spa
ZWrite Off
再来ZWriter Off,这句命令表示不写入深度缓存
呃~~~,而后咱们继续来科普概念吧(因为语文很差,后面一大段话是从别的网站复制的)3d
(1)什么是深度? 深度其实就是该像素点在3d世界中距离摄象机的距离,深度值(Z值)越大,则离摄像机越远。 (2)什么是深度缓存? 深度缓存中存储着每一个像素点(绘制在屏幕上的)的深度值!若是启用了深度缓冲区,在绘制每一个像素以前,OpenGL会把它的深度值和已经存储在这个像素的深度值进行比较。新像素深度值<原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,其颜色值和深度将被丢弃,最终屏幕显示的就是深度缓存中深度对应的像素点的颜色!(深度主要起的是比较的做用) (3)什么是深度测试? 在深度测试中,默认状况是将要绘制的新像素的z值与深度缓冲区中对应位置的z值进行比较,若是比深度缓存中的值小,那么用新像素的颜色值更新深度缓存中对应像素的颜色值。 (4)为何须要深度? 在不使用深度测试的时候,若是咱们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体由于后绘制,会把距离近的物体覆盖掉,这样的效果并非咱们所但愿的。而有了深度缓冲之后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。(越后绘制的东西,距离相机就越近) 那么,在unity中,若是知道了渲染队列,ZWrite,ZTest,如何肯定哪一个物体先显示呢? 首先,unity先将渲染队列中较前的进行渲染,而后再执行ZWrite,ZTest ZWrite能够取的值为:On/Off,默认值为On,表明是否要将像素的深度写入深度缓存中 ZTest能够取的值为:Greater/GEqual/Less/LEqual/Equal/NotEqual/Always/Never/Off,默认值为LEqual,表明如何将像素的颜色写入深度缓存中,例如当取默认值的状况下,若是将要绘制的新像素的z值小于等于深度缓存中的值,则将用新像素的颜色值更新深度缓存中对应像素的颜色值。须要注意的是,当ZTest取值为Off时,表示的是关闭深度测试,等价于取值为Always,而不是Never!Always指的是直接将当前像素颜色(不是深度)写进颜色缓冲区中;而Never指的是不要将当前像素颜色写进颜色缓冲区中,至关于消失。 由于ZWrite默认值为On,ZTest默认值为LEqual,因此这很好地解释了为何在unity中,距离相机近的东西会阻挡住距离相机远的东西。若是咱们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体由于后绘制,会把距离近的物体覆盖掉,这时咱们能够经过修改ZWrite和ZTest来改变物体的遮挡关系!
恩,好,介绍完了深度这个玩意儿咯, 那来解释下咱们为啥要关掉了(其实不关掉也能够,可是关掉GPU能够少作一步操做啊,提升性能性能性能性能性能性能,因此能关就关吧),由于咱们要写的是透明啊,无论前后顺序的,透明均可以看获得 - -
Blend One One
最后是Blend One One,这是个大概念,这个命令是写透明shader所必须的,由于它定义了透明的模式
然而,这玩意儿很简单的,命令是
Blend SrcFactor DstFactor
而后这个Factor 支持:
One 值为1,使用此设置来让源或是目标颜色彻底的经过。 Zero 值为0,使用此设置来删除源或目标值。 SrcColor 此阶段的值是乘以源颜色的值。 SrcAlpha 此阶段的值是乘以源alpha的值。 DstColor 此阶段的值是乘以帧缓冲区源颜色的值。 DstAlpha 此阶段的值是乘以帧缓冲区源alpha的值。 OneMinusSrcColor 此阶段的值是乘以(1 - source color) OneMinusSrcAlpha 此阶段的值是乘以(1 - source alpha) OneMinusDstColor 此阶段的值是乘以(1 - destination color) OneMinusDstAlpha 此阶段的值是乘以(1 - destination alpha)
而后下面是经常使用的搭配:
Blend SrcAlpha OneMinusSrcAlpha // Alpha blending alpha混合 Blend One One // Additive 相加混合 Blend One OneMinusDstColor // Soft Additive 柔和相加混合 Blend DstColor Zero // Multiplicative 相乘混合 Blend DstColor SrcColor // 2x Multiplicative 2倍相乘混合
具体效果,你们能够本身改代码看结果,这里就很少说了
Shader "LT/Lesson3_Silhouette" { Properties { _Color ("Color", Color) = (1, 1, 1, 0.5) } SubShader { Pass { ZWrite Off Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" uniform float4 _Color; appdata_base vert ( appdata_base input) { fixed3 tempNormal = normalize(mul(fixed4(input.normal, 0.0), _World2Object).xyz); fixed3 tempViewDir = normalize(_WorldSpaceCameraPos - mul(_Object2World, input.vertex).xyz); input.vertex = mul(UNITY_MATRIX_MVP, input.vertex ); input.normal.x = min(1.0, _Color.a / abs(dot(tempNormal, tempViewDir))); return input; } fixed4 frag (appdata_base input) : COLOR { return float4(float3(_Color.x,_Color.y,_Color.z), input.normal.x); } ENDCG } } }
这里没啥新东西,可是代码上把法向量用起来了(原理主要是经过法向量和摄像机朝向算出一个新的透明度用来替换而已)
解释一下吧:
fixed3 tempNormal = normalize(mul(fixed4(input.normal, 0.0), _World2Object).xyz); // 计算unity坐标系下的法向量 fixed3 tempViewDir = normalize(_WorldSpaceCameraPos - mul(_Object2World, input.vertex).xyz); // 计算unity坐标系啊的摄像机的向量 input.normal.x = min(1.0, _Color.a / abs(dot(tempNormal, tempViewDir))); // 将两个向量点乘而后换算给设定颜色的alpha通道,并缓存起来 // 至于为啥要点乘,而后用Color.a来除,这个是算法问题啦: // a = min(1, a/ |V·N|),公式是书上来的,数学问题了,不作赘述 return float4(float3(_Color.x,_Color.y,_Color.z), input.normal.x); // 使用设置的颜色的rgb值和算出来的新的alpha值
因为我的很喜欢这个效果,因此再来次效果展现
这样是否是有一点简单的立体效果咯捏(这不是光照,真的光照的反射啥的下一篇教程讲)
本篇教程的东西都很基础,可是在之后写shader的过程当中会用的很平凡,因此仍是单独拿出来说了一下,与咱们的快速上手指南其实有点背道而驰 o(╯□╰)o ,可是为了后面不会看不懂,你们仍是多本身写写熟悉一下,好比使用discard和Cull配合两个Pass写一个正反面渲染不一样颜色的shader,利用前面讲的SinTime和法向量啥的写一个动态变化效果 仍是那句话,又不懂得,QQ:821580467