Unity Shader - 模型消融/淡入淡出效果

141 篇文章 33 订阅
74 篇文章 4 订阅


之前看过一些溶解的方式来显示与消失模型,但我还没去尝试过,大概也知道如何实现。

那么先实现简单一些的

不知道为何,现在CSDN写博客会这么卡,我手提是高配版游戏本啊,真无语,因为我们到的现象是,左下角,每次提示自动保存,就会卡10秒左右。我觉得你们要是不决定这问题,迟早大家都换平台了

运行效果

在这里插入图片描述

在这里插入图片描述

上面的效果有时会抖动,是因为我的shader参数是再update里每帧传过去的,有些不够及时

消融边缘,我们还可以加上顶点挤出效果,对顶点面比较多的效果比较好,应该可以用细分、几何着色器添加顶点也行
效果如下:
在这里插入图片描述

顶点shader代码如下:

    v2f vert (appdata v) {
        v2f o;

        // 顶点在过渡的时候加一些法线挤出效果
        float3 ObjPosDir = v.vertex - p0;
        half distance = dot(ObjPosDir, upDir);
        fixed4 tintColor;
        if (distance > 0)
        {
            float t = max(0, 1 - saturate(distance / _CutRange));
            v.vertex.xyz += v.normal * _OffsetRange * _OffsetStrengthen * t;
        }

        o.objPos = v.vertex;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        o.normal = v.normal;
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);
        return o;
    }

边缘高亮

在这里插入图片描述
实际运行效果
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现思路

在这里插入图片描述

将上图思路写成代码就是

vec3 p0, p1, p2; 						// 平面三点
vec3 normalDir = cross((p1-p0),(p2-p0));// 根据两切线,算出法线
// 确定PlaneJ那面
vec3 po2J = J - p0;						// 指向J点
vec3 distance_and_facing = dot(po2J, normalDir); // 由点乘来计算:投影面向、距离
if (distance_and_facing >0) 
{ // 在plane平面上面
}
else
{ // 在plane平面下面,即:PlaneK那面
}

思路

  • 确定切面
  • 确定模型的片段在切面的哪一边
  • 根据与切面的距离做渐变过渡

确定切面

在这,一个切面,我使用的是,点法式:已知一点,与平面的法线,即可。
P , N P, N P,N
P P P是平面内的随便一个点
N N N是平面法线

并将平面信息传导shader,对应脚本:

// jave.lin 2019.08.08
using UnityEngine;

public class TestScript : MonoBehaviour
{
    private int p0Hash;
    private int upDirHash;

    private Material mat;

    public TestMoveVertices verticeObj;
    // Start is called before the first frame update
    void Start()
    {
        mat = this.GetComponent<MeshRenderer>().material;

        p0Hash = Shader.PropertyToID("p0");
        upDirHash = Shader.PropertyToID("upDir");
    }

    

    // Update is called once per frame
    void LateUpdate()
    {
        var p0 = verticeObj.p0T.transform.position;
        var p1 = verticeObj.p1T.transform.position;
        var p2 = verticeObj.p2T.transform.position;

        var upDir = Vector3.Cross(p1 - p0, p2 - p0).normalized;

        mat.SetVector(p0Hash, p0);          // 传入平面的某个点
        mat.SetVector(upDirHash, upDir);    // 传入平面法线
    }
}

确定模型的片段在切面的哪一边

我们将 P , N P,N P,N都传到了shader中,在片段着色器中,对片段判断是在切面的哪一边,使用:点乘即可:dot

如上面我手画的图中,将其中一个 P 0 P_0 P0点与法线传如shader中,然后再片段着色器中,使用当前模型空间的点 J J J减去模型空间的点 P 0 P_0 P0(这个点也是平面上的点,但我们*.cs脚本中将它转换到了模型空间中),即可得到指定该片段点的方向 P 0 J P_0J P0J。再使用该 P 0 J P_0J P0J N N N法线的点乘,就知道就在该平面的上面,还是下面(点乘>0:上面;点乘==0:共面;点乘<0:下面)。

脚本中将点,转换到对应模型空间

// jave.lin 2019.08.08
using UnityEngine;

public class TestMoveVertices : MonoBehaviour
{
    public Transform p0T;
    public Transform p1T;
    public Transform p2T;

    private Mesh mesh;

    public Transform target;

    // Start is called before the first frame update
    void Start()
    {
        mesh = GetComponent<MeshFilter>().mesh;
    }

    // Update is called once per frame
    void LateUpdate()
    {
        var o2w = this.gameObject.transform.localToWorldMatrix;     // 对象到世界
        var w2o = target.transform.worldToLocalMatrix;              // 世界到对象 
        // 先统一变换到世界空间
        var p0 = o2w.MultiplyPoint(mesh.vertices[0]);
        var p1 = o2w.MultiplyPoint(mesh.vertices[1]);
        var p2 = o2w.MultiplyPoint(mesh.vertices[2]);
        // 再变换到对应的target模型的空间,因为我们shader中在模型空间中处理计算的
        p0T.position = w2o.MultiplyPoint(p0);
        p1T.position = w2o.MultiplyPoint(p1);
        p2T.position = w2o.MultiplyPoint(p2);
    }
}

shader中求在哪一个面向:

        float3 ObjPosDir = i.objPos - p0;           // 片段模型空间与平面某个点的指向
        half distance = dot(ObjPosDir, upDir);      // 将该指向与法线点乘,就是再法线方向的投影长度,同时也是与平面的距离
        fixed4 tintColor;
        if (distance > 0) // 在上面
        {...
        }
        else// 下面
        {...
        }

根据与切面的距离做渐变过渡

计算点与面的距离

使用 P 0 J P_0J P0J与法线求点乘,就可以算出点 J J J与平面的距离

推导如下图(参考:平面方程与点到平面的距离

在这里插入图片描述
d = n ⋅ P 0 P 1 → ∣ n ∣ d=\frac{n \cdot \overrightarrow{P_0P_1}}{|n|} d=nnP0P1

而我们的 n n n我传入shader前就单位化了,所以 ∣ n ∣ = = 1 |n|==1 n==1,可以忽略

所以公式直接变换为: d d = n ⋅ P 0 P 1 → ∣ n ∣ = n ⋅ P 0 P 1 → 1 = n ⋅ P 0 P 1 → dd=\frac{n \cdot \overrightarrow{P_0P_1}}{|n|}=\frac{n \cdot \overrightarrow{P_0P_1}}{1}={n \cdot \overrightarrow{P_0P_1}} dd=nnP0P1 =1nP0P1 =nP0P1 ,在shader代码就是: d = d o t ( n , P 0 P 1 → ) d={dot(n, \overrightarrow{P_0P_1})} d=dot(n,P0P1 )

shader中求点与平面距离:

half distance = dot(ObjPosDir, upDir);

有了距离,剩下就是做lerp插值过渡处理

        if (distance > 0)                           // 在平面上面,我们就处理alpha过渡
        {
            float t = 1 - saturate(distance / _CutRange);
            tintColor = lerp(_PlaneUpColor, _PlaneIntersectedColor, t);     // 上部颜色与交点部分颜色的插值
            a = t;
        }
        else
        {
            float t = 1 - saturate(-distance / _CutRange);
            tintColor = lerp(_PlaneDownColor, _PlaneIntersectedColor, t);     // 下部颜色与交点部分颜色的插值
            a = 1;  // 下面alpha保持为1
        }

完整的shader

// jave.lin 2019.08.08
Shader "Test/Cylinder" {
    Properties {
        _PlaneUpColor ("Plane Up Color", Color) = (1,0,0,1)
        _PlaneDownColor ("Plane Down Color", Color) = (0,1,0,1)
        _PlaneIntersectedColor ("Plane Intersected Color", Color) = (1,1,0,1)
        _MainTex ("Texture", 2D) = "white" {}
        _Alpha ("Alpha", Range(0, 1)) = 0.3
        _CutRange("CutRange", Range(0, 1)) = 1
        _OffsetRange("OffsetRange", Float) = 0.3
        _OffsetStrengthen("OffsetStrengthen", Range(0, 1)) = 0.5
        _Brightness("Brightness", Range(0, 100)) = 2
    }
    
    CGINCLUDE
    #pragma vertex vert
    #pragma fragment frag
    #include "Lighting.cginc"
    #include "UnityCG.cginc"
    struct appdata {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
        float3 normal : NORMAL;
    };
    struct v2f {
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
        float3 normal : NORMAL;
        float3 worldPos : TEXCOORD01;
        float3 objPos : TEXCOORD02;
    };
    sampler2D _MainTex;
    float4 _MainTex_ST;
    fixed4 _PlaneUpColor;
    fixed4 _PlaneDownColor;
    fixed4 _PlaneIntersectedColor;
    float3 p0, upDir; // plane infos
    float _Alpha;
    float _CutRange;
    float _OffsetRange;
    float _OffsetStrengthen;
    float _Brightness;
    v2f vert (appdata v) {
        v2f o;

        // 顶点在过渡的时候加一些法线挤出效果
        float3 ObjPosDir = v.vertex - p0;
        half distance = dot(ObjPosDir, upDir);
        fixed4 tintColor;
        if (distance > 0)
        {
            float t = max(0, 1 - saturate(distance / _CutRange));
            v.vertex.xyz += v.normal * _OffsetRange * _OffsetStrengthen * t;
        }

        o.objPos = v.vertex;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        o.normal = v.normal;
        o.worldPos = mul(unity_ObjectToWorld, v.vertex);
        return o;
    }

    fixed4 frag (v2f i) : SV_Target
    {
        float a;
        /* =======1======
        float3 posDir = normalize(i.objPos - p0);
        half PdotU = dot(posDir, upDir);
        fixed4 tintColor;
        if (PdotU > 0) tintColor = _PlaneUpColor;
        else tintColor = _PlaneDownColor;
        */
        /* =======2======*/
        // 求点与平面距离
        // 参考:https://blog.csdn.net/qq_23869697/article/details/82688277
        // 其实就是求投影,刚好就是点乘:dot就完事了
        float3 ObjPosDir = i.objPos - p0;           // 片段模型空间与平面某个点的指向
        half distance = dot(ObjPosDir, upDir);      // 将该指向与法线点乘,就是再法线方向的投影长度,同时也是与平面的距离
        fixed4 tintColor;
        half bri;
        if (distance > 0)                           // 在平面上面,我们就处理alpha过渡
        {
            float t = 1 - saturate(distance / _CutRange);
            tintColor = lerp(_PlaneUpColor, _PlaneIntersectedColor, t);     // 上部颜色与交点部分颜色的插值
            a = t * _Alpha;
            bri = 1 + _Brightness * a;
        }
        else
        {
            float t = 1 - saturate(-distance / _CutRange);
            tintColor = lerp(_PlaneDownColor, _PlaneIntersectedColor, t);     // 下部颜色与交点部分颜色的插值
            a = _Alpha;  // 下面alpha保持为1
            bri = 1;
        }

        // ambient
        fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT;

        // diffuse
        half3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
        half3 normal = UnityObjectToWorldNormal(i.normal);
        half LdotN = dot(lightDir, normal) * 0.5 + 0.5;
        fixed3 diffuse = tex2D(_MainTex, i.uv).rgb * LdotN;
        diffuse *= tintColor;

        // specular
        half3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
        half3 halfVec = normalize(lightDir + viewDir);
        half HdotN = max(dot(halfVec, normal), 0);
        half3 specular = _LightColor0.rgb * pow(HdotN, 12) * LdotN;

        return fixed4((ambient + diffuse + specular) * bri, a);
    }
    ENDCG
    
    SubShader {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Pass {
            Cull Front
            ZWrite off
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
        Pass {
            ZWrite off
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
    }
}

Project

TestPlaneCut 提取码: qgnu

References

  • 3
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值