数字孪生工程中前端使用webrtc对接UE像素流技术介绍

  1. 像素流链接流程
  1. 三个主要特点

实时音视频通信:WebRTC提供了一套API,使得开发者可以通过简单的JavaScript代码实现浏览器之间的实时音视频通信。

数据传输:除了音视频,WebRTC还可以传输任意数据,这使得它非常适合用于文件共享、屏幕共享等场景。

P2P通信:WebRTC采用点对点(P2P)架构,数据直接在浏览器之间传输,减少了服务器的负载和延迟。

浏览器支持率是基本上所有的浏览器都支持。

网络协议:

HTTP/HTTPS:用于信令和初始化通信,处理与服务器的交互

WebSocket:可以用于信令传输,提供全双工通信

SDP(Session Description Protocol):用于描述媒体会话信息,如媒体类型、编解码器等

NAT穿越协议

ICE(Interactive Connectivity Establishment):用于解决NAT穿越问题,通过收集和交换候选地址来建立连接。

STUN(Session Traversal Utilities for NAT):允许位于NAT后的客户端发现自己的公网地址。

TURN(Traversal Using Relays around NAT):当直接连接失败时,提供中继服务。附图

  • 了解一下UE

UE是一个虚幻引擎,

1. 多关卡多玩家同步用户状态数据

在虚幻引擎(UE)中,多关卡多玩家同步用户状态数据主要通过网络复制(Replication)机制实现。以下是关键点:

属性同步:服务器将具有网络复制功能的Actor的属性同步到客户端。客户端接收到这些属性后,通过插值计算平滑过渡效果,避免卡顿。

RPC(Remote Procedure Calls):用于在服务器和客户端之间调用函数。RPC可以是可靠的(reliable)或不可靠的(unreliable),具体取决于是否需要保证消息按序送达。

同步频率:UE会按照尽可能快的速度发送同步数据。如果客户端性能非常好,帧数很高,那么一帧会产生很多移动RPC。理论上,如果没有丢包,即使服务器帧率很低,服务器也会按照客户端发来的数据逐个模拟,最后两端结果相同,仍然是流畅的。

延迟补偿:服务器收到其他客户端的射击消息时,将本地的所有角色回滚到“当前时间 - 网络延迟时间”时的位置再进行消息的处理和计算,以减少延迟带来的影响。

场景搭建

原生引擎为例,场景搭建涉及GIS数据和BIM数据的导入与处理。

GIS数据:

数据来源:采用CesiumLab发布的WGS84坐标系的GIS数据服务。

数据处理:下载后的数据需要在ArcGIS中拼接、裁剪,然后用CesiumLab进行切片,最后通过Tomcat或Nginx发布地图服务。

BIM数据:

导入问题:BIM数据导入时可能会遇到重新建模、修正、优化面数等问题。不同建模软件导入导出数据处理不同,导致物理属性和空间属性的丢失。

优化处理:为了追求效果,坝址区L3级进行匀色或一些优化处理。通常借助插件设置基点,进行坐标转换。

模型摆放:模型摆放时,由于模型不标准,各种时期的模型都有,汇集到场景时需要人工修正和干预。一1. 多关卡多玩家同步用户状态数据

在虚幻引擎(UE)中,多关卡多玩家同步用户状态数据主要通过网络复制(Replication)机制实现。以下是关键点:

属性同步:服务器将具有网络复制功能的Actor的属性同步到客户端。客户端接收到这些属性后,通过插值计算平滑过渡效果,避免卡顿。

RPC(Remote Procedure Calls):用于在服务器和客户端之间调用函数。RPC可以是可靠的(reliable)或不可靠的(unreliable),具体取决于是否需要保证消息按序送达。

同步频率:UE会按照尽可能快的速度发送同步数据。如果客户端性能非常好,帧数很高,那么一帧会产生很多移动RPC。理论上,如果没有丢包,即使服务器帧率很低,服务器也会按照客户端发来的数据逐个模拟,最后两端结果相同,仍然是流畅的。

延迟补偿:服务器收到其他客户端的射击消息时,将本地的所有角色回滚到“当前时间 - 网络延迟时间”时的位置再进行消息的处理和计算,以减少延迟带来的影响。

前端怎么对接 (同步、异步、如何产生回调)

发送函数参数

messageType:表示消息类型,是一个无符号的8位整数(Uint8

目录

三个主要特点

了解一下UE

场景搭建

前端怎么对接 (同步、异步、如何产生回调)

有哪些问题

UE4官方对接文件

app.js

stressTest.js

webRtcPlayer.js


)。

descriptor:表示消息描述符对象,是一个JavaScript对象,包含有关消息的详细信息。

函数逻辑

将描述符对象转换为JSON字符串:

使用 JSON.stringify(descriptor) 将 descriptor 对象转换为一个JSON字符串 descriptorAsString。

创建字节缓冲区:

使用 new DataView(new ArrayBuffer(...)) 创建一个 DataView 对象,它允许对字节缓冲区进行操作。缓冲区的大小计算如下:

1 字节用于存储消息类型。

2 字节用于存储JSON字符串的长度。

2 * descriptorAsString.length 字节用于存储JSON字符串本身,因为每个UTF-16字符占用2个字节。

将消息类型添加到缓冲区:

使用 data.setUint8(byteIdx, messageType) 将消息类型存储在缓冲区的第一个字节中。

更新 byteIndex的值,指向下一个要写入的位置。

将JSON字符串的长度添加到缓冲区:

使用 data.setUint16(byteIdx, descriptorAsString.length, true) 将JSON字符串的长度存储在缓冲区的接下来的两个字节中,使用小端字节序(true)。

更新 byteIdx 的值,指向下一个要写入的位置。

将JSON字符串的每个字符添加到缓冲区:

遍历 descriptorAsString 字符串中的每个字符。

使用 data.setUint16(byteIdx, descriptorAsString.charCodeAt(i), true) 将每个字符的Unicode码点值存储在缓冲区中,使用小端字节序。

更新 byteI函数参数

messageType:表示消息类型,是一个无符号的8位整数(Uint8)。

descriptor:表示消息描述符对象,是一个JavaScript对象,包含有关消息的详细信息。

函数逻辑

将描述符对象转换为JSON字符串:

使用 JSON.stringify(descriptor) 将 descriptor 对象转换为一个JSON字符串 descriptorAsString。

创建字节缓冲区:

使用 new DataView(new ArrayBuffer(...)) 创建一个 DataView 对象,它允许对字节缓冲区进行操作。缓冲区的大小计算如下:

1 字节用于存储消息类型。

2 字节用于存储JSON字符串的长度。

2 * descriptorAsString.length 字节用于存储JSON字符串本身,因为每个UTF-16字符占用2个字节。

将消息类型添加到缓冲区

使用 data.setUint8(byteIdx, messageType) 将消息类型存储在缓冲区的第一个字节中。

更新 byteIdx 的值,指向下一个要写入的位置。

将JSON字符串的长度添加到缓冲区:

使用 data.setUint16(byteIdx, descriptorAsString.length, true) 将JSON字符串的长度存储在缓冲区的接下来的两个字节中,使用小端字节序(true)。

更新 byteIdx 的值,指向下一个要写入的位置。

将JSON字符串的每个字符添加到缓冲区:

遍历 descriptorAsString 字符串中的每个字符。

使用 data.setUint16(byteIdx, descriptorAsString.charCodeAt(i), true) 将每个字符的Unicode码点值存储在缓冲区中,使用小端字节序。

更新 byteIdx 的值,指向下一个要写入的位置。

调用 sendInputData(data.buffer) 函数,将整个字节缓冲区发送到服务器。dx 的值,指向下一个要写入的位置。

发送数据到服务器:

有哪些问题

鼠标校准 setupNormalizeAndQuantize

问题其实是玩家的操作要怎么同步到服务器上,

    // 实现思路

    // 根据视频流和页面元素的尺寸比较,计算出归一化和量化处理的参数。根据页面元素和视频流的纵横比,确定了归一化和量化处理的方式,分为无符号和有符号两种处理方式。

    // 对于无符号的处理方式,将鼠标事件的坐标值从页面坐标系归一化到视频流坐标系,并进行量化处理,将坐标值转换为一个 0 到 65535 的整数,用于传输和处理。

    // 对于有符号的处理方式,将鼠标事件的坐标值从页面坐标系归一化到视频流坐标系,并进行量化处理,将坐标值转换为一个 - 32767 到 32767 的整数,用于传输和处理。

// 还实现了将量化处理后的坐标值进行反向处理,将其从视频流坐标系转换回页面坐标系,以便在页面中进行显示和交互。

沿视口轴线的比率

沿视口轴线的比率是指在视口(viewport)中,某个位置相对于视口边界的相对位置。这个比率通常是一个介于0.0到1.0之间的浮点数,表示从视口的左边缘到右边缘或从上边缘到下边缘的相对位置。例如,0.0表示视口的左边缘,0.5表示视口的中心,1.0表示视口的右边缘。

int16的定义沿视口轴线的比率

沿视口轴线的比率是指在视口(viewport)中,某个位置相对于视口边界的相对位置。这个比率通常是一个介于0.0到1.0之间的浮点数,表示从视口的左边缘到右边缘或从上边缘到下边缘的相对位置。例如,0.0表示视口的左边缘,0.5表示视口的中心,1.0表示视口的右边缘

int16的定义

int16是一种16位的有符号整数类型,它可以表示从-32768到32767的整数值

在WebRTC中,使用int16来量化沿视口轴线的比率,可以将比率的范围从-1.0到1.0映射到-32767到32767的整数范围内

玩家移动的量化

玩家移动的量化通常涉及到将玩家在游戏世界中的位置或移动方向转换为一种可以被计算机理解和处理的数值形式。例如,在一个2D游戏中,玩家的移动可以被量化为沿X轴和Y轴的增量,这些增量可以是整数或浮点数,表示玩家在每个方向上的移动距离

无符号与有符号的区别

无符号整数:无符号整数类型(如uint16)只能表示非负数,其范围是从0到最大值(例如,uint16的范围是0到65535)。无符号整数通常用于表示大小、索引等非负量

有符号整数:有符号整数类型(如int16)可以表示正数、零和负数,其范围是从最小值到最大值(例如,int16的范围是-32768到32767)。有符号整数适用于需要表示正负变化的场景

在WebRTC中,使用有符号整数(如int16)来量化沿视口轴线的比率,可以表示从-1.0到1.0的范围,这允许在视口的中心(0.0)两侧都有正负变化

int16是一种16位的有符号整数类型,它可以表示从-32768到32767的整数值。在WebRTC中,使用int16来量化沿视口轴线的比率,可以将比率的范围从-1.0到1.0映射到-32767到32767的整数范围内。


UE4官方对接文件

app.js

// Copyright Epic Games, Inc. All Rights Reserved.

// Window events for a gamepad connecting
let haveEvents = 'GamepadEvent' in window;
let haveWebkitEvents = 'WebKitGamepadEvent' in window;
let controllers = {};
let rAF = window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.requestAnimationFrame;
let kbEvent = document.createEvent("KeyboardEvent");
let initMethod = typeof kbEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";

let webRtcPlayerObj = null;
let print_stats = false;
let print_inputs = false;
let connect_on_load = false;

let is_reconnection = false;
let ws;
const WS_OPEN_STATE = 1;

let qualityControlOwnershipCheckBox;
let matchViewportResolution;
// TODO: Remove this - workaround because of bug causing UE to crash when switching resolutions too quickly
let lastTimeResized = new Date().getTime();
let resizeTimeout;

let onDataChannelConnected;
let responseEventListeners = new Map();

let freezeFrameOverlay = null;
let shouldShowPlayOverlay = true;
// A freeze frame is a still JPEG image shown instead of the video.
let freezeFrame = {
	receiving: false,
    size: 0,
    jpeg: undefined,
    height: 0,
    width: 0,
    valid: false
};

// Optionally detect if the user is not interacting (AFK) and disconnect them.
let afk = {
	enabled: false,   // Set to true to enable the AFK system.
    warnTimeout: 120,   // The time to elapse before warning the user they are inactive.
    closeTimeout: 10,   // The time after the warning when we disconnect the user.

    active: false,   // Whether the AFK system is currently looking for inactivity.
    overlay: undefined,   // The UI overlay warning the user that they are inactive.
    warnTimer: undefined,   // The timer which waits to show the inactivity warning overlay.
    countdown: 0,   // The inactivity warning overlay has a countdown to show time until disconnect.
    countdownTimer: undefined,   // The timer used to tick the seconds shown on the inactivity warning overlay.
}

// If the user focuses on a UE4 input widget then we show them a button to open
// the on-screen keyboard. JavaScript security means we can only show the
// on-screen keyboard in response to a user interaction.
let editTextButton = undefined;

// A hidden input text box which is used only for focusing and opening the
// on-screen keyboard.
let hiddenInput = undefined;

let t0 = Date.now();

function log(str) {
    console.log(`${Math.floor(Date.now() - t0)}: ` + str);
}

function scanGamepads() {
    let gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
    for (let i = 0; i < gamepads.length; i++) {
        if (gamepads[i] && (gamepads[i].index in controllers)) {
            controllers[gamepads[i].index].currentState = gamepads[i];
        }
    }
}


function updateStatus() {
    scanGamepads();
    // Iterate over multiple controllers in the case the mutiple gamepads are connected
    for (j in controllers) {
        let controller = controllers[j];
        let currentState = controller.currentState;
        let prevState = controller.prevState;
		// Iterate over buttons
        for (let i = 0; i < currentState.buttons.length; i++) {
            let currButton = currentState.buttons[i];
            let prevButton = prevState.buttons[i];
       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值