UnityShader学习笔记——动态效果

news/2025/2/8 14:05:18 标签: 学习, 笔记

——内容源自唐老狮的shader课程


目录

1.原理

2.Shader中内置的时间变量

3.Shader中经常会改变的数据

4.纹理动画

4.1.背景滚动

4.1.1.补充知识

4.1.2.基本原理

4.2.帧动画

4.2.1.基本原理

5.流动的2D河流

5.1.基本原理

5.2.关键步骤

5.3.补充知识

6.广告牌效果

6.1.概念

6.2.基本原理

7.顶点动画注意事项

7.1.批处理

7.1.1.为什么批处理会影响顶点动画

7.1.2.关闭批处理的问题

7.1.3.如何解决问题

7.2.阴影

7.2.1.问题的产生

7.2.2.解决

8.如有疏漏,还请指出


1.原理

        利用时间变化来改变数据,从而导致渲染结果改变,带来画面变化


2.Shader中内置的时间变量

        1.float4 _TIme:四个分量分别是(t/20,t,2t,3t),t代表游戏场景从加载开始所经过的时间

        2.float4 _SinTime:四个分量分别是(t/8,t/4,t/2,t),t代表游戏运行时间的正弦值

        3.float4 _CosTime:四个分量分别是(t/8,t/4,t/2,t),t代表游戏运行时间的余弦值

        4.float4 unity_DeltaTime:(dt,1/dt,smootDt,1/smootDt),dt代表帧间隔时间(上一帧到当前帧间隔时间),smootDt是平滑处理过的时间间隔,对帧间隔时间进行了某种平滑算法处理后得到的结果


3.Shader中经常会改变的数据

        1.颜色:通过时间控制颜色的变化,如 渐变,闪烁 等

        2.位置:利用时间使顶点在某个方向上移动,如 波动 等

        3.纹理坐标:利用时间变化来改变纹理坐标,如 水流,云彩,序列帧动画 等

        4.法线:利用时间动态修改法线方向,如 风吹草动 等

        5.缩放:利用时间改变物体缩放比例,如 脉动,跳动 等

        6.透明度:利用时间控制物体透明度,如 淡入淡出,闪烁 等


4.纹理动画

4.1.背景滚动

4.1.1.补充知识

        frac(x):内部计算规则为frac(x) = 1 - floor(x),它能保留一个数的小数部分,负数保留的是小数部分+1的结果。它能保证uv坐标在0-1之间。

4.1.2.基本原理

        不停地利用时间变量对uv坐标进行偏移运算,超过1的部分从0开始采样,小于1同理

Shader "Models_4/RollingBackground"
{
    Properties
    {
        _MainTex("Texture", 2D) = ""{}

        //控制流速
        _RollingSpeedU("RollingSpeedU", Float) = 1
        _RollingSpeedV("RollingSpeedV", Float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True"}

        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float _RollingSpeedU;
            float _RollingSpeedV;

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

            v2f vert (appdata_full v)
            {
                v2f data;
                data.pos = UnityObjectToClipPos(v.vertex);
                data.uv = v.texcoord.xy;

                return data;
            }

            fixed4 frag (v2f f) : SV_Target
            {
                float2 uv = frac(float2(_Time.y * _RollingSpeedU, _Time.y * _RollingSpeedV) + f.uv);
                fixed4 backTex = tex2D(_MainTex, uv);

                return backTex;
            }
            ENDCG
        }
    }
}
两个背景以不同速度滚动

4.2.帧动画

4.2.1.基本原理

        通过_Time.y确认当前具体应该是哪一帧,然后算出在图中的几行几列,即确认采样范围,然后将采样范围缩放到 0-1 之间,但由于uv采样是从左下角开始,故采样范围要经过一点变化。

Shader "Models_4/SequenceFrame"
{
    Properties
    {
        _MainTex("MainTex", 2D) = ""{}

        //图集行列
        _Rows("Rows", Int) = 8
        _Columns("Columns", Int) = 8

        _SequenceFrameSpeed("SequenceFrameSpeed", Float) = 1
    }

    SubShader
    {
        Tags { "RenderType" = "Transparent" "IgnoreProjector" = "True" "Queue" = "Transparent" }

        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            Tags {}

            CGPROGRAM

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            float _Rows;
            float _Columns;
            float _SequenceFrameSpeed;

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

            v2f vert(appdata_full v)
            {
                v2f data;
                data.pos = UnityObjectToClipPos(v.vertex);
                data.uv = v.texcoord.xy;

                return data;
            }

            fixed4 frag(v2f f) : SV_TARGET
            {
                //当前帧的索引 
                float frameIndex = floor(_Time.y * _SequenceFrameSpeed) % (_Rows * _Columns);
                //图片采样起始位置的运算,除以对应的行和列的目的是 将其转换到0-1的坐标范围内
                //这里1 - ,是因为要把图片坐标系(左上为原点)转换为 uv坐标系(左下为原点)
                //+ 1 也差不多这个原因
                float2 frameUV = float2((frameIndex % _Columns) / _Columns, 1 - ((floor(frameIndex / _Columns) + 1) / _Rows));
                float2 size = float2(1 / _Columns, 1 / _Rows);
                //* size 相当于把0 - 1范围 缩放到了0 - 1/8的范围内
                //+ frameUV 是把起始的采样位置 移到了 对应帧(小格子)的起始采样位置
                float2 uv = f.uv * size + frameUV;

                fixed4 frameColor = tex2D(_MainTex, uv);

                return frameColor;
            }

            ENDCG
        }
    }
}
帧动画


5.流动的2D河流

5.1.基本原理

        让我们的顶点在对应的轴上产生偏移,主要运用的是Shader中的内置函数sin以及内置时间变量_Time.y

        波浪感的关键因素:

        1.波长:                  其越大,波动越缓慢,周期越长
        2.波长的倒数:       其越大,波动越频繁,周期越短
        3.频率:                  单位时间内波动发生的次数
        4.幅度:                  波峰或波谷相对于中线的最大偏移位置

5.2.关键步骤

        1.让顶点上下动起来:让sin参与计算,如sin(_Time.y),可使其不断返回-1~1之间的值,而为了控制波动频率,可以用sin(_Time.y * 波动频率)

        问题是所有顶点的偏移都一样,会出现整体移动的效果

        2.让顶点有差异地动起来:以不同地坐标制造差异性,可以使用sin(_Time.y * 波动频率 + 顶点某轴的坐标),然后用得到的返回值作为顶点在某一轴向的偏移值,便可以让顶点有差异性的动起来

        问题是无法体现波长和波动幅度(振幅,或者说幅度)

        3.体现波长和幅度:使用

波动幅度 * sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数)

        倒数越大,波形周期越短

        具体轴向根据模型空间决定

5.3.补充知识

        渲染标签DisableBatching:其作用是是否对SubShader关闭批处理,原因是我们在制作顶点动画的时候,有时需要使用模型空间下的数据,而批处理会合并所有相关的模型,这些模型各自的模型空间会丢失,导致我们无法正确使用模型空间下相关数据。在实现2d河流效果时,我们就需要让顶点在模型空间下进行偏移,因此需要使用该标签,为Shader关闭批处理。

        同时,对于导入的模型资源,要观察其符不符合unity轴向标准(左右x,上下y,前后z)

Shader "Models_4/Water_2D"
{
    Properties
    {
        _MainTex("MainTex", 2D) = ""{}
        //类似漫反射颜色(大概)
        _Color("Color", Color) = (1, 1, 1, 1)
        //振幅
        _WaveAmplitude("WaveAmplitude", Float) = 1
        //波动频率
        _WaveFrequency("WaveFrequency", Float) = 1
        //波长的倒数
        _InvWaveLength("InvWaveLength", Float) = 1
        //纹理变化速度
        _Speed("Speed", Float) = 1
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" "IgnorProjector" = "True" "RenderType" = "Transparent" "DisableBatching" = "True" }


        Pass
        {
            Tags { "LightMode" = "ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _Color;
            float _WaveAmplitude;
            float _WaveFrequency;
            float _InvWaveLength;
            float _Speed;

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

            v2f vert(appdata_full v)
            {
                v2f data;

                float4 offset = float4(0, 0, 0, 0);

                //在世界空间下看,模型的x是y轴,我们要对模型的x轴进行改变,但不能直接改变世界空间下的y轴,因为世界空间下改变不会作用到模型空间下
                //直接改模型空间的点,并且对其原始状态上的轴做判断
                offset.x = _WaveAmplitude * sin(_WaveFrequency * _Time.y + v.vertex.z * _InvWaveLength);
                float4 vertex = v.vertex + offset;

                data.pos = UnityObjectToClipPos(vertex);
                data.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                //让主纹理也动
                data.uv += float2(0, _Time.y * _Speed);

                return data;
            }

            fixed4 frag(v2f f) : SV_TARGET
            {
                //float2 uv = frac(f.uv + float2(0, _Time.y * _Speed));
                fixed4 mainColor = tex2D(_MainTex, f.uv) * _Color;

                return mainColor;
            }

            ENDCG
        }
    }
}
2D河流效果

6.广告牌效果

6.1.概念

        是一种图形技术,用于确保对象始终面对摄像机,同时在某些轴上保持固定的方向(一般分为全向广告牌和轴对齐广告牌)

        全向广告牌:任何视角下,对象在所有轴上始终面向 摄像机

        轴对齐广告牌:对象在一个轴上保持固定方向,而在其他轴上面向摄像机。其中垂直广告牌尤为特殊,他在水平面(XZ)平面上旋转,但在垂直方向上始终保持不变

6.2.基本原理

        核心是旋转模型空间坐标系让其始终面向摄像机,故而需要构建一个基于模型空间的新坐标系。改坐标系有两个关键因素构成:

        1.原点:基于模型空间的,可以自定义,但一般还是用000

        2.三个轴向(x轴,y轴,z轴):通常情况下这仨由 右方向,垂直向上方向,视角方向 构成。

        轴的计算:获得视角向量(新z轴)后,将其与 旧y轴(垂直向上的轴,(0, 1, 0))叉乘得到 右方向 轴(即 新x轴),然后将 视角方向 与 右方向 叉乘得到 新y轴。

        最后,新顶点的位置如下:

偏移位置 = 顶点坐标 - Center

新顶点位置 = Center + X轴 * 偏移位置.x + Y轴 * 偏移位置.y + Z轴 * 偏移位置.z

        垂直广告牌只需要在计算视角方向(新x轴)的时候,让该轴的y分量为0即可

Shader "Models_4/BillboardEffect"
{
    Properties
    {
        _MainTex("MainTex", 2D) = ""{}
        _Color("Color", Color) = (1, 1, 1, 1)
        _VerticalAmount("VerticalAmount", Range(0, 1)) = 0
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" "DisableBatching" = "True" }

        Pass
        {
            Tags { "LightMode" = "ForwardBase" }

            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _Color;
            float _VerticalAmount;

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

            v2f vert(appdata_full v)
            {
                v2f data;
                float3 center = float3(0, 0, 0);
                float3 cameraInObjectPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1)); 
                //z
                float3 normalDir = cameraInObjectPos - center;
                //全向或是垂直
                normalDir.y *= _VerticalAmount;
                normalDir = normalize(normalDir);

                float3 oldUpDir = normalDir.y > 0.999 ? float3(0, 0, 1) : float3(0, 1, 0);
                //float3 oldUpDir = float3(0, 1, 0);
                //x
                //x
                float3 rightDir = normalize(cross(oldUpDir, normalDir));
                //y
                float3 newUpDir = normalize(cross(normalDir, rightDir));

                float3 centerOffset = v.vertex.xyz - center;
                float3 newVertex = center + rightDir * centerOffset.x + newUpDir * centerOffset.y + normalDir * centerOffset.z;

                data.pos = UnityObjectToClipPos(newVertex);
                data.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

                return data;
            }

            fixed4 frag(v2f f) : SV_TARGET
            {
                fixed4 mainColor = tex2D(_MainTex, f.uv) * _Color;

                return mainColor;
            }

            ENDCG
        }
    }
}
全向广告牌效果


7.顶点动画注意事项

7.1.批处理

7.1.1.为什么批处理会影响顶点动画

        unity中默认有静态批处理和动态批处理,而批处理的主要作用是合并多个对象,将它们作为一个DrawCall来处理。之所以批处理会对顶点动画带来影响,是因为 不同的对象拥有不同的变换矩阵(平移,旋转,缩放)。而进行批处理之后,它们的变换矩阵将会进行统一处理,进而令其失去独立性。

        举例子就是两个魔尺(颜色一样),分开各拼各的时候,你能分辨出它们各自的顶点(就当是每个三角衔接处),而如果把这俩魔尺合一块拼起来,那么某一个点究竟是哪把魔尺的就无法辨别了。

7.1.2.关闭批处理的问题

        DrawCall的提升,进而导致性能的问题,而如果因为关闭批处理带来了性能问题,并且必须优化带有定点动画的Shader,该如何解决呢

7.1.3.如何解决问题

        提前将独立的模型顶点存储起来:

        1.通过c#代码存储到网格的颜色属性中:在Shader中通过颜色属性获取顶点信息。我们可以在appdata_full中点出color成员来使用这些顶点

    private void SaveToMeshColor()
    {
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        if (meshFilter != null)
        {
            Mesh mesh = meshFilter.mesh;
            Vector3[] vertices = mesh.vertices;
            Color[] colors = new Color[vertices.Length];

            for (int i = 0; i < vertices.Length; i++)
            {
                colors[i] = new Color(vertices[i].x, vertices[i].y, vertices[i].z, 1);
            }

            mesh.colors = colors;
        }
    }

        2.通过c#代码存到uv中:与存储到color类似,但是一般只在存储两个值的时候使用。

7.2.阴影

7.2.1.问题的产生

        顶点动画通过Fallback所产生的阴影是根据 没有变形过的顶点 来的,所以对于2d河流这种,直接使用Fallback产生的阴影会跟实际不符

7.2.2.解决

        自己实现阴影,并在该Pass中及逆行顶点偏移的计算即可。无需进行裁剪空间坐标变换以及uv相关计算

        Pass
        {
            Tags { "LightMode" = "ShadowCaster" }

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            float _WaveAmplitude;
            float _WaveFrequency;
            float _InvWaveLength;

            struct v2f
            {
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_full v)
            {
                v2f data;

                float4 offset = float4(0, 0, 0, 0);

                offset.x = _WaveAmplitude * sin(_WaveFrequency * _Time.y + v.vertex.z * _InvWaveLength);
                v.vertex += offset;
                //这个会自动调用v的数据
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(data);

                return data;
            }

            fixed4 frag(v2f f) : SV_TARGET
            {
                SHADOW_CASTER_FRAGMENT(f);

                return fixed4(1, 1, 1, 1);
            }

            ENDCG
        }

 


8.如有疏漏,还请指出


http://www.niftyadmin.cn/n/5844933.html

相关文章

【Android】Android开发应用如何开启任务栏消息通知

Android开发应用如何开启任务栏消息通知 1. 获取通知权限2.编写通知工具类3. 进行任务栏消息通知 1. 获取通知权限 在 AndroidManifest.xml 里加上权限配置&#xff0c;如下。 <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android…

【unity小技巧】分享vscode如何进行unity开发,且如何开启unity断点调试模式,并进行unity断点调试(2025年最新的方法,实测有效)

文章目录 前言一、前置条件1、已安装Visual Studio Code&#xff0c;并且unity首选项>外部工具>外部脚本编辑器选择为Visual Studio Code [版本号]&#xff0c;2、在Visual Studio Code扩展中搜索Unity&#xff0c;并安装3、同时注意这个插件下面的描述&#xff0c;需要根…

Rust语言进阶之标准输出:stdout用法实例(一百零六)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

5.2Internet及其作用

5.2.1Internet概述 Internet称为互联网&#xff0c;又称英特网&#xff0c;始于1969年的美国ARPANET&#xff08;阿帕网&#xff09;&#xff0c;是全球性的网络。 互连网指的是两个或多个不同类型的网络通过路由器等网络设备连接起来&#xff0c;形成一个更大的网络结构。互连…

备战蓝桥杯:二维前缀和之激光炸弹

由于xi yi在0到5000之间&#xff0c;我们之前学的前缀和模板都是从下标1开始&#xff0c;我们就把x和y各自加1就好了&#xff0c;也就是在1到5001之间了&#xff0c;题目又说了&#xff0c;同一位置可能有多个目标&#xff0c;所以我们在一个位置求价值的时候应该用加等来求和 …

makefile 的strip,filter,ifeq,ifneq基础使用

目录 一、strip1.1 语法1.2 示例1.3 使用场景 二、filter2.1 语法2.2 示例2.3 使用 * 和 ? 通配符2.4 结合使用2.5 使用场景 三、ifeq 和 ifneq3.1 ifeq3.1.1 语法3.1.2 示例 3.2 ifneq3.2.1 语法3.2.2 示例 3.3 典型使用场景3.3.1 根据版本控制编译选项:3.3.2 选择不同的源文…

关于 SQL 内连接、外连接(左连接、右连接)的面试题

一、概念理解类 1. 请详细解释内连接&#xff08;INNER JOIN&#xff09;、左连接&#xff08;LEFT JOIN&#xff09;、右连接&#xff08;RIGHT JOIN&#xff09;在 SQL 中的概念和区别&#xff0c;并分别举例说明它们在实际查询场景中的应用。 在SQL中&#xff0c;内连接&a…

Vue:Table合并行于列

<template><div><el-table:data"tableData":span-method"mergeCells"style"width: 100%"><el-table-columnprop"date"label"日期"width"180"></el-table-column><el-table-colu…