ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

grandMA2 onPC控制UE4虚拟灯光:Art-Net与DMX映射

grandMA2 onPC控制UE4虚拟灯光:Art-Net与DMX映射 grandMA2 onPC 控制 UE4 灯光说白了就是拿一台软件控台去驱动引擎里的虚拟灯具。我最早接这活是在一个虚拟演播厅的预演项目上客户的要求很朴素——要能像真实演播厅一样推杆、跑 Cue、走时间码但现场一盏实体灯都没有只有一屋子显示器和两台工作站。当时我的第一反应是把 UE4 场景当成一排超大的摇头灯阵列让 onPC 的 Art-Net 数据直接喂进去做完之后发现这条路比 MIDI、OSC 都稳而且换项目时复用成本极低。这篇东西写给三类人手里有 grandMA2 onPC 但不知道怎么把数据送进引擎的灯光师、会 UE4 但对 DMX 那套补丁逻辑不熟的引擎同学以及做虚拟拍摄、舞台预演、展会互动需要把控台手感搬进实时渲染的人。中间会顺带聊两个常被问到的点ue4外接设备映射到底该怎么设计以及 ue4 查询和物理模拟器的区别在实际灯光交互里到底该怎么选。1. 先把链路想清楚三种接法的取舍与预算1.1 Art-Net、MIDI、OSC 到底差在哪三条路我都跑过先给结论性的对照再讲为什么。链路传输方式单次可寻址量刷新率控台侧改动适合场景Art-Net / sACNUDP6454 端口512 通道/宇宙约 30-44Hz基本为零灯具级连续调光、摇头、跑 CueMIDI虚拟 MIDI 端口16 通道 × 128 CC事件驱动需配 MIDI 映射推子、按钮、触发类交互OSCUDP端口自定义自定义事件驱动需第三方桥接遥控命令、上层逻辑联动Art-Net 和 sACN 本质上是把 DMX512 那套语义原封不动搬到网络上一个宇宙 512 个通道每个通道一个 0-255 的字节值定了就定了。MIDI 的强项是事件一个 CC 值对应一个旋钮但你要控制 24 台摇头灯的 Pan、Tilt、RGBW、频闪、棱镜通道数直接爆掉。OSC 更灵活可 grandMA2 的 OSC 支持重心在遥控指令上不是给高频连续调光设计的onPC 上要么走 MA 自己的 Remote 输入要么中间挂一层转换链路一长排障就变成噩梦。1.2 我把主力压在 Art-Net 上的三个理由第一是可寻址量级碾压。一个宇宙 512 通道按每台摇头灯 16 通道算一个宇宙就能挂 32 台三个宇宙就是近百台。MIDI 那 2048 个 CC 看似不少但一旦涉及 16bit 的 Pan/Tilt一个属性就要吃掉两个 CC实际能控的灯具数量会被压到个位数。第二是控台侧零改造。控台里做的 Patch、Group、Preset、Cue、Timecode 全部原样保留我这边只改接收端。这意味着灯光师的既有工作流完全不用变他还是在 MA 上干活只是灯从物理灯具变成了引擎里的虚拟灯。这一点在多人协作的项目里价值极大你不需要培训一个灯光师去学引擎。第三是网络层的可观测性。数据有没有出来抓一次包就知道不必去猜是控台问题还是引擎问题。我在现场用过最容易解释的一句话就是我这边抓到包了问题在你那台机器上。这句话能省掉半小时的扯皮。它当然也有代价UDP 会丢包8bit 精度在慢速 fade 时肉眼可见台阶所有归一化和曲线都得自己在引擎侧补。这些后面都会讲到。1.3 带宽和参数预算先算再动手很多人上来就配配到一半发现宇宙不够返工非常痛苦。Art-Net 一个包的开销很好算512 通道 × 1 字节 18 字节 Art-Net 头 530 字节加上 UDP 头 8 字节和 IP 头 20 字节大约是 558 字节一个包。按 40Hz 刷新算单个宇宙约 22KB/s也就是 180kbps 左右。这个数字意味着什么千兆内网跑几十个宇宙都毫无压力普通 Wi-Fi 扛一两个宇宙也没问题但 Wi-Fi 的抖动会让你在慢速 fade 时看到不规律的跳变。所以有条件的项目控台机和引擎机之间老老实实拉一根网线走交换机。参数预算的算法是倒推先列出场景里所有灯具类型和它们的通道占用。灯具类型典型通道占用通道构成单色 PAR1DimmerRGB PAR3R / G / BRGBW PAR4R / G / B / W基础摇头8Dim / RGBW / Pan / Tilt完整功能摇头16Dim / RGBW / Strobe / Pan / PanFine / Tilt / TiltFine / Prism / Focus光束灯20上面再加 Frost / Zoom / Gobo把总通道数除以 512 向上取整再乘 1.3 留余量就是你需要规划的宇宙数。我一般还会额外多留一个整宇宙做调试用——临时加几盏测试灯、打个全通道扫值非常方便。注意别把通道排得太紧凑。灯具起始通道按 8 或 16 对齐虽然看起来浪费了几个通道但后期加属性、换 fixture type 时你会感谢自己。2. UE4 侧插件、工程设置与外接设备映射的设计2.1 内置 DMX 插件还是自己写 UDP 接收UE4 在 4.26 / 4.27 上其实已经随编辑器分发了一套虚拟制作相关的插件在 Plugins 里搜 DMX能看到 DMX Engine、DMX Protocol 一类的条目再配合协议插件Art-Net / sACN就能直接收数据还自带 Fixture Patch、属性库和 DMX Component蓝图里能直接拿到归一化后的属性值。它的好处是省事、语义正确、能跟后续的虚拟制作流程对接。问题是版本相关性很强插件命名和 API 在不同小版本之间会变社区里能查到的资料又少一旦出问题你只能自己啃源码。自己写接收器的好处是完全可控。核心逻辑不到两百行监听 UDP 6454校验包头解析 OpDmx导出一个 512 长度的 float 数组。任何 4.x 版本都能跑调试时能直接打日志出问题一眼就知道卡在哪一步。我的建议是正式交付项目用内置插件快速验证、特殊映射、或者必须兼容老版本时用自写组件。下面两套做法我都会给到。2.2 工程设置里那几个必踩的坑端口用 6454这是 Art-Net 的标准端口不用改。控台机和引擎机放同一网段这一步没有捷径。网卡绑定是最容易出问题的环节。如果机器上有有线网卡、无线网卡、虚拟网卡、回环好几张Art-Net 的广播包可能从错的那张出去。onPC 侧可以在网络设置里指定输出接口引擎侧因为 bind 到 0.0.0.0 全收只要包能到就行。同机测试的回环问题。控台和引擎跑在同一台机器上时如果 onPC 的输出接口选的是某张实体网卡广播包不会走 127.0.0.1。实测最省事的做法就是两台机器走交换机实在只有一台机器就装一个回环软网卡并给 onPC 指定它。bind 冲突。6454 这个端口一个进程只能独占除非双方都设了地址复用。如果你同时开着预演可视化软件、又开了两个 UE 实例就会出现偶尔收得到、偶尔收不到的诡异现象。关掉多余的进程这是最快的验证方式。接收缓冲区。引擎侧的 UDP 收包缓冲区别用默认值直接开到 2MB 起步。40Hz 之下数据量不大但系统启动瞬间和场景加载卡顿的时候缓冲区小会直接丢包。2.3 ue4外接设备映射把通道号翻译成引擎属性这部分是整个项目里最需要设计、也最容易被糊弄过去的环节。所谓 ue4外接设备映射说到底就是一张翻译表外面来的通道号对应到引擎里哪个 Actor 的哪个属性中间做什么数值变换。我习惯把它拆成三级第一级是地址级Port-AddressNet / Subnet / Universe 组合出来的 15 位地址决定收哪一路数据。换算公式是PortAddress Net*256 Subnet*16 UniverseNet 范围 0-127Subnet 和 Universe 各 0-15。第二级是灯具级在宇宙内哪些通道属于哪台灯。这个用一张 DataTable 存最合适行名用灯具 ID比如 MH_01、PAR_03列包含 Universe、起始通道、灯具类型、Pan 角度范围、Tilt 角度范围、是否 16bit。第三级是属性级单个通道怎么变成引擎里的实际数值。这是最考功夫的一层。属性原始值处理目标值注意事项亮度raw / 255引擎强度 0 到最大值建议再过一条曲线平方律比线性更像真实调光颜色raw / 255FLinearColor必须做 sRGB 到线性的转换否则发灰Pan(coarse×256fine)/65535角度插值到 -270 至 27016bit 一定要用8bit 转 270 度会看到明显阶梯Tilt同上角度插值到 -135 至 135范围按灯具实际机械限位设别用理论值频闪raw / 255频率或开关阈值低于某个值直接关别让引擎去算极低频关于平滑这里要单独强调。控台出数据是 40Hz 左右引擎渲染是 60 到 120fps如果直接把收到的值写进灯光你会看到明显的台阶和抖动。正确做法是每一帧做一次插值逼近目标值Current FInterpTo(Current, Target, DeltaTime, Speed)。实测下来这组响应速度比较好用强度 8 到 12颜色 10 左右Pan / Tilt 15 到 25。太快会跟丢控台的慢速 fade太慢会在快速 Cue 切换时拖尾。实操心得把映射表做成 DataTable 资产而不是写死在蓝图里换项目时只需要换表不用改任何逻辑。我后来把这个表导出成 CSV 交给灯光师填他填完我导入双方都不用改代码。3. grandMA2 onPC 侧从补丁到输出3.1 网络会话与 Art-Net 输出onPC 启动之后第一件事进 Setup 里的网络会话界面确认当前 Session 里只有本机。多开实例、或者旁边还有别的控台挂在同一个 Session 里会互相抢参数表现为我这边推杆没反应但引擎里的灯在动非常迷惑。第二步是打开 DMX 输出协议选 Art-Net指定输出网卡按需填 Net / Subnet / Universe。这里有个现实问题onPC 能输出多少个宇宙取决于你手上的硬件形态和授权状态。纯软件形态先按 2 个宇宙规划对绝大多数预演场景已经够用要更多就上节点或控台机翼。关于广播还是单播我强烈建议在小规模项目里直接用单播。Art-Net 标准走广播但如果你的网卡掩码设得很怪比如 /16广播地址算出来可能不是你以为的那个包就发丢了。单播直接指向引擎机的 IP链路干净排障简单代价只是每加一个接收端就要多配一条。3.2 补丁规划给一张能直接抄的示例灯具 ID类型Universe起始通道占用关键属性MH_01RGBW 摇头1116Dim / R / G / B / W / Pan / Tilt / PanFine / TiltFineMH_02RGBW 摇头11716同上MH_03RGBW 摇头13316同上PAR_01RGB1653R / G / BPAR_02RGB1733R / G / B留空对齐BEAM_01光束灯2120另含 Prism / Focus注意通道按 8 对齐那几行虽然中间空了 5 个通道但换灯具的时候直接改起始通道就行不用重排后面所有灯。这里有个几乎人人都会踩的坑宇宙编号的起始差异。控台界面上显示的宇宙序号通常是 1 起始的而 Art-Net 的 Net / Subnet / Universe 是 0 起始的位段中间差一位非常常见。做法很简单也很有效——在引擎侧的接收器里把每个收到的包的 Port-Address 打印出来用 Print String 或屏幕调试文字跟控台的 DMX 列表对照一次对上以后再继续往下做。这一步花五分钟能省两小时。3.3 做几个看得见的程序逐个验证我见过太多人一上来就把整个 Cue List 挂上去结果灯不亮完全不知道是哪个环节的问题。正确的顺序是从一个通道开始第一步单灯推子测强度。控台里编一个只有 Dimmer 通道的执行器推上去看引擎里的灯亮不亮、线性度如何。这一步同时验证了链路通、通道对、归一化对。第二步Pan / Tilt 画圆。用两个圆形效果加一个 90 度相位差让灯头画圆。这一步验证 16bit 解析是否正确、角度映射是否抖动、有没有万向锁。如果圆画出来是个椭圆或者有卡顿问题基本在 Fine 通道的解析上。第三步颜色渐变 Chase。让一组灯跑彩虹观察 8bit 阶梯在慢速变化时是否可见。如果台阶明显说明引擎侧的平滑参数需要调。第四步挂时间码跑完整 Cue List。前面三步都过了这一步才是真正的验证引擎里的灯光变化和音乐节拍对不对得上切换 Cue 时的过渡是否自然。每一步单独验证出问题只在最后一个变量上找原因。这条经验我觉得比任何具体参数都值钱。4. 把 Art-Net 搬进 UE4C 接收组件加蓝图映射4.1 一个能直接用的 Art-Net 接收组件先把包头结构记清楚字节序混用是这个协议最容易栽的地方。偏移长度字段字节序说明08ID-固定 Art-Net\082OpCode小端0x5000 表示 OpDmx102ProtVer大端0x000E即版本 14121Sequence-递增序号0 表示不排序131Physical-可忽略141SubUni-高 4 位 Subnet低 4 位 Universe151Net-0-127162Length大端DMX 数据长度18NData-最多 512 字节我第一版就是把 ProtVer 按小端读了结果所有包都被判定成非法包排查了很久。记住OpCode 小端ProtVer 和 Length 大端。下面是组件头文件直接可以放进工程// ArtNetReceiverComponent.h #pragma once #include CoreMinimal.h #include Components/ActorComponent.h #include Common/UdpSocketReceiver.h #include Interfaces/IPv4/IPv4Endpoint.h #include ArtNetReceiverComponent.generated.h UCLASS(ClassGroup (Lighting), meta (BlueprintSpawnableComponent)) class YOURGAME_API UArtNetReceiverComponent : public UActorComponent { GENERATED_BODY() public: UArtNetReceiverComponent(); virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; // Art-Net 标准端口 UPROPERTY(EditAnywhere, Category ArtNet) int32 ListenPort 6454; // 只接收这个 Port-Address 的数据PortAddress Net*256 Subnet*16 Universe UPROPERTY(EditAnywhere, Category ArtNet) int32 NetAddress 0; // 0-127 UPROPERTY(EditAnywhere, Category ArtNet) int32 SubnetAddress 0; // 0-15 UPROPERTY(EditAnywhere, Category ArtNet) int32 UniverseAddress 0; // 0-15 // 统计收包频率正常应在 30-44 之间 UPROPERTY(BlueprintReadOnly, Category ArtNet) int32 PacketsPerSecond 0; // 蓝图读取通道值内部会加锁 UFUNCTION(BlueprintCallable, Category ArtNet) float GetChannel(int32 Index) const; private: void OnDataReceived(const FArrayReaderPtr Data, const FIPv4Endpoint From); TArrayfloat Channels; FSocket* Socket nullptr; FUdpSocketReceiver* Receiver nullptr; mutable FCriticalSection ChannelLock; int32 PacketCounter 0; double LastStatSeconds 0.0; };实现部分// ArtNetReceiverComponent.cpp #include ArtNetReceiverComponent.h #include SocketSubsystem.h #include Sockets.h #include Misc/ScopeLock.h UArtNetReceiverComponent::UArtNetReceiverComponent() { PrimaryComponentTick.bCanEverTick false; Channels.SetNumZeroed(512); LastStatSeconds FPlatformTime::Seconds(); } void UArtNetReceiverComponent::BeginPlay() { Super::BeginPlay(); // bind 到 0.0.0.0:6454接收所有网卡上的包 FIPv4Endpoint Endpoint(FIPv4Address::Any, ListenPort); Socket FUdpSocketBuilder(TEXT(ArtNetRecv)) .AsNonBlocking() .AsReusable() .BoundToEndpoint(Endpoint) .WithReceiveBufferSize(2 * 1024 * 1024) .Build(); if (!Socket) { UE_LOG(LogTemp, Error, TEXT([ArtNet] 端口 %d 绑定失败检查是否被占用), ListenPort); return; } Receiver new FUdpSocketReceiver(Socket, FTimespan::FromMilliseconds(5), TEXT(ArtNetRecvThread)); Receiver-OnDataReceived().BindUObject(this, UArtNetReceiverComponent::OnDataReceived); Receiver-Start(); } void UArtNetReceiverComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (Receiver) { Receiver-Stop(); delete Receiver; Receiver nullptr; } if (Socket) { Socket-Close(); ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)-DestroySocket(Socket); Socket nullptr; } Super::EndPlay(EndPlayReason); } float UArtNetReceiverComponent::GetChannel(int32 Index) const { FScopeLock Lock(ChannelLock); return Channels.IsValidIndex(Index) ? Channels[Index] : 0.0f; } void UArtNetReceiverComponent::OnDataReceived(const FArrayReaderPtr Data, const FIPv4Endpoint From) { // 注意这个回调跑在接收线程不是游戏线程 const uint8* Raw Data-GetData(); const int32 Size Data-Num(); if (Size 18 || FMemory::Memcmp(Raw, Art-Net, 7) ! 0) return; // OpCode 小端 const uint16 OpCode static_castuint16(Raw[8] | (Raw[9] 8)); if (OpCode ! 0x5000) return; // 只处理 OpDmx const int32 SubUni Raw[14]; const int32 Net Raw[15]; const int32 PacketAddr (Net 8) | SubUni; const int32 MyAddr (NetAddress 8) | ((SubnetAddress 0x0F) 4) | (UniverseAddress 0x0F); if (PacketAddr ! MyAddr) return; // Length 大端 const int32 Length (Raw[16] 8) | Raw[17]; const int32 Count FMath::Clamp(Length, 0, 512); { FScopeLock Lock(ChannelLock); for (int32 i 0; i Count; i) { Channels[i] Raw[18 i] / 255.0f; } } PacketCounter; const double Now FPlatformTime::Seconds(); if (Now - LastStatSeconds 1.0) { PacketsPerSecond PacketCounter; PacketCounter 0; LastStatSeconds Now; } }4.2 蓝图侧灯具 Actor 与属性写入先说一个很多人第一版就踩的坑整个场景只能开一个接收器。如果给每盏灯挂一个组件40 盏灯就是 40 个 socket 去抢 6454 端口结果就是端口冲突加性能崩盘。正确做法是把接收器做成一个单例GameInstanceSubsystem 或者场景里的一个 Manager Actor收到数据之后通过事件分发把值发给各盏灯。灯具 Actor 的结构我一般这么搭根节点是个 SceneComponent下面挂一个 Yoke 旋转节点负责 PanYoke 下面再挂一个 Head 节点负责 TiltHead 下面才是 SpotLight 组件。这么嵌套的原因很实在——如果直接对灯具 Actor 设置旋转Pan 和 Tilt 两个轴会互相干扰转到某些角度就会出现万向锁灯头会原地打转。分成两层之后Pan 只动 Yoke 的 Z 轴Tilt 只动 Head 的 Y 轴互不影响。属性写入的伪代码大概是这样每帧 Tick: 强度目标 GetChannel(DimIndex) * MaxIntensity 当前强度 FInterpTo(当前强度, 强度目标, DeltaTime, 10.0) SpotLight-SetIntensity(当前强度) 颜色目标 FLinearColor( GetChannel(R), GetChannel(G), GetChannel(B)).sRGBToLinear() 当前颜色 FLinearColorLerp(当前颜色, 颜色目标, DeltaTime * 10.0) SpotLight-SetLightColor(当前颜色) Pan目标 Lerp(PanMin, PanMax, GetChannel16Bit(PanCoarse, PanFine)) Yoke-SetRelativeRotation(FRotator(0, FInterpTo(当前Pan, Pan目标, DeltaTime, 20.0), 0))颜色那一步的sRGBToLinear千万别省。DMX 给的 0-1 值是感知空间的直接当线性值用画面会明显发灰、发亮暗部细节全丢。这个现象我调了很久才反应过来一开始还以为是控台的输出不对。4.3 性能与稳定性实测我那次项目的配置是一台带独立显卡的工作站1080p 输出实测的大致量级是这样的硬件不同会有出入看趋势就好宇宙数灯数平均帧率配置112120全部开动态阴影448110关闭大部分阴影89695关闭阴影不用动态材质89670每帧 80 次重叠查询结论很清楚掉帧的主因从来不是收包而是阴影和查询。单宇宙 40Hz 的收包开销摊到每帧大概 0.05 到 0.1 毫秒基本可以忽略。真正吃性能的是动态阴影贴图和大量的碰撞查询。所以优化顺序应该是先关阴影再减查询最后才考虑收包逻辑。注意灯具超过 20 盏时建议只让其中 3 到 5 盏主灯投射阴影其余全部关闭。视觉上几乎看不出差别帧率能回来一大截。5. ue4查询和物理模拟器的区别在灯光交互里怎么选5.1 两者到底差在哪这个问题被问得很多我用一句话概括查询回答现在是什么状态物理模拟回答接下来会变成什么状态。查询LineTrace、Sweep、Overlap本质是数学求交瞬时完成无状态这次查询的结果不影响下次查询。你从 A 点向 B 点打一条射线返回一个 HitResult里面命中哪个 Actor、命中点在哪、法线朝哪一清二楚。它不依赖帧率不依赖时间步今天跑和明天跑结果完全一样。物理模拟是另一回事。它由求解器按固定子步推进刚体有质量、阻尼、摩擦、关节约束状态跨帧延续。同一个场景帧率抖一下落点就可能差一点点。它不是不确定而是难以完全复现。开销模型也完全不同。查询是O(射线数 × 触及的碰撞体数量)可以精确预算物理模拟是O(活动刚体数 × 约束迭代次数)而且是每帧持续消耗物体越多、约束越复杂开销涨得越快。5.2 在这个灯光项目里的具体分工光束遮挡判断用查询。从灯具位置沿照射方向打一条LineTraceSingleByChannel命中就把那个 Actor 记下来给它加一个高亮材质参数模拟被光打亮的效果。一个 Cue 里有几盏灯就打几条射线几十条射线每帧毫无压力。这里要特别注意碰撞通道的选择。默认的 Visibility 通道可能会被一些透明物体、特效体积挡住导致误判。我一般会自定义一个 LightOcclusion 通道只让需要参与遮挡的静态几何体响应它。用 PhysicsBody 通道更是个灾难角色的胶囊体、布娃娃全都会被命中。地面光斑定位用扫描或者采样。用SweepSingleByChannel加一个球体近似光锥或者干脆沿锥体采样 5 到 9 条射线取平均值。这比用 OverlapSphere 去扫一片区域便宜得多而且结果更可控。灯具的物理摆动才用物理模拟。摇头灯的灯臂晃动、吊挂线缆的摆动、被风吹动的柔光灯罩这些确实需要物理。做法是把 DMX 传来的角度当作目标角度通过约束或者力去驱动而不是直接 SetRotation。这样能得到自然的惯性、滞后和轻微过冲看起来像真的。但这里有一条红线需要精确复位的灯具绝对不要用物理模拟。物理体的静止位置取决于它之前经历了什么你没法保证它每次都停在精确的 0 度。演出场景里灯位对不上是致命的。5.3 三条我踩出来的组合经验第一条用查询做感知用物理做表演。查询负责知道发生了什么物理负责看起来怎么样两者的职责不要混。第二条别把查询结果直接喂回物理目标位置。我试过一次用射线检测的结果去调整物理灯具的目标角度结果形成了正反馈灯头一直高频抖动。后来在中间加了一个一阶低通滤波才稳住。如果你非要做这种联动滤波是必须的。第三条物理灯具数量控制在个位数并且务必打开子步进。4.26 / 4.27 上主流还是 PhysX 路径在项目设置的物理选项里打开子步进最大子步时间设成 1/60 秒最大子步数设成 4。切换 Chaos 的话要注意约束求解器的行为不一样同样一组阻尼参数手感会明显不同得重新调。实操心得如果项目要严格对拍预演和现场必须一致把所有物理限制在纯装饰性物体上主灯全部走运动学路径。我吃过一次亏同一个 Cue List 跑两遍吊挂灯具的落点差了十几厘米现场对不上。6. 常见问题与排查技巧实录6.1 常见问题速查表现象最可能原因快速验证处理方式完全收不到数据输出协议没开、防火墙拦截、不同网段抓包看 6454 有没有流量开协议、关防火墙、检查网段偶尔收不到端口被多个程序占用、广播地址算错关掉可视化软件再试改成单播直指引擎 IP数据跳变闪烁跨线程读写没加锁、丢包打日志看是否出现突降为 0加临界区、丢包时保持上一帧值强度有台阶8bit 精度加没做平滑拉一个慢速 fade 看加插值和平滑曲线颜色发灰发白直接把 0-1 当线性值用对比 sRGBToLinear 前后做色彩空间转换灯完全不亮Mobility 不是 Movable、Affects World 关了看细节面板改成 Movable帧率骤降动态阴影加大量查询Stat GPU 和物理统计关阴影、减少查询时间码对不上刷新率与帧率不匹配、平滑参数过大单跑一条 Cue 看调小插值速度6.2 几条只有踩过才知道的经验先做一根推子再做全部。这句话我前面说过但它值得再说一遍。我见过太多项目卡在全都不对的状态就是因为一次引入了太多变量。一个通道通了整条链路就是通的。把关键数值直接打在屏幕上。Port-Address、收包频率、第一个通道的原始值用屏幕调试文字显示出来。调试速度能翻倍尤其是当你在现场、身边还站着一个等着看效果的客户的时候。保留一个安全状态。控台掉线、网线被踢、软件崩溃引擎里的灯不能直接归零现场会瞬间全黑。做法是接收器维护一个最后一次收到数据的时间戳超过一秒没收到就切到安全状态——要么保持最后一帧要么淡入一个预设亮度。录制和出片的时候关掉收包日志。日志写盘会明显拖帧尤其是在每分钟上万行的情况下。用条件编译或者一个开关控制。16bit 通道要先确认控台真的在输出。有些 fixture type 的 Fine 通道需要单独 patch或者默认不激活。你在引擎侧解析出来的 Fine 永远是 0就会以为是解析写错了实际上是源头就没有。给映射表留一个手动覆盖开关。现场总有意外比如控台的某个执行器坏了、或者灯光师临时想手动调一盏灯。在引擎里留一个调试面板能手动覆盖任意一盏灯的任意属性这个功能在现场救过我至少两次。最后分享一个我觉得挺有用的小做法把接收器的收包频率做成一个可见的指示灯绿的是正常 30 到 44Hz黄的是掉到 10 到 30Hz红的是低于 10Hz 或者直接断了。技术排查的时候一眼就能判断是链路问题还是引擎问题不用再开抓包工具。这个指示灯我后来加到了所有类似的项目模板里成本极低收益极高。
返回列表