Unity3D激光灯碰撞特效
Posted little_fat_sheep
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D激光灯碰撞特效相关的知识,希望对你有一定的参考价值。
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 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 运行效果
5 推荐阅读
声明:本文转自【Unity3D】激光灯、碰撞特效
以上是关于Unity3D激光灯碰撞特效的主要内容,如果未能解决你的问题,请参考以下文章
unity3d怎么做碰撞?unity3d如何添加碰撞体?求解!