美文网首页Unity3D
【Unity3D】选中物体消融特效

【Unity3D】选中物体消融特效

作者: LittleFatSheep | 来源:发表于2023-10-12 00:05 被阅读0次

    1 消融特效原理

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

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

    本文完整资源见→Unity3D选中物体消融特效,Shader Graph 实现的消融特效见→消融特效

    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】选中物体消融特效

        本文链接:https://www.haomeiwen.com/subject/jdvnbdtx.html