使用Unity3D实现选中物体消融特效的方法详解

 更新时间:2023年10月13日 15:40:48   作者:little_fat_sheep  
消融特效中基Shader Graph实现了消融特效,本文将基于 Shader 实现消融特效,当前实现消融特效的方法主要有 Alpha 测试消融、clip(或 discard)消融,它们的本质都是随机丢弃一些片元,以实现消融效果,文中有详细代码示例,需要的朋友可以参考下

1 消融特效原理

消融特效中基于 Shader Graph 实现了消融特效,本文将基于 Shader 实现消融特效。

当前实现消融特效的方法主要有 Alpha 测试消融、clip(或 discard)消融,它们的本质都是随机丢弃一些片元,以实现消融效果。

1)噪声纹理

为模拟随机效果,可以通过对噪声纹理进行采样实现,如下是一些常用的噪声纹理。

这些噪声纹理有一个共同特点:在较小的邻域范围内,灰度是渐变的,使得模拟的消融效果更加和谐。

2)Alpha 测试消融原理

将片元的 alpha 通道设置为随机值,通过 AlphaTest 剔除 alpha 值小于阈值的片元,以实现消融效果,代码如下。由于改变了 alpha 通道值,该方案会影响半透明物体的混合效果。

AlphaTest Greater [_AlphaThreshold]
...
fixed4 frag(v2f i) : SV_Target {
    fixed noise = tex2D(_NoiseTex, i.uvNoiseTex).r; // 噪声采样
    ...
    return fixed4(r, g, b, noise);
}

3)clip(或 discard)消融原理

对噪声纹理进行采样,使得每个片元都对应一个噪声值,通过 clip(或 discard)函数剔除噪声值小于阈值的片元,代码如下。

fixed4 frag(v2f i) : SV_Target {
    fixed noise = tex2D(_NoiseTex, i.uvNoiseTex).r; // 噪声采样
    float factor = noise - _BurnAmount;
    clip(factor); // 剔除factor小于0的片元, 即: if(factor < 0) discard;
    ...
}

2 消融特效实现

DieController.cs

using UnityEngine;
public class DieController : MonoBehaviour {
    private RaycastHit hit; // 碰撞信息
    private void Start() {
        hit = new RaycastHit();
    }
    private void Update() {
        if (Input.GetMouseButtonUp(0)) {
            GameObject hitObj = GetHitObj();
            if (hitObj != null) {
                GameObject rootObj = GetRootObj(hitObj);
                rootObj.AddComponent<DissolveEffect>();
            }
        }
    }
    private GameObject GetHitObj() { // 获取屏幕射线碰撞的物体
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit)) {
            return hit.collider.gameObject;
        }
        return null;
    }
 
    private GameObject GetRootObj(GameObject obj) { // 获取根对象
        while (obj.transform.parent != null && obj.layer == obj.transform.parent.gameObject.layer) {
            obj = obj.transform.parent.gameObject;
        }
        return obj;
    }
}

说明:DieController 脚本组件挂在相机上。

DissolveEffect.cs

using UnityEngine;
 
[DisallowMultipleComponent] // 不允许在同一对象上挂载多个该组件
public class DissolveEffect : MonoBehaviour {
    private Renderer[] renderers; // 渲染器
    private Material dissolveMat; // 消融材质
    private float burnSpeed = 0.25f; // 燃烧速度
    private float burnAmount = 0; // 燃烧量, 值越大模型镂空的越多
 
    private void Awake() {
        dissolveMat = Resources.Load<Material>("DissolveMat");
        renderers = GetComponentsInChildren<Renderer>();
    }
 
    private void OnEnable() {
        foreach (Renderer renderer in renderers) {
            Material[] materials = renderer.sharedMaterials;
            Material[] dissolveMaterials = new Material[materials.Length];
            for (int i = 0; i < materials.Length; i++) {
                Material newMaterial = new Material(dissolveMat);
                SetTexture(materials[i], newMaterial);
                SetColor(materials[i], newMaterial);
                newMaterial.SetFloat("_BurnAmount", 0);
                dissolveMaterials[i] = newMaterial;
            }
            renderer.sharedMaterials = dissolveMaterials;
        }
    }
 
    private void Update() {
        burnAmount += Time.deltaTime * burnSpeed;
        foreach (Renderer renderer in renderers) {
            Material[] materials = renderer.sharedMaterials;
            foreach (Material material in materials) {
                material.SetFloat("_BurnAmount", burnAmount);
            }
        }
        if (burnAmount >= 1f) {
            Destroy(gameObject);
        }
    }
 
    private void SetTexture(Material oldMaterial, Material newMaterial) { // 设置材质
        if (oldMaterial.HasTexture("_MainTex")) {
            Texture texture = oldMaterial.GetTexture("_MainTex");
            newMaterial.SetTexture("_MainTex", texture);
        }
    }
 
    private void SetColor(Material oldMaterial, Material newMaterial) { // 设置颜色
        Color color = Color.white;
        if (oldMaterial.HasColor("_Color")) {
            color = oldMaterial.GetColor("_Color");
        }
        newMaterial.SetColor("_Color", color);
    }
}

DissolveEffect.shader

Shader "MyShader/DissolveEffect" {
    Properties {
        _MainTex("Main Tex", 2D) = "white" {} // 主纹理
        _Color("Color", Color) = (1, 1, 1, 1) // 模型颜色
        _NoiseTex("Noise Tex", 2D) = "white"{} // 噪声纹理
        _BurnAmount("Burn Amount", Range(0, 1)) = 0 // 燃烧量, 值越大模型镂空的越多
        _LineWidth("Burn Line Width", Range(0, 0.2)) = 0.1 // 燃烧的线条宽度
        _BurnOuterColor("Burn Outer Color", Color) = (1, 0, 0, 1) // 燃烧线条的外侧颜色
        _BurnInnerColor("Burn Inner Color", Color) = (1, 0, 0, 1) // 燃烧线条的内侧颜色
    }
 
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        Pass {
            Tags { "LightMode"="ForwardBase" }
 
            Cull Off // 关闭剔除, 正反面都渲染, 因为消融会裸漏模型内部结构
            CGPROGRAM
            #include "Lighting.cginc"
 
            #pragma vertex vert
            #pragma fragment frag
            sampler2D _MainTex; // 主纹理
            fixed4 _Color; // 模型颜色
            sampler2D _NoiseTex; // 噪声纹理
            fixed _BurnAmount; // 燃烧量, 值越大模型镂空的越多
            fixed _LineWidth; // 燃烧的线条宽度
            fixed4 _BurnOuterColor; // 燃烧线条的外侧颜色
            fixed4 _BurnInnerColor; // 燃烧线条的内侧颜色
            float4 _MainTex_ST; // _MainTex的缩放和偏移
            float4 _NoiseTex_ST; // _NoiseTex的缩放和偏移
            struct a2v {
                float4 vertex : POSITION; // 模型空间顶点坐标
                float3 normal : NORMAL; // 模型空间顶点法线向量
                float4 texcoord : TEXCOORD0; // 顶点纹理坐标
            };
            struct v2f {
                float4 pos : SV_POSITION; // 裁剪空间顶点坐标
                float3 normal : Normal; // 世界空间顶点法线向量
                half2 uvMainTex : TEXCOORD0; // 主纹理的纹理坐标
                half2 uvNoiseTex : TEXCOORD1; // 噪声纹理的纹理坐标
                float3 worldPos : TEXCOORD2; // 世界空间顶点坐标
            };
            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
                o.normal = UnityObjectToWorldNormal(v.normal); // 将模型空间法线向量变换到世界空间(已归一化)
                o.uvMainTex = TRANSFORM_TEX(v.texcoord, _MainTex); // 主纹理坐标缩放和偏移
                o.uvNoiseTex = TRANSFORM_TEX(v.texcoord, _NoiseTex); // 噪声纹理坐标缩放和偏移
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; // 将模型空间顶点坐标变换到世界空间
                return o;
            }
            fixed4 frag(v2f i) : SV_Target {
                fixed noise = tex2D(_NoiseTex, i.uvNoiseTex).r; // 噪声采样
                float factor = noise - _BurnAmount;
                clip(factor); // 剔除factor小于0的片元, 即: if(factor < 0) discard;
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 世界空间灯光向量
                fixed4 albedo = tex2D(_MainTex, i.uvMainTex) * _Color; // 模型颜色
                fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo; // 环境光颜色
                fixed4 diffuse = _LightColor0 * albedo * max(0, dot(i.normal, lightDir)); // 漫反射光颜色
                fixed t = smoothstep(0, _LineWidth, factor);
                fixed4 burnColor = lerp(_BurnOuterColor, _BurnInnerColor, t);
                fixed4 finalColor = lerp(burnColor, ambient + diffuse, t);
                return fixed4(finalColor.xyz, 1);
            }
 
            ENDCG
        }
    }
 
    FallBack "Diffuse"
}

说明:在 Assets 目录下面新建 Resources 目录,接着在 Resources 目录下面创建材质,重命名为 DissolveMat,将 DissolveEffect.shader 与 DissolveMat 材质绑定,并将噪声纹理拖拽到 DissolveMat 的 Noise Tex 中。

3 运行效果

以上就是使用Unity3D实现选中物体消融特效的方法详解的详细内容,更多关于Unity3D选中物体消融特效的资料请关注脚本之家其它相关文章!

相关文章

  • C# 当前系统时间获取及时间格式详解

    C# 当前系统时间获取及时间格式详解

    这篇文章主要介绍了C# 当前系统时间获取及时间格式详解的相关资料,这里提供代码实例,帮助大家学习参考,需要的朋友可以参考下
    2016-12-12
  • C#实现简单俄罗斯方块

    C#实现简单俄罗斯方块

    这篇文章主要为大家详细介绍了C#实现简单俄罗斯方块,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • 使用C#实现将Excel转换为Markdown表格

    使用C#实现将Excel转换为Markdown表格

    Markdown 表格广泛应用于技术文档,README 文件以及各类静态站点生成器中,本文将介绍如何使用 C# 将 Excel 文件转换为 Markdown 表格,感兴趣的小伙伴可以了解下
    2026-01-01
  • C#学习笔记——基本语法

    C#学习笔记——基本语法

    本文给大家详细介绍了C#的基本语法知识以及一些基础知识的汇总,非常的简单基础,有需要的小伙伴可以参考下
    2017-02-02
  • C#高效比较两个DataTable数据差异化的方法实现

    C#高效比较两个DataTable数据差异化的方法实现

    本文主要介绍了C#高效比较两个DataTable数据差异化的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C#获取DICOM图像像素的像素值的代码详解

    C#获取DICOM图像像素的像素值的代码详解

    DICOM即医学数字成像和通信,是医学图像和相关信息的国际标准(ISO 12052),它定义了质量能满足临床需要的可用于数据交换的医学图像格式,这篇文章主要介绍了C#获取DICOM图像像素的像素值的方法,需要的朋友可以参考下
    2024-07-07
  • c#访问this关键字和base关键字示例

    c#访问this关键字和base关键字示例

    this关键字引用类的当前实例。静态成员方法中不能使用this关键字,this关键字只能在实例构造函数、实例方法或实例访问器中使用。base关键字用于从派生类中访问基类的成员。下面学习一下这二个关键字的使用方法
    2014-01-01
  • C#类的成员之Field字段的使用

    C#类的成员之Field字段的使用

    本文主要介绍了C#类的成员之Field字段的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C# 时间和日期的处理方法

    C# 时间和日期的处理方法

    这篇文章主要介绍了C#时间和日期的处理,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-06-06
  • WPF利用TextBlock实现查找结果高亮显示效果

    WPF利用TextBlock实现查找结果高亮显示效果

    在应用开发过程中,经常遇到这样的需求:通过关键字查找数据,把带有关键字的数据显示出来,同时在结果中高亮显示关键字,所以本文就来和大家介绍一下如何利用TextBlock实现查找结果高亮显示效果吧
    2023-08-08

最新评论