0


Unity Render Streaming 云渲染企业项目解决方案

Unity Render Streaming

文章目录

前言

原文链接
UnityRenderStreaming官方文档
Unity:2021.3.8f1c1
RenderStreaming:3.1.0-exp.4(Pre-release)
RenderStreaming WebServer:3.1.0-exp.3

效果展示

视频演示
在这里插入图片描述

外网视频演示
在这里插入图片描述

打开场景

使用HDRP示例模板作为测试场景
在这里插入图片描述

版本选择

本次RenderStreamingPackage使用3.1.0-exp.4,并没有正式版,但exp.4比exp.3版本开放了视频流编码、帧率、比特率、缩放比等设置,使用中也很稳定,方便根据自己项目需求调整。
WebServer使用3.1.0-exp.3,因为exp.4版本Server,网页访问路径检查有问题。
Git地址: UnityRenderStreaming
在这里插入图片描述
在这里插入图片描述

Web服务器

1.Server下载,Node.js安装

根据自己的平台选择Server,需要安装Node.js下载
使用

node -v
npm -v

检查是否安装成功
在这里插入图片描述

2.Server启动

Windows启动方式如下(其他平台启动方式参考官方文档)
找到webserver.exe,地址栏中输入cmd,按下Enter
在这里插入图片描述

输入

.\webserver.exe -w

,以WebSocket方式启动
在这里插入图片描述

打开浏览器,输入本机IP,下图就是以WebSocket启动后的内容
在这里插入图片描述

Unity项目设置

1.安装RenderStreaming

源码解压后,复制

com.unity.renderstreaming

文件,我这边放在了示例项目的Packages下,新建一个文件夹内。(根据自己的喜好存放)
在这里插入图片描述
在这里插入图片描述

打开PackageManager
在这里插入图片描述
点击+ ,选择

Add package from disk

在这里插入图片描述
找到刚才的文件中

package.json

,打开
在这里插入图片描述
打开后如下
在这里插入图片描述

2.安装WebRTC

选择

Add package by name

在这里插入图片描述
输入

com.unity.webrtc

,点击Add
在这里插入图片描述
安装后如下:
在这里插入图片描述

3.RenderStreaming设置

创建空物体,起名RenderStreaming
在这里插入图片描述
添加

RenderStreaming

Broadcast

VideoStreamSender

组件
修改各项参数如下:
在这里插入图片描述

4.音频传输(根据自己需求添加)

添加

AudioStreamSender

组件在

AudioListener

上,这个组件必须要在一个物体
在这里插入图片描述

Broadcast

组件记得添加

AudioStreamSender

组件
在这里插入图片描述

5.Unity启动测试

选择

ReceiverSample

在这里插入图片描述
效果如下,可根据自己的需求去调整

VideoStreamSender

中的各项参数
在这里插入图片描述
在这里插入图片描述

交互

1.基础交互项目设置

安装InputSystem
下面一些设置针对Unity2021.2以上版本,低版本参考链接
(该HDRP示例,已默认使用了InputSystem)
在这里插入图片描述
出现该弹窗后,选择Yes,同意使用New Input Sytem
在这里插入图片描述
或者在ProjectSettings/Player/OtherSettings/ActiveInputHanding中选择New或者Both
在这里插入图片描述
Run In Background开启(2021.2)

ProjectSettings/Player/Resolution and Presentation/Run in background

在这里插入图片描述
Input System Package 设置修改(2021.2)
在这里插入图片描述

2.场景交互设置

官方文档参考
添加

InputReceiver

组件,并添加到

Broadcast


在这里插入图片描述

3.键鼠交互

创建InputActions后打开,操作:

Create/InputActions

在这里插入图片描述
根据自己的项目需求配置Actions
在这里插入图片描述
将配置好的额InputActions文件拖入

InputReceiver/Actions

中,展开Events,可以看到配置的Action对应的事件,根据自己的需求去绑定
在这里插入图片描述

4.UI交互

EventSystem使用

InputSystemUIInputModule

在这里插入图片描述
**显示分辨率要和

VideoStreamSender

StreamingSize

保持一致,否则识别点位会发生偏移**
在这里插入图片描述
在这里插入图片描述

外网服务器部署

对于大多数 WebRTC 应用,服务器都需要在对等设备之间中继流量,因为在客户端之间通常无法实现直接套接字(除非这些应用在同一本地网络中)。解决此问题的常见方法是使用TURN 服务器。术语表示使用中继 NAT 的遍历,是一种中继网络流量的协议。
参考Unity文档中TURN 服务器设置

TURN服务器使用的端口需要公开,可设置最大最小值
协议端口TCP32355-65535、3478-3479UDP32355-65535、3478-3479
**Web端中更改

config.js

文件中的

config.iceServers

**

config.iceServers =[{urls:['stun:stun.l.google.com:19302']},{urls:['turn:xx.xx.xx.xx:3478?transport=tcp'],username:'username',credential:'password'}];

**Unity中修改

RenderStreaming

组件中

Ice Server

**
请添加图片描述

自定义WebServer

下面是如何制作自己的WebServer,更多详情查看官方文档
1.下载exp.3的源码
在这里插入图片描述
2.解压后找到WebApp
在这里插入图片描述
3.地址栏中输入cmd
在这里插入图片描述
**4.输入

npm install

或者

npm i

安装依赖**
在这里插入图片描述
如果安装过慢或者超时导致失败,可以先输入

npm config set registry https://registry.npm.taobao.org

更换npm的安装镜像源为国内
在这里插入图片描述
如果出现如下错误,可以输入

npm config set legacy-peer-deps true

,之后再次输入

npm install

或者

npm i

安装依赖**
在这里插入图片描述

**5.构建Server,输入

npm run build

**
在这里插入图片描述
**6.启动Server,输入

npm run start -- -w

**
在这里插入图片描述
如果启动异常,如下图,使用

npm run dev -- -w

启动
在这里插入图片描述
启动后如下,暂不清楚为何出现上述情况,有了解该原因的小伙伴,可以告诉我一下,谢谢。
在这里插入图片描述
7.制作启动快捷方式
为了方便以后快速启动,可以制作如下文件
在这里插入图片描述
**8.打包,

npm run pack

**
等待进度完成,将打包成exe
在这里插入图片描述
在这里插入图片描述
启动方式如上

.\webserver.exe -w

在这里插入图片描述

9.根据自己需求修改web文件
在这里插入图片描述

Web和Unity相互发自定义消息

发送消息使用

WebRTC

中的

RTCDataChannel

Send

函数,接收消息使用

OnMessage

1.Unity向Web发送消息

Unity项目中创建脚本如下,使用

inputReceiver

中的

Channel

对象进行

Send
usingUnity.RenderStreaming;usingUnityEngine;publicclassRenderStreamingManager:MonoBehaviour{privateInputReceiver inputReceiver;privatevoidAwake(){
        inputReceiver = transform.GetComponent<InputReceiver>();}publicvoidSendMsg(string msg){
        inputReceiver.Channel.Send(msg);}privatevoidUpdate(){if(Input.GetKeyDown(KeyCode.P)){SendMsg("Send Msg to web");}}}

记得添加到项目中
在这里插入图片描述
Web中接收如下
找到

receiver.js

文件中的

inputSenderChannel

对象,使用

onmessage

接收信息
在这里插入图片描述
收到的消息内容如下,字符串会在data中
在这里插入图片描述

2.web向Unity发送消息

web中使用send函数发送消息

this.inputSenderChannel.send("msg to unity");

Unity中

Receiver.cs

中OnMessage中通过RTCDataChannel接收了web发送的消息
在这里插入图片描述

企业项目如何应用

1.外网访问清晰和流畅之间的抉择

在服务器带宽允许的前提下,尽可能的提高传输质量,如下,是我在项目中的设置
在这里插入图片描述

可以根据自己带宽,调整

DepathBuffer,StreamingSize,Framerate,Bitrate,ScaleResolution


也可以获取VideoStreamSender组件,运行时修改对应参数,方便发布后测试。

VideoStreamSender videoStreamSender = transform.GetComponent<VideoStreamSender>();
videoStreamSender.SetTextureSize(newVector2Int(gameConfig.RSResolutionWidth,gameConfig.RSResolutionHeight));
videoStreamSender.SetFrameRate(gameConfig.RSFrameRate);
videoStreamSender.SetScaleResolutionDown(gameConfig.RSScale);
videoStreamSender.SetBitrate((uint)gameConfig.RSMinBitRate,(uint)gameConfig.RSMaxBitRate);

2.web制作按钮来替换Unity的按钮操作

因为目前受限于分辨率的变化导致Unity的按钮经常不能正确的进行点击,所以由Web同学制作对应的按钮,通过Web向Unity发消息的形式,响应对应按钮操作。
我这边是为所有按钮配置id,web发送按钮id,Unity接收后遍历配置表,进行Invoke

privatevoidOnClickButton(int elementId){// 在配置表中根据id,获取UIName,FunctionName,ParaList<UIMatchFuncModel> uiMatchFuncModels = UniversalConfig.Instance.GetUIMatchModels();foreach(var item in uiMatchFuncModels.Where(item => item.id == elementId)){ExecuteFunction(item);break;}}privatevoidExecuteFunction(UIMatchFuncModel matchFuncModel){UIPanel uiPanel = UIKit.GetPanel(matchFuncModel.uiName);if(uiPanel ==null){
            uiPanel = UIKit.OpenPanel(matchFuncModel.uiName);}
        uiPanel.Invoke(matchFuncModel.functionName,0);}

3.分辨率同步,参考下面iOS、Android中的分辨率同步方式

匹配不同设备分辨率,通过web中获取当前设备分辨率,连接成功后,发送设备宽高消息到Unity,Unity中重新设置输出分辨率来保持web设备中铺满显示。

publicvoidChangeScreenSize(int width,int height){    
    videoStreamSender.SetTextureSize(newVector2Int(width,height));}

4.手机端访问默认竖屏显示横屏画面注意事项

手机端由于竖屏展示画面过小,通常都是竖屏状态下展示横屏内容,因此:
1.上一点提到的分辨率设置时需要注意web端的设备是横屏还是竖屏;
2.项目中如果有滑动操作,需要注意根据横竖屏来反转操作。

5.检测Unity端是否断开服务

项目中有过每隔半小时到一小时服务就会断开的情况,因此添加了心跳检测,进行重连操作
如下,在

ISignaling

中添加

OnHeartBeatHandler
usingUnity.WebRTC;namespaceUnity.RenderStreaming.Signaling{publicdelegatevoidOnStartHandler(ISignaling signaling);publicdelegatevoidOnConnectHandler(ISignaling signaling,string connectionId,bool polite);publicdelegatevoidOnDisconnectHandler(ISignaling signaling,string connectionId);publicdelegatevoidOnOfferHandler(ISignaling signaling,DescData e);publicdelegatevoidOnAnswerHandler(ISignaling signaling,DescData e);publicdelegatevoidOnIceCandidateHandler(ISignaling signaling,CandidateData e);// addpublicdelegatevoidOnHeartBeatHandler(ISignaling signaling);publicinterfaceISignaling{voidStart();voidStop();eventOnStartHandler OnStart;eventOnConnectHandler OnCreateConnection;eventOnDisconnectHandler OnDestroyConnection;eventOnOfferHandler OnOffer;eventOnAnswerHandler OnAnswer;eventOnIceCandidateHandler OnIceCandidate;// addeventOnHeartBeatHandler OnHeartBeat;string Url {get;}float Interval {get;}voidOpenConnection(string connectionId);voidCloseConnection(string connectionId);voidSendOffer(string connectionId,RTCSessionDescription offer);voidSendAnswer(string connectionId,RTCSessionDescription answer);voidSendCandidate(string connectionId,RTCIceCandidate candidate);// addvoidSendHeartBeat();}}

修改报错
在这里插入图片描述
WebSocketSignaling中的SendHeartBeat函数中添加如下内容

publicvoidSendHeartBeat(){this.WSSend($"{{\"type\":\"heart\"}}");}

在这里插入图片描述

创建

RenderStreamingManager

脚本,代码如下

usingSystem.Collections;usingSystem.Threading;usingUnity.RenderStreaming;usingUnity.RenderStreaming.Signaling;usingUnityEngine;publicclassRenderStreamingManager:MonoBehaviour{privateRenderStreaming _renderStreaming;privateVideoStreamSender _videoStreamSender;privateISignaling _signaling;privateInputReceiver _inputReceiver;// heartbeat checkprivateCoroutine _heartBeatCoroutine;privatebool _isReceiveHeart;privatefloat _heartBeatInterval;privatevoidAwake(){// websocket
        _signaling =newWebSocketSignaling($"{"ws"}://{"192.0.0.0"}",5, SynchronizationContext.Current);if(_signaling !=null){
            _renderStreaming = transform.GetComponent<RenderStreaming>();
            _videoStreamSender = transform.GetComponent<VideoStreamSender>();
            _inputReceiver = transform.GetComponent<InputReceiver>();
            
            _renderStreaming.Run(_signaling);// heart beat
            _signaling.OnStart += OnWebSocketStart;
            _signaling.OnHeartBeat += OnHeatBeat;}}#region HeartBeatprivatevoidOnWebSocketStart(ISignaling signaling){StartHeartCheck();}privatevoidOnHeatBeat(ISignaling signaling){
        _isReceiveHeart =true;}privatevoidStartHeartCheck(){
        _isReceiveHeart =false;
        _signaling.SendHeartBeat();
        _heartBeatCoroutine =StartCoroutine(HeartBeatCheck());}privateIEnumeratorHeartBeatCheck(){yieldreturnnewWaitForSeconds(5);if(!_isReceiveHeart){// not receive msgReconnect();}else{StartHeartCheck();}}privatevoidReconnect(){StartCoroutine(ReconnectDelay());}privateIEnumeratorReconnectDelay(){
        _signaling.Stop();yieldreturnnewWaitForSeconds(1);
        _signaling.Start();}#endregion}

记得关闭

RenderStreaming

中的

RunOnAwake

在这里插入图片描述

WebServer

中找到

websocket.ts

websockethandler.ts

,添加如下内容
在这里插入图片描述
在这里插入图片描述
重新Build服务后启动Server,这样就可以Unity就可以和Server通过WebSocket的形式收发心跳消息
在这里插入图片描述

6.获取是否有用户连接成功

RenderStreamingManager

中绑定事件

privatevoidAwake(){// connect & disconnect
    _inputReceiver.OnStartedChannel += OnStartChannel;
    _inputReceiver.OnStoppedChannel += OnStopChannel;}privatevoidOnStartChannel(string connectionid){}privatevoidOnStopChannel(string connectionid){}

iOS、Android端如何制作

1.参考Sample中的Receiver场景

在这里插入图片描述

2.自定义Receiver场景内容

如图创建物体,添加

RenderStreaming
SingleConnection
VideoStreamReceiver
InputSender

组件

AudioStreamReceiver

是声音相关的,根据自己需要添加
在这里插入图片描述
新建Canvas,创建RawImage,作为接收Texture的载体
在这里插入图片描述
Audio相关的内容,根据Sample中配置,不需要声音相关的可以忽略
在这里插入图片描述

3.创建管理脚本

Awake里获取组件和绑定事件
StartConnect调用开始连接websocket,连接成功后开始webrtc的连接,之后VideoStreamReceiver绑定的OnUpdateReceiveTexture事件更新RawImage里面的画面。
OnStartedChannel里添加了发送当前设备分辨率信息,消息结构可自定义,我这边创建了ScreenMessage,添加了宽高信息,发送到客户端。
脚本如下:
在这里插入图片描述

usingSystem;usingSystem.Threading;usingUnity.RenderStreaming;usingUnity.RenderStreaming.Signaling;usingUnityEngine;usingUnityEngine.UI;publicclassRenderStreamingReceiverManager:MonoSingleton<RenderStreamingReceiverManager>{privateRenderStreaming _renderStreaming;privateVideoStreamReceiver _videoStreamReceiver;privateAudioStreamReceiver _audioStreamReceiver;privateInputSender _inputSender;privateSingleConnection _connection;[SerializeField]privateRawImage remoteVideoImage;[SerializeField]privateAudioSource remoteAudioSource;privateISignaling _signaling;privatestring _connectionId;privatevoidAwake(){// Get Component
        _renderStreaming = transform.GetComponent<RenderStreaming>();
        _videoStreamReceiver = transform.GetComponent<VideoStreamReceiver>();
        _inputSender = transform.GetComponent<InputSender>();
        _connection = transform.GetComponent<SingleConnection>();// Connect DisConnect
        _inputSender.OnStartedChannel += OnStartedChannel;
        _inputSender.OnStoppedChannel += OnStopChannel;

        _videoStreamReceiver.OnUpdateReceiveTexture += OnUpdateReceiveTexture;

        _audioStreamReceiver = transform.GetComponent<AudioStreamReceiver>();if(_audioStreamReceiver){
            _audioStreamReceiver.OnUpdateReceiveAudioSource += source =>{
                source.loop =true;
                source.Play();};}}// 外部调用Start    publicvoidStartConnect(){
        _signaling =newWebSocketSignaling($"{"ws"}://172.0.0.1:80",5, SynchronizationContext.Current);
        _signaling.OnStart += OnWebSocketStart;
        _renderStreaming.Run(_signaling);}// 外部调用StoppublicvoidStopConnect(){StopRenderStreaming();
        _signaling.OnStart -= OnWebSocketStart;
        _renderStreaming.Stop();}privatevoidOnWebSocketStart(ISignaling signaling){StartRenderStreaming();}privatevoidStartRenderStreaming(){if(string.IsNullOrEmpty(_connectionId)){
            _connectionId = Guid.NewGuid().ToString("N");}if(_audioStreamReceiver){
            _audioStreamReceiver.targetAudioSource = remoteAudioSource;}

        _connection.CreateConnection(_connectionId);}privatevoidStopRenderStreaming(){
        _connection.DeleteConnection(_connectionId);
        _connectionId = String.Empty;}voidOnUpdateReceiveTexture(Texture texture){
        remoteVideoImage.texture = texture;SetInputChange();}voidOnStartedChannel(string connectionId){
        Debug.Log("连接成功:"+ connectionId);// 发送当前设备的分辨率到ClientSendScreenToClient();SetInputChange();}privatevoidOnStopChannel(string connectionId){
        Debug.Log("断开连接:"+ connectionId);}voidSetInputChange(){if(!_inputSender.IsConnected || remoteVideoImage.texture ==null)return;// correct pointer positionVector3[] corners =newVector3[4];
        remoteVideoImage.rectTransform.GetWorldCorners(corners);Camera camera = remoteVideoImage.canvas.worldCamera;var corner0 = RectTransformUtility.WorldToScreenPoint(camera, corners[0]);var corner2 = RectTransformUtility.WorldToScreenPoint(camera, corners[2]);var region =newRect(
            corner0.x,
            corner0.y,
            corner2.x - corner0.x,
            corner2.y - corner0.y
        );var size =newVector2Int(remoteVideoImage.texture.width, remoteVideoImage.texture.height);
        _inputSender.SetInputRange(region, size);
        _inputSender.EnableInputPositionCorrection(true);}privatevoidSendScreenToClient(){ScreenMessage screenMessage =newScreenMessage(MessageType.Screen, Screen.width, Screen.height);string msg = JsonUtility.ToJson(screenMessage);SendMsg(msg);}privatevoidSendMsg(string msg){
        _inputSender.Channel.Send(msg);}}

Message定义结构,根据自己喜好编写

publicenumMessageType{
        Screen
    }publicclassRenderStreamingMessage{publicMessageType MessageType;}publicclassScreenMessage:RenderStreamingMessage{publicint Width;publicint Height;publicScreenMessage(MessageType messageType,int width,int height){
            MessageType = messageType;
            Width = width;
            Height = height;}}

4.客户端收到ScreenMessage后的屏幕自适应处理

收到消息后,解析拿到宽高,按照移动设备的分辨率计算得到合适的分辨率
使用_videoStreamSender.SetTextureSize修改输出分辨率
使用_inputReceiver.SetInputRange和_inputReceiver.SetEnableInputPositionCorrection(true)来修正分辨率,web端分辨率修改和这个一致。

privatevoidOnMessage(byte[] bytes){string msg = System.Text.Encoding.Default.GetString(bytes);if(string.IsNullOrEmpty(msg)||!msg.Contains("MessageType")){return;}RenderStreamingMessage messageObject = JsonUtility.FromJson<RenderStreamingMessage>(msg);if(messageObject ==null){return;}switch(messageObject.MessageType){case MessageType.Screen:ScreenMessage screenMessage = JsonUtility.FromJson<ScreenMessage>(msg);ResetOutputScreen(screenMessage);break;default:break;}}privatevoidResetOutputScreen(ScreenMessage screenMessage){int width = screenMessage.Width;int height = screenMessage.Height;float targetWidth, targetHeight;int configWidth =1920;int configHeight =1080;float configRate =(float) configWidth / configHeight;float curRate =(float) width / height;if(curRate > configRate){
            targetWidth = configWidth;
            targetHeight = targetWidth / curRate;}else{
            targetHeight = configHeight;
            targetWidth = targetHeight * curRate;}

        _videoStreamSender.SetTextureSize(newVector2Int((int) targetWidth,(int) targetHeight));// 这一步是分辨率偏移修正的关键代码
        _inputReceiver.SetInputRange(newVector2Int((int) _videoStreamSender.width,(int) _videoStreamSender.height),newRect(0,0, Screen.width, Screen.height));
        _inputReceiver.SetEnableInputPositionCorrection(true);}

5.发布前的项目设置

根据WebRTC文档中的要求更改
在这里插入图片描述

6.发布测试

在这里插入图片描述

更新计划

1.mac和iOS的发布bug分享

标签: unity 游戏引擎

本文转载自: https://blog.csdn.net/qq_22955617/article/details/127052164
版权归原作者 杰弗里斯_ 所有, 如有侵权,请联系我们删除。

“Unity Render Streaming 云渲染企业项目解决方案”的评论:

还没有评论