ShadersRoom - 遮挡透明

先上一张效果图:


OutLine.gif

物体在被遮挡后,被遮挡的部分边缘高亮显示,实现这效果大致思路是:
  使用2个pass块来渲染这个物体,没有被遮挡的部分正常显示,被遮挡的部分则使用边缘光显示。

这里需要引入一个知识点就是深度值深度缓存(z-buffer)
在场景中的每一个物体都有自己的深度值,距离摄像机越近深度越小,越远越大;
而在屏幕上显示的每个像素点都有一个深度缓存(z-buffer),会对当前需要渲染像素的深度值进行排序,默认情况下是深度值小的像素覆盖深度值大的像素;
所以说,在渲染队列一样的情况下,近距离的像素也就是深度值小的像素会被渲染。

Unity中提供了ZWrite 和 ZTest对应深度写入和深度测试:
  ZTest控制深度测试的模式,有如下参数,默认是LEqual:


ZTest

  ZWrite则用来控制,当测试通过时,是否需要写入到深度缓存(z-buffer)中,On为写入,off不写入,如果未通过测试的话也不会写入。


ZWrite

  好了,有上述的解释后,接下来实现一shader的两个pass块,第一个pass用来显示被遮挡的模型(注意代码中的注释解释),第二个pass则正常显示;

Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeColor("Edge Color", Color) = (1,1,1,1)
    }
 SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {

            Name "Edge pass"      //显示被遮挡时的pass
            ZTest Greater         //Greater表示像素点深度值大的通过,这样就可以显示被遮挡住的模型了。

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _EdgeColor;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

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

        Pass
        {
        
            Name "Model pass"
            ZTest Less 

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

完成上述功能后,显示被遮挡模型的功能就实现啦:

OutLine_2.gif

  然后在优化一下被遮挡的显示效果,单纯的显示这个颜色显然是不太好看的,这里我们使用到的是边缘光,同时和遮挡的物体做一个透明度混合。
  首先是透明度混合,在第一个pass块中加入blend one one:

    Pass
        {

            Name "Edge pass"
            ZTest Greater
            Blend One One

透明度混合常用的几种方式如下,可以自行测试一下:

          Blend SrcAlpha OneMinusSrcAlpha // 传统透明度
          Blend One OneMinusSrcAlpha // 预乘透明度
          Blend One One // 叠加
          Blend OneMinusDstColor One // 柔和叠加
          Blend DstColor Zero // 相乘——正片叠底
          Blend DstColor SrcColor // 两倍相乘

  接下来就是边缘光的实现:
  正常来说,物体法线与视线(从顶点至相机的方向)角度越一致,则表示越接近能被玩家看见的中间,物体的边缘一般与视线垂直,所以通过物体法线和视线的点乘即可计算轮廓光,即1-dot(normal,view).

dot(normal,view)的值越到中心越大,现在我们需要的是越到边缘越大,则使用1减去该值即可。

下面添加一些计算边缘光的代码参数:
  加入一个控制边缘光范围的参数,

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeColor("Edge Color", Color) = (1,1,1,1)
        _OutLine("OutTime",range(0.5,2)) = 1   //在属性面板中加入一个控制边缘光范围的参数
    }

  加入法线和视线方向,

struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

                float3 normal : NORMAL;   //取得模型的法线

            };
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

                float3 normal : NORMAL;  //法线的方向
                float3 viewDir : TEXCOORD1;//视线的方向
            };

  在顶点函数中进行坐标系的转化以及计算视野方向,

  v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.normal = UnityObjectToWorldNormal(v.normal);   //将法线坐标转化到世界坐标
                o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);  //计算视野的方向
                return o;
            }

  最后在片元函数中计算边缘光的颜色,

fixed4 frag (v2f i) : SV_Target
            {
                float NdotV = 1 - dot(i.normal, i.viewDir) * _OutLine;  //计算边缘光,OutLine越大靠近边缘
                return _EdgeColor * NdotV;
            }

完成到这一步,遮挡透明的效果就和一开始的动图一样啦~~~

git仓库:https://github.com/Looooooong/ShadersRoom

最后附上完整的shader:

Shader "ShadersRoom/OutLine" {
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeColor("Edge Color", Color) = (1,1,1,1)
        _OutLine("OutTime",range(0.5,2)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {

            Name "Edge pass"
            ZTest Greater
            Blend One One


            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;

                float3 normal : NORMAL;

            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;

                float3 normal : NORMAL;
                float3 viewDir : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _EdgeColor;
            float _OutLine;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                o.normal = UnityObjectToWorldNormal(v.normal);   //将法线坐标转化到世界坐标
                o.viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);  //计算视野的方向

                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float NdotV = 1 - dot(i.normal, i.viewDir) * _OutLine;  //计算边缘光,OutLine越大靠近边缘
                return _EdgeColor * NdotV;
            }
            ENDCG
        }

        Pass
        {
        
            Name "Model pass"
            ZTest Less 

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • 转载自VR设计云课堂[https://www.jianshu.com/u/c7ffdc4b379e]Unity S...
    水月凡阅读 1,013评论 0 0
  • 一.需要知道的概念 1.深度缓存 它的基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时...
    无职转生者阅读 1,158评论 0 0
  • 一、前提知识 (1)深度缓存 它的基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需...
    zzqlb阅读 3,105评论 0 1
  • 转载注明出处:点击打开链接 Shader(着色器)是一段能够针对3D对象进行操作、并被GPU所执行的程序。Shad...
    游戏开发小Y阅读 3,358评论 0 4
  • Unity中两种方法实现透明效果: 1.透明度测试(Alpha Test),无法得到真正半透明效果,另外一种是透明...
    李偌闲阅读 758评论 0 0