当前位置: 首页 > news >正文

Chapter11让画面动起来——Shader入门精要学习笔记

Chapter11让画面动起来

  • 一、Unity Shader中的内置变量(时间篇)
  • 二、纹理动画
    • 1.序列帧动画
    • 2.滚动背景
  • 三、顶点动画
    • 1.流动的河流
    • 2.广告牌
    • 3.注意事项
      • ①批处理问题
      • ②阴影投射问题

一、Unity Shader中的内置变量(时间篇)

Unity Shader 提供了一系列时间变量来允许我们方便地在Shader中访问运行时间
在这里插入图片描述

二、纹理动画

1.序列帧动画

  • 原理:依次播放关键帧图像,形成连续动画
 Properties{_Color ("Color Tint", Color) = (1,1,1,1)_MainTex ("Image Sequence", 2D) = "white"{}_HorizontalAmount ("Horizontal Amount", Float) = 4_VerticalAmount ("Vertical Amount", Float) = 4_Speed ("Speed", Range(1,100)) = 3}
  • _MainTex 就是包含了所有关键帧图像的纹理
  • _HorizontalAmount 和 _VerticalAmount 分别代表了该图像在水平方向和竖直方向包含的关键帧图像的个数
  • _Speed 用于控制序列帧动画的播放速度
SubShader
{Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}Pass {Tags {"LightMode" = "ForwardBase"}Zwrite OffBlend SrcAlpha OneMinusSrcAlpha
  • 序列帧通常是透明纹理,可以被当成一个半透明对象
v2f vert(a2v v)
{v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;
}
  • 把顶点纹理坐标存储到了v2f中
            fixed4 frag(v2f i):SV_Target {float time = floor(_Time.y * _Speed);float row = floor(time / _HorizontalAmount);float column = time - row * _HorizontalAmount;//				half2 uv = float2(i.uv.x /_HorizontalAmount, i.uv.y / _VerticalAmount);
//				uv.x += column / _HorizontalAmount;
//				uv.y -= row / _VerticalAmount;half2 uv = i.uv + half2(column, -row);uv.x /= _HorizontalAmount;uv.y /= _VerticalAmount;fixed4 c = tex2D(_MainTex, uv);c.rgb *= _Color;return c;}
  • 前三行计算了行列数
    • _Time.y * _Speed 相乘得到模拟的时间,floor函数用来对结果取整
    • time / _HorizontalAmount 商作为当前对应的行索引,余数是列索引
  • 使用行列索引值来构建真正的采样坐标 (注释的)
    • 我们可以首先把原纹理坐标 i.uv 按行数和列数进行等分, 得到每个子图像的纹理坐标范围
    • 使用当前的行列数对上面的结果进行偏移,得到当前子图像的纹理坐标范围
    • 竖直方向坐标偏移需要使用减法,因为Unity纹理坐标竖直方向顺序是从上到下,序列帧的图像是从下到上的,相反的
  • 将原纹理坐标 i.uv 加上一个向量 (column, -row),然后分别除以水平方向和竖直方向关键帧数量,得到当前子图像的纹理坐标范围。这种方法更加通用,即使关键帧图像大小不同也可以使用。

2.滚动背景

Properties
{_MainTex ("Base Layer (RGB)", 2D) = "white" {}_DetailTex ("2nd Layer (RGB)", 2D) = "white" {}_ScrollX ("Base layer Scroll Speed", Float) = 1.0_Scroll2X ("2nd layer Scroll Speed", Float) = 1.0_Multiplier ("Layer Multiplier", Float) = 1
}
  • _MainTex 表示第一层(较远),_DetailTex 表示第二层(较近)
  • _ScrollX 和 _Scroll2X 对应了各自的水平滚动速度
  • _Multiplier 控制整体亮度
v2f vert (a2v v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex) + frac(float2(_ScrollX, 0.0) * _Time.y);o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y);return o;
}
  • 最基本的顶点变换,把顶点变换到裁剪空间
  • 首先利用 TRANSFORM_TEX() 得到初始纹理坐标,再使用内置的_Time.y 变量在水平方向上对纹理坐标进行偏移
  • 把两张纹理存在一个uv中
fixed4 frag (v2f i) : SV_Target {fixed4 firstLayer = tex2D(_MainTex, i.uv.xy);fixed4 secondLayer = tex2D(_DetailTex, i.uv.zw);fixed4 c = lerp(firstLayer, secondLayer, secondLayer.a);c.rgb *= _Multiplier;return c;
}
  • 使用 i.uv.xy 和 i.uv.zw 对两张背景纹理进行采样

  • 使用第二层纹理的透明通道来混合两张纹理

  • 远处的背景应该移动的比近处的慢

三、顶点动画

1.流动的河流

  • 原理就是使用正弦函数来模拟水流波动效果
Properties {_MainTex ("Main Tex", 2D) = "white" {}_Color ("Color Tint", Color) = (1, 1, 1, 1)_Magnitude ("Distortion Magnitude", Float) = 1_Frequency ("Distortion Frequency", Float) = 1_InvWaveLength ("Distortion Inverse Wave Length", Float) = 10_Speed ("Speed", Float) = 0.5
}
  • _MainTex 是河流纹理, _Color 控制整体颜色,_Magnitude 控制水波流动的幅度,_Frequency 控制波动频率,_InvWaveLength 控制波长的倒数,_Speed 控制速度
SubShader {// Need to disable batching because of the vertex animationTags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
  • “DisableBatching”=“True” 通过该标签来直接指明是否对该SubShader使用批处理(需要特殊处理的Shader就包含了模型空间的顶点动画),批处理会合并所有相关模型,而这些模型各自的模型空间就会丢失,在本例中,需要在物体模型空间下对顶点进行位置偏移,因此需要取消批处理操作
v2f vert(a2v v) {v2f o;float4 offset;offset.yzw = float3(0.0, 0.0, 0.0);offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;o.pos = UnityObjectToClipPos(v.vertex + offset);o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);o.uv +=  float2(0.0, _Time.y * _Speed);return o;
}
  • 先计算顶点位移量,我们只希望顶点x方向进行位移,所以yzw都被设置为0
  • 利用_Frequency 属性和内置的 _Time.y 来控制正弦函数的频率
  • 为了让不同位置有不同的位移,我们对上述结果加上了模型空间下的位置分量,并×_InvWaveLength 来控制波长
  • 最后×_Magnitude 控制波动幅度,得到最终位移
  • 然后把位移量添加到顶点上,再进行顶点变换即可
fixed4 frag(v2f i) : SV_Target {fixed4 c = tex2D(_MainTex, i.uv);c.rgb *= _Color.rgb;return c;
} 
  • 直接对纹理进行采样再添加颜色即可

2.广告牌

  • 广告牌技术:会根据视角方向来旋转一个被纹理着色的多边形,使得好像总是面对着相机
  • 本质就是构建旋转矩阵
  • 一个变换矩阵需要3个基向量,广告牌技术使用的通常是 表面法线、指向上的方向、指向右的方向,除此之外,还需要指定一个 锚点(在旋转过程中固定不变的,以此来确认多边形在空间中的位置)
  • 计算基向量(相互正交的):
    • 法线方向: 通常为视角方向或固定方向
    • 指向上的方向: 通常为 (0, 1, 0) 或固定方向
    • 指向右的方向: 通过法线方向和指向上的方向计算得到 r i g h t = u p × n o r m a l right = up\times normal right=up×normal 叉积
    • r i g h t right right 归一化后,再由法线方向和指向右的方向计算出正交的指向上的方向 u p ′ = n o r a m l × r i g h t up' = noraml \times right up=noraml×right
      在这里插入图片描述
	Properties {_MainTex ("Main Tex", 2D) = "white" {}_Color ("Color Tint", Color) = (1, 1, 1, 1)_VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1 }
  • _MainTex 是广告牌的透明纹理,_Color 控制整体颜色,_VerticalBillboarding 调整是固定法线还是固定指向上的方向(0是向上,1是法线)
SubShader {// Need to disable batching because of the vertex animationTags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
  • 在广告牌技术中,我们需要使用物体模型空间下的位置来作为模电进行计算,所以要取消批处理
v2f vert (a2v v) {v2f o;// Suppose the center in object space is fixedfloat3 center = float3(0, 0, 0);float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));float3 normalDir = viewer - center;// If _VerticalBillboarding equals 1, we use the desired view dir as the normal dir// Which means the normal dir is fixed// Or if _VerticalBillboarding equals 0, the y of normal is 0// Which means the up dir is fixednormalDir.y =normalDir.y * _VerticalBillboarding;normalDir = normalize(normalDir);// Get the approximate up dir// If normal dir is already towards up, then the up dir is towards frontfloat3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);float3 rightDir = normalize(cross(upDir, normalDir));upDir = normalize(cross(normalDir, rightDir));// Use the three vectors to rotate the quadfloat3 centerOffs = v.vertex.xyz - center;float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;o.pos = UnityObjectToClipPos(float4(localPos, 1));o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);return o;
}
  • 所有的计算都是在模型空间下计算的 (下面解释顶点着色器内容)
  • 首先选择模型空间的原点作为广告牌的锚点,利用内置变量 mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1)) 获取模型空间下的视角位置
  • 然后开始计算三个正交基
float3 normalDir = viewer - center;// If _VerticalBillboarding equals 1, we use the desired view dir as the normal dir// Which means the normal dir is fixed// Or if _VerticalBillboarding equals 0, the y of normal is 0// Which means the up dir is fixednormalDir.y =normalDir.y * _VerticalBillboarding;normalDir = normalize(normalDir);
  • 根据观察位置和锚点计算目标法线方向,并使用_VerticalBillboarding 来控制垂直方向上的约束度
  • 1时法线方向固定为视角方向,0时向上方向固定为(0,1,0)
float3 upDir = abs(normalDir.y) > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
  • 接着粗略地得到向上方向,为了放置法线方向和向上方向平行,对法线方向的y分量进行判断
float3 rightDir = normalize(cross(upDir, normalDir));
  • 然后得到了向右方向,并进行归一化
upDir = normalize(cross(normalDir, rightDir));
  • 在根据准确的法线方向和向右方向得到最后向上方向
// Use the three vectors to rotate the quadfloat3 centerOffs = v.vertex.xyz - center;float3 localPos = center + rightDir * centerOffs.x + upDir * centerOffs.y + normalDir * centerOffs.z;
  • 根据三个正交基矢量和原始位置相对于锚点的偏移量,计算得到新顶点的位置
o.pos = UnityObjectToClipPos(float4(localPos, 1));o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
  • 最后把模型空间的顶点位置变换到裁剪空间
  • 片元着色器只需要对纹理进行采样,再与颜色相乘即可
fixed4 frag (v2f i) : SV_Target {fixed4 c = tex2D (_MainTex, i.uv);c.rgb *= _Color.rgb;return c;
}

3.注意事项

①批处理问题

  • 因为批处理会 合并模型,导致模型空间丢失,而顶点动画又需要依赖模型空间的位置进行计算
    • 合并模型:将所有使用同一 Shader 的材质的模型合并成一个批次进行渲染,每个模型都被转换到了一个统一的坐标系中
  • 解决方法
    • 禁用批处理:通过在 SubShader 标签中 使用 “DisableBatching” 标签,可以强制禁用对该 Shader 的批处理,从而避免动画效果被破坏,但这样做会降低性能,因为会增加 Draw Call
    • 使用相对位置和方向:尽量避免使用模型空间下的绝对位置和方向进行计算,例如,使用顶点颜色存储每个顶点到锚点的距离,而不是直接使用模型空间的中心作为锚点
      • 为模型的每个顶点设置一个顶点颜色值,该颜色值表示该顶点到模型中心点的距离,以将颜色值的 R 分量设置为距离值,G、B 分量设置为 0
      • 读取每个顶点的顶点颜色值,并从中提取出距离值

②阴影投射问题

  • 内置的 ShadowCaster Pass 无法处理顶点动画: Unity 的内置阴影投射 Pass 没有进行顶点动画的处理,因此会导致阴影与物体的实际位置不匹配
  • 解决方法:自定义ShadowCaster Pass (见书)

相关文章:

Chapter11让画面动起来——Shader入门精要学习笔记

Chapter11让画面动起来 一、Unity Shader中的内置变量(时间篇)二、纹理动画1.序列帧动画2.滚动背景 三、顶点动画1.流动的河流2.广告牌3.注意事项①批处理问题②阴影投射问题 一、Unity Shader中的内置变量(时间篇) Unity Shader…...

c++之命名空间详解(namespace)

引例 在学习之前我们首先了来看这样一个情形: 在c语言下,我们写了两个头文件:链表和顺序表的。我们会定义一个type(typedef int type)方便改变数据类型(比如将int改成char),来做到整体代换。 但是我们两个头文件里面…...

【大模型】在大语言模型的璀璨星河中寻找道德的北极星

在大语言模型的璀璨星河中寻找道德的北极星 引言一、概念界定二、隐私保护的挑战2.1 数据来源的道德考量2.2 敏感信息的泄露风险 三、偏见与歧视的隐忧3.1 训练数据的偏见传递3.2 内容生成的不公倾向 四、责任归属的模糊地带4.1 生成内容的责任界定4.2 自动化决策的伦理考量 五…...

嵌入式Linux之Uboot简介和移植

uboot简介 uboot 的全称是 Universal Boot Loader,uboot 是一个遵循 GPL 协议的开源软件,uboot是一个裸机代码,可以看作是一个裸机综合例程。现在的 uboot 已经支持液晶屏、网络、USB 等高级功能。 也就是说,可以在没有系统的情况…...

算法整理——【贪心算法练习(1)】

上一篇博客算法整理——【贪心算法简述】-CSDN博客,我们介绍了贪心算法的基础知识,现在我们要对此进行进一步练习。 一、跳跃游戏II 例题为45. 跳跃游戏 II - 力扣(LeetCode),给定一个长度为 n 的 0 索引整数数组 nu…...

人脸识别课堂签到系统【PyQt5实现】

人脸识别签到系统 1、运用场景 课堂签到,上班打卡,进出门身份验证。 2、功能类别 人脸录入,打卡签到,声音提醒,打卡信息导出,打包成exe可执行文件 3、技术栈 python3.8,sqlite3,opencv,face_recognition,PyQt5,csv 4、流程图 1、导入库 2、编写UI界面 3、打…...

Linux dig命令常见用法

Linux dig命令常见用法 一、dig安装二、dig用法 DIG命令(Domain Information Groper命令)是常用的域名查询工具,通过此命令,你可以实现域名查询和域名问题的定位,对于网络管理员和在域名系统(DNS)领域工作的小伙伴来说,它是一个非…...

数学建模论文写作文档word

目录 1. 摘要写法1.1 确定题目与方法1.2 编写开头段落1.3 填写问题一1.4 重复步骤3填写其他问题1.5 编写结尾段落1.6 编写关键词 2. 问题重述2.1 问题背景2.2 问题提出 3. 问题分析4. 问题X模型的建立与求解5. 模型的分析5.1 灵敏度分析5.2 误差分析(主要用于预测类…...

嵌入式C语言面试相关知识——CPU、进程和线程相关(相关问题很多,会经常过来更新)

嵌入式C语言面试相关知识——CPU、进程和线程相关 一、博客声明二、自问题目——CPU相关1、什么是中断?如何处理中断?2、解释上下文切换(Context Switch)?3、在嵌入式中如何优化CPU使用? 三、自问题目——进程相关1、什么是进程&a…...

Linux学习看这一篇就够了,超超超牛的Linux基础入门

引言 小伙伴们,不管是学习c还是学习其他语言在我们学的路上都绕不过操作系统,而且,老生常谈的Linux更是每个计算机人的必修,那么我们对Linux的了解可能只是从别人那听到的简单的这个系统很牛,巴拉巴拉的,但…...

el-scrollbar组件使用踩坑记录

一、el-scrollbar和浏览器原生滚动条一起出现 问题描述 el-scrollbar组件主要用于替换浏览器原生导航条。如下图所示,使用el-scrollbar组件后,发现未能成功替换掉浏览器原生导航条,二者同时出现。 引发原因 el-scrollbar的height属性如果…...

Linux计算机结构

1.计算机设计原理 冯诺依曼体系结构 通过该结构得出:中央处理器 2.操作系统整体框架 操作系统是不会让你直接乱使用底层的各种硬件,但为了依旧能够让你使用到该资源则会给你预留一些窗口去让你与其交互(类比银行,直接小窗口交互,…...

应用进程、SurfaceFlinger进程、HWC进程 之间的关系

应用进程、SurfaceFlinger进程、HWC(Hardware Composer)进程在Android系统中扮演着重要的角色,它们之间的关系和通信流程是Android图形显示系统的核心部分。以下是这三者之间关系和通信流程的详细分析: 一、三者之间的关系 应用进…...

66.Python-web框架-Django-免费模板django-datta-able的分页的一种方式

目录 1.方案介绍 1.1实现效果 1.2django.core.paginator Paginator 类: Page 类: EmptyPage 和 PageNotAnInteger 异常: 1.3 templatetags 2.方案步骤 2.1创建一个common app 2.2创建plugins/_pagination.html 2.3 其他app的views.py查询方法 2.4在AIRecords.html里…...

Python编程学习笔记(1)--- 变量和简单数据类型

1、变量 在学习编程语言之前,所接触的第一个程序,绝大多数都是: print("Hello world!") 接下来尝试使用一个变量。在代码中的开头添加一行代码,并对第二行代码进行修改,如下: message "…...

第二证券:资金抱团“高股息”,超三成A股年内创历史新低!

A股商场行情冰火两重天。 “预制菜榜首股”跌破发行价 7月8日,味知香盘中最低跌至19.26元/股,股价跌破发行价,并创前史新低。揭露资料显现,公司是集研发、生产、销售为一体的半成品菜企业,现在具有8大产品系列&#…...

ASAN排查程序中内存问题使用总结

简介 谷歌有一系列Sanitizer工具,可用于排查程序中内存相关的问题。常用的Sanitizer工具包括: Address Sanitizer(ASan):用于检测内存使用错误。Leak Sanitizer(LSan):用于检测内存…...

day01:项目概述,环境搭建

文章目录 软件开发整体介绍软件开发流程角色分工软件环境 外卖平台项目介绍项目介绍定位功能架构 产品原型技术选型 开发环境搭建整体结构:前后端分离开发前后端混合开发缺点前后端分离开发 前端环境搭建Nginx 后端环境搭建熟悉项目结构使用Git进行版本控制数据库环…...

Python爬虫与数据可视化:构建完整的数据采集与分析流程

Python爬虫技术概述 Python爬虫是一种自动化的数据采集工具,它可以模拟浏览器行为,访问网页并提取所需信息。Python爬虫的实现通常涉及以下几个步骤: 发送网页请求:使用requests库向目标网站发送HTTP请求。获取网页内容&#xf…...

Java---包装类与泛型

1.包装类 1.1 包装类 在Java中,由于基本数据类型不是继承Object类,为了在泛型代码中可以支持基本数据类型,Java给每个基本数据类型各自提供了一个包装类。 如下图 除了char和int基本数据类型的包装类型有点特别,其他的都是首字…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...