如果你正在开发一个需要将 Unreal Engine(UE)制作的 3D 应用或游戏部署到 Web 端,让用户无需下载几十个 G 的客户端,打开浏览器就能体验,那么“像素流送”技术就是你绕不开的解决方案。但很多开发者初次接触时,会误以为这只是简单的“视频推流”,上手后才发现,从 UE 程序打包、信令服务器搭建、前端 SDK 集成,到实现鼠标键盘交互、数据双向通信,每一步都可能遇到意想不到的坑。
这篇文章要解决的,正是这个核心痛点:如何将 UE 程序稳定、高效地“像素流送”到网页,并在此基础上,实现前端与 UE 应用之间真正的双向通信,而不仅仅是单向的视频观看。很多人卡在第一步,服务器跑起来了却看不到画面;或者看到了画面,却无法操作;又或者能操作了,但不知道怎么把网页上的数据(比如一个按钮点击、一个表单输入)实时传回 UE 逻辑里。
本文将从一个完整的、可落地的项目视角出发,带你一步步拆解 UE 像素流送的完整链路。你会看到:
- 核心原理:像素流送到底在底层做了什么,为什么它比传统 WebGL 方案更适合重型 UE 应用。
- 环境搭建:从 UE 项目设置、信令服务器(Signalling Server)部署,到前端页面的基础集成。
- 双向通信:这是重点,我们将深入讲解如何通过前端 JavaScript SDK 向 UE 发送自定义事件,以及如何在 UE 蓝图中接收并处理这些事件,实现业务逻辑的联动。
- 避坑指南:汇总了网络配置、编码参数、常见错误等高频问题,帮你节省大量排查时间。
无论你是前端工程师需要对接 UE 能力,还是 UE 开发者希望拓展 Web 交付渠道,这篇文章都将提供一份清晰的“施工图”。
1. 像素流送:不是推流,而是远程交互式渲染
在深入代码之前,我们必须先厘清一个关键概念:UE 的像素流送(Pixel Streaming)到底是什么?
它不是简单的 OBS 推流或 RTMP 直播。后者是单向的视频流广播,延迟高,且观众无法交互。而像素流送是一套低延迟、双向交互的远程渲染方案。其核心流程可以类比为“云电脑”:
- 服务端(UE 应用):在你的服务器或高性能机器上,运行着完整的、带图形界面的 UE 应用程序。它正常渲染每一帧画面。
- 编码与传输:UE 内置的像素流插件,会实时捕获渲染出的每一帧图像,通过高效的视频编码器(如 H.264/AVC 或 H.265/HEVC)将其压缩成视频流。
- 信令服务器(Signalling Server):这是一个关键的中间层,通常由 Node.js 实现。它不传输视频流本身,而是负责管理“会话”(Session)。当用户打开网页时,信令服务器协调前端播放器与后端 UE 应用实例之间的连接,交换双方的网络地址(IP/端口)等信息,相当于一个“媒人”。
- 前端播放器(Web 客户端):用户在浏览器中打开一个网页,其中嵌入了 Pixel Streaming SDK 提供的 JavaScript 播放器。这个播放器通过 WebRTC 协议,从 UE 服务端直接接收编码后的视频流,并在 HTML5 的
<video>元素或 Canvas 上解码、播放。 - 双向通信:同时,前端的鼠标、键盘、触摸、甚至自定义的 UI 事件,会通过同一个 WebRTC 数据通道(Data Channel)发送回 UE 服务端。UE 应用接收到这些输入后,就像在本地操作一样进行处理,从而实现真正的交互。
为什么选择像素流送,而不是将 UE 项目打包成 WebGL?对于中小型或风格化的 3D 内容,WebGL 是优秀的选择。但对于使用了大量高级渲染特性(如 Lumen 全局光照、Nanite 虚拟几何体)、复杂物理模拟或庞大资源包的 AAA 级 UE 项目,WebGL 在性能、兼容性和功能完整性上目前还无法胜任。像素流送将计算和渲染压力完全放在了服务端,用户端只需要一个能播放视频的现代浏览器即可,极大地降低了终端设备的门槛。
2. 环境准备:明确你的技术栈与工具版本
开始动手前,请确保你的环境符合以下要求。版本不一致是大多数问题的根源。
2.1 Unreal Engine 端
- UE 版本:本文基于UE 5.2编写,但核心流程适用于 4.27 及以上的大部分版本。请确保你的项目在该版本下能正常打包。
- 插件启用:在 UE 编辑器中,打开“编辑” -> “插件”,搜索并启用
Pixel Streaming插件。通常还需要启用相关的WebRTC插件,它们通常是像素流插件的依赖项。 - 项目设置:你的 UE 项目本身应该是一个可独立运行的应用程序(如第一人称或第三人称模板项目即可)。
- 操作系统:服务端推荐 Windows 10/11 或 Linux。Windows 对 DirectX 的支持更原生。注意:UE 编辑器本身无法直接作为像素流服务器,必须打包(Package)出独立程序。
2.2 信令服务器与前端
- Node.js:信令服务器需要 Node.js 环境。推荐安装LTS 版本(如 18.x, 20.x)。
- 信令服务器代码:UE 引擎自带了一套信令服务器示例。路径通常为:
[YourUEInstallation]/Engine/Source/Programs/PixelStreaming/WebServers。我们将使用这里的SignallingWebServer。 - 前端依赖:前端页面需要引入 UE 提供的 Pixel Streaming 库。这些库文件也包含在上述信令服务器目录中,无需额外 npm 安装。
- 现代浏览器:客户端需要 Chrome、Edge、Firefox 等支持 WebRTC 和现代视频编码的浏览器。
2.3 网络环境
- 关键要求:运行 UE 应用的服务端、运行信令服务器的机器、以及访问网页的客户端,三者必须在同一个局域网内,或者具有公网 IP 并能互相访问。这是 WebRTC 点对点连接建立的基础。在本地开发时,通常就是同一台电脑。
3. 第一步:从 UE 项目打包开始
我们的目标是生成一个可以接收网络命令、输出视频流的 UE 可执行文件。
3.1 启用像素流插件并打包
- 在 UE 编辑器中打开你的项目。
- 点击菜单栏的
编辑->插件。 - 在插件搜索框中输入
Pixel Streaming,勾选启用它,并重启编辑器。 - 点击
平台->Windows->打包项目,选择输出目录(例如D:\MyUEGame\Package)。等待打包完成,这会生成一个Windows文件夹,里面包含YourGame.exe等文件。
3.2 配置启动参数(关键步骤)直接运行YourGame.exe是不会开启像素流功能的。我们需要通过命令行参数来启动它。创建一个批处理文件(.bat)或直接使用命令行:
REM 文件路径:D:\MyUEGame\Package\Windows\run_with_pixelstreaming.bat @echo off cd /d %~dp0 start YourGame.exe -AudioMixer -PixelStreamingIP=127.0.0.1 -PixelStreamingPort=8888 -RenderOffScreen -ForceRes -ResX=1280 -ResY=720参数解释:
-PixelStreamingIP=127.0.0.1:指定信令服务器的 IP 地址。本地测试就用 127.0.0.1。-PixelStreamingPort=8888:指定信令服务器的端口,需与后续信令服务器配置一致。-RenderOffScreen:让 UE 应用在无头模式(没有可见窗口)下渲染。这对于服务器部署很重要。开发调试时可以先不加此参数,以便看到UE窗口。-ForceRes -ResX=1280 -ResY=720:强制指定渲染分辨率。流送的分辨率由此决定,并非前端页面大小。
运行这个批处理文件,你应该能看到 UE 应用启动(如果没加-RenderOffScreen,会看到一个窗口)。此时它正在等待信令服务器的连接。
4. 第二步:启动信令服务器
信令服务器是连接 UE 应用和前端的桥梁。我们使用 UE 自带的示例。
- 定位服务器文件:找到
[YourUEInstallation]/Engine/Source/Programs/PixelStreaming/WebServers/SignallingWebServer目录。 - 安装依赖:在该目录下打开命令行,运行:
这将会安装npm installexpress,ws(WebSocket) 等必要的 Node.js 模块。 - 配置服务器:查看目录下的
config.json或cirrus.js(不同版本可能不同)。我们需要关注监听端口。通常默认配置是监听 80 或 8080 端口。为了不与常用端口冲突,我们修改为 8888,与 UE 启动参数一致。- 找到配置文件(例如
cirrus.js)中类似listenPort: 80的地方,改为listenPort: 8888。 - 或者,可以通过命令行参数启动时指定端口。
- 找到配置文件(例如
- 启动服务器:在
SignallingWebServer目录下运行:
如果看到类似node cirrus.jsSignalling server listening on 0.0.0.0:8888的日志,说明信令服务器已成功启动。
此时,你的系统里应该运行着两个程序:
- UE 应用:在 127.0.0.1 的某个随机端口上,等待连接。
- 信令服务器:在 127.0.0.1:8888 上运行,等待前端和 UE 应用来“登记”。
5. 第三步:创建前端页面并建立基础连接
信令服务器目录下通常有一个public文件夹,里面包含了前端示例(如player.html)。我们可以基于它修改,或者自己创建一个。
5.1 基础 HTML 结构创建一个简单的index.html文件,放在信令服务器的public目录下,或者任何你能通过http://localhost:8888/访问到的地方。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>UE Pixel Streaming Demo</title> <style> #videoContainer { width: 1280px; height: 720px; border: 2px solid #333; position: relative; } #streamingVideo { width: 100%; height: 100%; object-fit: contain; /* 保持比例适应容器 */ } #controls { margin-top: 20px; } button { padding: 10px 20px; margin: 5px; font-size: 16px; } </style> </head> <body> <h1>UE 像素流交互演示</h1> <p>状态: <span id="status">正在初始化...</span></p> <!-- 视频流将显示在这个容器里 --> <div id="videoContainer"> <video id="streamingVideo" autoplay playsinline></video> </div> <!-- 控制按钮区域 --> <div id="controls"> <button id="btnJump">让角色跳跃</button> <button id="btnChangeColor">切换场景颜色</button> <input type="text" id="customMessage" placeholder="输入消息发送到UE"> <button id="btnSendMessage">发送自定义消息</button> </div> <!-- 引入 UE Pixel Streaming SDK --> <script src="/libs/UE.js"></script> <!-- 引入我们自己的逻辑脚本 --> <script src="app.js"></script> </body> </html>5.2 核心 JavaScript 逻辑 (app.js)这是实现连接和通信的核心。我们创建一个app.js文件。
// 文件:app.js let streamer = null; let config = null; document.addEventListener('DOMContentLoaded', function() { // 1. 初始化 Pixel Streaming 配置 config = { initialSettings: { AutoPlayVideo: true, AutoConnect: true, // 页面加载后自动连接 StartVideoMuted: false, WaitForStreamer: true, }, // 信令服务器地址 signallingServerUrl: `ws://${window.location.hostname}:8888`, // 自定义函数来处理来自 UE 的消息 onCustomMessage: handleMessageFromUE, }; // 2. 创建 PixelStreaming 实例 // `videoElementParent` 是视频容器元素的 ID streamer = new UE.PixelStreaming(config); streamer.addEventListener('playStream', () => { updateStatus('已连接,正在播放流'); }); streamer.addEventListener('disconnect', () => { updateStatus('连接断开'); }); streamer.addEventListener('error', (e) => { updateStatus('错误: ' + e.detail); console.error('Pixel Streaming Error:', e.detail); }); // 3. 为按钮绑定事件,发送指令到 UE document.getElementById('btnJump').addEventListener('click', () => { // 发送一个名为 "Jump" 的指令,不带参数 streamer.emitUIInteraction({ Command: 'Jump' }); console.log('Sent Jump command'); }); document.getElementById('btnChangeColor').addEventListener('click', () => { // 发送一个名为 "ChangeColor" 的指令,带一个颜色参数 let randomColor = `#${Math.floor(Math.random()*16777215).toString(16)}`; streamer.emitUIInteraction({ Command: 'ChangeColor', Color: randomColor }); console.log('Sent ChangeColor command:', randomColor); }); document.getElementById('btnSendMessage').addEventListener('click', () => { let message = document.getElementById('customMessage').value; if(message) { // 发送一个自定义结构的数据 streamer.emitUIInteraction({ Command: 'CustomEvent', UserMessage: message, Timestamp: Date.now() }); console.log('Sent custom message:', message); document.getElementById('customMessage').value = ''; } }); // 4. 尝试自动连接 connectToStreamer(); }); function connectToStreamer() { updateStatus('正在连接到信令服务器...'); // `videoElementId` 是页面中 video 元素的 ID streamer.connectToSignallingServer(config.signallingServerUrl, 'streamingVideo'); } function updateStatus(msg) { document.getElementById('status').textContent = msg; console.log('Status:', msg); } // 5. 处理从 UE 发来的消息(双向通信的另一半) function handleMessageFromUE(message) { console.log('Message from UE:', message); // 这里可以根据 message 的内容更新网页UI // 例如,如果 UE 发来 { "type": "scoreUpdate", "value": 100 } // 我们可以更新网页上的分数显示 if(message.type === 'scoreUpdate') { // 假设页面上有个 <span id="score">0</span> // document.getElementById('score').textContent = message.value; updateStatus(`UE 通知: 分数更新为 ${message.value}`); } }关键点解析:
UE.PixelStreaming:这是 SDK 暴露的核心类,管理整个连接生命周期。emitUIInteraction:这是前端向 UE 发送数据的主要方法。你可以传递任何可序列化为 JSON 的对象。onCustomMessage:这是一个回调函数配置项,用于接收从 UE 主动发往前端的消息,是实现“双向”通信的关键。connectToSignallingServer:该方法会通过 WebSocket 连接到我们启动的信令服务器(ws://localhost:8888),并开始协商 WebRTC 连接。
6. 第四步:在 UE 蓝图中接收并处理前端事件
前端发送了数据,UE 端必须能接收并做出反应。这需要在 UE 项目中编写蓝图或 C++ 逻辑。
6.1 创建蓝图接收器
- 在 UE 编辑器的内容浏览器中,右键 ->
蓝图类-> 选择Actor,命名为BP_PixelStreamingController。 - 打开这个蓝图,首先添加一个
Pixel Streaming组件。在组件面板点击“添加组件”,搜索Pixel,找到Pixel Streaming并添加。 - 在事件图表(Event Graph)中,我们需要监听来自前端的 UI 交互事件。
6.2 编写蓝图逻辑以下是关键节点的设置:
- 事件 BeginPlay:当游戏开始时,获取 Pixel Streaming 组件的引用,并绑定自定义事件。
- 绑定事件:使用
On Pixel Streaming UI Interaction节点。这个节点会在前端调用emitUIInteraction时被触发。 - 解析 JSON:该事件会传递一个
String类型的参数,即前端发送的 JSON 字符串。我们需要使用Parse JSON节点将其转换为蓝图可以操作的结构(JsonObject)。 - 判断与执行:从
JsonObject中读取Command等字段,根据不同的命令执行不同的游戏逻辑。
由于无法直接展示蓝图节点图,这里用伪代码描述其逻辑,你可以在蓝图中找到对应的节点:
Event BeginPlay -> Get a reference to the PixelStreaming component (self) -> Bind Event to OnPixelStreamingUIInteraction (Custom Event) Custom Event OnPixelStreamingUIInteraction (String Message) -> Parse JSON from the 'Message' string, output a JsonObject -> Get String Field from JsonObject, Field Name = "Command" -> Switch on (Command String): Case "Jump": // 找到你控制的角色,调用其跳跃函数 // 例如:Get Player Character -> Character Movement -> Jump Case "ChangeColor": // 从 JsonObject 获取 "Color" 字段 // 将颜色字符串转换为 LinearColor // 应用到某个场景物体或后处理体积上 Case "CustomEvent": // 从 JsonObject 获取 "UserMessage" 字段 // 在屏幕上显示或进行其他逻辑处理 // 同时,可以调用“发送消息到前端”的函数6.3 将蓝图放入场景将创建好的BP_PixelStreamingController拖放到你的游戏场景中。确保它在游戏开始时被实例化。
6.4 从 UE 发送消息到前端(实现完整双向)前端可以定义onCustomMessage回调,UE 端也需要能主动发送消息。在蓝图中,你可以这样做:
- 在需要发送消息的地方(例如,角色得分时、游戏状态改变时),获取
Pixel Streaming组件。 - 调用组件上的
Send Player Message或Emit UI Response节点(不同版本名称可能略有不同)。 - 该节点需要一个
String参数,你可以构建一个 JSON 字符串,例如:{"type": "scoreUpdate", "value": 100}。
这样,前端app.js中的handleMessageFromUE函数就会收到这个消息并处理。
7. 运行与验证:看到画面,实现交互
现在,让我们启动整个系统并验证。
启动顺序很重要:
- 启动信令服务器:在
SignallingWebServer目录下,运行node cirrus.js。确保它监听在 8888 端口。 - 启动 UE 应用:运行之前创建的批处理文件
run_with_pixelstreaming.bat。观察日志,看它是否在尝试连接127.0.0.1:8888。 - 打开前端页面:在浏览器中访问
http://localhost:8888/index.html(如果你的信令服务器正确配置了静态文件服务,index.html在public目录下)。
预期结果:
- 浏览器页面状态显示“正在连接...”,然后变为“已连接,正在播放流”。
- 网页中的
<video>元素会显示出 UE 应用程序实时渲染的画面,延迟通常在几十到几百毫秒,取决于网络和编码设置。 - 点击网页上的“让角色跳跃”按钮,UE 应用中的角色应执行跳跃动作。
- 点击“切换场景颜色”,UE 场景的后处理或某个物体的颜色应发生变化。
- 在输入框输入文字并点击发送,UE 端应能收到并打印或显示该消息。
- (可选)在 UE 端触发一个发送消息的蓝图(如角色碰到某个触发器),前端页面的状态或某个元素应能更新。
如果以上步骤都成功,恭喜你,你已经搭建了一个完整的、具备双向通信能力的 UE 像素流送系统!
8. 常见问题与排查思路(避坑指南)
在实际部署中,你几乎一定会遇到下面这些问题。这里提供一个快速排查清单。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 前端页面一直显示“正在初始化”或“连接中” | 1. 信令服务器未启动或端口错误。 2. UE 应用未启动或启动参数错误。 3. 前端 JS 中信令服务器地址配置错误。 | 1. 检查node cirrus.js是否运行,端口是否被占用。2. 检查 UE 应用命令行窗口是否有错误,确认 -PixelStreamingIP和-Port参数正确。3. 浏览器 F12 打开开发者工具 -> “网络”(Network) 标签,查看 WebSocket (ws://) 连接是否成功建立。 | 1. 更换信令服务器端口,确保防火墙开放。 2. 核对 UE 启动参数,本地测试确保 IP 为 127.0.0.1。3. 修改 app.js中的signallingServerUrl,确保是ws://[服务器IP]:[端口]。 |
| 能看到画面,但无法操作(鼠标键盘无响应) | 1. 前端播放器未获取输入焦点。 2. WebRTC 数据通道建立失败。 3. UE 端 Pixel Streaming 组件未正确添加或绑定。 | 1. 点击一下视频区域,看页面焦点是否在视频元素上。 2. 浏览器控制台查看是否有 WebRTC 相关错误。 3. 在 UE 编辑器中检查 BP_PixelStreamingController是否存在于场景,OnPixelStreamingUIInteraction事件是否被绑定。 | 1. 确保视频元素可点击,或在前端代码中手动触发焦点。 2. 检查网络环境,WebRTC 需要 UDP 端口可达,公司内网防火墙可能阻止。 3. 重新打包 UE 项目并部署。 |
| 前端发送消息,UE 端无反应 | 1. 前端发送的数据格式不是有效的 JSON 字符串。 2. UE 蓝图解析 JSON 失败。 3. 命令字符串不匹配(大小写敏感)。 | 1. 在浏览器控制台打印streamer.emitUIInteraction发送的对象。2. 在 UE 蓝图的 OnPixelStreamingUIInteraction事件后添加Print String节点,看是否触发。3. 仔细核对蓝图 Switch on String节点的 Case 值与前端发送的Command字段是否完全一致。 | 1. 使用JSON.stringify()确保发送的是字符串。2. 在蓝图中先不解析,直接打印收到的原始字符串,确认数据已送达。 3. 统一前后端的命令字命名规范。 |
| 延迟很高或画面卡顿 | 1. 网络带宽不足或抖动。 2. 编码参数(码率、分辨率)设置过高。 3. 服务器性能不足(GPU 编码压力大)。 | 1. 检查服务器和客户端的网络状况。 2. 调整 UE 启动参数,降低 -ResX和-ResY。3. 在 UE 的 Pixel Streaming插件设置中,调整编码器码率(Bitrate)。 | 1. 尽可能使用有线网络,保证服务器上行带宽。 2. 从 720P 开始测试,逐步调高。 3. 在 DefaultEngine.ini中配置[PixelStreaming]段的EncoderRateControl和TargetBitrate。 |
| 跨公网无法连接 | WebRTC 需要复杂的 NAT 穿透和 ICE 协商,在复杂网络环境下可能失败。 | 使用浏览器 WebRTC 内部日志(chrome://webrtc-internals)分析连接状态。 | 1. 使用TURN 服务器进行中继转发,这是解决对称型 NAT 等复杂网络问题的标准方案。可以部署 Coturn 等开源 TURN 服务器,并在信令服务器和前端配置中指定其地址和凭证。 |
9. 最佳实践与进阶建议
当你跑通基础流程后,以下建议可以帮助你将项目推向生产环境。
9.1 安全性与访问控制
- 信令服务器加固:示例服务器仅为演示。生产环境需要添加身份验证(如 Token)、防止 DDoS、设置 HTTPS/WSS。
- UE 应用沙盒:确保流送的 UE 应用运行在受限环境中,避免用户通过前端指令执行危险系统调用。
- 输入验证:在 UE 蓝图中,严格校验前端传入的所有参数,防止注入攻击。
9.2 性能与伸缩
- 自适应码率:UE 像素流支持根据网络状况动态调整码率。确保在插件设置中启用相关选项。
- 多实例与负载均衡:一个信令服务器可以协调多个 UE 应用实例。你需要一个匹配器(Matchmaker)服务,根据用户请求分配空闲的 UE 实例。UE 提供了相关示例代码。
- GPU 与编码:使用支持 NVENC (NVIDIA) 或 AMF (AMD) 硬编码的 GPU,能极大降低 CPU 负载,支持更多并发流。
9.3 前端体验优化
- 自定义 UI:SDK 允许你完全自定义播放器 UI。隐藏默认控件,用 HTML/CSS/JS 构建与你的网页风格一致的交互界面。
- 触摸与手势:移动端适配需要处理触摸事件,并将其转换为鼠标/键盘事件或自定义指令发送给 UE。
- 连接状态管理:完善前端重连逻辑、断线提示、加载动画,提升用户体验。
9.4 配置管理
- 不要将 IP、端口等配置硬编码在代码或批处理文件中。使用配置文件(如
.ini文件)或环境变量来管理不同环境(开发、测试、生产)的配置。 - 在
DefaultEngine.ini中配置像素流参数,比命令行参数更便于管理。
; 文件路径:YourProject/Config/DefaultEngine.ini [/Script/PixelStreaming] StreamerPort=8888 StreamerIP=127.0.0.1 EncoderRateControl=CBR TargetBitrate=5000000 ; 5 Mbps9.5 监控与日志
- 为信令服务器添加详细的访问日志和错误日志。
- 监控 UE 应用进程的资源占用(GPU 内存、编码帧率)。
- 在前端收集播放器的关键指标,如延迟、卡顿率、分辨率,并上报到你的监控系统。
从打通基础连接到考虑生产级部署,UE 像素流送提供了一个强大而灵活的框架。它本质上将 UE 应用变成了一个可通过网络 API 调用的“渲染微服务”。无论是用于产品云端演示、沉浸式 Web 展厅、还是复杂的仿真培训系统,掌握其核心原理和这套双向通信机制,都能让你在 Web 3D 交互领域拥有独特的解决方案。建议你将本文中的示例代码作为起点,根据实际项目需求,深入探索官方文档中关于高级配置、自定义信令、WebRTC 调优等部分,逐步构建起稳定可靠的流送服务。