使用Unity3D实现选中物体消融特效的方法详解
作者:little_fat_sheep
消融特效中基Shader Graph实现了消融特效,本文将基于 Shader 实现消融特效,当前实现消融特效的方法主要有 Alpha 测试消融、clip(或 discard)消融,它们的本质都是随机丢弃一些片元,以实现消融效果,文中有详细代码示例,需要的朋友可以参考下
1 消融特效原理
消融特效中基于 Shader Graph 实现了消融特效,本文将基于 Shader 实现消融特效。
当前实现消融特效的方法主要有 Alpha 测试消融、clip(或 discard)消融,它们的本质都是随机丢弃一些片元,以实现消融效果。
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选中物体消融特效的资料请关注脚本之家其它相关文章!