美文网首页Unity3D
【Unity3D】激光灯、碰撞特效

【Unity3D】激光灯、碰撞特效

作者: LittleFatSheep | 来源:发表于2023-03-19 09:49 被阅读0次

    1 需求描述

    本文将模拟激光灯(或碰撞)特效,详细需求如下:

    • 从鼠标位置发射屏幕射线,检测是否与物体发生碰撞
    • 当与物体发生碰撞时,在物体表面覆盖一层激光灯(或碰撞)特效

    本文代码见→激光灯、碰撞特效

    2 原理

    获取屏幕射线与物体的碰撞点,并在 shader 中计算顶点与碰撞点的距离(记为 dist),通过以下衰减函数计算顶点对应的透明度,透明度随碰撞点的距离增大逐渐减小,激光灯(或碰撞)效果逐渐减弱。

    alpha = pow(exp(-dist), 4)

    为使特效更加逼真,激光灯(或碰撞)特效的红色分量由以下漫反射公式控制。其中,red 为红色分量值,λ 为漫反射因子,值越大,漫反射效果越强,本文 λ = 0.1;lightDir 为顶点光源向量,normalDir 为顶点法线向量。

    red = λ * dot(lightDir, normalDir) + (1 - λ)

    3 需求实现

    SelectController.cs

    using UnityEngine;
     
    public class SelectController : MonoBehaviour { // 单击选中控制
        private Transform target; // 选中的目标
        private RaycastHit hit; // 碰撞信息
     
        private void Update() {
            if (Input.GetMouseButtonUp(0)) {
                Transform temp = GetHitTrans();
                if (temp != target) {
                    DrawEffect(target, temp);
                    target = temp;
                }
            }
        }
    
        private void DrawEffect(Transform old, Transform now) { // 绘制特效
            DrawEffect(old, false);
            DrawEffect(now, true);
        }
    
        private void DrawEffect(Transform trans, bool enable) { // 绘制特效
            if (trans != null) {
                foreach(Transform child in trans.transform.GetComponents<Transform>()) {
                    if (child.GetComponent<ColliderEffect>() == null) {
                        if (enable) {
                            child.gameObject.AddComponent<ColliderEffect>();
                        }
                    }
                    else {
                        child.GetComponent<ColliderEffect>().enabled = enable;
                    }
                }
            }
        }
     
        private Transform GetHitTrans() { // 获取屏幕射线碰撞的物体
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit)) {
                return hit.transform;
            }
            return null;
        }
    }
    

    ColliderEffect.cs

    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    [DisallowMultipleComponent]
    public class ColliderEffect : MonoBehaviour { // 激光灯(或碰撞)特效
        private Renderer[] renderers; // 当前对象及其子对象的渲染器
        private Material colliderMaterial; // 激光灯(碰撞)材质
        private Vector4 hitPos; // 碰撞点
        private RaycastHit hit; // 碰撞信息
    
        private void Awake() {
            renderers = GetComponentsInChildren<Renderer>();
            colliderMaterial = new Material(Shader.Find("MyShader/ColliderEffect"));
            hitPos = Vector4.zero;
            CombineSubmeshes();
        }
    
        private void OnEnable() {
            hitPos = GetHitPoint();
            colliderMaterial.SetVector("_HitPos", hitPos);
            foreach (var renderer in renderers) {
                List<Material> materials = renderer.sharedMaterials.ToList();
                materials.Add(colliderMaterial);
                renderer.sharedMaterials = materials.ToArray();
            }
        }
    
        private void Update() {
            hitPos = GetHitPoint();
            if (!hitPos.Equals(Vector4.zero)) {
                colliderMaterial.SetInt("_Enable", 1);
                colliderMaterial.SetVector("_HitPos", hitPos);
            } else {
                colliderMaterial.SetInt("_Enable", 0);
            }
        }
    
        private void OnDisable() {
            foreach (var renderer in renderers) {
                List<Material> materials = renderer.sharedMaterials.ToList();
                materials.Remove(colliderMaterial);
                renderer.sharedMaterials = materials.ToArray();
            }
        }
    
        private Vector4 GetHitPoint() {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, 1000) && hit.transform == transform) {
                return hit.point;
            }
            return Vector4.zero;
        }
    
        private void CombineSubmeshes() { // 绑定子网格
            foreach (var meshFilter in GetComponentsInChildren<MeshFilter>()) {
                var renderer = meshFilter.GetComponent<Renderer>();
                if (renderer != null) {
                    CombineSubmeshes(meshFilter.sharedMesh, renderer.sharedMaterials.Length);
                }
            }
            foreach (var skinnedMeshRenderer in GetComponentsInChildren<SkinnedMeshRenderer>()) {
                CombineSubmeshes(skinnedMeshRenderer.sharedMesh, skinnedMeshRenderer.sharedMaterials.Length);
            }
        }
    
        private void CombineSubmeshes(Mesh mesh, int materialsLength) { // 绑定子网格
            if (mesh.subMeshCount == 1) {
                return;
            }
            if (mesh.subMeshCount > materialsLength) {
                return;
            }
            mesh.subMeshCount++;
            mesh.SetTriangles(mesh.triangles, mesh.subMeshCount - 1);
        }
    }
    

    ColliderEffect.shader

    Shader "MyShader/ColliderEffect" {
        Properties {
            _HitPos ("HitPos", Vector) = (0, 0, 0, 0) // 屏幕射线碰撞位置
            _Enable ("Enable", Int) = 0 // 是否开启特效
        }
     
        SubShader {
            Tags {
                // 渲染队列: Background(1000, 后台)、Geometry(2000, 几何体, 默认)、Transparent(3000, 透明)、Overlay(4000, 覆盖)
                "Queue" = "Transparent+110"
                "RenderType" = "Transparent"
                "DisableBatching" = "True"
            }
     
            Pass {
                Blend SrcAlpha OneMinusSrcAlpha // 混合测试, 与背后的物体颜色混合
     
                CGPROGRAM
                #include "UnityCG.cginc"
     
                #pragma vertex vert
                #pragma fragment frag
     
                uniform int _Enable;
                uniform float4 _HitPos;
       
                struct a2v {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                };
     
                struct v2f {
                    float4 pos : SV_POSITION;
                    float4 worldPos : TEXCOORD0;
                    float3 worldNormal : Normal;
                };
     
                v2f vert(a2v v) {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex); // 裁剪坐标系下顶点坐标
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex); // 世界坐标系下顶点坐标
                    o.worldNormal = UnityObjectToWorldNormal(v.normal); // 世界坐标系下顶点法线向量
                    return o;
                }
     
                fixed4 frag(v2f i) : SV_Target {
                    if(_Enable == 1) {
                        float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 世界坐标系下由顶点指向光源的方向向量
                        float diffuse = 0.1 * dot(worldLightDir, i.worldNormal) + 0.9; // 漫反射颜色强度
                        float dist = distance(i.worldPos, _HitPos);
                        float alpha = pow(exp(-dist), 4); // 透明度(随距离衰减)
                        return float4(diffuse , 0, 0, alpha);
                    } else {
                        return float4(0, 0, 0, 0);
                    }
                }
     
                ENDCG
            }
        }
    }
    

    4 运行效果

    5 推荐阅读

    声明:本文转自【Unity3D】激光灯、碰撞特效

    相关文章

      网友评论

        本文标题:【Unity3D】激光灯、碰撞特效

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