最近在做一个数字孪生项目需要将虚幻引擎UE中构建的复杂3D场景实时推送到网页端并让用户在浏览器里就能与场景进行交互。调研了一圈发现UE自带的“像素流送”Pixel Streaming技术是解决这个需求的绝佳方案。它不仅能将高质量的3D渲染画面以视频流的形式送到浏览器还内置了一套完整的双向通信机制让网页前端可以像操作本地应用一样控制UE程序。本文将手把手带你完成从零搭建UE像素流送环境到前端网页加载UE程序并最终实现稳定双向通信的全过程。无论你是想为你的UE项目增加Web端展示能力还是想构建一个基于浏览器的3D应用这套方案都能直接复用。文章会包含详细的配置步骤、完整的代码示例以及部署过程中必然会遇到的“坑”和解决方案。1. 背景与核心概念为什么需要像素流送在深入配置之前我们有必要先搞清楚像素流送到底是什么以及它解决了什么核心痛点。1.1 什么是像素流送像素流送Pixel Streaming是虚幻引擎提供的一项核心技术。它的工作原理可以简单理解为“云端渲染终端显示”服务端UE程序在一台性能强大的机器可以是本地PC也可以是云服务器上运行一个特殊的UE应用程序。这个程序会像往常一样渲染3D场景但它不将画面输出到本地显示器而是通过编码器如H.264/AV1将每一帧画面压缩成视频流。网络传输编码后的视频流通过WebRTCWeb实时通信协议进行传输。WebRTC天生为低延迟、点对点的音视频传输设计非常适合交互式应用。客户端网页用户在浏览器中访问一个特定的网页。这个网页通过JavaScript连接到信号服务器Signalling Server获取视频流并在一个HTML5的video或canvas元素中播放。同时网页上捕获的鼠标、键盘、触摸等事件会通过同一套WebRTC数据通道Data Channel回传给UE程序。1.2 它解决了什么问题硬件门槛用户无需拥有高性能显卡的电脑只需要一个能播放视频的现代浏览器Chrome, Edge, Firefox等即可体验高质量的UE渲染画面。复杂的计算和渲染全部在服务端完成。跨平台与便捷分发无需为Windows、macOS、Linux分别打包和分发客户端。一个URL链接即可在任何设备上打开包括手机和平板。保护知识产权你的UE项目源码、资产、逻辑都运行在服务端用户端只能看到视频流有效防止了逆向工程和资源盗用。实时交互不同于简单的录屏或点播双向通信通道保证了用户操作如点击物体、移动视角能实时反馈到UE程序中并获得画面更新体验接近原生应用。1.3 核心组件一个完整的像素流送系统包含三个核心部分信令服务器Signalling Server负责协调UE应用程序信令发送者和网页客户端信令接收者之间的连接。它不传输视频数据只传递建立WebRTC连接所需的信令信息SDP/ICE候选者。Epic官方提供了基于Node.js的实现。像素流送插件Pixel Streaming Plugin集成在UE应用程序中负责捕获渲染画面、编码、并通过信令服务器与网页客户端建立WebRTC连接。前端SDKPixel Streaming Frontend SDK一组JavaScript库封装了连接信令服务器、接收视频流、发送输入事件、处理通信等所有前端逻辑。官方提供了可直接使用的示例前端。理解了这些我们就可以开始动手搭建了。2. 环境准备与版本说明工欲善其事必先利其器。以下是本次实践所需的环境和版本。请注意UE版本和像素流送插件的配置方式在不同版本间可能有差异本文以UE 5.3为例其他版本请参考官方文档调整。2.1 基础软件环境操作系统Windows 10/11 64位 或 Linux本文以Windows为例进行演示。虚幻引擎UE 5.3.0建议通过Epic Games Launcher安装。确保安装时包含了对应平台的编译工具如Windows平台下的Visual Studio 2022。Node.jsv18.x LTS 或更高版本。用于运行信令服务器。从 Node.js官网 下载安装。Python3.7。部分UE构建脚本需要。通常安装Visual Studio时会附带也可单独安装。现代浏览器Google Chrome 或 Microsoft EdgeChromium内核的最新稳定版。2.2 获取像素流送组件像素流送插件和前端SDK通常随UE引擎一同安装。插件位置[你的UE安装目录]\Engine\Plugins\Media\PixelStreaming\。例如C:\Program Files\Epic Games\UE_5.3\Engine\Plugins\Media\PixelStreaming\。前端SDK位置在插件目录下的WebInterface文件夹中。这是我们前端页面的基础。信令服务器在插件目录下的SignallingWebServer文件夹中。这是一个Node.js项目。2.3 示例项目为了演示我们创建一个最简单的UE第一人称模板项目命名为PixelStreamingDemo。3. 核心配置与原理拆解配置像素流送主要围绕三个部分启用插件、配置UE项目、配置信令服务器。3.1 在UE项目中启用像素流送插件打开你的PixelStreamingDemo.uproject文件。点击菜单栏的编辑(Edit)-插件(Plugins)。在插件搜索框中输入Pixel Streaming。找到Pixel Streaming插件确保其复选框被勾选启用。如果提示重启编辑器请重启。3.2 项目配置DefaultEngine.iniUE的像素流送行为主要通过配置文件控制。我们需要修改项目配置文件Config/DefaultEngine.ini。; Config/DefaultEngine.ini [/Script/Engine.GameEngine] NetDriverDefinitions(DefNameGameNetDriver,DriverClassNameOnlineSubsystemUtils.IpNetDriver,DriverClassNameFallbackOnlineSubsystemUtils.IpNetDriver) [/Script/PixelStreaming.PixelStreamingSettings] ; 允许从任何网页前端连接生产环境应限制为特定域名 SignallingDomain* ; 流送使用的编码器通常为软件编码器 EncoderAMF_H264 ; 前端控制UE的默认方式这里设为鼠标和键盘始终由前端控制 DefaultToMouseControlTrue ; 允许前端通过数据通道发送自定义指令 AllowPixelStreamingCommandsTrue ; 配置WebRTC相关参数 [/Script/PixelStreaming.PixelStreamingStreamerComponent] ; 设置流送分辨率可根据服务器性能调整 StreamFPS60 ; 设置码率比特率单位bps MaxBitrate10000000 ; 设置关键帧间隔单位秒 MaxFPS60 ; 启用硬件加速如果显卡支持 UseGPUDirectTrue ; 配置输入 [/Script/PixelStreamingInput] ; 允许前端发送键盘事件 RouteToGamepadFalse ; 允许前端发送鼠标事件 RouteToTouchFalse ; 允许前端发送触摸事件 RouteToMouseTrue RouteToKeyboardTrue关键参数解释SignallingDomain设置为*表示允许任何来源的网页连接便于开发测试。在生产环境中务必将其改为你的前端网页域名如https://yourdomain.com以防止恶意连接。Encoder编码器选择。AMF_H264适用于AMD显卡NVENC适用于NVIDIA显卡X264是软件编码CPU编码兼容性好但性能开销大。DefaultToMouseControlTrue这个设置非常重要它意味着网页前端一连接就会自动获得鼠标和键盘的控制权UE编辑器或打包后的程序窗口本身将不再响应鼠标键盘输入。这对于网页交互是必须的。AllowPixelStreamingCommandsTrue开启自定义指令通道这是我们实现双向通信中“前端到UE”的关键。3.3 信令服务器配置与启动信令服务器是连接UE应用和网页的桥梁。打开命令行进入信令服务器目录[PixelStreaming插件路径]\SignallingWebServer\。该目录下应有一个package.json文件。首次运行需要安装依赖npm install启动信令服务器。最简单的方式是使用其默认配置node cirrus.js启动后你会看到类似Signalling server listening on 0.0.0.0:80的输出表示服务器已在80端口运行。常用启动参数node cirrus.js --HttpPort8080 --StreamerPort8888--HttpPort: 网页前端访问的端口。--StreamerPort: UE应用程序连接信令服务器时使用的端口。为了测试方便我们可以创建一个启动脚本run_server.batecho off cd /d [你的完整路径]\Engine\Plugins\Media\PixelStreaming\SignallingWebServer node cirrus.js --HttpPort80 pause4. 完整实战打包、运行与前端集成现在我们将一个UE项目打包并通过自定义的前端页面加载它。4.1 打包UE项目Windows平台在UE编辑器中点击平台(Platforms)-Windows-打包项目(Package Project)...。选择输出目录例如项目目录\Packaged\Windows。等待打包完成。这个过程会编译所有内容并生成一个可执行的.exe文件。4.2 以像素流送模式运行UE程序打包后的程序不能直接双击运行需要通过命令行参数告诉它连接信令服务器。在打包输出目录如Windows文件夹下创建一个批处理文件run_pixel_streaming.bat。编辑其内容echo off PixelStreamingDemo.exe -AudioMixer -PixelStreamingURLws://localhost:80 pausePixelStreamingDemo.exe是你的打包程序名。-PixelStreamingURLws://localhost:80这是最重要的参数指定了信令服务器的WebSocket地址。端口需与信令服务器启动的--StreamerPort一致默认80。确保信令服务器node cirrus.js正在运行。双击运行run_pixel_streaming.bat。程序启动后你应该能在命令行窗口中看到类似LogPixelStreaming: Connected to signalling server的连接成功日志。此时UE程序会等待网页前端连接。4.3 创建自定义前端页面我们不完全使用官方SDK的默认页面而是基于它创建一个更简洁、可控的页面。在你的项目目录下创建一个新的文件夹例如WebFrontend。将官方前端SDK中的关键文件复制过来从[PixelStreaming插件路径]\WebInterface\复制css、scripts文件夹和favicon.ico。复制[PixelStreaming插件路径]\WebInterface\index.html作为参考但我们创建自己的index.html。创建自定义的index.html!DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleUE Pixel Streaming Demo/title !-- 引入官方样式 -- link relstylesheet hrefcss/pixelstreaming.css style body { margin: 0; padding: 20px; font-family: sans-serif; background-color: #f0f0f0; } #container { display: flex; flex-direction: column; align-items: center; max-width: 1200px; margin: 0 auto; } #streamingVideo { width: 1024px; height: 576px; background-color: #000; border: 2px solid #333; border-radius: 4px; } #controls { margin-top: 20px; padding: 15px; background: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); width: 100%; max-width: 1024px; } button { padding: 10px 20px; margin: 5px; border: none; border-radius: 4px; background-color: #0074d9; color: white; cursor: pointer; font-size: 16px; } button:hover { background-color: #0056a3; } #status { margin-top: 10px; padding: 10px; background-color: #e8f4fd; border-radius: 4px; font-family: monospace; } /style /head body div idcontainer h1虚幻引擎像素流送演示/h1 p状态: span idconnectionStatus正在初始化.../span/p !-- 视频流将渲染在这个div内 -- div idstreamingVideo/div div idcontrols h3控制面板/h3 button idbtnChangeColor改变方块颜色/button button idbtnSpawnCube生成一个立方体/button button idbtnGetPlayerLocation获取玩家位置/button div idstatus/div /div /div !-- 引入官方Pixel Streaming前端库 -- script srcscripts/pixelstreaming.js/script script srcscripts/aframe-components.js/script !-- 如果不需要AR/VR功能可不引入 -- script // 你的自定义JavaScript代码将在这里 /script /body /html创建核心的JavaScript文件app.js或在HTML的script标签内编写// app.js document.addEventListener(DOMContentLoaded, function() { // 1. 初始化Pixel Streaming配置 const pixelStreamingConfig { initialSettings: { AutoPlayVideo: true, AutoConnect: true, // 页面加载后自动连接 StartVideoMuted: false, WaitForStreamer: false, HoveringMouse: false, MatchViewportRes: false // 我们固定了视频流div大小 }, // 信令服务器地址 signallingServerAddress: ws://localhost:80 }; // 2. 创建Pixel Streaming实例 // 注意application 是官方前端库暴露的全局对象 const streamingVideoEl document.getElementById(streamingVideo); const pixelStreaming new application.PixelStreamingApplication(pixelStreamingConfig, streamingVideoEl); // 3. 注册事件监听器 const statusEl document.getElementById(connectionStatus); const statusDetailEl document.getElementById(status); pixelStreaming.addEventListener(playStream, () { statusEl.textContent 已连接正在播放流; statusEl.style.color green; statusDetailEl.innerHTML p✅ 成功连接到UE程序视频流已开始。/p; }); pixelStreaming.addEventListener(streamerDisconnected, () { statusEl.textContent 连接断开; statusEl.style.color red; statusDetailEl.innerHTML p❌ 与UE程序的连接已断开。/p; }); // 4. 建立连接 pixelStreaming.init(); pixelStreaming.connect(); // 5. 实现双向通信 - 前端发送指令到UE // 获取按钮和UE通信对象 const btnChangeColor document.getElementById(btnChangeColor); const btnSpawnCube document.getElementById(btnSpawnCube); const btnGetPlayerLocation document.getElementById(btnGetPlayerLocation); // 确保 pixelStreaming 对象已准备好其 stream 属性数据通道 function waitForDataChannel() { return new Promise((resolve) { if (pixelStreaming.stream pixelStreaming.stream.dataChannel) { resolve(pixelStreaming.stream); } else { const checkInterval setInterval(() { if (pixelStreaming.stream pixelStreaming.stream.dataChannel) { clearInterval(checkInterval); resolve(pixelStreaming.stream); } }, 500); } }); } // 发送指令到UE的通用函数 async function sendCommandToUE(command, data {}) { try { const stream await waitForDataChannel(); const message { type: command, command: command, ...data }; stream.dataChannel.send(JSON.stringify(message)); console.log(发送指令到UE:, message); statusDetailEl.innerHTML p 发送指令: ${command}/p; } catch (error) { console.error(发送指令失败:, error); statusDetailEl.innerHTML p❌ 发送指令失败: ${error.message}/p; } } // 绑定按钮事件 btnChangeColor.addEventListener(click, () { sendCommandToUE(ChangeColor, { color: #${Math.floor(Math.random()*16777215).toString(16)} }); }); btnSpawnCube.addEventListener(click, () { sendCommandToUE(SpawnCube, { location: { x: 0, y: 0, z: 200 } }); }); btnGetPlayerLocation.addEventListener(click, async () { // 这个指令期望UE返回一个响应 sendCommandToUE(GetPlayerLocation); }); // 6. 实现双向通信 - 接收来自UE的响应 // 监听来自UE数据通道的消息 pixelStreaming.stream?.addEventListener(datachannel, (event) { const dataChannel event.channel; dataChannel.addEventListener(message, (event) { try { const message JSON.parse(event.data); console.log(收到来自UE的消息:, message); if (message.type response) { statusDetailEl.innerHTML p 收到UE响应 [${message.command}]: ${JSON.stringify(message.data)}/p; } else if (message.type event) { // 处理UE主动推送的事件如玩家位置更新 statusDetailEl.innerHTML p UE事件 [${message.event}]: ${JSON.stringify(message.data)}/p; } } catch (e) { console.log(收到非JSON消息:, event.data); } }); }); });4.4 在UE中接收并处理前端指令前端发送了指令UE端必须有对应的逻辑来处理。我们需要在UE项目中添加一个蓝图或C类来监听数据通道。这里以蓝图为例展示如何接收ChangeColor指令在UE编辑器中打开蓝图-关卡蓝图或创建一个新的Actor蓝图。我们需要监听像素流送的数据通道消息。Epic提供了一个蓝图函数库Pixel Streaming Input。在事件图表中添加以下节点事件 BeginPlay开始播放时执行。Pixel Streaming On Pixel Streaming Message Received这是一个自定义事件当收到前端通过数据通道发来的消息时会触发。它有一个String类型的Message输出引脚。解析JSON消息从Message引脚拉出引线搜索Parse JSON String节点。我们需要先创建一个结构体来匹配前端发送的消息格式。在内容浏览器中右键 -蓝图-结构体命名为FS_FrontendCommand。在结构体中添加变量type(String),command(String),color(String, 可选),location(Vector, 可选) 等根据你的指令需求定义。回到关卡蓝图在Parse JSON String节点上将Struct类型选择为你刚创建的FS_FrontendCommand。根据指令执行操作从Parse JSON String的Result引脚拉出引线添加一个Branch节点。在Condition上比较Result.command是否等于ChangeColor。如果为真则执行改变颜色的逻辑。例如假设场景中有一个名为TargetCube的立方体静态网格ActorGet Actor by Name-TargetCube。Get Static Mesh Component。Create Dynamic Material Instance(基于其材质)。Set Vector Parameter Value将参数名如BaseColor设置为一个从Result.color字符串转换而来的颜色值需要将#RRGGBB转换为线性颜色。类似地可以处理SpawnCube指令使用Spawn Actor from Class节点在指定位置生成一个立方体。对于GetPlayerLocation指令UE端需要获取玩家控制器或角色的位置然后将数据发送回前端。UE端向前端发送消息 这是实现“双向”通信的另一半。UE蓝图提供了Send Pixel Streaming Response节点。在处理完GetPlayerLocation指令后构建一个返回消息例如一个包含type,command,data的JSON字符串。使用Send Pixel Streaming Response节点将消息字符串发送出去。前端JS中的dataChannel监听器就会收到这个消息。4.5 运行与验证启动顺序 a. 启动信令服务器node cirrus.js在80端口。 b. 以像素流送模式启动打包的UE程序运行run_pixel_streaming.bat。 c. 在浏览器中访问你的前端页面。如果你将前端文件放在了信令服务器的public文件夹下官方SDK结构可以直接访问http://localhost。如果像我们这样自定义位置你可能需要一个简单的HTTP服务器。在WebFrontend目录下运行npx serve .或python -m http.server 8080然后访问http://localhost:8080。预期结果浏览器页面显示“正在初始化...”然后变为“已连接正在播放流”视频区域开始显示UE程序的实时画面。你可以用鼠标和键盘在网页上直接控制UE中的角色移动和视角。点击“改变方块颜色”按钮UE场景中的目标方块颜色应随机改变。点击“生成一个立方体”UE场景中应在指定位置生成一个新的立方体。点击“获取玩家位置”下方的状态栏应显示从UE返回的玩家坐标信息。5. 常见问题与排查思路在部署像素流送时你几乎一定会遇到下面这些问题。这里提供一个排查清单。问题现象可能原因排查步骤与解决方案前端页面一直显示“等待流...”或“连接中”1. 信令服务器未启动或端口错误。2. UE程序未启动或未以像素流送模式启动。3. 防火墙/网络策略阻止了WebSocket连接。1. 检查信令服务器命令行确认无报错且监听在正确端口如:80。2. 检查UE程序命令行窗口确认有Connected to signalling server日志。3. 确认前端JS中signallingServerAddress的地址和端口与信令服务器一致ws://localhost:80。4. 暂时关闭防火墙或添加出入站规则允许相关端口的通信。能连接但视频黑屏或卡住1. 编码器不兼容或性能不足。2. 网络带宽或延迟过高。3. UE程序渲染分辨率或帧率设置不当。1. 在DefaultEngine.ini中尝试更换编码器如X264软件编码。2. 降低DefaultEngine.ini中的MaxBitrate如改为2000000和StreamFPS如改为30。3. 检查服务器GPU驱动是否为最新并确保有足够显存。鼠标键盘无法控制UE1.DefaultToMouseControl未设置为True。2. 前端页面未正确获取焦点。3. UE程序运行在后台或被其他窗口遮挡。1. 确认DefaultEngine.ini中[/Script/PixelStreaming.PixelStreamingSettings]下的DefaultToMouseControlTrue。2. 点击前端页面的视频区域确保其获得焦点。官方前端页面通常有一个“点击激活输入”的提示。3. 将UE程序窗口调到前台。自定义指令发送后UE无反应1. UE端未正确解析消息。2. 数据通道未成功建立或消息格式错误。3. 蓝图事件未绑定或逻辑有误。1. 在前端浏览器按F12打开开发者工具查看Console和Network-WebSockets标签确认消息是否成功发送。2. 在UE编辑器的输出日志中查看是否有收到消息的日志需要启用PixelStreaming插件的详细日志。3. 检查蓝图中的On Pixel Streaming Message Received事件是否被正确触发Parse JSON String节点的结构体是否匹配前端发送的JSON格式。在公网/局域网其他机器无法访问1. 信令服务器绑定在127.0.0.1。2. 路由器/防火墙未做端口映射。3. UE程序或前端配置中使用了localhost。1. 启动信令服务器时使用--publicIp你的公网IP或局域网IP参数。2. 前端JS中的signallingServerAddress需改为ws://服务器IP:端口。3. 运行UE程序的批处理文件中的-PixelStreamingURL也要改为ws://服务器IP:端口。4. 在云服务器上确保安全组开放了信令服务器端口如80、8080和UE程序可能用到的其他端口如被STUN/TRUN服务器使用。错误Unable to bind to port 8080端口被系统其他进程如IIS, Apache, Skype占用。1. 使用命令 netstat -ano6. 最佳实践与工程建议将像素流送用于实际项目时以下几点能帮你构建更稳定、安全、易维护的系统。6.1 安全与权限限制信令域名在生产环境DefaultEngine.ini中务必将SignallingDomain设置为你的前端域名防止任意网站连接你的UE程序。身份验证官方信令服务器支持基本的HTTP身份验证。你可以在启动时通过--key和--cert参数使用HTTPS并在前端连接时验证Token。指令白名单在UE端对接收到的command进行校验只执行预定义的安全指令防止恶意指令注入。6.2 性能优化编码器选择优先使用硬件编码器NVENC或AMF。如果质量不佳或出现兼容性问题再考虑X264软件编码CPU占用高。分辨率与码率根据目标用户网络状况调整。移动端可考虑720p30fps码率1-2 Mbps桌面端可考虑1080p60fps码率5-10 Mbps。在DefaultEngine.ini中配置。UE程序优化对流送的程序进行性能剖析减少Draw Call使用LOD关闭不必要的后期处理因为任何渲染负担都会直接增加编码器的压力。使用SFU可选对于多用户同时观看同一流如直播、演示的场景可以考虑使用选择性转发单元SFU避免服务器为每个用户单独编码一次。Epic的像素流送参考架构中包含了SFU组件。6.3 通信协议设计定义清晰的协议为前后端通信设计一个简单的JSON协议。例如// 前端 - UE { type: command, id: 123, command: interact, params: { objectId: cube_01, action: rotate } } // UE - 前端 { type: response, id: 123, success: true, data: { newRotation: 45 } } { type: event, event: playerMoved, data: { x: 100, y: 200, z: 300 } }添加请求ID对于需要响应的指令前端生成唯一IDUE响应时带回相同ID便于前端匹配请求和响应。错误处理UE端处理指令时发生错误应通过响应消息将错误信息传回前端方便调试。6.4 部署与运维使用进程管理不要直接通过命令行后台运行信令服务器和UE程序。使用PM2(Node.js) 或系统服务来管理进程实现崩溃自动重启、日志轮转。日志记录启用像素流送插件的详细日志在UE命令行加-LogCmdsPixelStreaming Verbose并妥善保存日志文件便于排查问题。健康检查可以编写一个简单的脚本定期检查信令服务器的WebSocket端口是否可连接UE程序进程是否存活。资源监控监控服务器的GPU显存、编码器负载、网络带宽和CPU使用率确保服务稳定。6.5 前端体验增强加载与重连在前端实现优雅的连接状态提示连接中、重连、断开。当连接断开时可以自动尝试重连。自适应布局视频流区域的大小可以根据前端页面布局动态调整并通过API通知UE程序调整渲染分辨率SendEncoderSettings。自定义UI完全接管官方前端SDK的UI将其与你网页应用的风格融为一体提供更专业的用户体验。通过以上步骤你不仅能够搭建一个可用的UE像素流送演示更能掌握将其产品化所需的核心配置、问题排查和优化思路。这套技术为在Web端交付高保真、可交互的3D体验打开了大门从产品演示、数字孪生到云游戏都有广阔的应用空间。