Unity中Shader面片一直面向摄像机

文章目录

  • 前言
  • 一、实现思路
    • 1、 我们要实现模型面片一直跟着摄像机旋转,那么就需要用到旋转矩阵
    • 2、确定 原坐标系 和 目标坐标系
    • 3、确定旋转后坐标系基向量
  • 二、确定旋转后 坐标系基向量 在 原坐标系 下的值
    • 1、Z轴基向量
    • 2、假设Y轴基向量 和 世界空间下 的Y轴方向一致竖直向上
    • 3、X轴基向量
    • 4、Y轴基向量
  • 三、顶点应用旋转
    • 法一:向量乘法
    • 法二:矩阵乘法
    • 最后转化到齐次裁剪空间
  • 四、最终效果
    • 最终测试代码


前言

在之前的文章中,我们实现了Shader的序列帧动画。

  • Unity中Shader序列帧动画(总结篇)

但是,我们会发现,我们的面片不会一直面向摄像机,当摄像机移动时,人物或特效就会出现穿帮的效果。所以,我们接下来就来实现让我们的面片面向摄像机。

类似的功能,还可能用于:特效、公告牌、八方旅人风格效果、饥荒风格效果。


一、实现思路

1、 我们要实现模型面片一直跟着摄像机旋转,那么就需要用到旋转矩阵

  • Unity中Shader旋转矩阵(二维旋转矩阵)
  • Unity中Shader旋转矩阵(四维旋转矩阵)
  • Unity中Shader矩阵变换的几何体现

2、确定 原坐标系 和 目标坐标系

  • 原坐标系 就是模型的本地坐标
    在这里插入图片描述
  • 目标坐标系
    因为我们的面片需要一直朝向摄像机。
    所以,可以确定旋转后的坐标系Z轴方向 需要 和 原模型本地空间坐标系原点 指向 摄像机 方向一致。
    在这里插入图片描述

3、确定旋转后坐标系基向量


二、确定旋转后 坐标系基向量 在 原坐标系 下的值

1、Z轴基向量

  • 求 摄像机坐标 在模型本地空间下的坐标值,在 归一化后 就是 Z轴基向量

float3 viewDir = mul(GetWorldToObjectMatrix(),float4(_WorldSpaceCameraPos,1)).xyz;

2、假设Y轴基向量 和 世界空间下 的Y轴方向一致竖直向上

在这里插入图片描述

float3 upDir = float3(0,1,0);

3、X轴基向量

  • X轴基向量 = Z轴基向量 × Y轴基向量

  • 叉积 的顺序取决于 坐标系类型 (逆时针叉积)

  • 模型本地空间坐标系为左手坐标系
    在这里插入图片描述

  • 左手坐标系中,X轴 = 食指 × 拇指 = Z × Y

float3 rightDir = normalize(cross(viewDir,upDir));

4、Y轴基向量

  • Y轴基向量 = X轴基向量 × Z轴基向量
  • 叉积 的顺序取决于 坐标系类型 (逆时针叉积)
  • 模型本地空间坐标系为左手坐标系
  • 左手坐标系中,Y轴 = 中指 × 食指 = X × Z

upDir = normalize(cross(rightDir,viewDir));


三、顶点应用旋转

法一:向量乘法

  • n e w P o s O S = X 基向量 ∗ p o s O S x + Y 基向量 ∗ p o s O S y + Z 基向量 ∗ p o s O S z newPosOS = X基向量*posOS_x + Y基向量*posOS_y+Z基向量*posOS_z newPosOS=X基向量posOSx+Y基向量posOSy+Z基向量posOSz

float3 newVertexOS = rightDir * v.vertexOS.x + upDir * v.vertexOS.y + viewDir * v.vertexOS.z;

法二:矩阵乘法

float4x4 M = float4x4
(
rightDir.x,upDir.x,viewDir.x,0,
rightDir.y,upDir.y,viewDir.y,0,
rightDir.z,upDir.z,viewDir.z,0,
0,0,0,1
);
float3 newVertexOS = mul(M,v.vertexOS).xyz;

最后转化到齐次裁剪空间

o.vertexCS = TransformObjectToHClip(newVertexOS);


四、最终效果

请添加图片描述

最终测试代码

Shader "MyShader/URP/P3_10_1"
{
    Properties
    {
        [Enum(UnityEngine.Rendering.BlendMode)]_SrcFactor("SrcFactor",int) = 0
        [Enum(UnityEngine.Rendering.BlendMode)]_DstFactor("DstFactor",int) = 0
        _Color("Color",Color) = (1,1,1,1)
        _MainTex("MainTex",2D) = "white"{}
        _Sequence("Row(X) Column(Y) Speed(Z)",Vector) = (1,1,1,1)
    }
    SubShader
    {
        Tags
        {
            //告诉引擎,该Shader只用于 URP 渲染管线
            "RenderPipeline"="UniversalPipeline"
            //渲染类型
            "RenderType"="Transparent"
            //渲染队列
            "Queue"="Transparent"
        }
        Blend [_SrcFactor] [_DstFactor]
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            struct Attribute
            {
                float4 vertexOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varying
            {
                float4 vertexCS : SV_POSITION;
                float2 uv : TEXCOORD1;
                float fogCoord : TEXCOORD2;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _Color;
                float4 _MainTex_ST;
                half4 _Sequence;
            CBUFFER_END

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            Varying vert(Attribute v)
            {
                Varying o;
                //Z轴基向量
                float3 viewDir = mul(GetWorldToObjectMatrix(),float4(_WorldSpaceCameraPos,1)).xyz;
                viewDir = normalize(viewDir);
                //假设Y轴基向量
                float3 upDir = float3(0,1,0);
                //X轴基向量(左手坐标系、逆时针叉乘)
                float3 rightDir = normalize(cross(viewDir,upDir));
                //Y轴基向量(左手坐标系、逆时针叉乘)
                upDir = normalize(cross(rightDir,viewDir));
                //顶点应用旋转
                //法一:向量乘法
                float3 newVertexOS = rightDir * v.vertexOS.x + upDir * v.vertexOS.y + viewDir * v.vertexOS.z;
                //法二:矩阵乘法
                /*float4x4 M = float4x4
                    (
                        rightDir.x,upDir.x,viewDir.x,0,
                        rightDir.y,upDir.y,viewDir.y,0,
                        rightDir.z,upDir.z,viewDir.z,0,
                        0,0,0,1
                    );
                float3 newVertexOS = mul(M,v.vertexOS).xyz;*/

                
                o.vertexCS = TransformObjectToHClip(newVertexOS);
                o.uv = float2(v.uv.x / _Sequence.y, v.uv.y / _Sequence.x + (_Sequence.x - 1) / _Sequence.x);
                o.uv.x += frac(floor(_Time.y * _Sequence.y * _Sequence.z) / _Sequence.y);
                o.uv.y -= frac(floor(_Time.y * _Sequence.y * _Sequence.z / _Sequence.y) / _Sequence.x);
                //o.uv.x += floor(_Time.y);
                //o.uv = float2(v.uv.x/4,v.uv.y/4);
                //o.uv = TRANSFORM_TEX(v.uv,_MainTex);
                o.fogCoord = ComputeFogFactor(o.vertexCS.z);
                return o;
            }

            half4 frag(Varying i) : SV_Target
            {
                float4 mainTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                float4 col = mainTex * _Color;
                col.rgb = MixFog(col.rgb, i.fogCoord);
                col.rgb = col.rgb * col.a;
                return col;
            }
            ENDHLSL
        }
    }
    
}

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

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

相关文章

科学的摇篮 - 贝尔实验室

AT&T贝尔实验室(AT&T Bell Laboratories)是美国电信公司AT&T的研究与开发部门,成立于1925年。它在20世纪的许多年里一直是科学与技术创新的重要中心,做出了众多重大贡献,并为多项科技成就奠定了基础。以下…

ReentrantLock底层原理学习二

以 ReentrantLock 作为切入点,来看看在这个场景中是如何使用 AQS 来实现线程的同步的 ReentrantLock 的时序图 调用 ReentrantLock 中的 lock()方法,源码的调用过程我使用了时序图来展现。ReentrantLock.lock() 这个是 reentrantLock 获取锁的入口 pu…

C++流媒体服务器 ZLMediaKit框架ZLToolKit源码解读

ZLMediaKit是国人开发的开源C流媒体服务器,同SRS一样是主流的流媒体服务器。 ZLToolKit是基于C11的高性能服务器框架,和ZLMediaKit是同一个作者,ZLMediaKit正是使用该框架开发的。 ZLMediaKit开源地址:https://github.com/ZLMedi…

SpringMVC-@RequestMapping注解

0. 多个方法对应同一个请求 RequestMapping("/")public String toIndex(){return "index";}RequestMapping("/")public String toIndex2(){return "index";}这种情况是不允许的,会报错。 1. 注解的功能 RequestMapping注…

C++面试宝典第15题:最长回文子串

题目 回文是一个正读和反读都相同的字符串,比如:"aba"是回文,而"abc"不是回文。现给定一个字符串s,找出s中最长的回文子串(可能有多个最长的,找出一个即可)。 示例 1: 输入: "babad" 输出: "bab"("aba" 也是一个有…

总420+,专业120+南京大学851信号与系统电子信息考研经验通信,电子信息

今年考研数学130,专业课120,总分420顺利被南京大学电通录取,梦圆南大,这一年的复习有过迷茫,有过犹豫,最后都坚持过来了,总结一下自己的复习经验,希望对大家有所帮助。数学 5-8月数…

MySQL-存储引擎

简介:存储引擎是存储数据,建立索引,更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的, (同一个数据库的不同表可以选择不同的存储引擎) 所以存储引擎也可被称为表类型。 我们输入 SHOW CREATE TAB…

操作系统内存碎片

大家好,我叫徐锦桐,个人博客地址为www.xujintong.com,github地址为https://github.com/jintongxu。平时记录一下学习计算机过程中获取的知识,还有日常折腾的经验,欢迎大家访问。 一、前言 内存碎片是指无法被利用的内…

AArch64 memory management学习(一)

提示 该博客主要为个人学习,通过阅读官网手册整理而来(个人觉得阅读官网的英文文档非常有助于理解各个IP特性)。若有不对之处请参考参考文档,以官网参考文档为准。AArch64 memory management学习一共分为两章,这是第一…

国科大计算机体系结构期末考试——更新中

题型一、第二章的画图 给一个逻辑表达式,画出晶体管级别的电路图 cmos电路的基本电路: 与非门的功能是对多个输入信号进行逻辑与操作,然后对结果进行取反。 或非门的功能是对多个输入信号进行逻辑或操作,然后对结果进行取反。 …

【算法提升】LeetCode每五日一总结【01/01--01/05】

文章目录 LeetCode每五日一总结【01/01--01/05】2023/12/31今日数据结构&#xff1a;二叉树的前/中/后 序遍历<非递归> 2024/01/01今日数据结构&#xff1a;二叉树的 前/中/后 序遍历 三合一代码<非递归>今日数据结构&#xff1a;二叉树的 前/中/后 序遍历 三合一代…

126基于matlab的孪生支持向量机(Twin support vector machine,TWSVM)是SVM的一种变形算法

基于matlab的孪生支持向量机&#xff08;Twin support vector machine,TWSVM&#xff09;是SVM的一种变形算法。该采用WSVM进行二分类&#xff0c;程序已注释数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 126matlabTWSVM模式识别 (xiaohongshu.com)

Nginx location 配置 - Part 2

接上文 链接: Nginx 简介和入门 - part1 上文 我们简单地在 nginx 创建了3个虚拟主机&#xff0c; 虽然这个3个主机都是用占用80端口 但是我们可以用不同的域名来实现区分访问3台虚拟主机。 但是&#xff0c; 实际项目上&#xff0c; 我们更加多地会使用location 配置而不是…

20240107查看Android11下移远的4G模块EC20在Firefly的AIO-3399J开发板跑通时的相关服务

20240107查看Android11下移远的4G模块EC20在Firefly的AIO-3399J开发板跑通时的相关服务 2024/1/7 11:24 缘起&#xff1a;友善之臂的SDK&#xff1a;rk3399-android-11-r20211216.tar.xz可以跑通EC20&#xff0c;但是Toybrick的不行&#xff01; 同样是Andrid11&#xff0c;因此…

抖音在线查权重系统源码,附带查询接口

抖音权重在线查询只需输入抖音主页链接&#xff0c;即可查询作品情况。 搭建教程 上传源码并解压 修改数据库“bygoukai.sql” 修改“config.php” 如需修改水印请修改第40行 如需修改限制次数&#xff0c;请修改第156行 访问域名user.php即可查看访问用户&#xff0c;停…

018、通用集合类型

Rust标准库包含了一系列非常有用的被称为集合的数据结构。大部分的数据结构都代表着某个特定的值&#xff0c;但集合却可以包含多个值。 与内置的数组与元组类型不同&#xff0c;这些集合将自己持有的数据存储在了堆上。这意味着数据的大小不需要在编译时确定&#xff0c;并且可…

Spring系列学习六、深入Spring AOP——揭开代理的神秘面纱

深入Spring AOP——揭开代理的神秘面纱 一、动态代理的实现原理二、CGLIB字节码增强的实现原理三、结语 上一章节&#xff0c;我们体验了Spring AOP强大的能力的同时&#xff0c;是不是也想弄明白&#xff0c;它是怎么原理是什么呢&#xff1f;如果自己要做一个类似的框架&…

阿里云服务器ECS入门与基础运维

一、云服务器简介 1、服务器&#xff1a; (1) 概念&#xff1a; 服务器本身就是一种电脑&#xff0c;同样具备CPU、内存、硬盘、网卡、电源等硬件。 互联网对外提供网站、游戏、在线会议、网盘等服务&#xff0c;都需要将这些互联网服务部署到服务器中。 (2) 特点&#xf…

Fluids —— DOP Nodes

目录 Gas SubStep —— 重复执行对应的子步 Switch Solver —— 切换解算器 Gas Attribute Swap —— 交换、复制或移动几何体属性 Gas Intermittent Solve —— 固定时间间隔计算子解算器 Gas External Forces —— 计算外部力并更新速度或速度场 Gas Particle Separate…

python学习笔记

四、列表 4.1 序列的索引及切片操作 s"helloworld" # 正向递增 for i in range(0,len(s)):print(i,s[i],end\t\t) print(\n) # 反向递减 for i in range(-len(s),0):print(i,s[i],end\t) print(\n) # 切片 for i in range(0,5,2):print(i,s[i],end\t)4.2 序列的相关…