纹理动画

纹理动画在游戏中的应用非常广泛。尤其在各种资源都比较局限的移动平台上,我们往往会使用纹理动画来代替复杂的粒子系统等模拟各种动画效果。

11.2.1序列帧动画


_Time是float4类型,
_Time.x表示当前时间 / 20,
_Time.y表示当前时间,
_Time.z表示当前时间 * 2,
_Time.w表示当前时间 * 3;

8*8的帧数量,设置播放速度30/s,需要2.6s播放完

Shader "MyShader/11-ImageSequenceAnimation"
{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)) = 30}SubShader {//由于序列帧图像通常包含了透明通道,因此可以被当成是一个半透明对象Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}Pass {Tags { "LightMode"="ForwardBase" }//关闭深度写入ZWrite Off//开启混合模式Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert  #pragma fragment frag#include "UnityCG.cginc"fixed4 _Color;sampler2D _MainTex;float4 _MainTex_ST;float _HorizontalAmount;float _VerticalAmount;float _Speed;struct a2v {  float4 vertex : POSITION; float2 texcoord : TEXCOORD0;};  struct v2f {  float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};  v2f vert (a2v v) {  v2f o;  o.pos = UnityObjectToClipPos(v.vertex);  //顶点纹理坐标采样o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);  return o;}  fixed4 frag (v2f i) : SV_Target {//_Time场景加载经过时间  计算当前模拟的时间   即帧float time = floor(_Time.y * _Speed);  //整除数量的到行列数, 得到对应模拟采样到小块纹理行列的时间   floor()向下取整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;
//                 Uity 中纹理坐标竖直方向的顺序(从下到上逐渐增大)和序列帧纹理中的顺序(播放顺序是从上到下)是相反的。这
//              i.uv是纹理的采样坐标,half2 uv = i.uv + half2(column, -row);//除以行列得到对应小纹理的坐标uv.x /=  _HorizontalAmount;uv.y /= _VerticalAmount;fixed4 c = tex2D(_MainTex, uv);c.rgb *= _Color;return c;}ENDCG}  }FallBack "Transparent/VertexLit"
}

我们需要使用行列索引值来构建真正的采样坐标。由于序列顿图像包含了许多关键帧图像,这意味着采样坐标需要映射到每个关键帧图像的坐标范围内。我们可以首先把原纹理坐标i.uv 按行数和列数进行等分,得到每个子图像的纹理坐标范围。然后,我们需要使用当前的行列数对上面的结果进行偏移,得到当前子图像的纹理坐标。需要注意的是,对坚直方向的坐标偏移需要使用减法,这是因为在 Unity 中纹理坐标直方向的顺序(从下到上逐渐增大)和序列帧纹理中的顺序(播放顺序是从上到下)是相反的。这对应了上面代码中注释掉的代码部分。我们可以把上述过程中的除法整合到一起,就得到了注释下方的代码。这样,我们就得到了真正的纹理采样坐标。

注意纹理坐标最大点是(1,1),这就是问什么要按照行列数缩放采样的原因

滚动背景

很多2D游戏都使用了不断滚动的背景来模拟游戏角色在场景中的穿梭,这些背景往往包含了多个层(layers)来模拟一种视差效果。而这些背景的实现往往就是利用了纹理动画。

我们首先分别利用iuvxy 和 uvzw 对两张背景纹理进行采样。然后,使用第二层纹理的明通道来混合两张纹理,这使用了CG的lerp 函数。

Shader "MyShader/Scrolling Background" {Properties {_MainTex ("Base Layer (RGB)", 2D) = "white" {} //纹理1_DetailTex ("2nd Layer (RGB)", 2D) = "white" {} //纹理2_ScrollX ("Base layer Scroll Speed", Float) = 1.0_Scroll2X ("2nd layer Scroll Speed", Float) = 1.0 _Multiplier ("Layer Multiplier", Float) = 1 //控制纹理亮度}SubShader {Tags { "RenderType"="Opaque" "Queue"="Geometry"}Pass { Tags { "LightMode"="ForwardBase" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;sampler2D _DetailTex;float4 _MainTex_ST;float4 _DetailTex_ST;float _ScrollX;float _Scroll2X;float _Multiplier;struct a2v {float4 vertex : POSITION;float4 texcoord : TEXCOORD0;};struct v2f {float4 pos : SV_POSITION;float4 uv : TEXCOORD0;};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); //我们利用内置的Time.y变量在水平方向上对纹理坐标进行偏移o.uv.zw = TRANSFORM_TEX(v.texcoord, _DetailTex) + frac(float2(_Scroll2X, 0.0) * _Time.y); //第二张纹理偏移return o;}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;}ENDCG}}FallBack "VertexLit"
}

顶点动画

如果一个游戏中所有的物体都是静止的,这样枯燥的世界恐怕很难引起玩家的兴趣。顶点动画可以让我们的场景变得更加生动有趣。在游戏中,我们常常使用顶点动画来模拟飘动的旗帜、湍流的小溪等效果。

河流的模拟是顶点动画最常见的应用之一。它的原理通常就是使用正弦函数等来模拟水流的波动效果。
(这里使用材质和网格渲染器生成模型,材质使用shader和纹理采样生成)

Shader "MyShader/11-Water"
{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 //纹理移动速度}SubShader {// 设置合适和标签   DisableBatching关闭批处理Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}// Pass {Tags { "LightMode"="ForwardBase" }ZWrite OffBlend SrcAlpha OneMinusSrcAlpha//关闭剔除Cull OffCGPROGRAM  #pragma vertex vert #pragma fragment frag#include "UnityCG.cginc" sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Color;float _Magnitude;float _Frequency;float _InvWaveLength;float _Speed;struct a2v {float4 vertex : POSITION;float4 texcoord : TEXCOORD0;};struct v2f {float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};v2f vert(a2v v) {v2f o;float4 offset;offset.yzw = float3(0.0, 0.0, 0.0); //只移动x分量//对于顶点来说只在一个方向移动offset.x = sin(_Frequency * _Time.y + v.vertex.x * _InvWaveLength + v.vertex.y * _InvWaveLength + v.vertex.z * _InvWaveLength) * _Magnitude;  // 改变顶点坐标,摄像机视角x轴向上// 变换后顶点位置o.pos = UnityObjectToClipPos(v.vertex + offset);o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); //纹理坐标采样o.uv +=  float2(0.0, _Time.y * _Speed); //帧采样,即采样速度return o;}fixed4 frag(v2f i) : SV_Target {fixed4 c = tex2D(_MainTex, i.uv);  //纹理采样c.rgb *= _Color.rgb; //混色return c;} ENDCG}}FallBack "Transparent/VertexLit"
}

实现这个效果,一方面要在顶点着色器对模型进行顶点变形,另一方面根据时间调整纹理采样速度。

广告牌

广告牌技术会根据视角方向来旋转一个被纹理着色的多边形(通常就是简单的四边形,这个多边形就是广告牌),使得多边形看起来好像总是面对着摄像机。广告牌技术被用于很多应用,比如渲染烟雾、云朵、闪光效果等。

广告牌技术的本质就是构建旋转矩阵,而我们知道一个变换矩阵需要3个基向量。广告牌技术使用的基向量通常就是表面法线(normal)、指向上的方向(up)以及指向右的方向(right)除此之外,我们还需要指定一个锚点 (anchorlocation),这个锚点在旋转过程中是固定不变的,以此来确定多边形在空间中的位置。

我们假设法线方向是固定的,首先,我们根据初始的表面法线和指向上的方向来计算出目标方向的指向右的方向(通过叉积操作):right-upxnormal


广告牌应用1:比如原神中火焰效果需要固定向上的轴向,法线不根据视角而变化(不随俯仰角度变化)。

广告牌应用2:而有的粒子效果,我们需要法线方向固定为视角方向(整个片面永远面向摄像机)。

Shader "MyShader/11-BillboardShader"
{Properties {_MainTex ("Main Tex", 2D) = "white" {}_Color ("Color Tint", Color) = (1, 1, 1, 1) //控制整体显示颜色_VerticalBillboarding ("Vertical Restraints", Range(0, 1)) = 1 //调整固定法线/固定向上}SubShader {// 取消对所有使用了该shader的模型的批量处理Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}Pass { Tags { "LightMode"="ForwardBase" }ZWrite OffBlend SrcAlpha OneMinusSrcAlphaCull OffCGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Color;fixed _VerticalBillboarding;struct a2v {float4 vertex : POSITION;float4 texcoord : TEXCOORD0;};struct v2f {float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};v2f vert (a2v v) {v2f o;// 所有计算都是在模型空间下进行的// 使用模型空间下原点作为广告牌的锚点  float3 center = float3(0, 0, 0);// 获取模型空间下相机的视角位置float3 viewer = mul(unity_WorldToObject,float4(_WorldSpaceCameraPos, 1));// 计算目标法线float3 normalDir = viewer - center;// 当VerticalBillboarding为1时,意味着法线方向固定为视角方向// 当VerticalBillboarding为0时,意味着向上方向固定为(0,1,0)normalDir.y =normalDir.y * _VerticalBillboarding;normalDir = normalize(normalDir);// 得到粗略的向上方方向// 为了防止法线方向和向上方向平行(如果平行,那么叉积得到的结果将是错误的)float3 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));// 使用这三个分量旋转得到新的顶点位置float3 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;}fixed4 frag (v2f i) : SV_Target {fixed4 c = tex2D (_MainTex, i.uv);c.rgb *= _Color.rgb;             return c;}ENDCG}} FallBack "Transparent/VertexLit"
}

法线方向固定广告牌

向上方向固定广告牌

动画注意事项

1 取消批处理会带来一定的性能下降

增加了draw call,应该尽量避免使用模型空间下的一些绝对位置和方向来进行计算。在广告牌的例子中,为了避免显式使用模型空间的中心来作为锚点,我们可以利用顶点颜色来存储每个顶点到锚点的距离值,这种做法在商业游戏中很常见

2 添加阴影的问题

如果我们想要对包含了顶点动画的物体添加阴影,那么如果仍然像 9.4 节中那样使用内置的 Difuse 等包含的阴影 Pass 来染,就得不到正确的影效果(这里指的是无法向其他物体正确地投射阴影。这是因为,我们讲过 Unity 的阴影绘制需要调用一个ShadowCaster Pass,而如果直接使用这些内置的ShadowCaster Pass,这个Pass中并没有进行相关的顶点动画,因此Unity会仍然按照原来的顶点位置来计算阴影,这并不是我们希望看到的。

自定义Shadow Pass:

Shader "MyShader/11-Vertex Animation With Shadow" {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}SubShader {// Need to disable batching because of the vertex animationTags {"DisableBatching"="True"}Pass {Tags { "LightMode"="ForwardBase" }Cull OffCGPROGRAM  #pragma vertex vert #pragma fragment frag#include "UnityCG.cginc" sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Color;float _Magnitude;float _Frequency;float _InvWaveLength;float _Speed;struct a2v {float4 vertex : POSITION;float4 texcoord : TEXCOORD0;};struct v2f {float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};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;}fixed4 frag(v2f i) : SV_Target {fixed4 c = tex2D(_MainTex, i.uv);c.rgb *= _Color.rgb;return c;} ENDCG}// Pass to render object as a shadow casterPass {Tags { "LightMode" = "ShadowCaster" }CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_shadowcaster#include "UnityCG.cginc"float _Magnitude;float _Frequency;float _InvWaveLength;float _Speed;struct v2f { //V2FSHADOWCASTER来定义阴影投射需要定义的变量V2F_SHADOW_CASTER;};v2f vert(appdata_base 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;v.vertex = v.vertex + offset;// unity计算阴影位置TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)return o;}fixed4 frag(v2f i) : SV_Target {// Unity完成阴影投射SHADOW_CASTER_FRAGMENT(i)}ENDCG}}FallBack "VertexLit"
}

水流需要设置为两面才能够显示出投影。

Unity Shader学习记录(18) —— Shader动画相关推荐

  1. Unity学习记录——模型与动画

    Unity学习记录--模型与动画 前言 ​ 本文是中山大学软件工程学院2020级3d游戏编程与设计的作业7 编程题:智能巡逻兵 1.学习参考 ​ 除去老师在课堂上讲的内容,本次作业代码与操作主要参考了 ...

  2. Unity+Hololens学习记录-射线应用

    Unity+Hololens学习记录-射线应用 前言 射线介绍: 射线应用元素介绍: Ray RaycastHit Raycast 射线应用实例: 射线碰撞信息获取 Camera发出Ray Gaze射 ...

  3. unity shader 学习记录

    记录下我学习unity shader的过程,并把我看到过的高质量教程推荐给大家! 借助插件shaderforge来熟悉着色的效果,并对照着shaderforge自动生成的shader代码手工优化和实现 ...

  4. shader 学习记录

    学习shader笔记 记录~ unity版本 2017.4.1 shader具体内容: basecolor , second tex blend , snow blend , invert color ...

  5. Unity Shader 学习记录(3) —— CG语言和Shader文件

    1 什么是语义 赋给shader的输入输出的字符串,表达了参数的含义.语义告诉shader从哪里获取数据,又把数据输出到哪里. 2 Shader的三种debug方法 1 假色彩图像 2 VStudio ...

  6. Unity Shader学习笔记 - 用UV动画实现沙滩上的泡沫

    这个泡沫效果来自远古时代的Unity官方海岛Demo, 原效果直接复制3个材质球在js脚本中做UV动画偏移,这里尝试在shader中做动画并且一个pass中完成: // Upgrade NOTE: r ...

  7. Unity Shader 学习记录(5) —— 实现漫反射光照模型

    1 公式计算 从公式可以看出,要计算漫反射需要知道4 个参数:入射光线的颜色和强度 cgh"材质的漫反射系数mdiuse,表面法线n以及光源方向I. 为了防止点积结果为负值,我们需要使用ma ...

  8. Unity Shader学习记录(6) —— 高光反射光照模型和内置计算函数

    1 高光反射光照模型计算公式 从公式可以看出,要计算高光反射需要知道 4 个参数:入射光线的颜色和强度c,材质的光反射系数 m,视角方向v以及反射方向r.其中,反射方向r可以由表面法线n和光源i计算得 ...

  9. Unity Shader学习记录(15) —— Unity的光源类型

    光源类型 Unity 一共支持4种光源类型:平行光.点光源.聚光灯和面光源 (area light).面光源仅在烘焙时才可发挥作用,因此不在本节讨论范围内.由于每种光源的几何定义不同,因此它们对应的光 ...

最新文章

  1. 使用jQuery和YQL,以Ajax方式加载外部内容
  2. 两个摄像头是如何将照片拼接在一起的
  3. docker存储结构解析
  4. JOOMLA中文安装时 数据库发生错误解块办法
  5. python 切片_全面解读Python高级特性切片
  6. 虚拟机Linux图形界面配置NAT-桥接
  7. nginx linux 长连接,Nginx实现长连接应用
  8. 利用zabbix web scenario 监控Web站点的可用性
  9. 叮当:一个开源的树莓派中文智能音箱项目
  10. bootstrap 日历
  11. java配置 path_java中path的配置
  12. leetcode:买卖股票的最佳时机2(python)
  13. 脱壳--00.aspack.exe
  14. 将word背景设成淡绿色方法
  15. oracle收回dba权限后的检查,Oracle RAC GI 权限 检查和修复 方法
  16. 橙瓜码字多端同步、十份云储存本地实时备份,最放心的码字软件
  17. opencv手势识别(2_KNN算法识别)
  18. 点击跳转打开新页--------window.open
  19. 04 MapReduce
  20. 人工与软件刷流量有什么区别,如何做刷流量效果才最好?

热门文章

  1. PCB 铜厚厚度和线宽的选择
  2. 箱线图的几种画法-Python
  3. arduino的基本函数
  4. malloc()动态分配内存
  5. rockyLinux 初体验PostgreSQL15详细安装教程
  6. python学习笔记分享(四十)网络爬虫(7)反爬虫问题,解决中文乱码,登陆和验证码处理
  7. 微信小程序云开发之云数据库查询及动态输入
  8. 【我的渲染技术进阶之旅】你知道数字图像处理的标准图上的女孩子是谁吗?背后的故事你了解吗?为啥这张名为Lenna的图会成为数字图像处理的标准图呢?
  9. 数学建模_统计回归模型的梳理与总结:逐步回归,残差检验,自相关
  10. python小游戏井字棋(人机对战)