首页 > 其他分享 >【Unity3D】激光灯、碰撞特效

【Unity3D】激光灯、碰撞特效

时间:2023-03-21 20:56:23浏览次数:62  
标签:特效 Unity3D void 碰撞 private renderer 激光灯

1 需求描述

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

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

img

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

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 appdata {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f {
                float4 worldPos : TEXCOORD0;
                float3 worldNormal : Normal;
                float4 clipPos : SV_POSITION;
            };

            v2f vert(appdata input) {
                v2f output;
                output.worldPos = mul(unity_ObjectToWorld, input.vertex); // 世界坐标系下顶点坐标
                output.worldNormal = UnityObjectToWorldNormal(input.normal); // 世界坐标系下顶点法线向量
                output.clipPos = UnityObjectToClipPos(input.vertex); // 裁剪坐标系下顶点坐标
                return output;
            }

            fixed4 frag(v2f input) : SV_Target {
                if(_Enable == 1)
				{
                    float3 worldLightDir = normalize(UnityWorldSpaceLightDir(input.worldPos)); // 世界坐标系下由顶点指向光源的方向向量
                    float diffuse = (0.1 * dot(worldLightDir, input.worldNormal.xyz) + 0.9); // 漫反射颜色强度
                    float dist = distance(input.worldPos.xyz, _HitPos);
                    float alpha = pow(exp(-dist), 4); // 透明度(随距离衰减)
                    return float4(diffuse , 0, 0, alpha);
				}
				else
				{
					return float4(0, 0, 0, 0);
				}
            }

            ENDCG
        }
    }
}

4 运行效果

img

5 推荐阅读

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

标签:特效,Unity3D,void,碰撞,private,renderer,激光灯
From: https://www.cnblogs.com/zhyan8/p/17238157.html

相关文章

  • 【Unity3D】基于模板测试和顶点膨胀的描边方法
    1前言​选中物体描边特效中介绍了基于模板纹理模糊膨胀的描边方法,该方法实现了软描边,效果较好,但是为了得到模糊纹理,对屏幕像素进行了多次渲染,效率欠佳。本文将介绍......
  • 【Unity3D】基于AssetBundle实现资源热更新
    1前言​Unity3D本地资源一般放在Resources目录下,但是Resouces文件夹的大小不能超过2G,使用AssetBundle管理资源可以解决Resources文件夹受限问题。​......
  • 【Unity3D】半球卷屏特效
    1原理​凸镜贴图和渐变凸镜贴图中介绍了使用OpenGL实现凸镜贴图及其原理,通过顶点坐标映射到纹理坐标,并构造三角形网格,构建了真正的三维凸镜模型。本文通过Shad......
  • 【Unity3D】卷轴特效
    1原理​当一个圆在地面上沿直线匀速滚动时,圆上固定点的运动轨迹称为旋轮线(或摆线、圆滚线)。本文实现的卷轴特效使用了旋轮线相关理论。​以下是卷轴特效原理及......
  • 【Unity3D】水波特效
    1水波特效原理​水波特效属于Unity3D后处理特效,其原理是:对渲染后的纹理进行局部挤压和拉升变换,即对局部uv坐标进行周期性的偏移运动,实现波纹效果。​1)波形......
  • 【Unity3D】选中物体描边特效
    1前言​描边的难点在于如何检测和识别边缘,当前实现描边特效的方法主要有以下几种:​1)基于顶点膨胀的描边方法​在SubShader中开2个Pass渲染通道,第一......
  • 【Unity3D】绘制物体表面三角形网格
    1仅绘制三角形网格​1)创建游戏对象​创建一个空对象,重命名为Grid,并在其下添加需要绘制网格的对象,如下:​场景显示如下:​2)添加脚本组件​Grid......
  • 【Unity3D】绘制物体外框线条盒子
    1需求描述​点选物体、框选物体、绘制外边框中介绍了物体投影到屏幕上的二维外框绘制方法,本文将介绍物体外框线条盒子绘制方法。内框:选中物体后,绘制物体的内框(紧......
  • 【Unity3D】点选物体、框选物体、绘制外边框
    1需求描述​绘制物体外框线条盒子中介绍了绘制物体外框长方体的方法,本文将介绍物体投影到屏幕上的二维外框绘制方法。点选物体:点击物体,可以选中物体,按住Ctrl追......
  • 【Unity3D】使用GL绘制线段
    1前言​线段渲染器LineRenderer、拖尾TrailRenderer、绘制物体表面三角形网格从不同角度介绍了绘制线段的方法,本文再介绍一种新的绘制线段的方法:使用GL绘制线段。......