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 appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
}; struct v2f {
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float3 worldNormal : Normal;
}; v2f vert(appdata 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】激光灯、碰撞特效的更多相关文章

  1. Unity3D使用碰撞体做触发器实现简单的自己主动开门

     在游戏制作中触发器的使用很的方便也很有用. 这一张我们简介一下怎样使用一个简单的触发器来实现自己主动开门关门的效果. 首先确保你已经对门进行了动画的设置. 详细流程例如以下. 选择Window- ...

  2. Unity3D --对撞机/碰撞器 介绍

    碰撞器一般都用作触发器而用,刚体一般用作真实碰撞. 静态对撞机:一个对象有对撞机组件,没有刚体组件. 这种情况在场景中的静态物体应用较多,比如墙体,房屋等静止不动的物体. 物理引擎假设静态对撞机是不会 ...

  3. Unity3d -- Collider(碰撞器与触发器)

    (2d与3d的Collider可以相互存在,但是无法相互协作,如2d是无法检测3d的,反之,一样) 在目前掌握的情况分析,在Unity中参与碰撞的物体分2大块:1.发起碰撞的物体.2.接收碰撞的物体. ...

  4. unity3d关于碰撞问题

    这个是我做忍者游戏出现的问题,做个记录也为以后有人遇到也可以借鉴.因为刚接触unity,所以对其所知甚少,说错的地方请指教. 问题:角色碰撞墙为什么会先触发碰撞地面,然后再触发碰撞墙 想要的效果:是角 ...

  5. Unity3D之碰撞体,刚体

    一 概念介绍 刚体 Rigidbody(刚体)组件可使游戏对象在物理系统的控制下来运动,刚体可接受外力与扭矩力用来保证游戏对象像在真实世界中那样进行运动.任何游戏对象只有添加了刚体组件才能受到重力的影 ...

  6. unity3d 摄像机抖动特效

    摄像机抖动特效 在须要的地方调用CameraShake.Shake()方法就能够  

  7. Unity3D图像后处理特效——Depth of Field 3.4

    Depth of Field 3.4 is a common postprocessing effect that simulates the properties of a camera lens. ...

  8. Unity3D特效-场景淡入淡出

    最近公司开始搞Unity3D..整个游戏..特效需求还是比较多的.关于UI部分的特效淡入淡出.看网上用的方法都是用个黑东东遮挡然后设置alpha这么搞....本大神感觉非常的low.而且很渣.故奋笔疾 ...

  9. [Unity3D]Unity资料大全免费分享

     都是网上找的连七八糟的资料了,整理好分享的,有学习资料,视频,源码,插件……等等 东西比较多,不是所有的都是你需要的,可以按  ctrl+F 来搜索你要的东西,如果有广告,不用理会,关掉就可以了,如 ...

  10. 《Unity3D》通过对象池模式,管理场景中的元素

    池管理类有啥用? 在游戏场景中,我们有时候会需要复用一些游戏物体,比如常见的子弹.子弹碰撞类,某些情况下,怪物也可以使用池管理,UI部分比如:血条.文字等等 这些元素共同的特性是:存在固定生命周期,使 ...

随机推荐

  1. org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 2

    1.报错 在运行SpringBoot项目时遇到报错: 17:44:47.558 [main] ERROR org.springframework.boot.SpringApplication -- A ...

  2. [转帖]Jmeter之界面语言设置

    https://developer.aliyun.com/article/1173114#:~:text=%E6%B0%B8%E4%B9%85%E6%80%A7%E8%AE%BE%E7%BD%AE%E ...

  3. [转帖]THE OSWATCHER ANALYZER USER'S GUIDE

    oswbba THE OSWATCHER ANALYZER USER'S GUIDE Carl DavisMay 7, 2019 To see how to use this tool and it' ...

  4. [转帖]金仓数据库KingbaseES误删除系统超级用户(superuser)权限的恢复方式

    https://blog.csdn.net/arthemis_14/article/details/129879269 在使用KingbaseES数据库的时候,系统默认存在一个跟系统初始化用户同名的S ...

  5. [转帖]linux 调优各项监控指标小记

    https://z.itpub.net/article/detail/8A4E4E96522BD59D45AB5A4CA442EDB3 自开始负责生产环境部署,中间遇到了若干线上环境内存以及CPU的问 ...

  6. HanLP — 感知机(Perceptron) -- Python

    HanLP - 感知机(Perceptron) 感知机 感知机是根据输入实例的特征向量 x 对其进行二类分类的线性模型: \[f(x)=sign(w\cdot x+b) \] 感知机模型对应于输入空间 ...

  7. 【发现一个小问题】坑爹的官方日志库`golang.org/x/exp/slog`,凭啥不让我设置debug级别日志

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 一个代码使用了官方的日志库"golang.org ...

  8. 收藏-即时通讯(IM)开源项目OpenIM-功能手册

    OpenIM简介 OpenIM是由IM技术专家打造的开源即时通讯组件,也是目前最受欢迎的开源IM项目之一,目前github star近万.开发者通过集成OpenIM组件,并私有化部署服务端,可以将即时 ...

  9. vim 从嫌弃到依赖(9)——命令模式进阶

    上一篇文章更新还是在51前,最近发生了很多事情了,全国各地的疫情又有蔓延的趋势,北京朝阳区都已经开始实施居家办公.各位小伙伴请注意安全,安全平安的度过这个疫情. 废话不多说了,接着上次的内容往下写. ...

  10. vim 从嫌弃到依赖(3)——vim 普通模式

    在上一篇中,我们提到vim的几种模式,并且给出了一些基本的操作命令,包括移动光标,删除.替换操作.并且给出了几个重要的公式,理解这个公式对于理解vim和提高使用vim的效率来说至关重要.所以在这篇文章 ...