从Texture2D更改为VideoPlayer纹理

时间:2018-04-03 20:56:53

标签: unity3d

使用Unity 2017.3

我有一个RawImage,我在其中显示一系列作为Texture2D加载的图像。完美无缝地工作。

然后我使用示例VideoPlayer代码将视频显示到同一个RawImage中,然后分配

rawImage.texture = videoPlayer.texture;

视频播放效果非常好,但作为从静止图像切换到视频的一部分,RawImage中有明显的闪烁,就好像显示了一帧或两帧黑色一样。视频的第一帧与我显示的最后一个静态图像相匹配,因此我原本期望过渡非常无缝。

请注意,视频已经准备好了#34;在所有这些之前 - 我的代码yield直到videoPlayer.isPrepared返回true,然后告诉视频播放并设置纹理。

我想也许有一个问题纹理还没有完全准备好,但我在调用Play之前和设置纹理之前尝试过一次或两次,但这对闪烁没有任何影响。

我看到这个项目:https://answers.unity.com/questions/1294011/raw-image-flickering-when-texture-changed.html表明这与设置的材质实例有关。我不完全理解答案中提出的解决方案,也不了解我如何使其适应我自己的案例,但也许对于那些比我更熟练的人来说意味着什么。

关于如何摆脱那个flickery框架的任何建议?

编辑:这是代码

public class VideoAnimation : MonoBehaviour, IAnimation {
    private VideoPlayer _videoPlayer;
    private UnityAction _completeAction;
    private bool _prepareStarted;

    public void Configure(VideoClip clip, bool looping)
    {
        _videoPlayer = gameObject.AddComponent<VideoPlayer>();
        _videoPlayer.playOnAwake = false;
        _videoPlayer.isLooping = looping;
        _videoPlayer.source = VideoSource.VideoClip;
        _videoPlayer.clip = clip;
        _videoPlayer.skipOnDrop = true;
    }

    public void Prepare()
    {
        _prepareStarted = true;
        _videoPlayer.Prepare();
    }

    public void Begin(RawImage destImage, UnityAction completeAction)
    {
        _completeAction = completeAction;
        _videoPlayer.loopPointReached += OnLoopPointReached;
        StartCoroutine(GetVideoPlaying(destImage));
    }

    private IEnumerator GetVideoPlaying(RawImage destImage)
    {
        if (!_prepareStarted)
        {
            _videoPlayer.Prepare();
        }

        while(!_videoPlayer.isPrepared)
        {
            yield return null;
        }

        _videoPlayer.Play();
        destImage.texture = _videoPlayer.texture;
    }

    public void OnLoopPointReached(VideoPlayer source)
    {
        if (_completeAction != null)
        {
            _completeAction();
        }
    }

    public void End()
    {
        _videoPlayer.Stop();
        _videoPlayer.loopPointReached -= OnLoopPointReached;
    }

    public class Factory : Factory<VideoAnimation>
    {
    }
}

在我处理的特定情况下,提前调用ConfigurePrepare,而RawImage显示视频之前的最后一个静态图像。然后,当它显示视频时,会调用Begin。因此,_prepareStarted在调用true时已经Begin。插入日志消息显示isPrepared在我拨打true时正在返回Begin,因此我也不会在那里循环播放。

我尝试改变两行的顺序

    _videoPlayer.Play();
    destImage.texture = _videoPlayer.texture;

但它似乎没有改变任何东西。我还认为,VideoPlayer可能以某种方式在普通视频之前输出黑框,但在yield之后和纹理集之前插入Play或三个没有区别。

在插入Texture纹理之前,我见过的所有样本都没有RawImage VideoPlayer。所以在那些中,RawImage开始是黑色的,这意味着额外的黑色框架不会引人注目。

编辑#2:

嗯,我想出了一个解决方案,我认为,这有点解释。

首先,VideoPlayer.frame被记录为&#34; VideoPlayer当前正在显示的帧索引。&#34;这不是严格意义上的。或者,它可能是VideoPlayer管道中的某个地方,但它并不是使用VideoPlayer纹理的代码可以观察到的帧。

当您Prepare VideoPlayer时,至少在我使用它的模式下,VideoPlayer会创建一个内部RenderTexture。你会想想,一旦玩家准备好了,那个纹理就会包含视频的第一帧。它没有。在那之前有一个非常明显的延迟。因此,当我的代码将RawImage纹理设置为播放器的纹理时,它正在安排一个纹理,至少在那个时刻,它是空的以便显示。这完美地解释了黑色闪烁,因为它是背景画布的颜色。

所以我第一次尝试解决方案是在这里插入循环:

    _videoPlayer.Play();
    while(_videoPlayer.frame < 1)
    {
        yield return null;
    }
    destImage.texture = _videoPlayer.texture;

Play和纹理集之间。

我认为,尽管有文档,但frame可能会显示关于的框架。如果是这样,这应该导致第一个(第0个)帧已经在缓冲区中,并且将消除闪烁。不。仍然闪烁着。但是当我改为

    _videoPlayer.Play();
    while(_videoPlayer.frame < 2)
    {
        yield return null;
    }
    destImage.texture = _videoPlayer.texture;

然后过渡是无缝的。所以我最初尝试在两者之间插入产量是正确的方法 - 我只是没有插入足够的。简而言之,事实上。我在循环中插入了一个计数器,它显示我在上面的循环中产生了4次,这是我所期望的,因为视频是30fps,而我在我的计算机上以60fps运行。 (同步锁定已开启。)

最后的实验表明:

    _videoPlayer.Play();
    while(_videoPlayer.frame < 1)
    {
        yield return null;
    }
    yield return null;
    destImage.texture = _videoPlayer.texture;

也没有导致闪烁。 (或者,至少,不是我能看到的那个。)因此,一旦VideoPlayer报告它显示第二帧(根据文档数字是基于0的),在过渡之前需要一个额外的游戏帧。无缝。 (除非我的眼睛能看到第60个闪烁的第二个闪烁。)那个游戏框架可能与Unity的图形管道或VideoPlayer管道有关 - 我不知道

所以,最重要的是,从您拨打Play开始,直到VideoPlayer纹理中的任何内容实际出现在屏幕上的任何内容都会有明显的延迟,除非您等待为此,你将展示什么&#34;没有&#34; (在我的情况下,导致黑色背景闪烁。)

我发现,由于VideoPlayer正在生成RenderTexture,因此也可以将之前的静态纹理blit到VideoPlayer的纹理(这样就会有一些东西)然后再进行立即切换。另一个试运行...

2 个答案:

答案 0 :(得分:0)

嗯,让我们尝试使用着色器,也许它可以帮到你。

首先,我们必须创建自定义着色器,它必须像标准UI着色器一样工作。 您可以在this link下载所有内置着色器。

UI-Default.shader并修改它。我为你修改了它!

只需在Unity中创建shader并粘贴此代码:

Shader "Custom/CustomShaderForUI"
{
Properties
{
    //[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    _CustomTexture ("Texture", 2D) = "white" {} // <--------------- new property
    _Color ("Tint", Color) = (1,1,1,1)

    _StencilComp ("Stencil Comparison", Float) = 8
    _Stencil ("Stencil ID", Float) = 0
    _StencilOp ("Stencil Operation", Float) = 0
    _StencilWriteMask ("Stencil Write Mask", Float) = 255
    _StencilReadMask ("Stencil Read Mask", Float) = 255

    _ColorMask ("Color Mask", Float) = 15

    [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}

SubShader
{
    Tags
    {
        "Queue"="Transparent"
        "IgnoreProjector"="True"
        "RenderType"="Transparent"
        "PreviewType"="Plane"
        "CanUseSpriteAtlas"="True"
    }

    Stencil
    {
        Ref [_Stencil]
        Comp [_StencilComp]
        Pass [_StencilOp]
        ReadMask [_StencilReadMask]
        WriteMask [_StencilWriteMask]
    }

    Cull Off
    Lighting Off
    ZWrite Off
    ZTest [unity_GUIZTestMode]
    Blend SrcAlpha OneMinusSrcAlpha
    ColorMask [_ColorMask]

    Pass
    {
        Name "Default"
    CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma target 2.0

        #include "UnityCG.cginc"
        #include "UnityUI.cginc"

        #pragma multi_compile __ UNITY_UI_CLIP_RECT
        #pragma multi_compile __ UNITY_UI_ALPHACLIP

        struct appdata_t
        {
            float4 vertex   : POSITION;
            float4 color    : COLOR;
            float2 texcoord : TEXCOORD0;
            UNITY_VERTEX_INPUT_INSTANCE_ID
        };

        struct v2f
        {
            float4 vertex   : SV_POSITION;
            fixed4 color    : COLOR;
            float2 texcoord  : TEXCOORD0;
            float4 worldPosition : TEXCOORD1;
            UNITY_VERTEX_OUTPUT_STEREO
        };

        fixed4 _Color;
        fixed4 _TextureSampleAdd;
        float4 _ClipRect;

        v2f vert(appdata_t v)
        {
            v2f OUT;
            UNITY_SETUP_INSTANCE_ID(v);
            UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
            OUT.worldPosition = v.vertex;
            OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

            OUT.texcoord = v.texcoord;

            OUT.color = v.color * _Color;
            return OUT;
        }

        //sampler2D _MainTex;
        sampler2D _CustomTexture; // <---------------------- new property

        fixed4 frag(v2f IN) : SV_Target
        {
            //half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
            half4 color = (tex2D(_CustomTexture, IN.texcoord) + _TextureSampleAdd) * IN.color; // <- using new property

            #ifdef UNITY_UI_CLIP_RECT
            color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
            #endif

            #ifdef UNITY_UI_ALPHACLIP
            clip (color.a - 0.001);
            #endif

            return color;
        }
    ENDCG
    }
}
}

接下来,创建一个材质并将着色器中的RenderTexture设置为纹理字段(RawImage组件中的不是)。

example of custom shader for ui, texture takes from shader

希望这有帮助!

答案 1 :(得分:0)

尝试在加载视频时隐藏RawImage游戏对象。这应该可以解决由于VideoPlayer未完全加载而引起的任何闪烁。

public VideoPlayer _videoPlayer;
public RawImage _videoImage;

private void PlayClip(VideoClip videoClip)
{
    StartCoroutine(PlayClipCoroutine(videoClip));
}

private IEnumerator PlayClipCoroutine(VideoClip clip) 
{
  _videoImage.gameObject.SetActive(false);
  _videoPlayer.clip = clip;
  _videoPlayer.Prepare();

  while (!_videoPlayer.isPrepared) 
  {
      yield return null;
  }

  _videoPlayer.Play();
  _videoImage.texture = _videoPlayer.texture;
  _videoImage.gameObject.SetActive(true);
}