스트리밍 컨트롤러
레이어

텍스처 스트리밍 API

이 페이지는 다음 섹션으로 구성됩니다.

개요

API를 사용하여 Unity가 텍스처를 스트리밍하는 방식을 더욱 세부적으로 제어하십시오. 특정 텍스처에 대해 로드할 밉맵 레벨을 오버라이드할 수 있으며, 텍스처 스트리밍 시스템이 다른 모든 텍스처를 자동으로 관리합니다. Unity가 특정 텍스처를 완전히 로드해야 하는 특정한 게임플레이 시나리오가 있을 수 있습니다. 예를 들어 먼 거리를 빠르게 이동하거나, 순간적인 카메라 컷을 사용하는 경우 텍스처 스트리밍 시스템이 디스크에서 메모리로 밉맵을 스트리밍하는 동안 눈에 띄는 텍스처 품질 변화가 일어날 수 있습니다. 이러한 문제를 줄이기 위해 API를 사용하여 새로운 카메라 위치에 밉맵을 미리 로드할 수 있습니다.

텍스처에 대해 텍스처 스트리밍을 활성화하고 제어하려면 다음 프로퍼티를 사용하십시오.

텍스처 스트리밍은 텍스처 스트리밍 Memory Budget 에 적합할 때까지 텍스처의 크기를 자동으로 줄입니다. 텍스처의 Mip Map Priority 숫자는 Memory Budget 에 대한 대략적인 밉맵 오프셋입니다. 예를 들어 우선 순위가 2인 경우 텍스처 스트리밍 시스템은 우선 순위가 0인 텍스처보다 밉 레벨이 2만큼 더 높은 밉맵을 사용합니다. 음수 값도 유효합니다. 이렇게 할 수 없는 경우에는 더 낮은 밉 레벨을 사용하여 Memory Budget 에 맞춥니다.

텍스처 스트리밍 시스템 제어

These are read-only at run time:

런타임 시점에 발생하는 일을 제어하려면 다음의 정적 프로퍼티를 사용하십시오.

To control the way Unity caches unused mip levels via script, use Texture2D.streamingTextureDiscardUnusedMips. It’s useful to set this to false for initial testing to check metrics, and to set a Memory Budget (set in Quality Settings when Texture Streaming is enabled, or via QualitySettings.streamingMipmapsMemoryBudget), otherwise Unity doesn’t drop any mips. The Memory Budget is set to 512MB by default.

텍스처 스트리밍용 카메라 제어

품질 설정(Edit > Project Settings > Quality)에서 Add All Cameras 를 사용하여 Unity가 프로젝트의 모든 카메라에 대해 텍스처 스트리밍을 계산해야 하는지를 지정합니다. 이 옵션은 기본적으로 활성화됩니다.

활성화할 카메라를 세밀하게 제어하려면 Camera 컴포넌트와 동일한 게임 오브젝트에 Streaming Controller 컴포넌트를 사용하십시오. 그러면 Camera 컴포넌트에서 직접 위치 및 카메라 설정(예: Field of View)을 가져옵니다.

카메라가 비활성화되어 있으면 Unity는 이 카메라에 대한 텍스처 스트리밍을 계산하지 않습니다. 단, 스트리밍 컨트롤러가 활성화되어 있고 사전 로딩 상태인 경우에는 예외입니다. 스트리밍 컨트롤러와 연결된 카메라가 활성화되어 있거나 스트리밍 컨트롤러가 사전 로딩 상태인 경우 Unity는 이 카메라에 대한 텍스처 스트리밍을 계산합니다. 스트리밍 컨트롤러가 비활성화되면 Unity는 이 카메라에 대한 텍스처 스트리밍을 계산하지 않습니다.

Streaming Controller 컴포넌트
Streaming Controller 컴포넌트

Streaming Controller 컴포넌트에는 Mip Map Bias 설정이 들어 있습니다. API를 통해 이 설정을 제어하려면 StreamingController.streamingMipmapBias를 사용하십시오.

이 설정을 사용하면 텍스처 스트리밍 시스템이 텍스처에 대해 선택한 것보다 더 작거나 큰 밉맵 레벨을 로드하도록 Unity를 강제 설정할 수 있습니다. 숫자 필드를 사용하여 Unity가 밉맵 레벨에 적용하는 오프셋을 설정하십시오. Unity는 이 카메라에서 보이는 모든 텍스처에 이 오프셋을 추가합니다.

카메라 컷

한 위치에서 다른 위치로 컷할 때 텍스처 스트리밍 시스템은 필요한 텍스처를 Unity에 스트리밍할 시간이 필요합니다. 비활성화된 타겟 카메라 위치에서 사전 로딩을 트리거하려면 타겟 카메라의 Streaming Controller 컴포넌트에 대해 StreamingController.SetPreloading을 호출하십시오. 사전 로딩 단계를 끝낼 타임아웃을 지정할 수도 있습니다. 사전 로딩 단계가 끝날 때 카메라를 자동으로 활성화하려면 스크립트에서 activateCameraOnTimeout 플래그를 true로 설정하십시오. 새로운 위치로 컷한 후 카메라를 비활성화하려면 해당 카메라를 disableCameraCuttingFrom 파라미터로 전달해야 합니다.

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

사전 로딩 상태를 취소하거나 쿼리하려면 다음 메서드를 사용하십시오.

텍스처 스트리밍 시스템이 여전히 텍스처를 로드하는지 확인하기 위해 다음 프로퍼티를 쿼리할 수 있습니다.

카메라를 활성화하는 시점과 이러한 프로퍼티가 0 이외의 값이 되는 시점 사이에는 지연이 존재합니다. 이러한 지연은 텍스처 스트리밍 시스템이 타임 슬라이스 프로세싱을 사용하여 밉맵을 계산하기 때문에 일어납니다. 이러한 이유로 카메라 컷 동안에는 컷 전에 최소 시간을 대기해야 합니다. 텍스처 메모리 할당량과 씬 이동으로 인해 연속 텍스처 스트리밍이 발생할 수 있으므로 컷 전에 최대 시간도 설정해 두어야 합니다.

특정 밉맵 로드

특정 텍스처에 대한 밉 레벨 계산을 오버라이드하려면 Texture2D.requestedMipmapLevel을 사용하십시오. 이 값은 0에서 특정 텍스처의 최대 밉 레벨 사이에 존재하는 정확한 밉 레벨이거나, 또는 해당 값이 0보다 낮으면 Max Level Reduction 값입니다. 0은 최고 해상도 밉입니다.

요청한 밉 레벨이 로드되었는지 확인하려면 Texture2D.IsRequestedMipmapLevelLoaded를 사용하십시오.

요청한 밉 레벨을 더 이상 오버라이드하길 원하지 않고, 대신에 시스템이 밉맵 레벨을 계속 계산하길 원하는 경우 Texture2D.ClearRequestedMipmapLevel을 사용하여 값을 재설정하십시오.

메시에 대한 UV 밀도 근사치를 얻으려면 다음을 사용하십시오.

float Mesh.GetUVDistributionMetric(int uvSetIndex)

UV 배포 지표를 사용하여 카메라의 포지션에 따라 필요한 밉맵 레벨을 계산할 수 있습니다. 예제 코드는 Mesh.GetUVDistributionMetric을 참조하십시오.

시스템을 오버라이드하고 모든 밉을 강제로 로드하려면 Texture.streamingTextureForceLoadAll을 사용하십시오.

관련 API 메서드

머티리얼에 할당된 텍스처를 가져오거나 설정하려면 다음을 사용하십시오.

머티리얼에 대한 모든 텍스처 프로퍼티를 가져오려면 다음을 사용하십시오.

텍스처 스트리밍 디버그

Unity에는 빌트인 텍스처 스트리밍 디버깅 뷰 모드가 있습니다. 이 모드에 액세스하려면 씬 뷰 컨트롤 드롭다운을 클릭한 후 Texture Streaming 을 선택하십시오. 이 뷰 모드는 텍스처 스트리밍 시스템의 상태에 따라 게임 오브젝트에 다음 컬러를 입힙니다.

  • 녹색 : 텍스처 스트리밍 시스템으로 인해 밉맵 수가 감소한 텍스처.
  • 빨간색: 텍스처 스트리밍 시스템에 모든 밉맵을 로드할 만큼 리소스가 충분하지 않아서 적은 수의 밉맵을 가진 텍스처.
  • 파란색 : 스트리밍이 설정되지 않은 텍스처. 또는 밉 레벨을 계산할 렌더러가 없음.

스크립트를 통한 디버그

디버그 모드용 머티리얼 프로퍼티를 업로드하려면 Texture.SetStreamingTextureMaterialDebugProperties를 사용하십시오.

디버그 모드용 머티리얼 프로퍼티를 업로드하려면 다음 프로퍼티를 사용하십시오.

텍스처 스트리밍 시스템이 상호작용하는 렌더러 또는 텍스처 개수에 대한 정보를 확인하려면 다음 프로퍼티를 사용하십시오.

밉맵 레벨에 관한 정보를 확인하려면 다음 프로퍼티를 사용하십시오.

라이트맵

텍스처 스트리밍 시스템을 사용하여 라이트맵을 스트리밍할 수 있습니다. 텍스처 설정을 직접 편집할 수 있지만, Unity가 라이트맵을 다시 생성하면 기본값으로 재설정됩니다. 플레이어 설정(Edit > Project Settings > Player)은 생성된 라이트맵에 대해 스트리밍과 우선 순위를 설정할 수 있도록 두 가지 제어 옵션, 즉 Lightmap Streaming EnabledStreaming Priority 를 제공합니다.

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

Play 모드

기본적으로 텍스처 스트리밍은 Play 모드에서 활성화됩니다. 하지만 Play 모드에서 실행 중일 때 에디터 오버헤드가 통계치를 왜곡할 수 있습니다. 정확한 수치를 얻으려면 타겟 디바이스에서 애플리케이션을 테스트하십시오.

Texture Streaming makes toggling in and out of Play Mode take slightly longer, because it needs to load extra information and more calculations. To disable Texture Streaming in Play Mode, go to the Editor Settings (Edit > Project Settings > Editor), navigate to Streaming Settings and disable Enabled Texture Streaming in Play Mode. This prevents Unity from unloading and reloading mipmap data, and should speed up the Play Mode workflow.

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
}
스트리밍 컨트롤러
레이어