本页包含以下部分:
使用 API 可对 Unity 串流纹理的方式进行更有力的控制。您可以覆盖要为特定纹理加载的 Mipmap 级别,而 Mipmap 串流系统将自动管理所有其他纹理。在特定的游戏项目中,您可能知道 Unity 需要完全加载某些纹理。例如,远距离快速移动或者使用瞬间摄像机切换镜头,可能会导致明显的纹理质量变化,而 Mipmap 串流系统会将 Mipmap 从磁盘串流到内存。为了减少这个问题,可以使用 API 在新的摄像机位置预加载 Mipmap。
要对纹理启用和控制 Mipmap 串流,请使用以下属性:
Mipmap 串流可自动减小纹理的大小,直到它们符合 Mipmap 串流内存预算 (Memory Budget)。纹理的 Mipmap 优先级 数值大致是内存预算 的 Mipmap 偏移。例如,优先级为 2 时,Mipmap 串流系统尝试使用比优先级为 0 的其他纹理高出两个 Mip 级别的 Mipmap。负值也有效。如果无法实现,则会使用更低的 Mip 级别来适应内存预算 (Memory Budget)。
以下属性在运行时为只读:
要控制运行时的行为,请使用以下静态属性:
QualitySettings.streamingMipmapsMemoryBudget(默认为 512 MB)
要通过脚本控制 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 是否应该为项目中的所有摄像机计算 Mipmap 串流。默认情况下会启用此选项。
为了更精细地控制哪些摄像机处于活动状态,请在与 Camera 组件相同的游戏对象上使用 Streaming Controller 组件。这会直接从 Camera 组件获取位置和摄像机设置(如 Field of View)。
如果禁用了摄像机,除非启用 Streaming Controller 并处于预加载状态,否则 Unity 不会为摄像机计算 Mipmap 串流。启用了 Streaming Controller 或关联的摄像机时,或者如果 Streaming Controller 处于预加载状态,则 Unity 将为此摄像机计算 Mipmap 串流。如果禁用了 Streaming Controller,则 Unity 将不会为此摄像机计算 Mipmap 串流。
Streaming Controller 组件包含 Mip Map Bias 设置。要通过 API 来控制此设置,请使用 StreamingController.streamingMipmapBias。
使用此设置可以强制 Unity 加载比 Mipmap 串流系统为这些纹理选择的更高或更低的 Mipmap 级别。使用数字字段来设置 Unity 应用于 Mipmap 级别的偏移。Unity 可将此偏移添加到在此摄像机中可见的所有纹理。
从一个位置切换到另一个位置时,Mipmap 串流系统需要一定的时间将所需纹理串流到 Unity。要在某个禁用的目标摄像机位置触发预加载,请在目标摄像机的 Streaming Controller 组件上调用 StreamingController.SetPreloading。可指定一个超时来结束预加载阶段。要在预加载阶段结束时自动启用摄像机,请在脚本中将 activateCameraOnTimeout
标志设置为 true。要在从某个摄像机切换到新摄像机之后禁用此摄像机,请以 disableCameraCuttingFrom
参数传递该摄像机。
void StreamingController.SetPreloading(float timeoutSeconds=0.0f, bool activateCameraOnTimeout=false, Camera disableCameraCuttingFrom=null)
要取消或查询预加载状态,请使用下列方法:
要确定 Mipmap 串流系统是否仍在加载纹理,可以查询以下属性:
请注意,启用摄像机与这些属性变为非零值之间存在延迟。这种延迟是因为 Mipmap 串流系统使用时间切片处理技术来计算 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。
要获取和设置分配给某一材质的纹理,请使用:
要获取某一材质中的所有纹理属性,请使用:
Unity has a built-in Mip Map Streaming debugging view mode. To access it, click the Scene view control drop-down and select Texture Streaming. This view mode tints GameObjects the following colours, depending on their status in the Mip Map Streaming system:
要为调试模式上传材质属性,请使用 Texture.SetStreamingTextureMaterialDebugProperties。
要为调试模式上传材质属性,请使用以下属性:
要获取与 Mipmap 串流系统交互的纹理或渲染器的数量信息,请使用以下属性:
要获取有关 Mipmap 级别的信息,请使用以下属性:
可以使用 Mipmap 串流系统来串流光照贴图。可以直接编辑纹理设置,但在 Unity 重新生成光照贴图时,纹理设置会重置为默认值。Player 设置 (Edit > Project Settings > Player) 中提供了如下两个控件来设置生成的光照贴图的串流和优先级:Lightmap Streaming Enabled 和 Streaming Priority。
默认情况下,在运行模式下会启用 Mipmap 串流。但是,在运行模式下运行时,Editor 开销会导致统计信息出现偏差。要获取精确数字,请在目标设备上测试应用程序。
如果在运行模式下启用了 Mipmap 串流,但在编辑模式下未启用(或者是相反的情况),则切换到运行模式和从运行模式退出时所花费的时间会稍长一些。要在运行模式下禁用 Mipmap 串流,请选择 Editor 设置 (Edit > Project Settings > Editor),导航到 Streaming Settings,并禁用 Enabled Texture Streaming in Play Mode。这会阻止 Unity 卸载和重新加载 Mipmap 数据,并且应该会加快运行模式工作流程。
Add the following script to a GameObject in the Scene to display the Mip Map Streaming status. This is useful if you want to determine the correct memory budget to set.
Shader "Show Mip Map 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 > 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
}