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

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
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机器学习深度学习——卷积的多输入多输出通道

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——从全连接层到卷积 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮…

高效构建 vivo 企业级网络流量分析系统

作者&#xff1a;vivo 互联网服务器团队- Ming Yujia 随着网络规模的快速发展&#xff0c;网络状况的良好与否已经直接关系到了企业的日常收益&#xff0c;故障中的每一秒都会导致大量的用户流失与经济亏损。因此&#xff0c;如何快速发现网络问题与定位异常流量已经成为大型企…

c高级:day3

作业: 1. 整理思维导图 2.判断家目录下,普通文件的个数和目录文件的个数 #!/bin/bash ######################################################################## # File Name: zy1.sh # Created Time: 2023年08月04日 星期五 19时13分08秒 ##############################…

面试之HashMap

1.什么是集合框架 Java的集合主要有两个根接口Collection和Map派生出来的&#xff0c;Collection派生出来了三个子接口&#xff1a;List,Queue,Set。因此Java集合大致可分为List,Queue,Set,Map四种体系结构。 2.HashMap与TreeMap HashMap是直接实现Map接口&#xff0c;而Tree…

线上通过Nginx部署前端工程,并且配置SSL

介绍、为了更好的帮助大家学习&#xff0c;减少歧义,IP地址我就不隐藏了&#xff0c;公司也是我自己的公司。你们就别来攻击了。 下面给出步骤: 一、前期准备工作 通过在目标服务器上安装宝塔面板、安装redis、mysql、nginx、jdk环境等 1、 2、前端工程通过npm run build 打…

【Elasticsearch】Elasticsearch快速入门,掌握这些刚刚好!(官网入门案例)

文章目录 1. 简介2. 相关概念3. 安装4. 集群状态查看5. 索引操作6. 文档操作7. 数据搜索数据准备搜索入门(match_all)条件搜索(match)组合搜索(bool)过滤搜索(filter)搜索聚合(aggs) 8. 参考资料 本文的主要功能是带领从0到1入门Elasticsearch的基础使用&#xff0c;重点是Elas…

python机器学习(六)决策树(上) 构造树、信息熵的分类和度量、信息增益、CART算法、剪枝

决策树算法 模拟相亲的过程&#xff0c;通过相亲决策图&#xff0c;男的去相亲&#xff0c;会先选择性别为女的&#xff0c;然后依次根据年龄、长相、收入、职业等信息对相亲的另一方有所了解。 通过决策图可以发现&#xff0c;生活中面临各种各样的选择&#xff0c;基于我们的…

招投标系统简介 企业电子招投标采购系统源码之电子招投标系统 —降低企业采购成本 tbms

​功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

视频汇聚平台EasyCVR视频广场侧边栏支持拖拽

为了提升用户体验以及让平台的操作更加符合用户使用习惯&#xff0c;我们在EasyCVR v3.3版本中&#xff0c;支持面包屑侧边栏的广场视频、分组列表、收藏这三个模块拖拽排序&#xff0c;并且该操作在视频广场、视频调阅、电子地图、录像回放等页面均能支持。 TSINGSEE青犀视频…

python解析帆软cpt及frm文件(xml)获取源数据表及下游依赖表

#!/user/bin/evn python import os,re,openpyxl 输入&#xff1a;帆软脚本文件路径输出&#xff1a;帆软文件检查结果Excel#获取来源表 def table_scan(sql_str):# remove the /* */ commentsq re.sub(r"/\*[^*]*\*(?:[^*/][^*]*\*)*/", "", sql_str)# r…

探索硬件王国:计算机硬件信息一览(使用powershell获得计算机硬件信息)

获得运行权限&#xff1a; 请确保在运行脚本文件之前&#xff0c;设置了适当的执行策略。如果需要&#xff0c;可以使用 Set-ExecutionPolicy 命令更改执行策略。例如&#xff0c;可以使用以下命令将执行策略设置为 RemoteSigned&#xff1a; Set-ExecutionPolicy RemoteSign…

云真机调研

1. 主流云真机 目前市面上主流的远程真机服务商有:Testin云测、百度MTC、TestBird、精灵云测、腾讯Wetest、泽众云等,设备上基本覆盖Android、iOS和鸿蒙等主流设备,通过远程真机可以进行手工测试、代码调试、自动化脚本录制及执行等 2. testin 登录-云测&#xff0c;助力产业…

通用指令(汇编)

一、数据处理指令1&#xff09;数学运算数据运算指令的格式数据搬移指令立即数伪指令加法指令带进位的加法指令减法指令带借位的减法指令逆向减法指令乘法指令数据运算指令的扩展 2&#xff09;逻辑运算按位与指令按位或指令按位异或指令左移指令右移指令位清零指令 3&#xff…

【枚举+trie+dfs】CF514 C

Problem - 514C - Codeforces 题意&#xff1a; 思路&#xff1a; 其实是trie上dfs的板题 先把字符串插入到字典树中 对于每次询问&#xff0c;都去字典树上dfs 注意到字符集只有3&#xff0c;因此如果发现有不同的字符&#xff0c;去枚举新的字符 Code&#xff1a; #in…

学习单片机的秘诀:实践与坚持

在学习单片机时&#xff0c;将实践与学习结合起来是一个很好的方法。不要一上来就死磕指令和名词&#xff0c;而是边学边做实验&#xff0c;循序渐进地理解和应用指令。通过实验&#xff0c;你能亲身感受到指令的控制效果&#xff0c;增强对单片机的理解和兴趣。 学习单片机不…

【iOS】App仿写--天气预报

文章目录 前言一、首页二、搜索界面三、添加界面四、浏览界面总结 前言 最近完成了暑假的最后一个任务——天气预报&#xff0c;特此记录博客总结。根据iPhone中天气App的功能大致可以将仿写的App分为四个界面——首页&#xff0c;搜索界面&#xff0c;添加界面&#xff0c;浏…

dflow工作流使用1——架构和基本概念

对于容器技术、工作流等概念完全不懂的情况下理解dflow的工作方式会很吃力&#xff0c;这里记录一下个人理解。 dflow涉及的基本概念 工作流的概念很好理解&#xff0c;即某个项目可以分为多个步骤&#xff0c;每个步骤可以实现独立运行&#xff0c;只保留输入输出接口&#x…

WebGL Shader着色器GLSL语言

在2D绘图中的坐标系统&#xff0c;默认情况下是与窗口坐标系统相同&#xff0c;它以canvas的左上角为坐标原点&#xff0c;沿X轴向右为正值&#xff0c;沿Y轴向下为正值。其中canvas坐标的单位都是’px’。 WebGL使用的是正交右手坐标系&#xff0c;且每个方向都有可使用的值的…

c语言野指针int*p、空指针int*p = NULL、万能指针void* p

1、野指针&#xff0c;既没有初始化的指针&#xff0c;//如果没有给指针初始化&#xff0c;则指针p的内容为随机地址&#xff0c;会随机指向&#xff0c;故成为野指针&#xff0c;不可以操作野指针 #include "stdio.h" #include <stdlib.h>int main() {//1、野…

ORACLE常用基础

. 1.oracle开机启动流程 su - oracle lsnrctl start lsnrctl status sqlplus / as sysdba startup 2、如何查看数据库版本 select * from v$version; 3.如何查看用户从那个设备连接的数据库 SELECT DISTINCT machine , terminal FROM V$SESSION; 4.如何查看表结构 selec…