Version: 2019.1
串流控制器 (Streaming Controller)

纹理串流 API

本页包含以下部分:

概述

使用 API 可对 Unity 串流纹理的方式进行更有力的控制。您可以覆盖要为特定纹理加载的 Mipmap 级别,而纹理串流系统将自动管理所有其他纹理。在特定的游戏项目中,您可能知道 Unity 需要完全加载某些纹理。例如,远距离快速移动或者使用瞬间摄像机切换镜头,可能会导致明显的纹理质量变化,而纹理串流系统会将 Mipmap 从磁盘串流到内存。为了减少这个问题,可以使用 API 在新的摄像机位置预加载 Mipmap。

要对纹理启用和控制纹理串流,请使用以下属性:

纹理串流可自动减小纹理的大小,直到它们符合纹理串流__内存预算 (Memory Budget)。纹理的 Mipmap 优先级 (Mip Map Priority)__ 数值大致是__内存预算 (Memory Budget)__ 的 Mipmap 偏移。例如,优先级为 2 时,纹理串流系统尝试使用比优先级为 0 的其他纹理高出两个 Mip 级别的 Mipmap。负值也有效。如果无法实现,则会使用更低的 Mip 级别来适应__内存预算 (Memory Budget)__。

控制纹理串流系统

以下属性在运行时为只读:

要控制运行时的行为,请使用以下静态属性:

要通过脚本控制 Unity 缓存未使用的 Mip 级别的方式,请使用 Texture2D.streamingTextureDiscardUnusedMips。一种很有效的做法是在初始测试中将此属性设置为 true 来检查指标,并设置 Memory Budget__(如果启用了 Texture Streaming__,则在 Quality 设置中进行此设置,或者通过 QualitySettings.streamingMipmapsMemoryBudget 进行设置),否则 Unity 不会丢弃任何 Mip。默认情况下,__Memory Budget__ 设置为 512MB。

控制纹理串流的摄像机

在 Quality 设置 (Edit > Project Settings > Quality) 中,使用 Add All Cameras 指定 Unity 是否应该为项目中的所有摄像机计算纹理串流。默认情况下会启用此选项。

为了更精细地控制哪些摄像机处于活动状态,请在与 Camera 组件相同的游戏对象上使用 Streaming Controller 组件。这会直接从 Camera 组件获取位置和摄像机设置(如 __Field of View__)。

如果禁用了摄像机,除非启用 Streaming Controller 并处于预加载状态,否则 Unity 不会为摄像机计算纹理串流。启用了 Streaming Controller 或关联的摄像机时,或者如果 Streaming Controller 处于预加载状态,则 Unity 将为此摄像机计算纹理串流。如果禁用了 Streaming Controller,则 Unity 将不会为此摄像机计算纹理串流。

Streaming Controller 组件
Streaming Controller 组件

Streaming Controller 组件包含 Mip Map Bias 设置。要通过 API 来控制此设置,请使用 StreamingController.streamingMipmapBias

使用此设置可以强制 Unity 加载比纹理串流系统为这些纹理选择的更高或更低的 Mipmap 级别。使用数字字段来设置 Unity 应用于 Mipmap 级别的偏移。Unity 可将此偏移添加到在此摄像机中可见的所有纹理。

摄像机切换镜头

从一个位置切换到另一个位置时,纹理串流系统需要一定的时间将所需纹理串流到 Unity。要在某个禁用的目标摄像机位置触发预加载,请在目标摄像机的 Streaming Controller 组件上调用 StreamingController.SetPreloading。可指定一个超时来结束预加载阶段。要在预加载阶段结束时自动启用摄像机,请在脚本中将 activateCameraOnTimeout 标志设置为 true。要在从某个摄像机切换到新摄像机之后禁用此摄像机,请以 disableCameraCuttingFrom 参数传递该摄像机。

void StreamingController.SetPreloading(float timeoutSeconds=0.0f, bool activateCameraOnTimeout=false, Camera disableCameraCuttingFrom=null)

要取消或查询预加载状态,请使用下列方法:

要确定纹理串流系统是否仍在加载纹理,可以查询以下属性:

请注意,启用摄像机与这些属性变为非零值之间存在延迟。这种延迟是因为纹理串流系统使用时间切片处理技术来计算 Mipmap。鉴于此原因,在摄像机切换镜头时,应该在切换镜头之前等待一个最短时间。纹理预算和场景移动会导致连续的纹理串流,所以还需要设置在切换镜头之前的最长等待时间。

加载特定 Mipmap

要覆盖特定纹理的 Mip 级别计算,请使用 Texture2D.requestedMipmapLevel。这是一个精确的 Mip 级别,范围从 0 到特定纹理的最高 Mip 级别或者是 Max Level Reduction 值(如果该值更大)。0 表示最高分辨率的 Mip。

要检查您请求的 Mip 级别是否已加载,请使用 Texture2D.IsRequestedMipmapLevelLoaded

如果不再希望覆盖所请求的 Mip 级别,而是希望系统继续计算 Mipmap 级别,请使用 Texture2D.ClearRequestedMipmapLevel 来重置值。

要估算网格上的 UV 密度,请使用以下代码行:

float Mesh.GetUVDistributionMetric(int uvSetIndex)

可以使用 UV 分布指标根据摄像机的位置来计算所需的 Mipmap 级别。请参阅 Mesh.GetUVDistributionMetric 以查看代码示例。

要覆盖系统并强制加载所有 Mip,请使用 Texture.streamingTextureForceLoadAll

相关的 API 方法

要获取和设置分配给某一材质的纹理,请使用:

要获取某一材质中的所有纹理属性,请使用:

调试纹理串流

Unity 具有内置的纹理串流调试视图模式。要访问此模式,请单击 Scene 视图控制下拉菜单,然后选择 Texture Streaming。此视图模式根据游戏对象在纹理串流系统中的状态将游戏对象显示为以下颜色:

  • __绿色__表示由于纹理串流系统而降低了 Mipmap 的纹理。
  • __红色__表示由于纹理串流系统没有足够资源来加载全部 Mipmap 而减少了 Mipmap 的纹理。
  • __蓝色__表示未设置为串流的纹理,或者在没有渲染器计算 Mip 级别的情况下的纹理。

通过脚本调试

要为调试模式上传材质属性,请使用 Texture.SetStreamingTextureMaterialDebugProperties

要为调试模式上传材质属性,请使用以下属性:

要获取与纹理串流系统交互的纹理或渲染器的数量信息,请使用以下属性:

要获取有关 Mipmap 级别的信息,请使用以下属性:

光照贴图

可以使用纹理串流系统来串流光照贴图。可以直接编辑纹理设置,但在 Unity 重新生成光照贴图时,纹理设置会重置为默认值。Player 设置 (Edit > Project Settings > Player) 中提供了如下两个控件来设置生成的光照贴图的串流和优先级:__Lightmap Streaming Enabled__ 和 Streaming Priority

Edit > Project Settings > Player > Other Settings
Edit > Project Settings > Player > Other Settings

播放模式

默认情况下,在播放模式下会启用纹理串流。但是,在播放模式下运行时,Editor 开销会导致统计信息出现偏差。要获取精确数字,请在目标设备上测试应用程序。

如果在运行模式下启用了纹理串流,但在编辑模式下未启用(或者是相反的情况),则切换到运行模式和从运行模式退出时所花费的时间会稍长一些。要在运行模式下禁用纹理串流,请选择 Editor 设置 (Edit > Project Settings > Editor),导航到 Streaming Settings__,并禁用 Enabled Texture Streaming in Play Mode__。这会阻止 Unity 卸载和重新加载 Mipmap 数据,并且应该会加快运行模式工作流程。

Edit > Project Settings > Editor > Streaming Settings
Edit > Project Settings > Editor > Streaming Settings

调试状态脚本

将以下脚本添加到场景中的游戏对象以显示纹理串流状态。如果希望确定要设置的正确内存预算,此脚本非常有用。

Shader "Show Texture Streaming" {
    Properties {
        _MainTex ("", 2D) = "white" {}
        _Control ("Control (RGBA)", 2D) = "red" {}
        _Splat3 ("Layer 3 (A)", 2D) = "white" {}
        _Splat2 ("Layer 2 (B)", 2D) = "white" {}
        _Splat1 ("Layer 1 (G)", 2D) = "white" {}
        _Splat0 ("Layer 0 (R)", 2D) = "white" {}
        _BaseMap ("", 2D) = "white" {}
        _Cutoff ("Cutoff", float) = 0.5
    }


CGINCLUDE
// Common code used by most of the things below
# include "UnityCG.cginc"
struct v2f {
    float4 pos : SV_POSITION;
    float2 uv : TEXCOORD0;
};
uniform float4 _MainTex_ST;
uniform float4 _MainTex_TexelSize;
uniform float4 _MainTex_MipInfo;

UNITY_DECLARE_TEX2D(_MainTex);
UNITY_DECLARE_TEX2D(_SceneViewMipcolorsTexture);

uint GetMipCount(Texture2D tex)
{
# if defined(SHADER_API_D3D11) || defined(SHADER_API_D3D12) || defined(SHADER_API_D3D11_9X) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_PSSL)
    #define MIP_COUNT_SUPPORTED 1
# endif
# if (defined(SHADER_API_OPENGL) || defined(SHADER_API_VULKAN)) && !defined(SHADER_STAGE_COMPUTE)
    // OpenGL only supports textureSize for width, height, depth
    // textureQueryLevels (GL_ARB_texture_query_levels) needs OpenGL 4.3 or above and doesn't compile in compute shaders
    // tex.GetDimensions converted to textureQueryLevels
    #define MIP_COUNT_SUPPORTED 1
# endif
    // Metal doesn't support high enough OpenGL version

# if defined(MIP_COUNT_SUPPORTED)
    uint mipLevel, width, height, mipCount;
    mipLevel = width = height = mipCount = 0;
    tex.GetDimensions(mipLevel, width, height, mipCount);
    return mipCount;
# else
    return 0;
# endif
}

float4 GetStreamingMipColor(uint mipCount, float4 mipInfo)
{
    // alpha is amount to blend with source color (0.0 = use original, 1.0 = use new color)

    // mipInfo :
    // x = quality setings minStreamingMipLevel
    // y = original mip count for texture
    // z = desired on screen mip level
    // w = loaded mip level
    uint originalTextureMipCount = uint(mipInfo.y);

    // If material/shader mip info (original mip level) has not been set it’s either not a streamed texture 
    // or no renderer is updating it
    if (originalTextureMipCount == 0)
        return float4(0.0, 0.0, 1.0, 0.5);

    uint desiredMipLevel = uint(mipInfo.z);
    uint mipCountDesired = uint(originalTextureMipCount)-uint(desiredMipLevel);
    if (mipCount == 0)
    {
        // Can't calculate, use the passed value
        mipCount = originalTextureMipCount - uint(mipInfo.w);
    }

    if (mipCount < mipCountDesired)
    {
        // red tones when not at the desired mip level (reduction due to budget). Brighter is further from original, alpha 0 when at desired
        float ratioToDesired = float(mipCount) / float(mipCountDesired);
        return float4(1.0, 0.0, 0.0, 1.0 - ratioToDesired);
    }
    else if (mipCount >= originalTextureMipCount)
    {
        // original color when at (or beyond) original mip count
        return float4(1.0, 1.0, 1.0, 0.0);
    }
    else
    {
        // green tones when not at the original mip level. Brighter is closer to original, alpha 0 when at original
        float ratioToOriginal = float(mipCount) / float(originalTextureMipCount);
        return float4(0.0, 1.0, 0.0, 1.0 - ratioToOriginal);
    }
}

float3 GetDebugStreamingMipColorBlended(float3 originalColor, Texture2D tex, float4 mipInfo)
{
    uint mipCount = GetMipCount(tex);
    float4 mipColor = GetStreamingMipColor(mipCount, mipInfo);
    return lerp(originalColor, mipColor.rgb, mipColor.a);
}


v2f vert( appdata_base v ) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
    
    return o;
}

fixed4 frag(v2f i) : COLOR
{
    fixed4 col = UNITY_SAMPLE_TEX2D(_MainTex, i.uv);
    half4 res;
    res.rgb = GetDebugStreamingMipColorBlended(col.rgb, _MainTex, _MainTex_MipInfo);
    res.a = col.a;
    return res;
}

struct v2fGrass {
    float4 pos : SV_POSITION;
    fixed4 color : COLOR;
    float2 uv : TEXCOORD0;
};

fixed4 fragGrass(v2fGrass i) : COLOR
{
    fixed4 col = UNITY_SAMPLE_TEX2D(_MainTex, i.uv);
    half4 res;
    res.rgb = GetDebugStreamingMipColorBlended(col.rgb, _MainTex, _MainTex_MipInfo);
    res.a = col.a * i.color.a;
    return res;
}
ENDCG

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="Opaque" }
    Pass {
CGPROGRAM

// As both normal opaque shaders and terrain splat shaders
// have "Opaque" render type, we need to do some voodoo
// to make both work.

# pragma vertex vertWTerrain
# pragma fragment fragWTerrain
# pragma target 2.0
# pragma exclude_renderers gles

struct v2fterr {
    float4 pos : SV_POSITION;
    float2 uvnormal : TEXCOORD0;
    float4 uv[3] : TEXCOORD2;
    float nonterrain  : TEXCOORD5;
};

uniform float4 _Splat0_ST,_Splat1_ST,_Splat2_ST,_Splat3_ST,_Splat4_ST;
uniform float4 _Splat0_TexelSize,_Splat1_TexelSize,_Splat2_TexelSize,_Splat3_TexelSize,_Splat4_TexelSize;
uniform float4 _BaseMap_TexelSize;

v2fterr vertWTerrain( appdata_base v ) {
    v2fterr o;
    o.pos = UnityObjectToClipPos(v.vertex);
    // assume it's not a terrain if _Splat0_TexelSize is not set up.
    float nonterrain = _Splat0_TexelSize.z==0.0 ? 1:0;
    // collapse/don't draw terrain's add pass in this mode, since it looks really bad if first pass
    // and add pass blink depending on which gets drawn first with this replacement shader
    // TODO: make it display mips properly even for two-pass terrains. 
    o.pos *= _MainTex_TexelSize.z==0.0 && _Splat0_TexelSize.z!=0.0 ? 0 : 1;
    // normal texture UV
    o.uvnormal = TRANSFORM_TEX(v.texcoord,_MainTex);
    // terrain splat UVs
    float2 baseUV = v.texcoord.xy;
    o.uv[0].xy = baseUV;
    o.uv[0].zw = half2(0,0);
    o.uv[1].xy = TRANSFORM_TEX (baseUV, _Splat0);
    o.uv[1].zw = TRANSFORM_TEX (baseUV, _Splat1);
    o.uv[2].xy = TRANSFORM_TEX (baseUV, _Splat2);
    o.uv[2].zw = TRANSFORM_TEX (baseUV, _Splat3);
    
    o.nonterrain = nonterrain;
    return o;
}
UNITY_DECLARE_TEX2D(_Control);
UNITY_DECLARE_TEX2D(_Splat0);
UNITY_DECLARE_TEX2D(_Splat1);
UNITY_DECLARE_TEX2D(_Splat2);
UNITY_DECLARE_TEX2D(_Splat3);
UNITY_DECLARE_TEX2D(_BaseMap);
fixed4 fragWTerrain(v2fterr i) : COLOR
{
    // sample regular texture
    fixed4 colnormal = UNITY_SAMPLE_TEX2D(_MainTex, i.uvnormal);
    
    // sample splatmaps
    half4 splat_control = UNITY_SAMPLE_TEX2D(_Control, i.uv[0].xy);
    half3 splat_color = splat_control.r * UNITY_SAMPLE_TEX2D(_Splat0, i.uv[1].xy).rgb;
    splat_color += splat_control.g * UNITY_SAMPLE_TEX2D(_Splat1, i.uv[1].zw).rgb;
    splat_color += splat_control.b * UNITY_SAMPLE_TEX2D(_Splat2, i.uv[2].xy).rgb;
    splat_color += splat_control.a * UNITY_SAMPLE_TEX2D(_Splat3, i.uv[2].zw).rgb;
    
    // lerp between normal and splatmaps
    half3 col = lerp(splat_color, colnormal.rgb, (half)i.nonterrain);

    half4 res;
    // TODO: Take splat mips into account
    res.rgb = GetDebugStreamingMipColorBlended(col.rgb, _MainTex, _MainTex_MipInfo);
    res.a = colnormal.a;
    
    return res;
}
ENDCG
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="Transparent" }
    Pass {
        Cull Off
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
ENDCG
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="TransparentCutout" }
    Pass {
        AlphaTest Greater [_Cutoff]
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
ENDCG
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="TreeBark" }
    Pass {
CGPROGRAM
# pragma vertex vertTreeBark
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "UnityCG.cginc"
# include "UnityBuiltin3xTreeLibrary.cginc"
v2f vertTreeBark (appdata_full v) {
    v2f o;
    TreeVertBark(v);
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
}
ENDCG
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="TreeLeaf" }
    Pass {
CGPROGRAM
# pragma vertex vertTreeLeaf
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "UnityCG.cginc"
# include "UnityBuiltin3xTreeLibrary.cginc"
v2f vertTreeLeaf (appdata_full v) {
    v2f o;
    TreeVertLeaf (v);
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
}
ENDCG
        AlphaTest GEqual [_Cutoff]
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="TreeOpaque" }
    Pass {
CGPROGRAM
# pragma vertex vertTree
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
struct appdata {
    float4 vertex : POSITION;
    fixed4 color : COLOR;
    float2 texcoord : TEXCOORD0;
};
v2f vertTree( appdata v ) {
    v2f o;
    TerrainAnimateTree(v.vertex, v.color.w);
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
}
ENDCG
    }
} 

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="TreeTransparentCutout" }
    Pass {
        Cull Off
CGPROGRAM
# pragma vertex vertTree
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
struct appdata {
    float4 vertex : POSITION;
    fixed4 color : COLOR;
    float4 texcoord : TEXCOORD0;
};
v2f vertTree( appdata v ) {
    v2f o;
    TerrainAnimateTree(v.vertex, v.color.w);
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
}
ENDCG
        AlphaTest GEqual [_Cutoff]
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="TreeBillboard" }
    Pass {
        Cull Off
        ZWrite Off
CGPROGRAM
# pragma vertex vertTree
# pragma fragment frag
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
v2f vertTree (appdata_tree_billboard v) {
    v2f o;
    TerrainBillboardTree(v.vertex, v.texcoord1.xy, v.texcoord.y);
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv.x = v.texcoord.x;
    o.uv.y = v.texcoord.y &gt; 0;
    return o;
}
ENDCG
        
        SetTexture [_MainTex] { combine primary, texture }
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="GrassBillboard" }
    Pass {
        Cull Off
CGPROGRAM
# pragma vertex vertGrass
# pragma fragment fragGrass
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
v2fGrass vertGrass (appdata_full v) {
    v2fGrass o;
    WavingGrassBillboardVert (v);
    o.color = v.color;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
}
ENDCG
        AlphaTest Greater [_Cutoff]
    }
}

SubShader {
    Tags { "ForceSupported" = "True" "RenderType"="Grass" }
    Pass {
        Cull Off
CGPROGRAM
# pragma vertex vertGrass
# pragma fragment fragGrass
# pragma target 2.0
# pragma exclude_renderers gles
# include "TerrainEngine.cginc"
v2fGrass vertGrass (appdata_full v) {
    v2fGrass o;
    WavingGrassVert (v);
    o.color = v.color;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.uv = v.texcoord;
    return o;
}
ENDCG
        AlphaTest Greater [_Cutoff]
    }
}

Fallback Off
}
串流控制器 (Streaming Controller)