【Unity3D】选中物体消融特效

news2024/9/20 14:37:24

1 消融特效原理

        当前实现消融特效的方法主要有 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 运行效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/725981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux操作系统详解

文章目录 引言1. 认识Linux1.1 操作系统概述1.2 认识Linux1.3 虚拟机介绍1.4 远程连接Linux操作系统1.5 WSL1.6 虚拟机快照 2. Linux基础命令2.1 Linux的目录结构2.2 命令入门2.3 目录切换相关命令&#xff08;cd/pwd&#xff09;2.4 相对路径&#xff0c;绝对路径和特殊路径符…

当某个微服务重启后,GateWay网关访问服务出现503的问题

因为开发阶段可能需要经常重启微服务&#xff0c;但有时会莫名奇妙返回503 Service Unavailable 由于从springcloud2020版本开始&#xff0c;弃用了Ribbon&#xff0c;因此Alibaba在2021及之后版本的nacos中删除了Ribbon的jar包&#xff0c;因此无法通过loadbalancer路由到指定…

SpringBoot使用AOP

Spring相信大家都学过&#xff0c;就不多述了。 自定义注解&#xff0c;注解的类中所有的接口都会执行AOP增强&#xff0c;注解的接口会执行AOP增强。 注解类&#xff1a; package xin.students.examManagement.annotation.springConfiguration;import java.lang.annotation…

tensorboard 如何导出数据

tensorboard 如何导出数据 场景描述&#xff1a;有时候在第一遍跑实验的时候&#xff0c;由于epoch和内部循环变量的原因&#xff0c;做出来的图可能不是我们想要的&#xff0c;这个时候&#xff0c;需要自己导出数据并且重新画图&#xff0c;本文介绍如何导出数据道json或csv格…

超声波功率放大器在超声驱动技术中的应用

超声波功率放大器是一种能够将低功率信号放大到足够高的电平的电子器件。在超声驱动技术中&#xff0c;超声波功率放大器被广泛应用于超声波发生器、超声波换能器和超声波传感器等部件中&#xff0c;以保证这些部件的正常工作和高效性能。 超声波功率放大器在超声驱动技术中的应…

C++primer(第五版)第十二章(动态内存)

C中内存包含静态内存,栈内存,自由空间(堆). 静态内存用于保存局部的static(静态)对象,以及定义于任何函数以外的变量(全局变量). 栈内存用来保存定义在函数内的非static对象,由编译器自动创建和销毁. 程序可以用堆来存储动态分配的对象,同时也需要由我们来显式地销毁. 12.1…

【第四天学习】数组

数组批量数据处理 数组的定义 速度是一个相同类型的用同一个标识符封装到一起的基本数据类型&#xff0c;可以使用一个统一的速度名或者是索引来唯一确定速度中的每个元素&#xff0c;它的执行效率非常高。 数组中每一个变量称为数组的元素&#xff0c;数组能够容纳元素的数…

Centos7安装wordpress图文教程

宝塔面板安装WordPress有两种方法&#xff1a; 自己手动安装&#xff08;推荐&#xff09;宝塔后台一键部署跳转提示 推荐使用手动安装&#xff0c;因为一键部署的WordPress版本不是最新的&#xff0c;而且自己上传的文件比较放心。 第一步&#xff0c;上传WordPress安装包 …

提升UE5写实效果的项目设置

随着虚幻引擎5&#xff08;Unreal Engine 5&#xff0c;简称UE5&#xff09;的发布&#xff0c;游戏开发者和数字艺术家们迎来了一个全新的机会&#xff0c;可以在其强大的渲染引擎下创建更加逼真和令人惊叹的游戏和虚拟场景。然而&#xff0c;要实现出色的写实效果&#xff0c…

经典轻量级神经网络(2)MobileNetV2及其在Fashion-MNIST数据集上的应用

经典轻量级神经网络(2)MobileNet V2及其在Fashion-MNIST数据集上的应用 1 MobileNet V2的简述 MobileNet V2 创新性的提出了具有线性bottleneck 的Inverted 残差块。这种块特别适用于移动设备和嵌入式设备&#xff0c;因为它用到的张量都较小&#xff0c;因此减少了推断期间的…

Redis 入门指南

Redis 入门指南 目录 Redis 入门指南 Redis 基本概念 Redis 常用命令 字符串String操作命令&#xff1a; 哈希Hash操作命令&#xff1a; 列表list操作命令 集合set操作命令&#xff1a; 有序集合sorted set 操作命令 通用命令 在Java中操作 Redis 1.Jedis 2.Spring …

windows后台窗口启动jar包

启动命令 chcp 65001 //切换utf-8 echo off start javaw -jar test-1.0-SNAPSHOT.jar --spring.profiles.activeprod echo 启动成功&#xff0c;按任意键结束 pause exit查看后台进程命令 wmic process where caption"javaw.exe" get processid,caption,commandli…

手机里的视频怎么转换成MP4格式?简单的转换方法分享

MP4格式是一种广泛使用的视频格式&#xff0c;几乎所有设备和操作系统都支持MP4格式的视频播放。无论是使用 iPhone、iPad、安卓手机、电视等各种设备&#xff0c;都可以播放 MP4 格式的视频。这种广泛的兼容性使得 MP4 成为一种非常方便的视频格式&#xff0c;我们可以随时随地…

IDEA自动生成实体类

操作流程 第一次需要配置Generate POJOs.groovy 新建该文件 Generate POJOs.groovy import com.intellij.database.model.DasTable import com.intellij.database.model.ObjectKind import com.intellij.database.util.Case import com.intellij.database.util.DasUtil import…

嵌入式面试刷题(day1)

文章目录 前言一、由for( ; ;)引出的一系列问题二、sizeof和strlen的误区三、字符串转义字符带来的问题四、结构体概念模糊五、&#xff01;和~概念分不清六、switch case误区七、短路运算八、复合赋值运算符带来的问题九、什么是左值什么是右值十、struct和union总结 前言 最…

cuda sample-asyncAPI(01)

C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.0\0_Simple\asyncAPI 这个例子说明了CUDA事件在GPU计时和CPU与GPU重叠执行方面的应用。 事件被插入到CUDA的调用流中。 由于CUDA流调用是异步的&#xff0c;CPU可以在GPU执行时进行计算&#xff08;包括主机和设备之间的DM…

【前端系列】前端如何使用websocket发送消息

序言 今天来学习一下前端如何使用websocket发送消息 1 基础介绍 1.1 什么是WebSocket WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议&#xff0c;它可以让客户端和服务器之间进行实时的双向通信。与传统的 HTTP 请求不同&#xff0c;WebSocket 使用了一个长连接&…

Django实现简单的音乐播放器 1

使用django框架开发一个简单的音乐播放器。 效果&#xff1a; 目录 环境准备 安装django 创建项目 创建应用 注册应用 配置数据库 设置数据库配置 设置pymysql库引用 创建数据库 创建数据表 生成表迁移文件 执行表迁移 配置时区 配置语言 配置子应用路由 在pla…

外部中断实验(stm32)

目录 EXIT的相关代码exit.cexit.h LED的相关代码KEY的相关代码BEEP的相关代码main.cGPIO 跟中断线的映射关系图 说明&#xff1a;以下内容参考正点原子资料 EXIT的相关代码 exit.c void EXTIX_Init(void) { EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitS…

el-dialog中,el-form中表单信息未收集齐全时禁用提交按钮,动态控制按钮是否禁用

需求 在el-dialog中放置了一个表单&#xff0c;打开el-dialog时&#xff0c;表单没有收集内容&#xff0c;各项为空&#xff0c;此时表单的提交按钮被禁用&#xff0c;只有每个表单项都收集到内容时&#xff0c;才会将提交按钮设置为可用 预期效果 解决方案 <el-button c…