ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity全景视频播放:球体+RenderTexture链路与真机避坑指南

Unity全景视频播放:球体+RenderTexture链路与真机避坑指南 简介一份系统讲解 Unity 2017 中全景视频播放实现的 Word 文档面向需要为 VR/AR 场景提供 360° 沉浸式视频的 Unity 开发者也适合刚接触全景播放的初级技术人员。文档以 MovieTexture 为主线完整梳理了创建球体并放置摄像机、将全景视频作为材质纹理、用 C# 脚本控制播放/暂停/停止等关键步骤并重点说明了如何通过 AudioSource 组件解决视频画面与音频不同步的问题。针对移动端打包需求文档进一步点出 MovieTexture 在 Android 平台不可用的限制介绍了 Handheld.PlayFullScreenMovie 的功能局限并客观对比自行开发播放器与使用第三方插件如 EasyMovieTexture的成本、周期和兼容性。资源仅有 1 个 docx 文件、约 563KB内容精炼步骤之间有运行效果与报错排查思路可直接对照实践。目前已有 116 人学习适合需要快速搭建全景视频 Demo 或做技术预研的开发者。 很多人第一次做 Unity 全景视频播放时以为放一个 VideoPlayer 再把视频拖进去就能用鼠标看到 360°。但跑起来后画面要么铺在屏幕上要么球体内壁一片黑只有 UI 上的预览还在动。原因是把播放平面的思路用在了球面上缺了 RenderTexture、球面 UV 和法线方向三个关键环节。Unity 全景视频播放的本质是一条“视频 - RenderTexture - 球体内表面 - 相机在球心”的渲染链路。下面按这条链路把选型、最小实现、Pico4 一体机和安卓真机上的坑一次讲清楚。这篇适合做 VR 展厅、全景看房、沉浸式教学的从业者新手能跟着复现熟手可以直接跳到第 5 章看排查。2. 播放链路选型为什么是球体 RenderTexture而不是 VideoPlayer 直接出画面2.1 三种常见播放链路对比为什么最后选了球体 RenderTextureUnity 内置的 VideoPlayer 可以理解成一个视频源它本身不知道什么是“全景”。要把一个平面视频变成可以转头观看的 360° 画面必须决定“视频内容最终渲染到哪一层”。做这个决定之前我建议先看一遍三种常见链路因为它们在接入成本、可交互性和格式兼容性上的差别非常大。第一种链路是 VideoPlayer 直接输出到 Canvas 下的 RawImage。这个做法很像视频网站的播放器视频以平面矩形显示在 UI 上。好处是调试最快UI 覆盖、进度条、倍速都好做坏处是它永远只有一个视角做不到转头看后面。第二种链路是 Skybox 加 RenderTexture。做法是写一个自定义 Skybox Shader把 RenderTexture 作为天空盒的主纹理。这个方案能做出全景效果但天空盒的坐标系和经纬映射有自己一套逻辑遇到接缝、上下颠倒时排查比较麻烦也不方便在特定角度挂热区交互。第三种链路是球体 Mesh 加 RenderTexture。创建一个 Sphere把视频渲染到 RenderTexture再赋给球体表面的 Unlit Shader相机放在球心。这个方案对数据流完全可控既能转视角也能在球面上做射线交互所以下面整篇都基于这条链路展开。三种方案对比下来真正决定我选第三种的是“可排查性”。全景视频播放这个需求一旦上真机特别是 Pico4 这类 Android 一体机出现黑屏、花屏、颜色不对时你很难直接看到渲染中间态。而球体加 RenderTexture 的链路是透明的VideoPlayer 有没有把帧写进 RenderTexture材质有没有采样到这张纹理全部可以在 Inspector 里单独检查。Skybox 方案的问题恰恰是 Unity 对 Skybox 的渲染路径封装太紧出问题时黑匣子感很强。方案是否支持 360°交互可扩展性排查难度接入成本RawImage 平面播放否高UI 原生支持低最低Skybox RenderTexture是低热区难挂中中球体 RenderTexture是高可用射线和 Mesh低中另外需要明确一点全景视频的主流封装格式是等距柱状投影也就是把 360° 视场展开成一张宽高比 2:1 的矩形画面。球体 Mesh 的 UV 天然和这种投影方式对应只需要处理 v 方向翻转和法线朝向两个细节。如果你选择 Cubemap则要面对六个面单独供应视频的难题没有现成视频源会给你这种格式。2.2 球面 UV 翻转和内表面剔除两个必须写进 Shader 的细节用球体做全景屏工程上绕不开两个渲染细节UV 的 v 方向以及内表面剔除。先看 UV。等距柱状投影视频的第一行像素通常对应北半球最上方最后一行对应南半球最下方而 Unity 内置球体的 UV 约定里v0 在球体底部v1 在顶部。直接采样就会得到上下颠倒的画面所以全景 Shader 里最常见的第一个操作是uv.y 1.0 - uv.y。成本极低但漏掉它你会浪费一晚上调试。然后是法线方向。Unity 球体模型默认法线朝外面剔除也按正面处理。只有相机在球外时外部表面才可见。而全景播放要求相机在球心看到的应该是球体的内表面。解决这个问题有两个常见做法一是把球体 Scale 设为负数例如 (-100, 100, 100)利用负缩放把法线翻到内侧二是在 Shader 里加一句Cull Front让 GPU 跳过正面、只渲染背面。我强烈建议用Cull Front。负缩放看着方便但它在阴影、光照、碰撞体和部分移动端图形 API 下会引发奇怪问题尤其是做 VR 项目时负缩放模型容易在 Pico4 的真机上出现裁剪闪烁。用 Shader 剔除正面逻辑清晰以后做热区射线检测也更准确。还有一个配套参数是相机位置。球体半径建议给到 50 到 100相机的 near clip plane 调到 0.01far clip plane 至少大于球体半径。如果 near 太大靠近球心的相机会把球体内部切出一个尖锐的截面画面边缘出现明显的透视变形。这个不是项目 bug而是裁剪面参数没配好。在我的落地流程里球体、相机和 Shader 三者是一起调整的球体半径 100相机 near0.01far1000摄像机放在原点整个球体作为相机的子物体跟随移动。这样才能保证头显转动时不会穿出球体。2.3 音频链路与帧率选择AudioSource 输出比 Direct 更稳VideoPlayer 的音频输出有 Direct 和 AudioSource 两种常见模式。Direct 模式在 PC 编辑器里最省事直接把音频交给系统播放延迟低。但到 Android 一体机上Direct 模式经常和 Unity 的音频系统抢资源声音滞后、爆音、声道错乱都遇到过。我在移动端项目里一律使用 AudioSource 模式创建 AudioSource 组件把 VideoPlayer 的 audioOutputMode 设为 VideoAudioOutputMode.AudioSource再用 SetTargetAudioSource 把输出指向它。这里有个容易疏忽的参数AudioSource 的 Spatial Blend。全景视频的声音本身是立体声或环绕声它的声场已经包含在视频文件里不需要 Unity 的三维空间化处理。如果把 Spatial Blend 设为 1声音会随相机旋转发生错误的声像漂移听起来像整个声场被钉在场景里。正确做法是保持 Spatial Blend 为 0关闭空间化音量按普通 2D 声音播放。如果项目里加了 Reverb Zone 或全局混响也建议在全景播放场景里临时关掉否则 VR 展厅环境会叠加回声非常脏。帧率选择也是音频和画面同步的地基。全景视频我优先压成 30fps。60fps 的 4K 视频在 Pico4 这类一体机上会让解码器满载发热后 CPU/GPU 降频画面掉帧音频反而更卡。VideoPlayer 的 waitForFirstFrame 建议打开这样 Prepare 完成后才进入播放避免黑屏时声音先出。skipOnDrop 在低端机上建议打开允许丢帧保同步如果是做录屏或离线渲染再把它关掉。3. 从零跑通最小工程球体、材质、VideoPlayer 的配置清单3.1 搭建球体场景的 7 个步骤从 Sphere 到 VideoPlayer在已有工程里跑通最小全景播放我习惯按固定步骤从头建一遍这样可以确认每一步都在自己的掌控里而不是依赖某个半成品预制体。新建场景后先创建一个 Sphere命名为 VideoSphere。这个球体不需要 Collider因为相机在球心如果保留 Collider射线检测会先打到球体影响后续的交互射线和 Gaze 判定。把 Collider 直接删掉。第二步创建 RenderTexture。在 Project 窗口右键 Create RenderTexture保持默认大小后续我会把它改成与视频源分辨率匹配的参数。第三步创建材质Shader 选择下面 3.2 里的 Panorama/Equirect。第四步把 RenderTexture 拖到材质的 Main Texture 插槽。第五步把材质赋给 VideoSphere 的 MeshRenderer。第六步在场景里新建一个空物体挂上 VideoPlayer 和 AudioSource。第七步把相机放到球心并把 VideoSphere 拖成相机的子物体。这个顺序看起来简单但每一步都有必须注意的边界。RenderTexture 赋给材质以后如果材质显示紫红色通常是 Shader 没有编译成功要回到 Shader 代码检查是否有语法错误或平台不支持的写法。球体删 Collider 是因为后续做“视线停留高亮”或手柄射线交互时你不希望射线先被一个巨大的球挡住。把球体挂到相机子物体下是为了让相机无论怎么移动和旋转都保持在球心这一步不做玩家走出球体就直接穿模看到场景背景。3.2 自定义 Equirect Shader 的完整代码与参数说明Shader 名称我用 Panorama/Equirect方便在材质面板里识别。整个 Shader 的职责非常单一从 RenderTexture 里采样等距柱状投影像素显示在球体表面。Shader Panorama/Equirect { Properties { _MainTex (Video RenderTexture, 2D) black {} } SubShader { Tags { QueueBackground RenderTypeOpaque } Cull Front ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; float4 _MainTex_ST; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { // 等距柱状投影第一行是北极Unity 球体 V0 在南极翻转 v 方向 float2 uv float2(i.uv.x, 1.0 - i.uv.y); // 如果视频左右反向把上一行改成 uv.x 1.0 - uv.x 即可 fixed4 col tex2D(_MainTex, uv); // 调试网格取消下面两行注释可以显示经度和纬度网格线 // float grid step(0.98, frac(i.uv.x * 36.0)) step(0.98, frac(i.uv.y * 18.0)); // col.rgb lerp(col.rgb, fixed3(1, 0, 0), saturate(grid)); return col; } ENDCG } } }这段代码里真正决定全景显示效果的只有三处Cull Front、uv.y 翻转、默认主纹理设为黑色。Cull Front 让球体内表面被渲染不需要负缩放uv 翻转解决南北颠倒默认黑色让视频还没准备好时材质显示成黑而不是紫红也方便观察 RenderTexture 是否有内容写入。参数方面Queue 设置为 Background 表示这个球体作为背景优先渲染这样后续叠加 UI 或热区物体时深度排序会自然把球体放在最后。ZWrite Off 是我实际项目里的习惯因为球体远在 100 米外且内表面渲染不需要参与深度写入打开 ZWrite 在某些移动端驱动上反而会造成球体和 UI 之间的深度冲突。如果你在编辑器里看到球体遮挡了 Canvas优先检查 Canvas 的 Render Mode而不是急着改 ZWrite。3.3 播放控制器 C# 脚本最小可用实现与参数有了球体和材质还需要一段脚本把 VideoPlayer 和 RenderTexture、AudioSource 串起来。下面这段是我每次新建全景项目时最先写的控制脚本去掉所有业务逻辑只保留播放链路。using UnityEngine; using UnityEngine.Video; [RequireComponent(typeof(VideoPlayer))] public class PanoramaVideoPlayer : MonoBehaviour { public VideoClip localClip; public string url; // 本地路径或 https 地址 public RenderTexture targetTexture; public AudioSource audioSource; private VideoPlayer vp; void Start() { vp GetComponentVideoPlayer(); vp.renderMode VideoRenderMode.RenderTexture; vp.targetTexture targetTexture; vp.source localClip ! null ? VideoSource.VideoClip : VideoSource.Url; vp.clip localClip; vp.url url; vp.playOnAwake false; vp.waitForFirstFrame true; vp.audioOutputMode VideoAudioOutputMode.AudioSource; vp.SetTargetAudioSource(0, audioSource); vp.skipOnDrop true; vp.Prepare(); vp.prepareCompleted OnPrepared; } void OnPrepared(VideoPlayer player) { // 保证球体始终包住相机位置清零后相机就在球心 player.transform.SetParent(Camera.main.transform, false); player.transform.localPosition Vector3.zero; player.Play(); } public void TogglePlay() { if (vp.isPlaying) vp.Pause(); else vp.Play(); AudioListener.pause !vp.isPlaying; } }这段代码里需要重点说明的参数有三个。renderMode 和 targetTexture 必须成对出现只设 targetTexture 但不改 renderModeVideoPlayer 可能走的是 Camera Near/Far Plane 渲染模式最终画面会叠加在相机裁剪面上根本不会写进 RenderTexture。waitForFirstFrame 设为 true 后Prepare 完成时会触发 prepareCompleted这是最可靠的播放时机。如果设成 false在 Android 真机上经常出现画面黑屏但声音先出的情况因为音频解码比视频帧渲染更快。AudioListener.pause 那行是我的个人习惯。全景视频暂停时如果只停 VideoPlayer音频流可能还在继续播放尤其在使用 AudioSource 输出模式时把 AudioListener 一起暂停声音和画面能保持同步。Camera.main 在脚本里用了快捷获取方式实际工程如果有多个相机我建议改成 public Camera targetCamera 手动指定避免在 XR 项目里抓到 Editor 相机而不是头盔相机。3.4 RenderTexture 设置参数表分辨率、色彩格式与 Filter ModeRenderTexture 的参数直接决定画质和性能我整理成一张表按照实际项目的优先级排列。参数推荐值原因Size与视频原生分辨率一致或略低高于源视频分辨率没有意义反而增加 GPU 带宽Depth Buffer0 bits只作为颜色源不需要深度测试Color FormatARGB32Android / iOS / PC 兼容性最好sRGB与项目色彩空间配合Gamma 项目保持默认Wrap ModeClamp防止全景视频边缘采样出现渐变黑边Filter ModeBilinear兼顾清晰度和性能移动端避免 TrilinearSize 是我最在意的一项。如果你拿到的素材是 4K 全景视频RenderTexture 也用 4K在 PC 上没问题但在 Pico4 上会让 GPU 负载明显上升。我一般先把 RenderTexture 开到 2560×1280 测试真机发热如果画面细节够用就保持这个值不够再升。Depth Buffer 对视频播放没有作用保持 0 可以减少内存占用。Filter Mode 这里有个容易踩的细节。全景视频顶部和底部存在极点压缩使用 Bilinear 采样时北极附近的像素会被拉伸成大片色块。这是视频源本身的问题不是参数错误。如果你发现画面精细但边缘闪烁可以临时把 Filter Mode 改成 Point 观察能快速判断是纹理过滤问题还是视频码率问题。4. 移动端与 Pico4 一体机适配从 PC 能播到真机不黑屏的调试路径4.1 Android 的 StreamingAssets 路径为什么必须拷贝到 persistentDataPathPC 编辑器里Application.streamingAssetsPath 直接指向 Assets/StreamingAssets 文件夹VideoPlayer 用 file 协议打开就能播。但打进 Android 包以后StreamingAssets 目录里的文件会被压缩进 APK它不是真实文件系统路径VideoPlayer.url 直接指向它是打不开的。Pico4 本质上是一台 Android 设备同样受这个限制。常见做法是先把视频从 StreamingAssets 拷贝到 Application.persistentDataPath然后给 VideoPlayer.url 传一个带 file:// 前缀的真实路径。在 Android 上不能用 File.ReadAllBytes 直接读 StreamingAssets必须走 UnityWebRequest。using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Video; public class StreamingVideoLoader : MonoBehaviour { public VideoPlayer videoPlayer; public string fileName showroom.mp4; IEnumerator Start() { string src Path.Combine(Application.streamingAssetsPath, fileName); string dst Path.Combine(Application.persistentDataPath, fileName); if (!File.Exists(dst)) { using (UnityWebRequest req UnityWebRequest.Get(src)) { yield return req.SendWebRequest(); if (req.result UnityWebRequest.Result.Success) File.WriteAllBytes(dst, req.downloadHandler.data); else Debug.LogError(拷贝失败: req.error); } } videoPlayer.url file:// dst; videoPlayer.Prepare(); videoPlayer.prepareCompleted _ videoPlayer.Play(); } }这段代码把视频从包内搬到应用私有目录之后 VideoPlayer 就能像读普通文件一样读取。注意持久化目录在 iOS 和 Android 上路径不同但 Application.persistentDataPath 已经处理了平台差异。如果视频体积超过 500MB我会建议直接放远程服务器而不是打进包内否则每次更新版本都要重新下载整个安装包用户成本太高。4.2 硬解编码与紫红色材质先查 MediaInfo 再查 ShaderAndroid 上的视频解码依赖系统硬解而系统硬解支持的编码格式有限。PC 上 Windows 播放器什么都能软解所以工程里最常见的翻车点是视频在编辑器里正常打包到 Pico4 后直接黑屏、闪退或只出声音不出画面。这时候第一件事不是改 Unity 参数而是用 MediaInfo 看视频编码。常见的 H.265 High Profile、10bit 色深、60fps 4K对一体机硬解都是巨大压力。我通常用 ffmpeg 转一版兼容性最高的视频参数固定如下ffmpeg -i input.mp4 \ -c:v libx264 -profile:v high -level 4.1 \ -pix_fmt yuv420p -r 30 -g 30 -keyint_min 30 \ -c:a aac -b:a 192k -ac 2 -movflags faststart \ output.mp4参数含义profile 设置为 high不要用 high10pix_fmt 用 yuv420p这是 Android 硬解最基本的要求r 30 把帧率固定到 30g 30 和 keyint_min 30 让关键帧间隔约 1 秒后面拖进度条会明显更顺movflags faststart 把 moov 元数据放到文件头部利于网络播放。音频用 AAC 双声道避免 5.1 声道在一体机上触发奇怪的回声或延迟。紫红色材质在移动端还有一个常见来源Shader 用了只支持桌面端的语法或者工程启用了 URP而这个 Shader 面向 Built-in 管线编写。URP 工程里 CGPROGRAM 并不保证兼容。遇到紫红色先打开 Frame Debugger 看 Shader 编译错误如果代码没问题再检查当前工程是不是 URP是的话把材质 Shader 换成简化版 URP Shader或者把项目里的相关渲染流程调整到 Built-in。画面拉伸则是另一个问题球体缩放不是均匀的才会拉伸保持 VideoSphere 的 Scale 三轴一致并在 Player Settings 里锁定 Android 的渲染分辨率不要依赖运行时 Screen.SetResolution 去强行适配。4.3 视角跟随与 UI 稳定球体跟随相机和 Overlay Canvas全景播放要求相机始终在球心。静态演示项目里相机不动球体也不用动但只要有漫游、电梯或移动平台球体就得跟着相机跑。最省事的是把球体挂到相机子物体下这样相机的位移和旋转全部同步到球体。注意挂载后要把球体 localPosition 清零否则相机不在球心画面会偏。// 放在相机控制逻辑里每帧同步球体位置 videoSphere.transform.SetParent(targetCamera.transform, false); videoSphere.transform.localPosition Vector3.zero; // 保持球体不随相机缩放 videoSphere.transform.localScale Vector3.one * 100f;这里的 SetParent 第二个参数 worldPositionStays 传 false意思是挂到相机下后保持本地坐标再清零位置。这样无论相机如何移动球心始终在相机位置。如果你用的是 XR 设备相机由插件控制不要手动改它的 position否则头部追踪会乱。UI 在全景场景里最容易被球体干扰。Canvas 的 Render Mode 设置为 Screen Space OverlayUI 会直接叠加在最终画面上不受相机旋转影响这是最省心的方案。如果你非要用 World Space Canvas 做 3D 字幕务必要把 Canvas 挂在相机子物体下并让它的 localPosition 保持在固定距离比如 (0, 0, 2) 米localScale 不要随意放大否则字会随镜头拉近拉远。这也就是“物体不随镜头放大缩小”的常见处理思路把物体放进相机坐标系脱离世界坐标缩放。4.4 远程视频播放HTTPS、cleartext 与服务端 Range网络视频是很多全景项目的最终选择因为本地包体太大更新也不方便。VideoPlayer.url 支持 http/https 地址但 Android 9 开始默认禁止明文 HTTP 流量。开发阶段常用 http 内网 IP 调试结果真机上一加载 URL 就报错。解决方法是给 Android 配置 cleartext 许可但注意这只建议在测试包用。!-- AndroidManifest.xml 的 application 标签里加 -- application android:usesCleartextTraffictrue ...生产环境务必用 HTTPS。除此以外远程视频最容易让用户感知到的问题就是拖动进度条拖不动。原因是服务端没有返回 Range 响应头VideoPlayer 无法随机读取字节。检查方法是用 curl 看响应头里有没有 Accept-Ranges: bytes没有的话CDN 或文件服务必须支持 Range。码率方面Pico4 一体机我建议把视频压到 4K 30fps平均码率 20 Mbps 以内8K 素材先降到 4K 再上线否则解码和内存都可能扛不住。5. 全景视频播放的四个高频坑从黑屏、颠倒到拖拽失效5.1 球体内壁全黑但同一视频在 RawImage 上正常现象把同一个视频放到 RawImage 上预览画面正常一换到球体材质上球体内壁就是黑的连一点视频画面都没有。原因这个坑九成是法线朝向问题。球体默认只渲染朝外的表面相机在球心时看到的是球体背面被面剔除挡掉了。还有少部分情况是 RenderTexture 没有真正赋给材质Retained Mode 下 VideoPlayer 的 targetTexture 和材质主纹理不是同一个对象。解决先把 Shader 里的 Cull Front 加上并在材质面板确认 _MainTex 指向的是 VideoPlayer 正在写入的 RenderTexture。运行时可以在 OnPrepared 里 Debug.Log 一下 targetTexture 指针和 targetTexture.IsCreated()。如果两者都对再用 Frame Debugger 抓一帧看 VideoSphere 有没有被渲染、被剔除。这个坑本质上是渲染状态问题逐层检查最稳。5.2 画面上下颠倒或左右镜像现象视频播放出来地板跑到了头顶天花板在脚下或者左右方向反了往左转头实际看到的是右侧画面。原因全景视频拼接软件输出的方位约定和 Unity 球体 UV 不总一致。大部分工具输出时把第一行当作北极Unity 球体的 v0 在底部因此上下颠倒最常出现。左右镜像则是某些相机把画面做了水平翻转用于自拍或后置镜头预览。解决改 Shader 里的 UV 映射不要旋转球体。uv.y 1.0 - uv.y解决上下颠倒uv.x 1.0 - uv.x解决左右镜像。如果你发现上下和左右同时反那就是 u 和 v 都要翻转。注意别用球体旋转代替 UV 翻转因为等距柱状投影在极点附近的畸变不会随着球体旋转而修正最终画面依然是错位的。5.3 Android 手机上视频一播就闪退或黑屏现象PC 编辑器一切正常打包到 Android 手机或 Pico4 后点击播放直接闪退或者一直黑屏Logcat 里能看到 MediaCodec 相关报错。原因视频编码格式超出 Android 硬解能力。常见元凶是 H.265 10bit、ProRes、AV1、超高帧率 HEVC。另一个可能原因是 RenderTexture 分辨率远大于目标 GPU 支持上限导致分配失败。解决先用 MediaInfo 检查编码按 4.2 里的 ffmpeg 参数转成 H.264 High Profile yuv420p 30fps。然后降低 RenderTexture 到 2560×1280 以下再测。最后打开 Logcat搜索 MediaCodec 和 VideoPlayer 关键字真机报错总会留下具体异常。如果没有 Logcat至少用 Unity 的 Device Log 面板抓取完整日志。千万不要只凭“换台手机试试”来排查坑会很难定位。5.4 拖动进度条后画面卡在第一帧音频却在正常走现象用户在播放器中拖到后面某个时间点画面停在第一帧或黑屏但能听到对应时间点的声音进度条也在走。原因视频关键帧间隔太长。如果视频文件里关键帧每隔 5 秒甚至 10 秒才出现一次解码器跳帧时只能跳到上一个关键帧随后中间帧无法立刻重建画面就卡住了。解决转码时把关键帧间隔压到 1 秒。ffmpeg 里-g 30 -keyint_min 30就是为 30fps 视频生成每秒一个关键帧。如果已经上线来不及转码可以在代码里用 VideoPlayer.frame 做精确跳转但不是根治方法。另外网络播放要确认服务端支持 Range否则跳转请求会退化成整文件下载表现为进度条拖动极慢。5.5 声音先出画面黑屏随后才亮现象点击播放后耳机里已经响起视频声音但画面还是黑的几秒后才出第一帧。原因VideoPlayer 的 waitForFirstFrame 没有开启Prepare 完成后没有等待第一帧渲染就直接播放了。音频解码比视频渲染快所以声音先行。解决把 waitForFirstFrame 设为 true并且在 prepareCompleted 回调里再调用 Play。如果你用的是远程 URL还要额外处理isPrepared状态因为网络加载慢时 prepare 可能不会立刻完成。我一般会在准备阶段显示一个“加载中”的 UI避免用户以为应用卡死。6. 进阶在 Shader 里加调试网格验证全景视频的分辨率分配全景视频的实际清晰度分布经常和直觉相反等距柱状投影把大量像素浪费在天空和地面地平线附近的细节反而不足。以前我在 Pico4 上播放一个“看起来挺清晰”的素材结果播到建筑细节时画面发糊第一反应以为是换机后来才发现素材的码率分配有问题。后来养成一个习惯在 Shader 里加一个调试网格开关直接看 UV 展开覆盖是否合理。调试网格的原理很简单在 frag 里根据 UV 坐标画经线和纬线。均匀的经纬网格在球体上应该是均匀分布的如果发现某个区域网格线扭曲、拉伸说明视频源本身或 UV 映射有偏差。代码只需要改两处Properties 里加一个_DebugGrid开关frag 里在正常采样后叠加网格线。// Properties 块里加 _DebugGrid (Debug Grid, Range(0, 1)) 0 // frag 函数里采色后加 float grid step(0.98, frac(i.uv.x * 36.0)) step(0.98, frac(i.uv.y * 18.0)); col.rgb lerp(col.rgb, fixed3(1, 0, 0), saturate(grid) * _DebugGrid);这段代码把 36 条经线和 18 条纬线画成红色网格。打开开关后你可以转动相机观察网格在球面上的分布。如果网格线在赤道区域稀疏、在两极密集说明视频源把分辨率更多分给了上下区域这对VR观看是不利的因为人眼最关注地平线附近。相反如果赤道区域网格线也明显拥挤说明源文件本身分辨率足够问题出在 RenderTexture 分配到 4K 后的显示压榨不够。性能验证方面我除了看帧率还关注 VideoPlayer 的帧率掉帧情况。在 OnPrepared 之后可以在 Update 里记录videoPlayer.frame的变化连续几帧不增就是解码器跟不上。简单做法是显示一个实时 FPS 文本但我更推荐看解码器丢帧skipOnDrop为 true 时丢帧是静默的不能只靠数字判断卡顿。我在曝光前习惯跑一遍完整流程先用 1080p 测试视频验证硬解和 Shader再上 4K 高码率正式素材确认没有紫红色、黑屏、声音不同步最后才把调试网格关掉出包。这套流程帮我省下过不少在客户现场翻车的时间希望帮到你。本文还有配套的精品资源点击获取
返回列表