Unity Shader 简单地挖一个洞

11月就要过去了,2020年已经走到尾声。从月中开始就苦苦思考有什么值得写的东西,结果发现这个月没有写什么太值得深纠的东西,就一直拖到了现在。

效果描述

其大致效果是在地上挖一个洞,然后有东西从洞里面升起来,具体参考如下:

在这里插入图片描述
关键点在于营造出地上真的挖了一个坑的效果。

想法一:抠洞

那就真的在地上挖一个洞,即在地面的Shader 中添加一个额外的Clip或者功能,然后在释放技能的时候用代码传进来一个世界坐标,此时可以用世界坐标对地面进行裁剪或者透明处理。
我这里做了一个最简单Clip的方式的挖洞,可以延申成采图进行Clip 或者透明,以下是代码:

Shader "MyShader/PalneWorld"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _GetPos ("Start Pos(XYZ)",Vector)=(0,0,0,1)
        _Radius ("Radius", Float) = 0.5
        [Toggle]_Factor ("Factor", Int) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            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;
                float3 worldPos:TEXCOORD1;
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float3 _GetPos;
            float _Radius;
            int _Factor;
            
            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).xyz;
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                float dist=distance(i.worldPos,_GetPos);
                clip(dist-_Radius*_Factor);

                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

效果:

在这里插入图片描述
但是这个思路牵扯到的地方太多,而且要改的地方比较多,就给放弃了。

想法二:模板测试

在不抠洞的前提下,这里主要是想解决中间物体穿过地面时候的穿插问题,这个问题相当头疼,唯一的办法是让穿透的物体永远通过深度测试,但是又引发一系列问题,最后不得已引入模板测试,这是最复杂的方式了。
这里将会分成三个部分:

地板上的洞

这一部分分只是个平面,只是为了遮住地板,如图:

在这里插入图片描述
看起来确实像是挖了一个洞,Shader如下:

Shader "MyShader/EffectPalne2"
{
    Properties
    {
        _Scale("Main Texture Scale", Float) = 1 
        [NoScaleOffset]
        _MainTex ("Main Texture", 2D) = "white" {}
        _NoiseTex ("Distort Texture", 2D) = "white" {}
        _Strength ("Distort Strength", Range(-1,1)) = 0.5
        _SpeedX ("Speed X", Float) = 1
        _SpeedY ("Speed Y", Float) = 1
        _Radius ("Radius", Float) = 0.5
    }
    SubShader
    {
        Tags {"RenderType" = "Opaque"}
        LOD 100

        Pass
        {
            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;
                float4 screenPos:TEXCOORD1;
            };
            
            sampler2D _MainTex;
            sampler2D _NoiseTex;
            half4 _NoiseTex_ST;
            half _Scale,_SpeedX,_SpeedY,_Strength;
            float _Radius;
            
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _NoiseTex);
                o.screenPos=ComputeScreenPos(o.vertex);
            
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                float dist=distance(i.uv,0.5);
                clip(_Radius-dist);

                half t=_Time.x;
                // sample the texture
                half2 scrUV=_Scale*i.screenPos.xy/i.screenPos.w;
                //scrUV+=noise;
                scrUV+=half2(_SpeedX,_SpeedY)*t;
                half2 noiseUV=scrUV*_NoiseTex_ST.xy+_NoiseTex_ST.zw;
                half2 noise=tex2D(_NoiseTex,noiseUV).rg*_Strength;
                scrUV+=noise;
                fixed4 col = tex2D(_MainTex,scrUV);
                return col;
            }
            ENDCG
        }
    }
}

接下来是要考虑中间穿过的物体,因为正常来说,当物体和平面会被遮挡掉,如图:

在这里插入图片描述
所以,我把球的ZTest 设置成Always

在这里插入图片描述
但是这又会造成球体会永远显示,造成遮挡不正确:

在这里插入图片描述
最后想到一个办法,使用模板测试,将球的显示区域控制在一个范围内,出了这个区域球就不显示,并且这个区域要和底下的坑的区域一致,我这里用的是是一个半球:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
球(穿过物体的)Shader

Shader "MyShader/Inside"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        
        Stencil
        {
            Ref 1
            Comp Equal
        }
        
        ZWrite Off
        ZTest Always

        Pass
        {
            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;
            
            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
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

半球(遮罩物体,这里需要使用GrabPass)的Shader

Shader "MyShader/Occlusion"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags {"RenderType" = "Transparent" "Queue"="Transparent" "DisableBatching" = "True"}
        LOD 100
        Cull Off
        
        Stencil
        {
            Ref 1
            Comp Always
            Pass Replace
        }

        GrabPass{"_ScreenTex"}

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            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;
                float4 grabPos:TEXCOORD1;
            };
            
            sampler2D _MainTex;
            sampler2D _ScreenTex;
            float4 _MainTex_ST;
            
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.grabPos=ComputeGrabScreenPos(o.vertex);
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2Dproj(_ScreenTex,i.grabPos);
                //col.rgb=1-col.rgb;
                return col;
            }
            ENDCG
        }
    }
}

这里还需要注意的是遮罩的序列要要小于中间物体,好让遮罩先写入模板缓冲,比如:

在这里插入图片描述
在这里插入图片描述
但是即使这样,依旧有很大的瑕疵(包括穿插以及镜头拉近的问题),但是我之前通过一些更复杂的手法把这些瑕疵都解决了,结果今天调了一天也没调好,放弃了,今天的重点也不在它。

想法三:渲染顺序

这个法不是我自己想的,但是真的很妙,缺点就是不能使用透明,因为都是在Geometry里进行的——取决于地面所在的序列,想要透明也行那就是地面也要在透明序列里,这种情况还是少…实现的效果和第一个差不多,只是不用改动地面的Shader

在这里插入图片描述
这里我简单说一下思路,分别是地面,穿过的物体,挖洞的片片,还有底下要显示的东西,他们的序列是地面(比如是2000) > 挖洞的片片(1999)> 穿过的物体可以和底下要显示的东西一个层级(1998),这样就会出现一个穿透的效果,可以使用Offset -1,-1解决Z-Fighting问题,代码不是主要,主要是渲染序列的问题,就不贴代码了,可以看一下这个链接GroundCrack DepthMaskShader Tutorial(需要翻墙)。
好了,大概就是这么多了,28号写的,30号才写完,明天就12月啦。

在这里插入图片描述

  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
当使用Unity中的Shader来绘制一个圆时,可以通过使用一个平面的片段着色器来实现。以下是一个基本的示例代码: ```csharp Shader "Custom/CircleShader" { Properties { _Center ("Center", Vector) = (0, 0, 0, 0) _Radius ("Radius", Range(0, 1)) = 0.5 _Color ("Color", Color) = (1, 1, 1, 1) } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; float4 _Center; float _Radius; float4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { float2 uv = i.vertex.xy * 0.5 + 0.5; // 将顶点坐标转换为纹理坐标 // 计算当前像素点到圆心的距离 float dist = distance(uv, _Center.xy); // 判断当前像素点是否在圆内 if (dist <= _Radius) { return _Color; } else { discard; // 跳过该像素点的绘制 } } ENDCG } } } ``` 在上述代码中,我们定义了一个名为"Custom/CircleShader"的Shader。在Properties块中,我们定义了三个可调参数:_Center代表圆心的位置,_Radius代表圆的半径,_Color代表圆的颜色。 在SubShader块中,我们定义了一个Pass来实现顶点和片段着色器。顶点着色器(vert)将传入的顶点位置转换为裁剪空间坐标。片段着色器(frag)在每个像素上执行,根据像素距离圆心的距离判断是否在圆内,并返回对应颜色或丢弃该像素。 要使用这个Shader,将其保存为一个文件(例如CircleShader.shader),然后将其添加到Unity项目中的任何材质上。在材质上设置_Center、_Radius和_Color参数的值,即可在场景中绘制一个圆形。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值