王小兵 2024-12-07T22:07:59+08:00 spencer.wong@hotmail.com Unity Sentis:Runtime 使用 AI 模型 2024-05-19T00:00:00+08:00 王小兵 https://peakcoder.com/unity/ai/2024/05/19/sentis SentisUnity 推出的一个用于在 Unity 环境中运行机器学习模型的框架,可以简化将机器学习模型集成到 Unity 项目中的过程。

使用方法

1. 安装 Sentis

打开 Window > Package Manager
点击 + 选择 Add package by name
输入 com.unity.sentis 安装

2. 导入模型

Hugging Face 选择并下载你需要的模型,或者去 ModelZoo 之类下载。
Hugging Face 上,模型通常以 PyTorchTensorFlow 格式提供,需要转换为 ONNX 格式。

假设你使用的是 PyTorch 模型,可以使用 torch.onnx.export 方法来完成转换。

import torch
from transformers import AutoModel

# Load your Hugging Face model
model = AutoModel.from_pretrained("your-model-name")

# Set the model to evaluation mode
model.eval()

# Dummy input for model export
dummy_input = torch.randn(1, 3, 224, 224)  # Adjust dimensions as needed

# Export the model to ONNX format
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=11)

Tips:

如果不使用 Unity 想直接在 iOS 中使用,则需要再讲 ONNX 转化为 Core Ml 格式。

3. 加载和运行模型

using UnityEngine;
using Unity.Sentis;
using System.IO;

public class SentisExample : MonoBehaviour
{
    private Model runtimeModel;
    private IWorker worker;

    void Start()
    {
        // 加载 ONNX 模型文件
        var modelFilePath = Application.dataPath + "/model.onnx";
        var modelData = File.ReadAllBytes(modelFilePath);

        // 加载模型到 Sentis
        runtimeModel = ModelLoader.Load(modelData);
        worker = WorkerFactory.CreateWorker(WorkerFactory.Type.ComputePrecompiled, runtimeModel);

        // 准备输入数据(示例)
        // 根据模型调整维度
        var inputTensor = new Tensor(1, 3, 224, 224); 

        // 执行推理
        worker.Execute(inputTensor);
        var outputTensor = worker.PeekOutput();

        // 处理输出数据
        Debug.Log(outputTensor);

        // 清理资源
        inputTensor.Dispose();
        outputTensor.Dispose();
        worker.Dispose();
    }
}

模型优化技术

为了在移动设备上高效运行,模型通常需要经过优化,包括:

1. 量化(Quantization)

将模型从32位浮点数表示转换为8位整数表示,可以显著减少模型的大小和计算需求。

2. 模型剪枝(Pruning)

移除模型中冗余的权重和节点,减少计算复杂度。

3. 知识蒸馏(Knowledge Distillation)

将复杂模型的知识转移到一个较小的模型中。

最新的iPhone能够运行包含数百万到数千万参数的优化模型。例如,MobileNetTinyBERT 等模型经过量化和其他优化技术处理后,可以在iPhone上高效地运行。

–EOF–

]]>
分花:愿你有无数的鲜花与浪漫 2024-04-22T00:00:00+08:00 王小兵 https://peakcoder.com/indie/2024/04/22/petals

发一个年初做的 APP「分花」 凑一篇 Blog 吧。
大概这辈子再也不会做这种类型的软件了…



有人要内购兑换码吗:)
可以邮件: zaijing@alu.hit.edu.cn
小红书: peakcoder
愿你有无数的鲜花与浪漫

–EOF–

]]>
Git .gitattributes 使用指北 2024-01-05T00:00:00+08:00 王小兵 https://peakcoder.com/git/2024/01/05/gitattributes .gitattributes 文件在 Git 仓库中用于定义各种属性,以指定 Git 如何处理仓库中的文件。
这个文件可以用于多种用途,包括但不限于以下几种:

1. 行尾转换 (End-of-Line Conversion)

指定文件的行尾风格,例如,强制在 Windows 和 UNIX 系统之间一致地使用 LF 或 CRLF。

*.txt text eol=lf
*.bat text eol=crlf

2. 合并策略 (Merge Strategies)

为特定文件或路径指定合并策略,比如使用文本方式合并或使用特定的合并驱动。

*.png merge=theirs

3. 大文件存储 (Large File Storage, LFS)

将大文件如视频、音频或大型二进制文件交给 Git LFS 管理。

*.mp4 filter=lfs diff=lfs merge=lfs -text

4. 导出忽略 (Export Ignore)

指定在使用 git archive 时应该忽略的文件。

*.log export-ignore

5. 语言统计 (Language Statistics)

控制 git 命令如何统计语言使用情况,可以用于排除某些文件。

*.md linguist-documentation

6. 差异展示 (Diff)

指定文件的差异展示方式,如是否应该被 Git 识别为文本,并如何显示差异。

*.jpg binary
*.html diff=html

7. 禁用压缩 (Disable Compression)

禁用对特定文件的 Git 压缩。

*.jpg -filter

8. 自定义过滤器 (Custom Filters)

为文件设置自定义的清理(clean)和还原(smudge)过滤器。

*.json filter=jsonFilter

9. 核对属性 (Check Attributes)

在提交前检查文件是否符合特定的属性要求。

*.py check=python

10. 文本属性 (Text Attributes)

指定文件是否应被视为文本,对行尾进行规范化,或者被视为二进制文件。

*.txt text
*.bin binary

–EOF–

]]>
Open Source a Pixel-Art Game 'PuppyTouch' 2023-03-06T00:00:00+08:00 王小兵 https://peakcoder.com/cocos2d-x/2023/03/06/puppy-touch 开源一个游戏 PuppyTouch 这是我在大学刚毕业时候做的,是一个2d像素风的小游戏,使用cocos2d-x引擎开发的。手写识别使用的是华盛顿大学的 $1 Unistroke Recognizer 算法。

之前在AppStore上架过,长久不更新现已下架。
代码不多,大量的时间都在美术上消耗掉了,年少时的我是真的很爱画像素画 :)

–EOF–

]]>
Unity Editor 添加 Custom Shortcuts 2023-01-31T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2023/01/31/editor-shortcut 1. 在 MenuItem 标签内使用特殊字符
  • % (ctrl on Windows and Linux, cmd on macOS),
  • ^ (ctrl on Windows, Linux, and macOS),
  • # (shift),
  • & (alt).

例如以下代码的快捷键为 CTRL + G

[MenuItem("Test/example %g")]
private static void Test()
{
    Debug.Log("TEST!");
}

2. ShortcutManager(Editor -> Shortcuts) 直接添加全局快捷键

–EOF–

]]>
Non-Convex MeshCollider 设备上不响应碰撞 2023-01-09T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2023/01/09/mesh-collider 非凸多边形的 MeshCollider,在 Editor 下响应碰撞,设备上不响应。
并且会报错 CollisionMeshData couldn’t be created because the mesh has been marked as non-accessible.
需要在 FBX 导入的设置开启 Read/Write

这可能是以下情况导致的:

  • When you read from or write to the Mesh data in your code.
  • When you pass the Mesh to StaticBatchingUtility.Combine() to combine the Mesh at run time.
  • When you pass the mesh to CanvasRenderer.SetMesh.
  • When you use the Mesh to bake a NavMesh using the NavMesh building components at run time.
  • When the Mesh is convex, you use the Mesh with a Mesh Collider, and the Mesh Collider’s Transform has negative scaling (for example, (–1, 1, 1)).
  • When you use the Mesh with a Mesh Collider, and the Mesh Collider’s transform is skewed or sheared (for example, when a rotated Transform has a scaled parent Transform).
  • When you use the Mesh with a Mesh Collider, and the Mesh Collider’s Cooking Options flags are set to any value other than the default.
  • When using a Mesh with a Particle System’s Shape module or Renderer module ·when not using GPU instancing.

–EOF–

]]>
Shader Support VR Single Pass Instanced 2022-07-13T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/07/13/single-pass 在 VR 中,有时只有一只眼睛可以看见,另一只眼睛没有渲染
是因为 Shader 没有支持 Single Pass Instanced Render Mode
不用怕性能损失的话,可以 直接使用 Multi-pass Mode 解决

否则需要以下操作让 Shader 在 Single Pass Instanced 模式下让渲染 compatible

1. #include "UnityCG.cginc" - near the top of the CGPROGRAM block

2. UNITY_INSTANCE_ID - should be in the the vert input data struct (ex: appdata)

3. UNITY_SETUP_INSTANCE_ID(v); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); - should be in the vert function

4. UNITY_VERTEX_OUTPUT_STEREO - should be in the frag input data struct (ex: v2f)

–EOF–

]]>
Custom UnityAppController 2022-05-10T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/05/10/custom-appcontroller 之前项目都是编译后处理脚本里做文件替换 UnityAppController.mm
以下是更优雅的方式:

/path/to/unity/project/Assets/Plugins/iOS/CustomAppController.mm
注意,文件名必须是 ___AppController,前缀可自选,但不能省略;否则在 Build 项目的时候,会被移动到错误的目录中去
使用宏 IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController) 定制启动调用

#import "UnityAppController.h"
#import <UIKit/UIKit.h>
#import <FIRApp.h>
@interface CustomAppController : UnityAppController
@end

IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController)

// 如果使用 modules import,则不能用.mm混编 只能用.m
@import GoogleMobileAds;

@implementation CustomAppController

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    [super application:application didFinishLaunchingWithOptions:launchOptions];
    [FIRApp configure];
    [[GADMobileAds sharedInstance] startWithCompletionHandler:nil];
    return YES;
}

@end

–EOF–

]]>
Find and remove missing components 2022-03-09T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/03/09/remove-missing
public static void RemoveInGo(GameObject g)
{
    var components = g.GetComponents<Component>();
    
    var r = 0;
    
    for (var i = 0; i < components.Length; ++i)
    {
        if (components[i] != null) 
            continue;
        
        var s = g.name;
        var t = g.transform;
        while (t.parent != null) 
        {
            s = t.parent.name +"/"+s;
            t = t.parent;
        }
        
        Debug.Log ($"{s} has a missing script at {i}", g);
        
        var serializedObject = new SerializedObject(g);
        
        var prop = serializedObject.FindProperty("m_Component");
        
        prop.DeleteArrayElementAtIndex(i-r);
        r++;
    
        serializedObject.ApplyModifiedProperties();
    }
    
    foreach (Transform childT in g.transform)
    {
        RemoveInGo(childT.gameObject);
    }
}

官方解决方案可以使用
GameObjectUtility.RemoveMonoBehavioursWithMissingScript

–EOF–

]]>
Unity Mecanim Animation System 2022-02-28T00:00:00+08:00 王小兵 https://peakcoder.com/unity/2022/02/28/mecanim 1. Mecanim 动画系统

基于状态机的动画控制系统,是一个面向动画应用的动画系统 动画的制作编辑(Animation)功能相对有限 重在如何从资源中提取动画,配置融合动画,应用动画,复用动画 以及为实际开发提供可视化的GUI编辑和监视功能

2. 角色模型动画的创建与应用工作流程

  1. 建模
  2. 制作动画,导出资源
  3. Unity 提取人物模型,提取动画切片 Clip
  4. 通过 Mecanim 动画系统进行编辑:创建动画状态,动画的融合、过渡调整,设定过渡条件,结合脚本控制

3. AnimtionClip 动画片段

Unity 中的动画片段分为两种 通过剪辑 FBX 中的整段动画生成的动画切片(Clip) 以及由 Animation 窗口创建出的动画片段(Clip)

4. AnimationEvent 动画事件

通过AnimatorEvent动画事件 我们可以在动画播放到某个特定位置时,触发脚本中的特定方法(发射子弹,生成特效,受击信息)

5. Animator 动画状态机

5.1 脚本说明

Controller: 状态机的配置资源文件,整合Clip资源,创建状态机状态(State),设置动画过渡与融合

Avatar: 动画节点导引替身,与动画复用(尤其是人形动画复用)有关,通过配置和应用Avatar,可以实现不同FBX模型之间的动画复用

Apply Root Motion: 是否将动画中的根节点位移,植入到Unity中的物体位移上

Update Mode: 状态机播放动画的时间模式

5.2 动画参数的类型有 4 种

  1. Float:就是 C# 种的单精度浮点性
  2. Int:C# 中的整形
  3. Bool:C# 中的布尔型
  4. Trigger:一种特殊的布尔性参数,它作为触发条件,触发过渡后会被自动置为 false
    Trigger 的触发必须对当前状态机的运行状态进行判断
    一个Trigger,只能对应触发一个过渡,而一个布尔可以触发不同动画分层中的多个过渡

5.3 过渡线配置

选中一条过渡线 Gizmo,我们可以在右侧 Inspector 窗口中配置这个过渡的相关属性
Interruption Source:
Current State 或 NextState指该过渡,允许被其它从 CurrentState 或 NextState 出发的过渡打断
CurrentStateThenNextState,指从 CurrentState 或 NextState 出发的过渡均可打断
但 CurrentState 出发的过渡打断优先于 NextState 出发的过渡

5.4 State 配置

Speed:动画片段的播放速度(置为-1可倒放动画)
Multiplier:Speed的一个倍率因子,需要勾选Parameter可选定一个浮点动画参数来动态控制这个因子
Mirror:是否进行左右翻转
Cycle Offset:偏移量(和之前Clip中的偏移相同),0到1之间的浮点值,可勾选Parameter选定一个浮点动画参数来动态控制
Foot IK:脚部的IK反向力学修正,针对人物上坡,上楼梯时的踏空而行进行修正
Write Default: 主要是针对人物骨骼之外的一些节点,例如武器,跟随物,这些节点只在部分互动动作之中被用到,那些没有使用该节点的动作执行时,是要让节点处于初始状态,还是上一次互动动作末尾时的状态

5.5 Animator Override Controller

是一种Controller状态机配置资源文件,它可以继承一种指定的Animator Controller,并在此基础上进行修改,从而在复用Controller的同时,做出针对性的修改

OverrideController 只能继承自 Controller,不能形成继承树

5.6 Apply Root Motion

RootMotion 总共有三种状态,勾选,不勾选 Handled by Script
Hadndled by Script 状态,是由于我们在 Animator 所在物体的脚本中, 定义方法 OnAnimatorMove()

5.7 Avatar的作用与配置

Avatar替身(人物节点导引),主要与动画的复用,尤其是人物动画的复用有关

5.8 动画系统脚本 API

// 这里的名称要与Animator窗口中,动画参数的名称对应
int runHash=Animator.StringToHash("Run");

// 通常对于调用频繁的动画参数我们使用哈希值进行快速访问
// 下面设置/获取动画参数均有使用String参数名称进行映射的重载和使用哈希值进行映射的重载

// 获取设置Float类型参数,通常结合Input轴线
animator.GetFloat(blendHash);animator.SetFloat(blendHash, Input.GetAxis("Horizontal"));

// 获取设置Int类型参数
animator.GetInteger(intHash);animator.SetInteger(intHash,Number);

// 获取设置Bool类型参数
animator.GetBool(boolHash);animator.SetBool(boolHash, true / false);

// 触发,取消触发Trigger的方法
animator.SetTrigger(jumpHash);animator.ResetTrigger(jumpHash);

// 我们先预设状态的哈希值
int idleHash = Animator.StringToHash("Idle");

int layerID = animator.GetLayerIndex("Base Layer");
animatorStateInfo = animator.GetCurrentAnimatorStateInfo(layerID);

// 判定当前状态是否是Idle状态
if (animatorStateInfo.shortNameHash == idleHash)
{
    Debug.Log("OnState Idle");
}

// 过渡状态的 nameHash userNameHash
AnimatorTransitionInfo transitionInfo;
transitionInfo = animator.GetAnimatorTransitionInfo(layerID);
Debug.Log(transitionInfo.nameHash);
Debug.Log(transitionInfo.userNameHash);

5.9 动画分层

动画分层可以让我们使用多层动画来对人物/角色不同组分分别配置动画,再使用一定的混合模式,从而实现一些复杂的效果

5.10 IK 反向动力学

5.11 BlendTree 混合树

BlendTree的作用是将多个动画状态混合成为一个动画状态,并通过动画参数来影响状态的输出结果。
使用 BlendTree 的目的是应对状态机的高耦合导致的低扩展缺点,尤其是在人物运动相关的动作(前后左右的行走奔跑)这类繁多的动作
在状态过渡时 BlendTree 就相当于一个普通的 State,可以创建指向和指出 BlendTree 的过渡。BlendTree 的 State 配置也和普通 State 一致

–EOF–

]]>