Unity 引擎做残影效果——3、顶点偏移方式

news2024/11/14 2:07:55

Unity实现残影效果


  大家好,我是阿赵。
  继续讲Unity引擎的残影做法。这次的残影效果和之前两种不太一样,是通过顶点偏移来实现的。
  具体的效果是这样:
在这里插入图片描述 在这里插入图片描述

  与其说是残影,这种效果更像是移动速度很快时造成的速度线,所以在移动过程中的效果还是非常好的,截图的感觉没有视频的感觉那么有冲击力。

一、原理介绍

1、顶点偏移

  这个做法很简单,在c#里面对比当前帧和上一帧的坐标,如果坐标有变化,就把两个坐标相减,算出一个世界空间坐标的向量出来,并做标准化处理。
  得到了这么一个向量之后,就可以把它传入到shader里面了。
  既然是顶点偏移,那么肯定是在顶点着色器程序里面做修改了。不过这里有个比较值得注意的问题。一般写shader的时候,如果没有特殊的需要,我们都是直接把顶点坐标从物体局部坐标直接就转换到裁剪空间了,比如使用Untiy提供的方法:

UnityObjectToClipPos(v.vertex);

  但在这个例子里面,我们从c#传入的是世界空间坐标的向量,所以我们不能通过模型局部坐标或者裁剪空间去叠加这个偏移向量,而应该在同样的世界坐标上面去偏移。

float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
worldPos += _MoveDir * _MoveDis;

  其中_MoveDir就是标准化后的移动向量, _MoveDis是用于控制偏移距离的变量。
  这个时候,可以看到,整个模型都在拉伸了
在这里插入图片描述

  但这种效果明显不是我们想要的。

2.控制偏移的范围

  我们想要的效果是怎样的呢?是不是应该是移动的过程中,前面的部分是不变形的,只有最后一点位置出现拉伸?
  怎么去控制拉伸的范围呢?其实很简单,假如我们把刚才那个移动的向量想象成是灯光,就可以求出这样的一个效果:
在这里插入图片描述

  很明显的,模型高亮的部分,其实就是我们想拉伸的方向了。所以,其实很简单,把移动方向和模型的法线做点乘,就可以求出一个我们想偏移的范围了。
  不过现在高亮的部分还是有点多,所以我们用一个power运算,让对比度变得更高,就变成了下面这个情况。
在这里插入图片描述

  现在,白色的部分只有很小的范围了。接下来就对这个白色的范围做顶点偏移:
在这里插入图片描述

  基本上就出现了我们想要的效果了。

二、优缺点

1、优点

  首先,之前介绍的不管是BakeMesh还是屏幕后处理,都是基于对模型的多次渲染上的,所以性能上并不那么友好。
  用顶点偏移的方式,并没有太多额外的计算量,所以从性能上来说,它是比前两种方式都要好的。
  然后,顶点偏移这种手段,其实是一种非常简单的技术含量比较低的手段,难点是在于你怎样想出这个办法而已,所以知道了方法之后,其实非常容易就能实现了。

2、缺点

  这个做法的缺点也是很明显的。
  首先,它不是真正的残影效果,只是速度线的类似效果,所以我个人感觉只适合用于特定的风格里面,比如卡通之类。如果写实风格的游戏用这种效果可能不太行。
  然后,由于是要对模型做顶点偏移,那么就要意味着需要修改模型原有的Shader了。对于比较正规的团队来说,这问题不大,因为项目里面每个Shader都应该是经过TA的定制的,需要统一加入一些元素是很简单的。
  但对于不那么正规的团队,甚至是纯美术人员组成的团队来说,说不定已经在用的Shader都是从不知道哪个网站上面复制下来的,要统一修改,难度会非常大。
  所以,如果想使用这种技术手段,还是要先考虑一下自己的实际情况的。

三、代码

  同样的,只是demo,所以代码只是在于实现部分,没有过多优化。

1、C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveVertexOffsetCtrl : MonoBehaviour
{
    public float moveDis = 1;
    private bool isMove = false;
    private Vector3 oldPos = Vector3.zero;
    public GameObject role;
    private Vector3 moveDir = Vector3.zero;
    private List<Material> matList;
    public float pow = 1;

    // Start is called before the first frame update
    void Start()
    {
        matList = new List<Material>();
        if(role)
        {
            SkinnedMeshRenderer[] renders = role.GetComponentsInChildren<SkinnedMeshRenderer>();
            for(int i = 0;i<renders.Length;i++)
            {
                for(int j = 0;j<renders[i].materials.Length;j++)
                {
                    Material mat = renders[i].materials[j];
                    if (matList.IndexOf(mat) < 0)
                    {
                        matList.Add(mat);
                    }
                }
                
            }
        }
    }

    // Update is called once per frame
    void Update()
    {
        CheckMove();
        SetMaterial();
    }

    private void CheckMove()
    {
        if(role)
        {
            if(Vector3.Distance(role.transform.position,oldPos)>0)
            {
                moveDir =  oldPos - role.transform.position;
                moveDir = moveDir.normalized;
                oldPos = role.transform.position;
                isMove = true;
            }
            else
            {
                isMove = false;
            }
        }
        else
        {
            isMove = false;
        }
    }

    private void SetMaterial()
    {
        if(matList!=null&&matList.Count>0)
        {
            for(int i = 0;i<matList.Count;i++)
            {
                if(isMove==false)
                {
                    matList[i].SetFloat("_MoveDis", 0);
                }
                else
                {
                    matList[i].SetFloat("_MoveDis", moveDis);
                    matList[i].SetVector("_MoveDir", moveDir);
                    matList[i].SetFloat("_MovePow", pow);
                }
            }
        }
    }
}

2、Shader

Shader "azhao/MoveVertexBase"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_MoveDir("MoveDir",Vector) = (0,0,0,0)
		_MoveDis("MoveDis",float) = 0
		_MovePow("MovePow",float) = 1

    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
				half3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
			float4 _MoveDir;
			float _MoveDis;
			float _MovePow;
            v2f vert (appdata v)
            {
                v2f o;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
				float3 worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				float ndotl = saturate(dot(worldNormal, _MoveDir));
				ndotl = pow(ndotl, _MovePow);
				worldPos += _MoveDir * _MoveDis*ndotl;
				//世界空间顶点坐标转观察空间坐标
				float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
				//观察空间坐标转裁剪空间坐标
				float4 clipPos = mul(UNITY_MATRIX_P, viewPos);
				o.vertex = clipPos;
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                // sample the texture
                half4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

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

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

相关文章

Java对象创建回收全过程

目录 1 前言 2 Java对象创建 2.1 类加载检查 2.1.1 谁来加载 2.1.2 如何加载 2.2 分配内存 2.3 初始化零值 2.4 设置对象头 2.5 执行clinit 3 对象回收 4 补充Tomcat打破双亲委派机制 在讲java创建之前,我们先来了解下Java虚拟机内存组成,当Java虚拟机启动后,会…

RabbitMQ(一) - 基本结构、SpringBoot整合RabbitMQ、工作队列、发布订阅、直接、主题交换机模式

RabbitMQ结构 Publisher &#xff1a; 生产者 Queue: 存储消息的容器队列&#xff1b; Consumer:消费者 Connection&#xff1a;消费者与消息服务的TCP连接 Channel:信道&#xff0c;是TCP里面的虚拟连接。例如&#xff1a;电缆相当于TCP&#xff0c;信道是一条独立光纤束&…

动手学深度学习(二)线性神经网络

推荐课程&#xff1a;跟李沐学AI的个人空间-跟李沐学AI个人主页-哔哩哔哩视频 目录 一、线性回归 1. 线性模型 2. 损失函数&#xff08;衡量预估质量&#xff09; 3.梯度下降算法&#xff08;优化算法&#xff09; 3.1 梯度下降公式 3.2 选择学习率 3.3 小批量随机梯度下…

2023华数杯数学建模C题完整5问代码思路分析

目前已经写出2023华数杯C题母亲身心健康对婴儿成长的影响全部5问的完整代码和42页论文&#xff08;正文30页&#xff0c;论文部分摘要如下&#xff1a; 本文共解决了五个问题&#xff0c;涉及婴儿行为特征、睡眠质量与母亲的身体指标和心理指标的关系&#xff0c;以及如何优化…

YOLOv5模型压缩方法:综述

文章目录 YOLOv5模型压缩方法&#xff1a;综述摘要1、介绍2、剪枝2.1、修剪的显著性标准2.1.1、 ℓn-norm2.1.2、 特征图激活 YOLOv5模型压缩方法&#xff1a;综述 摘要 Model Compression Methods for YOLOv5: A Review (arxiv.org) 在过去的几年里&#xff0c;广泛的研究致…

vue3引入video.js

一.引入video.js yarn add video.js videojs-player/vue --save 或者 npm install video.js videojs-player/vue --save 二.vue3项目main.js引入 import VueVideoPlayer from "videojs-player/vue" import "video.js/dist/video-js.css" const app cr…

DP(各种模型)

数字三角形模型 摘花生 Hello Kitty想摘点花生送给她喜欢的米老鼠。 她来到一片有网格状道路的矩形花生地(如下图)&#xff0c;从西北角进去&#xff0c;东南角出来。 地里每个道路的交叉点上都有种着一株花生苗&#xff0c;上面有若干颗花生&#xff0c;经过一株花生苗就…

Doris(三)-集群部署3个FE+3个BE

前置 1&#xff09;配置java环境 1st 解压jdk包 unzip jdk1.8.0_171-amd64.zip 2nd 配置环境变量 vim /etc/profile#文末添加JAVA_HOME/data/jdk1.8.0_171-amd64 PATH$JAVA_HOME/bin:$PATHexport PATH JAVA_HOME3rd 启用配置 source /etc/profile 4th 验证 java -versi…

离散 Hopfield 神经网络的分类与matlab实现

1 案例背景 1.1离散 Hopfield 神经网络学习规则 离散型 Hopfield神经网络的结构、工作方式,稳定性等问题在第9章中已经进行了详细的介绍,此处不再赘述。本节将详细介绍离散Hopfield神经网络权系数矩阵的设计方法。设计权系数矩阵的目的是: ①保证系统在异步工作时的稳…

Consul实战

Consul实战 什么是Consul Consul是一种为分布式系统提供服务发现、配置共享和健康检查的开源工具&#xff1b; 可以用来做微服务架构里的注册中心和配置中心。Consul的特定和功能有&#xff1a; 1.服务发现 consul允许微服务注册自己的实例到Consul, 并查询consul来获取可用的…

flex 弹性布局

Flex 布局的使用 任何一个容器都可以指定为 Flex 布局。 .box{ display: flex; //flex作为display的一个属性使用 } 行内元素也可以使用 Flex 布局。 .box{ display: inline-flex; } 注意&#xff1a;设为 Flex 布局以后&#xff0c;子元素的float、clear和vertical-align…

jmeter使用步骤

jmeter 使用步骤 1&#xff0c;进入jmeter目录中的bin目录&#xff0c;双击jmeter.bat 打开 2&#xff0c;右键test plan 创建线程组 3&#xff0c;配置线程组参数 4&#xff0c;右键刚刚创建的线程组&#xff0c;创建请求&#xff0c;填写请求地址 5&#xff0c;需要携带to…

【力扣刷题 | 第二十四天】

目录 前言&#xff1a; 416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 总结 前言&#xff1a; 今晚我们爆刷动态规划类型的题目。 416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这…

MPP架构和Hadoop架构的区别

1. 架构的介绍 mpp架构是将许多数据库通过网络连接起来&#xff0c;相当于将一个个垂直系统横向连接&#xff0c;形成一个统一对外的服务的分布式数据库系统。每个节点由一个单机数据库系统独立管理和操作该物理机上的的所有资源&#xff08;CPU&#xff0c;内存等&#xff09…

flask------消息闪现 flash

1介绍 flask提供了一个非常有用的flash()函数&#xff0c;它可以用来“闪现”需要提示给用户的消息&#xff0c;比如当用户登录成功后显示“欢迎回来&#xff01;”。在视图函数调用flash()函数&#xff0c;传入消息内容&#xff0c;flash&#xff08;&#xff09;函数把消息存…

【网络基础进阶之路】设计网络划分的实战详解

PS&#xff1a;本要求基于华为的eNSP模拟软件进行 具体要求&#xff1a; 完成步骤&#xff1a; 1、对192.168.1.0/24进行子网划分 2、对每一个路由器进行IP的配置 3、开始静态路由的书写&#xff0c;在写之前&#xff0c;我们可以先对每一个路由器写一条通向右边的缺省路由&…

【C++入门到精通】C++入门 —— 内存管理(new函数的讲解)

目录 一、C/C内存分布 1. 栈&#xff08;Stack&#xff09; 2. 堆&#xff08;Heap&#xff09; 3. 全局区/静态区&#xff08;Global Area/Static Area&#xff09; 4. 常量区&#xff08;Constant Area&#xff09; 5. 代码区&#xff08;Code Area&#xff09; 二、C…

【HAL库】STM32CubeMX开发----STM32F407----LAN8720A----移植FreeModbus实现ModbusTCP

前言 本次实验以 STM32F407VET6 芯片为MCU&#xff0c;使用 25MHz 外部时钟源。 以太网PHY层芯片为 LAN8720A&#xff0c;移植FreeModbus实现ModbusTCP网口通信。 具体内容参考文章&#xff1a;【HAL库】STM32CubeMX开发----STM32F407----ETHLAN8720ALWIP----ping通 本次移植…

基于开源模型搭建实时人脸识别系统(三):人脸关键点、对齐模型概览与模型选型

续 基于开源模型搭建实时人脸识别系统&#xff08;二&#xff09;&#xff1a;人脸检测概览与模型选型_CodingInCV的博客-CSDN博客 摘要 人脸对齐&#xff08;face alignment&#xff09;或者人脸关键点&#xff08;face alignment&#xff09;是定位人脸上的关键点&#xff…

chatGLM 本地部署(windows+linux)

chatGLM算是个相对友好的模型&#xff0c;支持中英文双语的对话交流&#xff0c;清华出的 我的教程无需特别的网络设置&#xff0c;不过部分情况因为国内网络速度慢&#xff0c;需要反复重复 chatGLM github地址 一、硬件需求 N卡8G显存以上&#xff0c;最好16G以上&#xff…