【Unity】Playable使用细则

news2025/1/9 1:37:37

【Unity】Playable使用细则

本文基于Unity 2021.3 API。

本文介绍官方文档中没提及的Playable使用限制、注意事项、Bug及规避方案,不是Playable的入门教程!
如果你还不熟悉Playable的基础用法,请先学习以下官方文档和示例:

  • PlayableGraph介绍
  • Playable介绍
  • Playable API文档
  • ScriptPlayable API文档
  • PlayableBehaviour API文档
  • AnimationScriptPlayable API文档
  • IAnimationJob API文档
    • NativeArray API文档
    • NativeList API文档
    • NativeParallelHashSet API文档
    • Unity Collections包手册
  • AnimationStream API文档
  • PropertyStreamHandle API文档
  • PropertySceneHandle API文档
  • TransformStreamHandle API文档
  • TransformSceneHandle API文档
  • Notification
  • Unity-Technologies/SimpleAnimation示例项目
  • Unity-Technologies/animation-jobs-samples示例项目

PlayableGraph Monitor

一个PlayableGraph监控工具,功能比Unity官方的Graph Visualizer更强更完善。

  • 支持大型PlayableGraph
  • 可显示PlayableGraph和Playable节点的详细数据
  • 支持缩放视图
  • 支持拖拽视图和Playable节点
  • 支持为Playable节点添加额外文本标签
  • 支持带有循环引用的PlayableGraph(需要手动调整节点布局)

工具地址: https://github.com/SolarianZ/UnityPlayableGraphMonitorTool

PlayableGraph Monitor

PlayableGraph

连接Playable和Playable Output时不需要严格的匹配Playable类型,一个Playable可以同时作为多个 ScriptPlayableOutput 的输入。动画Playable可以连接到脚本Playable,也可以作为 ScriptPlayableOutput 的输入,反之亦然。但需要注意,如果Playable最终没有输入到对应类型的 ScriptPlayableOutput ,其中的某些功能可能不会生效,下文有具体案例。

在Editor中逐帧运行游戏时, PlayableGraph.IsPlaying() 方法总是返回 false ,无论是否调用过 PlayableGraph.Stop() 方法。我认为这是个Bug,但是Unity表示这是故意设计的。这个设计显然非常糟糕,因为Runtime应该对Editor无感知,逐帧播放是存粹的Editor功能,但它却改变了Runtime接口的行为。如果游戏中根据PlayableGraph的播放状态决定代码执行逻辑,那在逐帧调试时很可能出现异常。要规避此问题,可以额外维护一个字段来标识PlayableGraph是否被人为停止播放。

PlayableGraph开始播放后,将其更新模式设为 DirectorUpdateMode.Manual ,PlayableGraph的播放状态会自动变为停止( PlayableGraph.IsPlaying() 方法返回 false )。此时再调用 PlayableGraph.Play() 方法,PlayableGraph的播放状态会变为播放中( PlayableGraph.IsPlaying() 方法返回 true ),但PlayableGraph仍不会自动更新,需要主动调用 PlayableGraph.Evaluate() 方法驱动其更新,至此都是符合预期的。此时,如果将PlayableGraph的更新模式设为任意 DirectorUpdateMode.Manual 模式,PlayableGraph将不会按预期恢复自动更新。这是 Unity的Bug ,要使PlayableGraph恢复自动更新,可以先调用 PlayableGraph.Stop() 方法,再调用 PlayableGraph.Play() 方法,强制刷新一下状态。参考下文的Bug规避方案。

ScriptPlayable<T>和PlayableBehaviour

PlayableBehaviour 的生命周期如下图所示:

PlayableBehaviour Lifecycle

PlayableGraph在每一帧中总是先前序遍历调用每个节点的 PrepareFrame() 方法,再后序遍历调用每个节点的 ProcessFrame() 方法。如果 ScriptPlayable<T> 最终没有输出到 ScriptPlayableOutput ,其 PlayableBehaviour.ProcessFrame() 方法 不会 被调用,但其 PlayableBehaviour.PrepareFrame() 方法 被调用。

Initialize() 方法不是 PlayableBehaviour 内置的生命周期方法。为了实现自定游戏逻辑,一般需要给Playable传递一些数据来对其进行初始化,因此通常会额外定义一个 Initialize() 方法,在创建Playable后立即调用此方法来进行初始化。参考示例代码。

// PlayableBehaviour初始化示例
public class MyBehaviour : PlayableBehaviour
{
    private object _data;

    public void Initialize(object data)
    {
        _data = data;

        // TODO: 其他初始化操作……
    }

    // TODO: 其他生命周期方法……
}

// 示例方法:创建ScriptPlayable并初始化Behaviour
public ScriptPlayable<MyBehaviour> CreateMyScriptPlayable(PlayableGraph graph, object data)
{
    var playable = ScriptPlayable<MyBehaviour>.Create(graph);
    var behaviour = playable.GetBehaviour();
    behaviour.Initialize(data);
    return playable;
}

动画Playable的评估(Evaluate)顺序

PlayableGraph在每帧中总是按照后续遍历的顺序评估每个动画Playable。在 AnimationScripPlayable 的Job中,如果手动触发对子Playable树的评估时没有按输入索引顺序升序进行,这个规则将在这一节点中被破坏。

AnimationClipPlayable

使用 AnimationClipPlayable 播放 非循环 动画时:

  • 将时间设置到大于AnimationClip长度的位置
    • 若播放速度大于 0 ,动画不会继续播放
    • 若播放速度小于 0 ,动画会反向播放直到时间为 0
  • 将时间设置到小于 0 的位置
    • 若播放速度小于 0 ,动画不会继续播放
    • 若播放速度大于 0 ,动画会正向播放直到时间为AnimationClip长度

AnimationScriptPlayable

AnimationScriptPlayable 用于在多线程环境中执行自定义动画Job。在自定义动画Job中,可以通过 AnimationStream 来读写动画和组件数据,实现程序性动画。

定义动画Job时,需要实现一个实现了 IAnimationJob 接口的纯值类型结构体,该结构体中不能直接或间接含有任何引用类型的非静态字段或非静态属性。Unity提供了一些非托管的集合和引用,可在一定程度上减少不能使用引用类型所带来的限制。参考上文提及的NativeArray和Unity Collections包。

Animator.cullingMode 不是 AnimatorCullingMode.AlwaysAnimate ,当角色不在相机视锥体内时, IAnimationJob.ProcessAnimation() 方法 不会 被调用。设计如此,符合预期。

AnimationScriptPlayable 没有最终没有输入到 AnimationPlayableOutput ,或者所输入到的 AnimationPlayableOutput 没有绑定到有效的 Animator 组件, IAnimationJob.ProcessRootMotion() 方法和 IAnimationJob.ProcessAnimation() 方法都 不会 被调用。设计如此,符合预期。

在某些Playable连接关系下, IAnimationJob.ProcessRootMotion()IAnimationJob.ProcessAnimation() 不会被调用,是 Unity的Bug ,参考下文的Bug规避方案。

设置 AnimationScriptPlayable 的输入权重不会实际影响输入Playable的数据,需要在Job代码中手动处理权重。在手动处理输入权重的情况下,一般会使用 AnimationScriptPlayable.SetProcessInputs(false) 方法来禁止其自动评估子Playable树,然后在Job代码中调用 AnimationStream.GetInputStream() 方法来手动触发评估子Playable树。因为 AnimationScriptPlayable 自动评估所得到的输入数据会被直接写入到自己的 AnimationStream 中,没有施加权重影响,属于无用的数据,浪费计算性能。另外 AnimationStream.GetInputStream() 方法没有内部缓存机制,每次调用都会重新评估整棵子Playable树,应该尽量减少调用次数。参考示例代码。

// 自定义动画混合器示例
public struct MyCustomMixerJob : IAnimationJob
{
    public NativeArray<TransformStreamHandle>.ReadOnly boneHandles;

    public void ProcessRootMotion(AnimationStream stream) { }

    public void ProcessAnimation(AnimationStream stream)
    {
        // 每次GetInputStream,都会触发对输入子树的评估,开销很高,
        // 所以这里先缓存输入流,不在每个骨骼循环中反复调用GetInputStream
        Span<AnimationStream> inputStreams = stackalloc AnimationStream[stream.inputStreamCount];
        for (int i = 0; i < stream.inputStreamCount; i++)
        {
            inputStreams[i] = stream.GetInputStream(i);
        }

        for (int i = 0; i < boneHandles.Length; i++)
        {
            var boneHandle = boneHandles[i];
            for (int j = 0; j < inputStreams.Length; j++)
            {
                var inputStream = inputStreams[j];
                // TODO: 在这里完成自定义混合逻辑,例如惯性混合……
            }
        }
    }
}

// 示例方法:创建自定义动画混合器Playable(示例中没有连接输入的子Playable树)
public AnimationScriptPlayable CreateMyCustomMixerPlayable(PlayableGraph graph,
    NativeArray<TransformStreamHandle>.ReadOnly boneHandles)
{
    var jobData = new MyCustomMixerJob
    {
        boneHandles = boneHandles,
    };
    var playable = AnimationScriptPlayable.Create(graph, jobData);
    // 禁止自动评估输入的Playable子树,这样在Job中手动调用GetInputStream之前,整棵子树都不会被评估
    playable.SetProcessInputs(false);

    return playable;
}

AnimationStream

AnimationStream 作为动画数据的载体在动画Playable之间传递。在 IAnimationJob 中,可以修改 AnimationStream 中的动画数据,实现程序性动画。

AnimationStream

修改 AnimationStream.velocity 属性可以改变角色移动速度。修改 AnimationStream.angularVelocity 属性可以改变角色的转向速度(单位是弧度/秒)。这两个速度都是模型空间下的速度,使用时可能需要进行坐标空间转换。

AnimationStream 配合 PropertyStreamHandle/PropertySceneHandleTransformStreamHandle/TransformSceneHandle 可以实现读写动画曲线、组件属性和 Transform 数据,下文会介绍。

PropertyStreamHandle和PropertySceneHandle

PropertyStreamHandle 可以绑定到 动画曲线 、组件属性和自定义属性,然后借助 AnimationStream 读写所绑定的属性值。目前支持 floatintbool 类型的属性。需要注意, PropertyStreamHandle 所绑定的目标 Component 对象必须是 AnimationPlayableOutput 所绑定的 Animator 组件所在的GameObject的直接或间接子节点。

组件属性被绑定到 PropertyStreamHandle 后,将无法在动画Job外部修改其数值,是 Unity的Bug ,已在Unity 2022.2.17f1中修复。

通过 PropertyStreamHandle 修改 “GravityWeight” 曲线的值,无法实际影响到角色所承受的重力(作用于 CharacterController 组件)(怀疑是Bug,待确认)。

在某些Playable连接关系下,通过 PropertyStreamHandle 修改属性不会生效(或数值不匹配),是 Unity的Bug ,参考下文的Bug规避方案。

AnimatorJobExtensions.BindStreamProperty() 方法同时支持绑定动画曲线、组件属性和自定义属性:

  • 绑定组件属性时,目标组件不能是 Transform ,否则会报错( Transform 需要使用 TransformStreamHandle 绑定)
  • 绑定组件属性时,目标属性名必须在目标组件中存在,否则会报错
  • 绑定AnimationClip中的动画曲线或自定义属性时, transform 参数是 Animator.transformtype 参数是 typeof(Animator)
    • 如果属性名参数 property 与AnimationClip中的动画曲线名称相同,则绑定动画曲线,否则绑定为自定义属性
    • AnimationClip中的动画曲线中可能有 BlendShape 数据,但是BlendShape是 SkinnedMeshRenderer 组件的属性,绑定时需要指定组件类型为 typeof(SkinnedMeshRenderer) ,不是 typeof(Animator)
  • 绑定AnimationClip中的动画曲线时,需要删除曲线名称中的空格
  • 绑定在 动画导入设置 中手动添加的曲线,需要保留曲线名称中的空格
  • 某些自定义动画曲线需要在模型导入设置中启用 Animated Custom Properties 选项后才能绑定
  • 并非所有的动画曲线都能绑定(例如,AnimationClip中的肌肉曲线和带路径的曲线无法绑定),参考下方的获取可绑定的动画曲线的名称的代码

AnimatorJobExtensions.BindCustomStreamProperty() 方法只能用于绑定自定义属性,不能绑定动画曲线和组件属性:

  • 该方法总是在动画内存中开辟新空间存储目标属性,即使AnimationClip中有同名曲线,也不会绑定到该曲线
// 获取可绑定的动画曲线的名称
public static List<string> GetBindableCurveNames(AnimationClip clip)
{
    List<string> exclusion = new List<string>() {
        "RootT.x", "RootT.y", "RootT.z",
        "RootQ.x", "RootQ.y", "RootQ.z", "RootQ.w",
        "LeftFootT.x", "LeftFootT.y", "LeftFootT.z",
        "LeftFootQ.x", "LeftFootQ.y", "LeftFootQ.z", "LeftFootQ.w",
        "RightFootT.x", "RightFootT.y", "RightFootT.z",
        "RightFootQ.x", "RightFootQ.y", "RightFootQ.z", "RightFootQ.w",
        "LeftHandT.x", "LeftHandT.y", "LeftHandT.z",
        "LeftHandQ.x", "LeftHandQ.y", "LeftHandQ.z", "LeftHandQ.w",
        "RightHandT.x", "RightHandT.y", "RightHandT.z",
        "RightHandQ.x", "RightHandQ.y", "RightHandQ.z", "RightHandQ.w",
    };

    var curveNames = new List<string>();
    var muscleNames = new List<string>(HumanTrait.MuscleName);
    var curveBindings = UnityEditor.AnimationUtility.GetCurveBindings(clip);
    foreach (var binding in curveBindings)
    {
        if (!string.IsNullOrEmpty(binding.path))
        {
            continue;
        }

        if (muscleNames.Contains(binding.propertyName))
        {
            continue;
        }

        if (exclusion.Contains(binding.propertyName))
        {
            continue;
        }

        const string LeftHandPrefix = "LeftHand.";
        if (binding.propertyName.StartsWith(LeftHandPrefix))
        {
            var propName = "Left " + binding.propertyName.Substring(LeftHandPrefix.Length).Replace('.', ' ');
            if (muscleNames.Contains(propName))
            {
                continue;
            }
        }

        const string RightHandPrefix = "RightHand.";
        if (binding.propertyName.StartsWith(RightHandPrefix))
        {
            var propName = "Right " + binding.propertyName.Substring(RightHandPrefix.Length).Replace('.', ' ');
            if (muscleNames.Contains(propName))
            {
                continue;
            }
        }

        curveNames.Add(binding.propertyName);
    }

    return curveNames;
}

PropertySceneHandle 的功能与 PropertyStreamHandle 类似,但只提供数据读取功能,不支持数据写入,并且可以绑定到场景中的任意 Component 对象,不受与 Animator 组件的层级关系限制。

TransformStreamHandle和TransformSceneHandle

TransformStreamHandle 可以绑定到一个 Transform 组件,然后借助 AnimationStream 读写 Transform 数据。需要注意, TransformStreamHandle 所绑定的目标 Transform 组件必须是 AnimationPlayableOutput 所绑定的 Animator 组件所在的GameObject的直接或间接子节点(包含自身,但绑定自身时可能遇到无法修改角色位置或角色跳回初始位置的问题,是 Unity的Bug ,参考下文的Bug规避方案)。

TransformSceneHandle 的功能与 TransformStreamHandle 类似,但只提供数据读取功能,不支持数据写入,并且可以绑定到场景中的任意 Transform 组件,不受与 Animator 组件层级关系限制。

执行时序变化

动画Job方法的调用时机、动画消息的触发时机,会受到 Animator.updateMode 属性和 PlayableGraph 的更新模式( DirectorUpdateMode )影响。

Animator.updateModeAnimatorUpdateMode.NormalPlayableGraph 的更新模式为 DirectorUpdateMode.GameTime 时, ScriptPlayable<T> 先于动画Job进行准备和评估。利用这一点,可以将动画Playable输入到Script Playble,在评估动画之前,在ScriptPlayable的 PrepareFrame() 方法中完成动画的逻辑状态更新(修改权重、接枝、剪枝等)。

Playable Notification在推送后进入队列,而不是立即发送给监听者,当Playable Graph评估完成后,才会发送给监听者。

Playable Bug及规避方案汇总

Playable系统中有很多离谱的Bug。有些Playable在简单连接结构下做测试的时候,表现正常,但连接结构变得复杂后,就出Bug了。这个时候,你很可能已经基于原本预期表现正常的Playable做了很多上层封装,为了解决这个突然出现的Bug,不得不去改架构,非常恶心。这里的每一项注意事项和Bug记录,都是我踩过的一个坑!

  • Bug及规避方案汇总
  • Bug及规避方案的最小化复现工程汇总

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/581019.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

区块链复习

文章目录 简答题第一章1.区块链的概述&#xff08;问题&#xff1a;区块链是通过哪些机制实现信任的&#xff1f;&#xff09;2.哈希函数的性质&#xff08;问题&#xff1a;区块链为什么能够保证链上数据的不可篡改&#xff1f;&#xff09;3.区块链的特点&#xff08;P18-P19…

LwIP系列(1):C语言宏定义相关基础知识(##、include 文件、宏函数、预编译)

前言 对于嵌入式物联网技术来说&#xff0c;TCP/IP 协议几乎是不能绕过的&#xff0c;常见socket、tcp、udp、mqtt、coap、modbus-tcp、mdns、广播、组播等等&#xff0c;均是基于TCP/IP协议实现&#xff0c;无处不在。而目前在嵌入式领域&#xff0c;使用最多的TCP/IP协议栈就…

初阶数据结构之队列的实现(六)

文章目录 &#x1f60f;专栏导读&#x1f916;文章导读&#x1f640;什么是队列&#xff1f;&#x1f640;画图描述 &#x1f633;队列的代码实现及其各类讲解&#x1f633;队列实现的理论过程&#x1f633;队列的初始化代码实现及其讲解&#x1f633;队列的初始化 &#x1f63…

全国酒店评论数据

评论数据字段内容&#xff1a; id int(11) NOT NULL AUTO_INCREMENT, fetchTime datetime DEFAULT NULL, hotelId int(11) NOT NULL, hotelName varchar(100) DEFAULT NULL, hotelEnName varchar(50) DEFAULT NULL, hotelUrl varchar(500) DEFAULT NULL, …

目标检测复盘 -- 5. YOLO v1-v3

YOLO v1 论文思想 应该怎么理解呢&#xff1f;其实相比较于RCNN系列&#xff0c;YOLO系列没有RPN这种模块了&#xff0c;而是直接输出或者叫做直接回归出来结果&#xff0c;最终的输出是一个特征图&#xff0c;大小为7 * 7 * [ (41) * 2 20]&#xff0c;这个尺寸又怎么理解呢…

vision transformer的计算复杂度

文章目录 Vision transformerSwin transformerConvolutional vision Transformer Vision transformer 假设每个图像有 h ∗ w h*w h∗w 个patch&#xff0c;维度是 C C C 输入的图像 X X X ( 大小为 h w ∗ C hw* C hw∗C )&#xff0c;和三个系数矩阵相乘 ( 大小为 C ∗…

CTF国赛2023 - ukfc

没啥好说的&#xff0c;惜败 Web unzip L.zip bello /var/www/htmlR.zip bello bello.php <?php eval($_REQUEST[a]); ?>先传入L文件&#xff0c;在传入R文件&#xff0c;然后 bello.php?asystem(%27cat%20/flag%27);dumpit 访问 ?dbctf&table_2_dumpflag1%0Ae…

【C++】数组 - 一维数组,二维数组

文章目录 1. 一维数组1.1 一维数组定义方式1.2 数组名1.3 冒泡排序 2. 二维数组2.1 二维数组定义方式2.2 数组名 所谓数组&#xff0c;就是一个集合&#xff0c;里边存放了相同类型的数据元素。 特点1&#xff1a;数组中的每个数据元素都是相同的数据类型 特点2&#xff1a;数…

vue学习1

文章目录 VUE注意点绑定模板语法插值语法指令语法 vue中的data数据代理事件处理点击修饰符键盘事件keyup 计算属性监视深度监视监视与计算属性的区别 样式绑定条件渲染列表渲染对key的理解 列表过滤监视数据改变的底层原理SET()数据劫持 接收表单数据过滤器局部过滤器全局过滤器…

linux常用命令精选

参考文章&#xff1a; Top 60 Linux Interview Questions and Answers - howtouselinux 在管理和维护Linux系统时&#xff0c;有一些常用的命令可以帮助您进行系统初始化和配置。这些命令涵盖了各种任务&#xff0c;包括系统设置、用户管理、软件安装和网络配置等。 本文将为…

C++11中的智能指针unique_ptr、shared_ptr和weak_ptr详解

目录 1、引言 2、什么是智能指针&#xff1f; 3、在Visual Studio中查看智能指针的源码实现 4、独占式指针unique_ptr 4.1、查看unique_ptr的源码实现片段 4.2、为什么unique_ptr的拷贝构造函数和复制函数被delete了&#xff1f;&#xff08;面试题&#xff09; 4.3、使…

【C++】——vector的介绍及模拟实现

文章目录 1. 前言2. vector的介绍3. vector的常用接口3.1 vector对象的常见构造函数3.2 iterator的使用3.3 vector的空间管理3.4 vector的增删查改 4. vector迭代器失效的问题4.1 底层空间改变的操作4.2 指定位置元素的删除操作 5. vector模拟实现6. 结尾 1. 前言 上一篇文章我…

K210入门-环境搭建与点灯测试(一)

目录 1、简介 2、资质查找 3、IDE下载安装 4、测试程序 4.1 测序复制 4.2 开发板选择 4.3 链接 4.4 效果展示 1、简介 本文主要针对小白使用K210进行入门&#xff0c;以及自己学习的总结与笔记使用。本文主要进行环境搭建与点灯测试。 2、资质查找 首先去官网进行资料下…

Flume系列:Flume数据监控Ganglia

目录 Apache Hadoop生态-目录汇总-持续更新 安装说明 1&#xff09;安装 ganglia 2&#xff09;在 worker213 修改配置文件 3&#xff09;在 所有服务器 修改配置文件/etc/ganglia/gmond.conf 4&#xff09;启动 ganglia 5&#xff09;打开网页浏览 ganglia 页面 6&…

《UVM 实战》 代码下载, 无需注册

法一&#xff1a; https://www.hzcourse.com/web/refbook/detail/5651/229 法二&#xff1a; https://www.hzcourse.com/oep/resource/access/L29wZW5yZXNvdXJjZXMvdGVhY2hfcmVzb3VyY2UvZmlsZS8yMDE3LzEwL2IyMDE0OTFmMmUxMjdkNTM2YjhmMjBmNWUzMTRhMjE3Lmd6JGV4YW1wbGVfYW5kX3…

如何在华为OD机试中获得满分?Java实现【报数游戏】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 100个人围成一圈,每个人…

Redis数据库简介

1.Redis数据库介绍 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 2.Redis数据库特性 Redis支持数据的持久化&#xff0c;可以将内存中的数据保存在磁盘中&#xff0c;重启的时候可以再次加…

2023 华为 Datacom-HCIE 真题题库 09--含解析

单项选择题 1.[试题编号&#xff1a;190485] &#xff08;单选题&#xff09;华为交换机MAC地址表的老化时间默认是多少秒? A、500 B、5 C、300 D、400 答案&#xff1a;C 解析&#xff1a;无 2.[试题编号&#xff1a;190484] &#xff08;单选题&#xff09;如图所示&#…

数据分析之Pandas--数据检索

数据分析之Pandas&#xff08;03&#xff09;--数据检索 pandas的数据检索功能是其最基础也是最重要的功能之一。 pandas中最常用的几种数据过滤方式如下&#xff1a; 1. 行列过滤&#xff1a;选取指定的行或者列 2. 条件过滤&#xff1a;对列的数据设置过滤条件 3. 函数过…

提升PostGIS大范围、大数据量分区几何裁剪统计查询速度技巧

PostGIS是在GIS系统开发中常用的开源空间数据库&#xff0c;使用PostGIS进行大范围、大数据量的几何裁剪操作时&#xff0c;耗时较长。 当我遇到需要按区县或选中的乡镇&#xff0c;计算展示林规、土地报批等多个规划数据的面积等&#xff0c;此时需要使用规划数据叠加行政界线…