找回密码
 立即注册
查看: 355|回复: 5

Unity Shader 透视扫描效果

[复制链接]
发表于 2022-2-28 16:01 | 显示全部楼层 |阅读模式
大家好,我是老莫,今天学习一下游戏中的透视扫描效果。
本文在unity built-in管线中实现,在新版URP管线中,已经可以通过render feature功能实现,操作简单效率高。后期我会再写一篇关于render feature的使用方法。
但这篇文章可以让你更深入地了解菲涅尔效果的实现、深度测试的原理、pass的调用。
<hr/>首先看一下成品。


分析
这个效果很简单,只有两个部分:扫描效果+遮挡透视
<hr/>首先,我们来只做扫描效果。
添加三个属性:颜色、线条宽度、扫描速度
       [HDR]_LineColor("Color",Color) = (1,1,1,1)
        _LineSize("LineSize",float)   = 1
        _Speed("Speed",float)         = 1
注意,我们的扫描效果是包含了边缘光的,所以先实现菲涅尔效果。
打开菲涅尔效果公式。
fresnel = fresnel基础值 + fresnel缩放量*pow( 1 - dot( N, V ), 5 )
float fresnel = (0.2 + 2.0 * pow (1.0 - ndotv, 2.0));后面我再写一篇关于菲涅尔原理的文章,这里不多赘述。我们在unityshader中实现菲涅尔效果,最重要的两个向量是摄像机到顶点的方向(Vdir),顶点的法线方向(Ndir),进行点击运算。其Vdir的获取方式是摄像机的坐标减去顶点坐标,Ndir的则直接用unity内置函数获取,使用前对两个值进行归一化处理。
                fixed3 V = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 N = normalize(i.worldNormal);
                fixed VdotN = dot(V,N);
写完公式,开始添加我们所需要的参数,分别是世界空间下的顶点坐标和世界空间下的法线方向。这里我喜欢先写公式后加参数,按个人喜好来。
v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld ,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
return VdotN,我们看一下效果。


这里已经有了菲涅尔效果的雏形,我们接着套用菲涅尔公式,乘以一个颜色。这里我暴露了一个属性_FresnelWidth。
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 frag (v2f i) : SV_Target
            {
                fixed3 V = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 N = normalize(i.worldNormal);
                fixed VdotN = dot(V,N);
                fixed fresnel = 0.2+2* pow(1-VdotN,_FresnelWidth);
                fixed4 col = tex2D(_MainTex, i.uv);
                return fresnel * _LineColor;
            }


感觉有点生硬,把材质球改为透明材质,并且高亮叠加
Tags { "Queue" = "Transparent" }
        Blend One One


菲尼尔效果完成后,开始制作扫描效果。
首先是得到一个模型水平方向的线条,对模型世界空间下的顶点坐标Y方向做frac处理。frac可以得到一张0-1的渐变图,原理也很简单,为了使效果更明显,我在此基础上乘以了10。
                fixed line_Y = frac(i.worldPos.y*10);
                return fresnel * _LineColor*line_Y;


到这一步,线条还是固定不动的,再做一个Y方向的流动效果,这时候就用上了我们一开始准备好的_Speed和_LineWidth属性
                fixed line_Y = frac(i.worldPos.y*_LineSize+_Time.y*_Speed);
               


<hr/>扫描效果完成后,开始实现透视功能
透视功能的原理可以简单概括为,当模型深度大于另一个模型时,则通多渲染,否则不通过。
所谓深度,也就是顶点距离摄像机的距离,距离越远,深度值越大,反之越小。所以模型深度值大于另一个模型,也就是代表它被这个模型遮挡住了。
这里就不得不用到深度测试了,并且设为Greater。


现在用一个Cube遮挡住模型,看一下效果




现在,我们只需要保存shader文件,随便打开一个shader,在里面调用它就可以了。
首先将这个shader中的pass命名
            Name "TEST"
            Tags { "Queue" = "Transparent" }
            Blend One One
            ZTest Greater
在新创建的shader中调用此pass
UsePass"MyShader/Xray02/TEST"

这时候遇到了bug,看样子应该是调用成功了,因为深度测试已经被打开,并且是按照Greater执行的。此时我们需要打开frame debug,寻找原因。




可以看到本来已经实现了pass的调用,效果也是对的,但是在mesh渲染第二个pass(也就是自身原有的pass)时,纹理又被覆盖了。问了各位大佬和查阅资料后,最终解决方法是在调用的pass(透视效果)中,在深度测试后关闭深度写入。
原因是unityshader中默认是ZWrite On,在我们UsePass后,下一个Pass中再次进行了深度写入,这样原有的测试就失效了,也就无法判断哪些像素通过了深度测试,从而看不到透视效果。
            Name "TEST"
            Tags { "Queue" = "Transparent" }
            Blend One One
            ZTest Greater
            ZWrite Off


<hr/>到此为止,就完成了unity shader透视效果的实现,谢谢大家,欢迎大佬们指点。
写文章真的是个体力活,求求各位大佬收藏的同时点个赞,评论个666也行。        
<hr/>下面是源码
Shader "MyShader/Xray02"
{
    Properties
    {
        _MainTex ("Texture", 2D)         = "white" {}
        [HDR]_LineColor("Color",Color) = (1,1,1,1)
        _FresnelWidth ("FresnelWidth",float) = 1
        _LineSize("LineSize",float)      = 1
        _Speed("Speed",float)            = 1
    }
    SubShader
    {
        Pass
        {
            Name "TEST"
            Tags { "Queue" = "Transparent" }
            Blend One One
            ZTest Greater
            ZWrite Off
            LOD 100
            
            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 worldPos : TEXCOORD1;
                half3 worldNormal: TEXCOORD2;
            };

            sampler2D _MainTex;float4 _MainTex_ST;
            fixed4 _LineColor;
            float _LineSize;
            float  _Speed;
            float _FresnelWidth;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 V = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 N = normalize(i.worldNormal);
                fixed VdotN = dot(V,N);
                fixed fresnel = 2* pow(1-VdotN,_FresnelWidth);
                fixed4 col = tex2D(_MainTex, i.uv);

                fixed line_Y = frac(i.worldPos.y*_LineSize+_Time.y*_Speed);
                return fresnel * _LineColor*line_Y;
            }
            ENDCG
        }
    }
}

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-2-28 16:07 | 显示全部楼层
[思考]
发表于 2022-2-28 16:14 | 显示全部楼层
大佬,在透视pass中  深度测试后面 关闭深度写入。 直接没有了透视效果[捂脸]
发表于 2022-2-28 16:23 | 显示全部楼层
创建一个新的shader调用该pass试试
发表于 2022-2-28 16:24 | 显示全部楼层
我把两个pass写一个shader里没问题,  但是用别的shader  UsePass调用就不行。。。
发表于 2022-2-28 16:29 | 显示全部楼层
1、检查UsePass"XX/XX/XX"引号中你的shader层级对不对
2、UsePass必须和其它Pass写在同一个SubShader中,与其它Pass同级
3、将调用的Pass中的属性复制到你所是用的shader——properties中
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 17:31 , Processed in 0.094888 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表