特效处理

This commit is contained in:
2026-10-20 18:30:03 +08:00
parent 40bd235e67
commit bee2d42356
131 changed files with 837 additions and 456 deletions
@@ -1,85 +1,312 @@
Shader "Boke/Addmul_niuqu" {
// ============================================================================
// 材质属性(Material Properties)
// 在 Unity Inspector 中看到的可调参数都定义在这里
// ============================================================================
Properties {
// --- 系统属性(Inspector 中隐藏,由代码控制)---
[HideInInspector] _EmissionColor ("Emission Color", Vector) = (1,1,1,1)
[HideInInspector] _AlphaCutoff ("Alpha Cutoff ", Range(0, 1)) = 0.5
_Color ("Color", Vector) = (1,1,1,1)
_MainTex ("Main Tex", 2D) = "white" {}
_Main_Desaturate ("Main_Desaturate", Range(0, 1)) = 0
_qd ("qd", Float) = 0
_opacity ("opacity", Float) = 0
// --- 主贴图与颜色 ---
_Color ("Color", Vector) = (1,1,1,1) // 整体色调
_MainTex ("Main Tex", 2D) = "white" {} // 主纹理
_Main_Desaturate ("Main_Desaturate", Range(0, 1)) = 0 // 去饱和度 0~1
// --- 扭曲参数 ---
_qd ("扭曲强度", Float) = 0 // 扭曲幅度,越大变形越强
_opacity ("不透明度", Float) = 0 // 整体透明度
// --- 主贴图流动速度(UV 滚动)---
_Main_Tex_speed_x ("Main_Tex_speed_x", Float) = 0
_Main_Tex_speed_y ("Main_Tex_speed_y", Float) = 0
_Noise ("Noise", 2D) = "white" {}
_Noise_qd_y ("Noise_qd_y", Float) = 0
_Noise_qd_x ("Noise_qd_x", Float) = 0
_Noise_speed_x ("Noise_speed_x", Float) = 0
_Noise_speed_y ("Noise_speed_y", Float) = 0
_Mask ("Mask", 2D) = "white" {}
_Mask_speed_x ("Mask_speed_x", Float) = 0
_Mask_speed_y ("Mask_speed_y", Float) = 0
_Mask2 ("Mask2", 2D) = "white" {}
_Mask2_speed_x ("Mask2_speed_x", Float) = 0
_Mask2_speed_y ("Mask2_speed_y", Float) = 0
// --- 噪声贴图(用于 UV 扭曲方向)---
_Noise ("Noise", 2D) = "white" {} // 噪声图:R通道→X扭曲,G通道→Y扭曲
_Noise_qd_y ("Noise 密度 Y", Float) = 0 // Y 方向重复次数(越大条纹越密)
_Noise_qd_x ("Noise 密度 X", Float) = 0 // X 方向重复次数(越大条纹越密)
_Noise_speed_x ("Noise 流动速度 X", Float) = 0 // X 方向滚动速度
_Noise_speed_y ("Noise 流动速度 Y", Float) = 0 // Y 方向滚动速度
// --- 遮罩贴图 1(用于控制 RGB 与 Alpha 的区域)---
_Mask ("Mask", 2D) = "white" {} // 遮罩纹理(读取 R 通道)
_Mask_speed_x ("Mask 流动速度 X", Float) = 0
_Mask_speed_y ("Mask 流动速度 Y", Float) = 0
// --- 遮罩贴图 2(与 Mask 1 相乘,叠加控制)---
_Mask2 ("Mask2", 2D) = "white" {} // 第二层遮罩(读取 R 通道)
_Mask2_speed_x ("Mask2 流动速度 X", Float) = 0
_Mask2_speed_y ("Mask2 流动速度 Y", Float) = 0
// --- 渲染状态 ---
[Enum(Off,0,Front,1,Back,2)] __CullMode ("CullMode(Back时背面看不见,Off时双面可见)", Float) = 0
[Enum(Off,0,On,1)] __ZWrite ("ZWrite(开启时,会写入深度)", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] __ZTest ("ZTest(深度测试)", Float) = 4
_StencilRef ("StencilRef(模板测试时的参考值)", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("StencilComp(模板测试的比较方法)", Float) = 0
// --- UI 遮罩:uGUI MaskableGraphic / UIParticle 依赖以下属性名,勿改名 ---
_Stencil ("Stencil ID", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Float) = 0
_StencilReadMask ("Stencil Read Mask", Float) = 255
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
// --- 兼容旧的模板测试命名(保留声明,避免旧材质缺属性)---
_StencilRef ("StencilRef(模板测试时的参考值)", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilPassOp ("StenciPassOp(模板测试成功时的操作)", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilFailOp ("StenciFailOp(模板测试失败时的操作)", Float) = 0
}
//DummyShaderTextExporter
SubShader{
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// ============================================================================
// SubShader — URP 渲染管线的 Pass
// ============================================================================
SubShader
{
// --- 渲染标签 ---
Tags
{
"RenderType" = "Transparent" // 透明渲染类型
"Queue" = "Transparent" // 透明渲染队列(半透明物体后渲染)
"RenderPipeline" = "UniversalPipeline" // 指定 URP 管线
"IgnoreProjector" = "True" // 忽略投影器
}
LOD 200
float4x4 unity_ObjectToWorld;
float4x4 unity_MatrixVP;
float4 _MainTex_ST;
// ========================================================================
// Forward Pass — 前向渲染的主 Pass
// ========================================================================
Pass
{
Name "Forward"
Tags { "LightMode" = "UniversalForward" }
struct Vertex_Stage_Input
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
// --- 渲染状态 ---
Cull [__CullMode] // 剔除模式(从材质面板读取)
ZWrite [__ZWrite] // 深度写入(从材质面板读取)
ZTest [__ZTest] // 深度测试(从材质面板读取)
struct Vertex_Stage_Output
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
};
// --- 混合模式(Blend)---
// OneMinusDstColor One = 逐像素:(1 - 背景色) × 前景色 + 背景色 × 1
// 这是经典的 "AddMul"(叠加乘法)混合,用于发光/流光效果
// 背景越暗前景越亮,背景纯白时前景不显示
Blend OneMinusDstColor One
ColorMask [_ColorMask]
Vertex_Stage_Output vert(Vertex_Stage_Input input)
{
Vertex_Stage_Output output;
output.uv = (input.uv.xy * _MainTex_ST.xy) + _MainTex_ST.zw;
output.pos = mul(unity_MatrixVP, mul(unity_ObjectToWorld, input.pos));
return output;
}
// --- 模板测试(Stencil,改用 uGUI 属性名以支持 UI 遮罩)---
Stencil
{
Ref [_Stencil] // 参考值
Comp [_StencilComp] // 比较方式
Pass [_StencilOp] // 通过时的操作
ReadMask [_StencilReadMask] // 读取掩码
WriteMask [_StencilWriteMask] // 写入掩码
}
Texture2D<float4> _MainTex;
SamplerState sampler_MainTex;
float4 _Color;
// ====================================================================
// HLSL 着色器代码
// ====================================================================
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
struct Fragment_Stage_Input
{
float2 uv : TEXCOORD0;
};
// 引入 URP 核心库(提供 TransformObjectToHClip 等函数)
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
float4 frag(Fragment_Stage_Input input) : SV_TARGET
{
return _MainTex.Sample(sampler_MainTex, input.uv.xy) * _Color;
}
// --- 纹理与采样器声明 ---
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
TEXTURE2D(_Noise); SAMPLER(sampler_Noise);
TEXTURE2D(_Mask); SAMPLER(sampler_Mask);
TEXTURE2D(_Mask2); SAMPLER(sampler_Mask2);
ENDHLSL
}
}
Fallback "Hidden/InternalErrorShader"
//CustomEditor "UnityEditor.ShaderGraph.PBRMasterGUI"
}
// --- 材质参数常量缓冲区(UnityPerMaterial)---
// 每次材质变化时更新,性能优于单独 uniform
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST; // 主贴图 UV 缩放/偏移(Tiling & Offset)
float4 _Color; // 整体颜色
float _Main_Desaturate; // 去饱和度 0~1
float _qd; // 扭曲强度
float _opacity; // 不透明度
float _Main_Tex_speed_x; // 主贴图 X 方向流动速度
float _Main_Tex_speed_y; // 主贴图 Y 方向流动速度
float4 _Noise_ST; // 噪声贴图 Tiling & Offset(来自 Inspector)
float _Noise_qd_x; // 噪声 X 方向密度
float _Noise_qd_y; // 噪声 Y 方向密度
float _Noise_speed_x; // 噪声 X 方向滚动速度(双层交叉淡化)
float _Noise_speed_y; // 噪声 Y 方向滚动速度(双层交叉淡化)
float _Mask_speed_x; // 遮罩 1 X 方向流动速度
float _Mask_speed_y; // 遮罩 1 Y 方向流动速度
float _Mask2_speed_x; // 遮罩 2 X 方向流动速度
float _Mask2_speed_y; // 遮罩 2 Y 方向流动速度
float _AlphaCutoff; // 保留兼容,未使用
CBUFFER_END
// ====================================================================
// 顶点输入结构(Attributes)
// 从 Mesh 读取的数据
// ====================================================================
struct Attributes
{
float4 positionOS : POSITION; // 对象空间顶点位置
float2 uv : TEXCOORD0; // 主 UV 坐标
float4 color : COLOR; // 顶点颜色(可被材质 Color 叠加)
};
// ====================================================================
// 片元输入结构(Varyings)
// 顶点着色器输出 → 片元着色器输入
// ====================================================================
struct Varyings
{
float4 positionCS : SV_POSITION; // 裁剪空间位置(给 GPU 用)
float2 uv : TEXCOORD0; // UV 坐标(插值后的)
float4 color : COLOR; // 顶点颜色(插值后的)
};
// ====================================================================
// 顶点着色器(Vertex Shader)
// 负责:对象空间 → 裁剪空间的坐标变换
// ====================================================================
Varyings vert(Attributes IN)
{
Varyings OUT;
// 对象空间 → 裁剪空间(URP 标准函数)
OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.uv = IN.uv; // 传递 UV(无需变换)
OUT.color = IN.color; // 传递顶点颜色
return OUT;
}
// ====================================================================
// 片元着色器(Fragment Shader)
// 负责:逐像素计算最终颜色和透明度
// ====================================================================
half4 frag(Varyings IN) : SV_Target
{
// --- 时间系统 ---
// _Time.y 是 Unity 内置的时间变量(单位:秒)
// ⚠️ 必须用 float:时间值持续增长,half(f16) 在大值区精度崩塌,
// 导致 UV 跳跃式步进 → 移动端像素块
float time = _Time.y;
float flowTime = time * 1.0;
// =================================================================
// 第一步:计算主贴图 UV 坐标
// UV = 原始UV × Tiling + Offset + 滚动偏移
// ⚠️ float2 保证 UV 精度(大贴图/高 Tiling 下 half 不够)
// =================================================================
float2 mainUV = IN.uv * _MainTex_ST.xy + _MainTex_ST.zw;
// 加上时间驱动的流动偏移(速度 × 时间)
mainUV += float2(_Main_Tex_speed_x, _Main_Tex_speed_y) * flowTime;
// =================================================================
// 第二步:噪声扭曲(UV 偏移)
// 用噪声贴图的 R/G 通道产生一个 2D 偏移向量,加到主贴图 UV 上。
// 采用"双层反向滚动 + 三角波交叉淡入淡出"技术:
// - A 层正向滚动,B 层反向滚动
// - 当 A 层到达纹理回绕边界时,正好切换到 B 层
// - 三角波 0→1→0 循环过渡,完全消除回绕突兀感
// =================================================================
if (_qd != 0.0h)
{
// --- 噪声密度计算 ---
// _Noise_qd_x/y 控制噪声在 UV 上的重复次数
// 1 = 贴满 1 次,2 = 重复 2 次(更密)
// 默认 0 时自动设为 1(避免所有像素采样同一点导致整体同步晃动)
// noiseQD 是简单的 [0, N] 缩放因子,half 足够
half2 noiseQD = half2(
_Noise_qd_x != 0.0h ? _Noise_qd_x : 1.0h,
_Noise_qd_y != 0.0h ? _Noise_qd_y : 1.0h
);
// --- 双层反向滚动(相位错开 0.5) ---
// 层 A:正向滚动,层 B:反向滚动 + 错开半格
// 这样当 A 到达回绕边界时,B 正好在纹素中间,不会同时回绕
// ⚠️ scroll/baseUV/noiseUV 用 float:含 time 累加 + UV 缩放
float2 scroll = float2(_Noise_speed_x, _Noise_speed_y) * flowTime;
float2 baseUV = IN.uv * _Noise_ST.xy * noiseQD + _Noise_ST.zw;
float2 noiseUV_A = baseUV + scroll;
float2 noiseUV_B = baseUV - scroll + 0.5;
half4 noise_A = SAMPLE_TEXTURE2D(_Noise, sampler_Noise, noiseUV_A);
half4 noise_B = SAMPLE_TEXTURE2D(_Noise, sampler_Noise, noiseUV_B);
// --- 三角波交叉淡化 ---
// frac(scroll.x) = 0 → 用 B(A 正回绕,B 在中间)
// frac(scroll.x) = 0.5 → 用 A(B 正回绕,A 在中间)
// 确保永远不会同时采样到回绕边界
// ⚠️ fade 用 float:scroll 是 float,避免 half 截断使渐变不平滑
float fade = abs(frac(scroll.x) * 2.0 - 1.0);
half4 noise = lerp(noise_A, noise_B, (half)fade);
// --- 扭曲方向 ---
// noise.r → [-1, 1] → X 方向偏移
// noise.g → [-1, 1] → Y 方向偏移
// R/G 独立控制 XY,变化不同步,过渡更自然
half2 twistDir = noise.rg * 2.0h - 1.0h;
// --- 应用扭曲 ---
// _qd 控制扭曲强度,乘以扭曲方向加到主贴图 UV 上
// _qd = 0:无扭曲;_qd = 0.05:微弱扭曲;_qd > 0.2:强烈扭曲
mainUV += twistDir * _qd;
}
// =================================================================
// 第三步:采样主贴图
// 此时 mainUV 已经包含了基础滚动 + 噪声扭曲偏移
// =================================================================
half4 mainTex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, mainUV);
// =================================================================
// 第四步:颜色计算
// 最终 RGB = 贴图颜色 × 材质颜色 × 顶点颜色 × 贴图Alpha × 不透明度
// =================================================================
half3 finalRGB = mainTex.rgb * _Color.rgb * IN.color.rgb * mainTex.a;
finalRGB *= _opacity;
// =================================================================
// 第五步:可选的去饱和度
// =================================================================
if (_Main_Desaturate != 0.0h)
{
// 用 BT.601 亮度公式将 RGB 转灰度
half gray = dot(finalRGB, half3(0.299, 0.587, 0.114));
// _Main_Desaturate = 0 时全彩,= 1 时完全灰度
finalRGB = lerp(finalRGB, gray, _Main_Desaturate);
}
// =================================================================
// 第六步:遮罩计算
// 两层遮罩相乘,共同控制 RGB 亮度和透明度
// 遮罩也带时间滚动(可选)
// UV = 原始UV + 滚动偏移(遮罩跟随 mesh UV)
// ⚠️ float2 保证滚动精度,避免 half 步进导致遮罩采样抖动
// =================================================================
float2 maskUV = IN.uv + float2(_Mask_speed_x, _Mask_speed_y) * flowTime;
half mask1 = SAMPLE_TEXTURE2D(_Mask, sampler_Mask, maskUV).r;
float2 mask2UV = IN.uv + float2(_Mask2_speed_x, _Mask2_speed_y) * flowTime;
half mask2 = SAMPLE_TEXTURE2D(_Mask2, sampler_Mask2, mask2UV).r;
// 两层遮罩相乘,合并为一个遮罩值
half combinedMask = mask1 * mask2;
// 应用到 RGB
finalRGB *= combinedMask;
// =================================================================
// 第七步:最终透明度
// Alpha = 不透明度 × 顶点Alpha × 遮罩
// =================================================================
half finalAlpha = _opacity * IN.color.a * combinedMask;
// =================================================================
// 输出最终颜色
// 混合模式是 OneMinusDstColor One,颜色会与背景按"加乘"方式混合
// =================================================================
return half4(finalRGB, finalAlpha);
}
ENDHLSL
}
}
// --- Fallback(兜底方案)---
Fallback "Universal Render Pipeline/Unlit"
}
@@ -9,8 +9,15 @@ Shader "Boke/Addmul" {
[HideInInspector] _texcoord ("", 2D) = "white" {}
[Enum(Off,0,Front,1,Back,2)] __CullMode ("CullMode(Back时背面看不见,Off时双面可见)", Float) = 0
[Enum(Off,0,On,1)] __ZWrite ("ZWrite(开启时,会写入深度)", Float) = 0
_StencilRef ("StencilRef(模板测试时的参考值)", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("StencilComp(模板测试的比较方法)", Float) = 0
// --- UI 遮罩:uGUI MaskableGraphic / UIParticle 依赖以下属性名,勿改名 ---
_Stencil ("Stencil ID", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Float) = 0
_StencilReadMask ("Stencil Read Mask", Float) = 255
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
// --- 兼容旧的模板测试命名(保留声明,避免旧材质缺属性)---
_StencilRef ("StencilRef(模板测试时的参考值)", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilPassOp ("StenciPassOp(模板测试成功时的操作)", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilFailOp ("StenciFailOp(模板测试失败时的操作)", Float) = 0
}
@@ -26,13 +33,15 @@ Shader "Boke/Addmul" {
Cull [__CullMode]
ZWrite [__ZWrite]
Blend SrcAlpha One
ColorMask [_ColorMask]
ZTest LEqual
Stencil {
Ref [_StencilRef]
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilPassOp]
Fail [_StencilFailOp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Pass {
@@ -1,4 +1,4 @@
Shader "Boke/Addmul_niuqu" {
Shader "Boke/Addmul_niuqu" {
Properties {
[HideInInspector] _EmissionColor ("Emission Color", Vector) = (1,1,1,1)
[HideInInspector] _AlphaCutoff ("Alpha Cutoff ", Range(0, 1)) = 0.5
@@ -29,8 +29,15 @@
[Enum(Off,0,Front,1,Back,2)] __CullMode ("CullMode(Back时背面看不见,Off时双面可见)", Float) = 0
[Enum(Off,0,On,1)] __ZWrite ("ZWrite(开启时,会写入深度)", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] __ZTest ("ZTest(深度测试)", Float) = 4
_StencilRef ("StencilRef(模板测试时的参考值)", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("StencilComp(模板测试的比较方法)", Float) = 0
// --- UI 遮罩:uGUI MaskableGraphic / UIParticle 依赖以下属性名,勿改名 ---
_Stencil ("Stencil ID", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Float) = 0
_StencilReadMask ("Stencil Read Mask", Float) = 255
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
// --- 兼容旧的模板测试命名(保留声明,避免旧材质缺属性)---
_StencilRef ("StencilRef(模板测试时的参考值)", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilPassOp ("StenciPassOp(模板测试成功时的操作)", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilFailOp ("StenciFailOp(模板测试失败时的操作)", Float) = 0
[Toggle(UNITY_UI_CLIP_RECT)] _UNITY_UI_CLIP_RECT ("启用粒子遮罩", Float) = 0
@@ -55,13 +62,15 @@
ZWrite [__ZWrite]
ZTest [__ZTest]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Stencil
{
Ref [_StencilRef]
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilPassOp]
Fail [_StencilFailOp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
HLSLPROGRAM
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 6d70e0edfc472a840a18040d83893daf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,925 +0,0 @@
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
public class AnimationClipFrameChecker : EditorWindow
{
private List<AnimationClip> clips = new List<AnimationClip>();
private Vector2 scrollPos;
private Vector2 clipListScrollPos;
private Vector2 resultListScrollPos;
private bool hasRunCheck = false;
// 检测结果缓存
private struct ClipResult
{
public string name;
public string assetPath;
public int frameCount;
public float length;
public float frameRate;
}
private List<ClipResult> results = new List<ClipResult>();
// 大背景图片
private Texture2D bgImageGlobal;
private const string BgImagePrefKey = "ClipFrameChecker_BgImagePath";
// GIF动画背景
private Texture2D[] gifFrames;
private int[] gifDelays;
private int gifTotalDuration;
private bool isGif;
private string gifSourcePath = "";
private double gifStartTime;
private double lastGifRepaintTime;
[MenuItem("Tools/动画帧数检测工具")]
public static void ShowWindow()
{
var win = GetWindow<AnimationClipFrameChecker>("动画帧数检测工具");
win.minSize = new Vector2(500, 400);
win.LoadBgImage();
}
private void OnEnable()
{
EditorApplication.update -= OnEditorUpdate;
EditorApplication.update += OnEditorUpdate;
}
private void OnDestroy()
{
EditorApplication.update -= OnEditorUpdate;
ClearGif();
}
private void OnEditorUpdate()
{
if (isGif && gifFrames != null && gifFrames.Length > 1)
{
if (EditorApplication.timeSinceStartup - lastGifRepaintTime > 0.033)
{
Repaint();
lastGifRepaintTime = EditorApplication.timeSinceStartup;
}
}
}
private void OnGUI()
{
// 配色方案
Color headerBg = new Color(0.3f, 0.5f, 0.8f, 1f);
Color section1Bg = new Color(0.3f, 0.65f, 0.35f, 0.6f); // 绿 - 动画列表
Color section2Bg = new Color(0.65f, 0.35f, 0.7f, 0.6f); // 紫 - 检测按钮
Color section3Bg = new Color(0.2f, 0.6f, 0.55f, 0.6f); // 青 - 检测结果
Color section4Bg = new Color(0.7f, 0.45f, 0.2f, 0.6f); // 橙 - 背景设置
// 绘制大背景
Texture2D bgToDraw = null;
if (isGif && gifFrames != null && gifFrames.Length > 0)
{
int elapsed = (int)((EditorApplication.timeSinceStartup - gifStartTime) * 1000) % gifTotalDuration;
int accum = 0;
for (int i = 0; i < gifFrames.Length; i++)
{
accum += gifDelays[i];
if (elapsed < accum) { bgToDraw = gifFrames[i]; break; }
}
if (bgToDraw == null) bgToDraw = gifFrames[0];
}
else if (bgImageGlobal != null)
{
bgToDraw = bgImageGlobal;
}
if (bgToDraw != null)
{
Rect fullRect = new Rect(0, 0, position.width, position.height);
GUI.DrawTexture(fullRect, bgToDraw, ScaleMode.StretchToFill);
}
// 整体滚动
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
// 大标题
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("动画帧数检测工具", new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 18,
alignment = TextAnchor.MiddleCenter,
fontStyle = FontStyle.Bold
});
GUILayout.Space(8);
// 1. 动画列表
DrawSectionHeader("1. 拖入 Animation Clip", headerBg);
DrawSectionContent(DrawClipListInner, section1Bg);
GUILayout.Space(6);
// 2. 检测按钮
DrawSectionHeader("2. 检测动画", headerBg);
DrawSectionContent(DrawCheckSection, section2Bg);
GUILayout.Space(6);
// 3. 检测结果
if (hasRunCheck)
{
DrawSectionHeader("3. 检测结果", headerBg);
DrawSectionContent(DrawResultListInner, section3Bg);
}
GUILayout.Space(6);
// 4. 背景设置
showBgSettings = EditorGUILayout.Foldout(showBgSettings, "🎨 界面背景设置", true);
if (showBgSettings)
{
DrawSectionContent(DrawBgSettingsInner, section4Bg);
}
GUILayout.Space(8);
EditorGUILayout.EndScrollView();
}
private bool showBgSettings = false;
// ===================== 动画列表 =====================
private void DrawClipListInner()
{
EditorGUILayout.HelpBox("将 Animation Clip 拖入下方列表,或点击按钮选择文件。支持批量操作。", MessageType.Info);
GUILayout.Space(4);
// 拖拽区域
Rect dropArea = GUILayoutUtility.GetRect(0, 40, GUILayout.ExpandWidth(true));
GUIStyle dropStyle = new GUIStyle("box")
{
alignment = TextAnchor.MiddleCenter,
fontSize = 12,
fontStyle = FontStyle.Italic
};
GUI.Box(dropArea, "📂 拖拽 Animation Clip 到此处", dropStyle);
// 处理拖拽
if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)
{
if (dropArea.Contains(Event.current.mousePosition))
{
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (Event.current.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (var obj in DragAndDrop.objectReferences)
{
if (obj is AnimationClip clip && !clips.Contains(clip))
clips.Add(clip);
}
}
}
}
GUILayout.Space(6);
// 按钮行
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("➕ 添加动画", GUILayout.Height(28)))
{
string path = EditorUtility.OpenFilePanelWithFilters("选择 Animation Clip", "", new string[] { "Animation Files", "anim", "All Files", "*" });
if (!string.IsNullOrEmpty(path))
{
string assetPath = GetAssetPath(path);
if (string.IsNullOrEmpty(assetPath))
assetPath = path;
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetPath);
if (clip != null && !clips.Contains(clip))
clips.Add(clip);
}
}
if (GUILayout.Button("📂 从文件夹批量导入", GUILayout.Height(28)))
{
string folder = EditorUtility.OpenFolderPanel("选择包含动画的文件夹", "", "");
if (!string.IsNullOrEmpty(folder))
{
string assetFolder = GetAssetPath(folder);
if (string.IsNullOrEmpty(assetFolder))
{
// 用户选了项目外目录,尝试直接加载
string[] files = System.IO.Directory.GetFiles(folder, "*.anim", System.IO.SearchOption.AllDirectories);
foreach (string f in files)
{
string ap = GetAssetPath(f);
if (!string.IsNullOrEmpty(ap))
{
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(ap);
if (clip != null && !clips.Contains(clip)) clips.Add(clip);
}
}
}
else
{
string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new string[] { assetFolder });
foreach (string guid in guids)
{
string ap = AssetDatabase.GUIDToAssetPath(guid);
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(ap);
if (clip != null && !clips.Contains(clip)) clips.Add(clip);
}
}
Repaint();
}
}
if (GUILayout.Button("🗑️ 清空列表", GUILayout.Height(28)))
{
clips.Clear();
results.Clear();
hasRunCheck = false;
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(6);
// 显示当前列表
if (clips.Count > 0)
{
EditorGUILayout.LabelField($"已添加 {clips.Count} 个动画", EditorStyles.boldLabel);
GUILayout.Space(4);
clipListScrollPos = EditorGUILayout.BeginScrollView(clipListScrollPos, GUILayout.MaxHeight(150));
for (int i = 0; i < clips.Count; i++)
{
EditorGUILayout.BeginHorizontal();
GUIContent clipIcon = EditorGUIUtility.IconContent("d_AnimationClip Icon");
GUILayout.Label(clipIcon, GUILayout.Width(20), GUILayout.Height(20));
GUILayout.Label(clips[i].name, GUILayout.Width(200));
GUILayout.FlexibleSpace();
GUILayout.Label(AssetDatabase.GetAssetPath(clips[i]), EditorStyles.miniLabel);
if (GUILayout.Button("✕", GUILayout.Width(30)))
{
clips.RemoveAt(i);
i--;
if (hasRunCheck) { hasRunCheck = false; results.Clear(); }
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndScrollView();
}
}
// ===================== 检测按钮 =====================
private void DrawCheckSection()
{
EditorGUILayout.BeginHorizontal();
GUIStyle btnStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 14,
fontStyle = FontStyle.Bold,
fixedHeight = 36
};
if (GUILayout.Button("🔍 开始检测动画帧数", btnStyle, GUILayout.ExpandWidth(true)))
{
RunCheck();
}
EditorGUILayout.EndHorizontal();
if (hasRunCheck && results.Count > 0)
{
EditorGUILayout.Space(4);
int totalFrames = 0;
foreach (var r in results) totalFrames += r.frameCount;
EditorGUILayout.HelpBox($"检测完成:共 {results.Count} 个动画,总计 {totalFrames} 帧", MessageType.Info);
}
}
// ===================== 检测结果 =====================
private void DrawResultListInner()
{
if (results.Count == 0)
{
EditorGUILayout.HelpBox("没有检测结果。", MessageType.Warning);
return;
}
GUILayout.Space(4);
// 表头
EditorGUILayout.BeginHorizontal();
GUILayout.Space(28);
GUIStyle colStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 11 };
GUILayout.Label("动画名称", colStyle, GUILayout.Width(200));
GUILayout.Label("帧数", colStyle, GUILayout.Width(70));
GUILayout.Label("时长(秒)", colStyle, GUILayout.Width(70));
GUILayout.Label("帧率(FPS)", colStyle, GUILayout.Width(70));
GUILayout.Label("工程路径", colStyle);
EditorGUILayout.EndHorizontal();
GUI.Box(GUILayoutUtility.GetLastRect(), "");
GUILayout.Space(2);
// 使用单独的滚动区域
resultListScrollPos = EditorGUILayout.BeginScrollView(resultListScrollPos, GUILayout.MaxHeight(400));
for (int i = 0; i < results.Count; i++)
{
var r = results[i];
// 交替行背景
Color rowColor = (i % 2 == 0) ? new Color(0, 0, 0, 0.1f) : new Color(0, 0, 0, 0.05f);
GUIStyle rowStyle = new GUIStyle(GUI.skin.box);
Texture2D rowTex = new Texture2D(1, 1);
rowTex.SetPixel(0, 0, rowColor);
rowTex.Apply();
rowStyle.normal.background = rowTex;
EditorGUILayout.BeginVertical(rowStyle);
EditorGUILayout.BeginHorizontal();
GUILayout.Space(5);
GUILayout.Label((i + 1).ToString(), GUILayout.Width(22));
// 动画名称
GUIContent clipIcon = EditorGUIUtility.IconContent("d_AnimationClip Icon");
GUILayout.Label(clipIcon, GUILayout.Width(20), GUILayout.Height(20));
GUILayout.Label(r.name, GUILayout.Width(200));
// 帧数
GUIStyle frameStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter
};
GUILayout.Label(r.frameCount.ToString(), frameStyle, GUILayout.Width(70));
// 时长
GUILayout.Label(r.length.ToString("F3"), GUILayout.Width(70));
// 帧率
GUILayout.Label(r.frameRate.ToString("F1"), GUILayout.Width(70));
// 路径(可点击定位)
GUILayout.FlexibleSpace();
string displayPath = r.assetPath;
if (GUILayout.Button("📍 定位", GUILayout.Width(50), GUILayout.Height(20)))
{
// 在Project窗口中高亮选中该资源
var obj = AssetDatabase.LoadAssetAtPath<AnimationClip>(r.assetPath);
if (obj != null)
{
EditorGUIUtility.PingObject(obj);
Selection.activeObject = obj;
}
}
GUIStyle pathStyle = new GUIStyle(EditorStyles.miniLabel)
{
fontSize = 10
};
GUILayout.Label(displayPath, pathStyle);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndScrollView();
}
// ===================== 背景设置 =====================
private void DrawBgSettingsInner()
{
EditorGUILayout.HelpBox("拖入背景图片(Texture2D)或选择GIF文件,拉伸填充整个工具窗口。", MessageType.Info);
GUILayout.Space(4);
Texture2D newBg = (Texture2D)EditorGUILayout.ObjectField("背景图片", bgImageGlobal, typeof(Texture2D), false);
if (newBg != bgImageGlobal)
{
bgImageGlobal = newBg;
ClearGif();
SaveBgImage();
}
string gifPath = EditorGUILayout.TextField("GIF文件路径", isGif ? gifSourcePath : "");
if (GUILayout.Button("选择GIF文件"))
{
string selected = EditorUtility.OpenFilePanel("选择GIF图片", "", "gif");
if (!string.IsNullOrEmpty(selected))
{
LoadGifFromFile(selected);
gifPath = selected;
}
}
if (GUILayout.Button("清除GIF"))
{
ClearGif();
gifPath = "";
}
GUILayout.Space(4);
}
// ===================== 核心检测逻辑 =====================
/// <summary>
/// 将绝对路径转换为 Unity AssetDatabase 可用的 Assets 相对路径
/// </summary>
private static string GetAssetPath(string absolutePath)
{
if (string.IsNullOrEmpty(absolutePath)) return "";
try
{
string dataPath = System.IO.Path.GetFullPath(Application.dataPath).Replace('\\', '/').TrimEnd('/');
string fullPath = System.IO.Path.GetFullPath(absolutePath).Replace('\\', '/').TrimEnd('/');
if (fullPath.StartsWith(dataPath, System.StringComparison.OrdinalIgnoreCase))
return "Assets" + fullPath.Substring(dataPath.Length);
}
catch { }
return "";
}
private void RunCheck()
{
if (clips.Count == 0)
{
EditorUtility.DisplayDialog("提示", "请先添加 Animation Clip!", "确定");
return;
}
results.Clear();
foreach (var clip in clips)
{
string assetPath = AssetDatabase.GetAssetPath(clip);
float length = clip.length;
float frameRate = 0f;
int frameCount = 0;
// 获取帧率:优先读取AnimationClip的帧率设置
// Unity中AnimationClip没有直接的frameRate属性,
// 通过遍历所有curve的最后一个keyframe的时间推算帧率
frameRate = GetClipFrameRate(clip);
if (frameRate > 0f)
frameCount = Mathf.RoundToInt(length * frameRate);
else
frameCount = GetClipFrameCountFromCurves(clip);
results.Add(new ClipResult
{
name = clip.name,
assetPath = assetPath,
frameCount = frameCount,
length = length,
frameRate = frameRate > 0f ? frameRate : (frameCount > 0 ? frameCount / length : 0f)
});
}
// 按时长(秒)从小到大排序
results.Sort((a, b) => a.length.CompareTo(b.length));
hasRunCheck = true;
Repaint();
Debug.Log($"[帧数检测] 完成,共检测 {results.Count} 个动画");
}
/// <summary>
/// 通过分析动画曲线推算帧率。
/// 取所有绑定中最大的 keyframe 时间差,近似帧间隔,帧率 = 1/间隔。
/// </summary>
private float GetClipFrameRate(AnimationClip clip)
{
if (clip == null) return 0f;
// 优先使用 EditorCurveBinding 读取曲线
var bindings = AnimationUtility.GetCurveBindings(clip);
if (bindings.Length == 0) return 0f;
float minInterval = float.MaxValue;
foreach (var binding in bindings)
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve == null || curve.length == 0) continue;
float prevTime = curve.keys[0].time;
for (int i = 1; i < curve.length; i++)
{
float diff = curve.keys[i].time - prevTime;
if (diff > 0.001f && diff < minInterval)
minInterval = diff;
prevTime = curve.keys[i].time;
}
}
if (minInterval < float.MaxValue && minInterval > 0f)
return 1f / minInterval;
return 0f;
}
/// <summary>
/// 备用方案:直接计算所有 curve 中不同时间点的数量作为帧数。
/// </summary>
private int GetClipFrameCountFromCurves(AnimationClip clip)
{
var bindings = AnimationUtility.GetCurveBindings(clip);
var frameTimes = new HashSet<float>();
foreach (var binding in bindings)
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
if (curve == null) continue;
foreach (var key in curve.keys)
frameTimes.Add((float)System.Math.Round(key.time, 4));
}
return frameTimes.Count;
}
// ===================== 通用 UI 辅助 =====================
private delegate void GUIAction();
private void DrawSectionHeader(string title, Color bgColor)
{
EditorGUILayout.Space(3);
var prevBg = GUI.backgroundColor;
GUI.backgroundColor = bgColor;
EditorGUILayout.BeginHorizontal("box");
GUI.backgroundColor = prevBg;
GUIStyle headerStyle = new GUIStyle(EditorStyles.boldLabel)
{
fontSize = 13,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleLeft,
padding = new RectOffset(4, 4, 2, 2)
};
GUIContent iconContent = EditorGUIUtility.IconContent("d_SettingsIcon");
GUILayout.Label(iconContent, GUILayout.Width(20), GUILayout.Height(20));
GUILayout.Label(title, headerStyle);
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
}
private static readonly Dictionary<string, Texture2D> bgTextures = new Dictionary<string, Texture2D>();
private Texture2D GetOrCreateBgTexture(string key, Color color)
{
if (!bgTextures.TryGetValue(key, out var tex))
{
tex = new Texture2D(1, 1);
tex.SetPixel(0, 0, color);
tex.Apply();
bgTextures[key] = tex;
}
return tex;
}
private void DrawSectionContent(GUIAction drawAction, Color bgColor)
{
GUIStyle bgStyle = new GUIStyle(GUI.skin.box);
bgStyle.normal.background = GetOrCreateBgTexture(bgColor.GetHashCode().ToString(), bgColor);
EditorGUILayout.BeginVertical(bgStyle);
drawAction();
EditorGUILayout.EndVertical();
}
// ===================== 背景图片/GIF =====================
private void SaveBgImage()
{
if (isGif && !string.IsNullOrEmpty(gifSourcePath))
{
EditorPrefs.SetString(BgImagePrefKey, "GIF:" + gifSourcePath);
}
else if (bgImageGlobal != null)
{
EditorPrefs.SetString(BgImagePrefKey, AssetDatabase.GetAssetPath(bgImageGlobal));
}
else
{
EditorPrefs.DeleteKey(BgImagePrefKey);
}
}
private void LoadBgImage()
{
string raw = EditorPrefs.GetString(BgImagePrefKey, "");
if (string.IsNullOrEmpty(raw)) return;
if (raw.StartsWith("GIF:"))
{
string gifPath = raw.Substring(4);
if (System.IO.File.Exists(gifPath))
LoadGifFromFile(gifPath);
else
EditorPrefs.DeleteKey(BgImagePrefKey);
}
else
{
bgImageGlobal = AssetDatabase.LoadAssetAtPath<Texture2D>(raw);
if (bgImageGlobal == null)
EditorPrefs.DeleteKey(BgImagePrefKey);
}
}
private void ClearGif()
{
if (gifFrames != null)
{
for (int i = 0; i < gifFrames.Length; i++)
{
if (gifFrames[i] != null) DestroyImmediate(gifFrames[i]);
}
gifFrames = null;
gifDelays = null;
}
isGif = false;
gifSourcePath = "";
gifTotalDuration = 0;
}
private void LoadGifFromFile(string filePath)
{
ClearGif();
if (!System.IO.File.Exists(filePath)) return;
try
{
byte[] data = System.IO.File.ReadAllBytes(filePath);
if (data.Length < 13 || data[0] != 'G' || data[1] != 'I' || data[2] != 'F')
{
Debug.LogWarning("不是GIF格式文件");
return;
}
int pos = 6;
int screenWidth = data[pos] | (data[pos + 1] << 8);
int screenHeight = data[pos + 2] | (data[pos + 3] << 8);
pos += 4;
byte packed = data[pos++];
pos++; // bg color index
pos++; // pixel aspect ratio
Color32[] gct = null;
if ((packed & 0x80) != 0)
{
int gctCount = 1 << ((packed & 0x07) + 1);
gct = ReadColorTable(data, ref pos, gctCount);
}
var frames = new List<Texture2D>();
var delays = new List<int>();
Color32[] canvas = new Color32[screenWidth * screenHeight];
Color32 bgColor = (gct != null) ? gct[0] : new Color32(0, 0, 0, 255);
for (int i = 0; i < canvas.Length; i++) canvas[i] = bgColor;
int gceDisposal = 0, gceTransIdx = -1, gceDelay = 100;
Color32[] savedCanvas = null;
while (pos < data.Length)
{
byte blockType = data[pos++];
if (blockType == 0x3B) break; // trailer
if (blockType == 0x21) // extension
{
byte label = data[pos++];
if (label == 0xF9) // GCE
{
pos++; // block size
byte gcPacked = data[pos++];
gceDisposal = (gcPacked >> 2) & 0x07;
gceTransIdx = ((gcPacked & 0x01) != 0) ? data[pos++] : -1;
if (gceTransIdx == -1) pos++;
gceDelay = (data[pos] | (data[pos + 1] << 8)) * 10;
if (gceDelay == 0) gceDelay = 100;
pos += 2;
pos++; // terminator
}
else
{
// skip other extensions
while (pos < data.Length)
{
int sz = data[pos++];
if (sz == 0) break;
pos += sz;
}
}
continue;
}
if (blockType == 0x2C) // image descriptor
{
int left = data[pos] | (data[pos + 1] << 8);
int top = data[pos + 2] | (data[pos + 3] << 8);
int width = data[pos + 4] | (data[pos + 5] << 8);
int height = data[pos + 6] | (data[pos + 7] << 8);
pos += 8;
byte imgPacked = data[pos++];
Color32[] lct = null;
if ((imgPacked & 0x80) != 0)
{
int lctCount = 1 << ((imgPacked & 0x07) + 1);
lct = ReadColorTable(data, ref pos, lctCount);
}
Color32[] colorTable = lct ?? gct ?? new Color32[256];
int minCodeSize = data[pos++];
int lzwPos = pos;
int bytesRemaining = 0;
int bytePos = 0;
byte[] subBlock = new byte[256];
int clearCode = 1 << minCodeSize;
int eoiCode = clearCode + 1;
int codeSize = minCodeSize + 1;
int codeMask = (1 << codeSize) - 1;
int oldCode = -1;
var codeTable = new Dictionary<int, List<byte>>();
InitializeLzwTable(codeTable, clearCode);
int x = 0, y = 0;
bool interlaced = (imgPacked & 0x40) != 0;
int pass = 0;
int[] interlaceOffsets = { 0, 4, 2, 1 };
int[] interlaceSteps = { 8, 8, 4, 2 };
int pixelIdx = 0;
var pixelBuffer = new List<byte>();
while (pixelIdx < width * height)
{
int code = ReadNextLzwCode(data, ref lzwPos, ref bytesRemaining, ref bytePos, ref subBlock, minCodeSize, codeSize, ref codeMask, ref codeTable, ref oldCode, clearCode, eoiCode, pixelBuffer);
if (code == -1) break;
foreach (byte b in pixelBuffer)
{
if (pixelIdx >= width * height) break;
int px = x;
int py = interlaced ? GetInterlacedY(y, ref pass, height) : y;
if (px < width && py < height)
{
int idx = (top + py) * screenWidth + (left + px);
if (idx >= 0 && idx < canvas.Length)
{
int cidx = b;
if (gceTransIdx >= 0 && cidx == gceTransIdx)
{ }
else if (cidx < colorTable.Length)
canvas[idx] = colorTable[cidx];
}
}
x++;
if (x >= width) { x = 0; y++; }
pixelIdx++;
}
}
// 保存disposal=3需要的画布
if (gceDisposal == 3)
{
savedCanvas = new Color32[canvas.Length];
for (int ci = 0; ci < canvas.Length; ci++) savedCanvas[ci] = canvas[ci];
}
// 生成帧纹理
var frameTex = new Texture2D(screenWidth, screenHeight, TextureFormat.RGBA32, false);
frameTex.SetPixels32(canvas);
frameTex.Apply(false, false);
frames.Add(frameTex);
delays.Add(gceDelay);
// disposal处理
if (gceDisposal == 2) // restore to bg
{
Color32 bgCol = (gct != null && data[11] < gct.Length) ? gct[data[11]] : new Color32(0, 0, 0, 255);
for (int fy = top; fy < top + height && fy < screenHeight; fy++)
for (int fx = left; fx < left + width && fx < screenWidth; fx++)
canvas[fy * screenWidth + fx] = bgCol;
}
else if (gceDisposal == 3 && savedCanvas != null)
{
for (int ci = 0; ci < canvas.Length; ci++) canvas[ci] = savedCanvas[ci];
}
// 跳到下一个块
while (pos < data.Length)
{
int sz = data[pos++];
if (sz == 0) break;
pos += sz;
}
}
}
if (frames.Count > 0)
{
gifFrames = frames.ToArray();
gifDelays = delays.ToArray();
gifTotalDuration = 0;
foreach (int d in gifDelays) gifTotalDuration += d;
isGif = true;
gifSourcePath = filePath;
gifStartTime = EditorApplication.timeSinceStartup;
SaveBgImage();
Debug.Log($"[GIF背景] 加载完成:{gifFrames.Length} 帧,总时长 {gifTotalDuration}ms");
}
}
catch (System.Exception e)
{
Debug.LogError("GIF解析失败: " + e.Message);
ClearGif();
}
}
private Color32[] ReadColorTable(byte[] data, ref int pos, int count)
{
Color32[] table = new Color32[count];
for (int i = 0; i < count; i++)
{
table[i] = new Color32(data[pos], data[pos + 1], data[pos + 2], 255);
pos += 3;
}
return table;
}
private static void InitializeLzwTable(Dictionary<int, List<byte>> table, int clearCode)
{
table.Clear();
for (int i = 0; i < clearCode; i++)
table[i] = new List<byte> { (byte)i };
table[clearCode] = new List<byte>(); // clear
table[clearCode + 1] = new List<byte>(); // eoi
}
private static int ReadNextLzwCode(byte[] data, ref int lzwPos, ref int bytesRemaining, ref int bytePos, ref byte[] subBlock, int minCodeSize, int codeSize, ref int codeMask, ref Dictionary<int, List<byte>> codeTable, ref int oldCode, int clearCode, int eoiCode, List<byte> output)
{
output.Clear();
int code = 0;
int bitsNeeded = codeSize;
int bitsRead = 0;
while (bitsRead < bitsNeeded)
{
if (bytesRemaining == 0)
{
if (lzwPos >= data.Length) return -1;
int sz = data[lzwPos++];
if (sz == 0) return -1;
bytesRemaining = sz;
for (int i = 0; i < sz; i++)
subBlock[i] = data[lzwPos++];
bytePos = 0;
}
int bitsAvail = 8 * bytesRemaining - bytePos;
int bitsToRead = System.Math.Min(bitsAvail, bitsNeeded - bitsRead);
int mask = (1 << bitsToRead) - 1;
int val = (subBlock[bytePos >> 3] >> (bytePos & 7)) & mask;
bytePos += bitsToRead;
code |= val << bitsRead;
bitsRead += bitsToRead;
}
if (code == clearCode)
{
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
oldCode = -1;
InitializeLzwTable(codeTable, clearCode);
return -1;
}
if (code == eoiCode) return -1;
if (codeTable.TryGetValue(code, out var entry))
{
output.AddRange(entry);
if (oldCode >= 0 && codeTable.ContainsKey(oldCode))
{
var newEntry = new List<byte>(codeTable[oldCode]);
newEntry.Add(entry[0]);
codeTable[codeTable.Count] = newEntry;
if (codeTable.Count > codeMask + 1 && codeSize < 12)
{
codeSize++;
codeMask = (1 << codeSize) - 1;
}
}
oldCode = code;
}
return code;
}
private static int GetInterlacedY(int y, ref int pass, int height)
{
int[] offsets = { 0, 4, 2, 1 };
int[] steps = { 8, 8, 4, 2 };
int py = offsets[pass] + y * steps[pass];
while (py >= height)
{
pass++;
if (pass > 3) { pass = 0; return y; }
py = offsets[pass] + y * steps[pass];
}
return py;
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 380ff3b6ea2924046a0d665235d4bd65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -9,6 +9,13 @@ Shader "BZC/addmul_pannar" {
_v_speed ("v_speed", Float) = 0
_Mask ("Mask", 2D) = "white" {}
[HideInInspector] _texcoord ("", 2D) = "white" {}
// --- UI 遮罩:uGUI MaskableGraphic / UIParticle 依赖以下属性名,勿改名 ---
[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
[Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Float) = 0
_StencilReadMask ("Stencil Read Mask", Float) = 255
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
}
//DummyShaderTextExporter
SubShader {
@@ -27,6 +34,15 @@ Shader "BZC/addmul_pannar" {
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Cull Off
ColorMask [_ColorMask]
Stencil {
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
HLSLPROGRAM
#pragma vertex vert
@@ -8,7 +8,7 @@ Material:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: eff_lizi_009
m_Shader: {fileID: 10720, guid: 0000000000000000f000000000000000, type: 0}
m_Shader: {fileID: 4800000, guid: 6738f71d6ce61f9458b8be51b9611803, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []