3

Texture Streaming的使用疑问

 3 years ago
source link: https://blog.uwa4d.com/archives/TechSharing_246.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1)Texture Streaming的使用疑问
​2)Unity 3D场景UI被打断合批的原因
3)Asset Provider和Asset Bundle Provider的意义
4)Addressables更新资源时只加载最初始资源
5)描边算法显示问题


这是第246篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)

Editor

Q:Unity 2019.2 Texture Streaming在Editor下不生效吗?按照Unity官方介绍,可以在SceneView里面Debug Texture Streaming。但是实际上切换过去会变成浅蓝色/深蓝色,并没有其他任何变化。有人遇到相同的情况吗?

A:1.需要测试脚本才能知道节省了多少内存,场景是看不出来的。

using System;
using UnityEditor;
using UnityEngine;

public class ShowTextureStreamingSummary : MonoBehaviour
{
    public float X = 10;
    public float Y = 20;
    public int MemoryBudgetPercentThreshold = 80;
    public int TextureStreamingPercentThreshold = 50;
    private ulong HighestDesiredTextureMemory;
    private Rect TextRect;

    public void Start()
    {
        HighestDesiredTextureMemory = 0;
        Texture.streamingTextureDiscardUnusedMips = false;
      QualitySettings.masterTextureLimit = 2;//强制等级为2,为了配合下面的测试脚本
    }

    public string HumanReadableSize(ulong size)
    {
        return string.Format("{0:0.0}M", (float)size / (float)(1024 * 1024));
    }

    void ShowText(string text)
    {
        float yInc = GUI.skin.font.lineHeight;
        GUI.Label(TextRect, text);
        TextRect.y += yInc;
    }

    public void OnGUI()
    {
        TextRect = new Rect(X, Y, Screen.width - X, Screen.height - Y);
        GUI.color = Color.red;
        if (!SystemInfo.supportsMipStreaming)
            ShowText("Texture streaming unsupported");
        if (!QualitySettings.streamingMipmapsActive)
            ShowText("Texture streaming disabled");
        else if (QualitySettings.streamingMipmapsMemoryBudget == 0)
            ShowText("No texture streaming budget");
        else if (Texture.totalTextureMemory == 0)
            ShowText("No texture memory needed");
        else
        {
            // Reduced highest memory usage
            if (Texture.desiredTextureMemory > HighestDesiredTextureMemory)
                HighestDesiredTextureMemory = Texture.desiredTextureMemory;

            // Show stats
            ulong budget = (ulong)(1024 * 1024 * QualitySettings.streamingMipmapsMemoryBudget);
            float percentUsed = (float)(100 * Texture.desiredTextureMemory) / (float)budget;
            ShowText(string.Format("Memory budget utilisation {0:0.0}% of {1} texture budget", percentUsed, HumanReadableSize(budget)));

            if (HighestDesiredTextureMemory > budget)
            {
                ulong memoryExcess = HighestDesiredTextureMemory - budget;
                ShowText(string.Format("Memory exceeds budget by {0}", HumanReadableSize(memoryExcess)));
            }
            else
            {
                ulong memorySaving = Texture.totalTextureMemory - HighestDesiredTextureMemory;
                float percentReduction = (float)(100 * HighestDesiredTextureMemory) / (float)Texture.totalTextureMemory;
                ShowText(string.Format("Memory saving at least {0} with streaming enabled ( at {1:0.0}% of original {2}) - ignoring caching", HumanReadableSize(memorySaving), percentReduction, HumanReadableSize(Texture.totalTextureMemory)));
            }

            // Advice section
#if UNITY_EDITOR
            ShowText("Run in standalone app for accurate figures. When run in Play Mode the stats are biased by editor textures");
#endif                
            if (percentUsed < (float)MemoryBudgetPercentThreshold)
                ShowText(string.Format("Reduce the Memory Budget closer to {0}", HumanReadableSize(Texture.desiredTextureMemory)));
            else if (Texture.desiredTextureMemory > budget)
                ShowText(string.Format("Raise Memory Budget above {0}", HumanReadableSize(Texture.desiredTextureMemory)));
           
            float percentStreaming = (float)(100 * (Texture.totalTextureMemory - Texture.nonStreamingTextureMemory)) / (float)Texture.totalTextureMemory;
            if (percentStreaming < (float)TextureStreamingPercentThreshold)
                ShowText(string.Format("Mark more textures streaming to improve savings ({0:0.0}% texture memory marked as streaming)", percentStreaming));

            if (!Texture.streamingTextureDiscardUnusedMips)
                ShowText("Consider turning on Texture.streamingTextureDiscardUnusedMips to analyse without cached textures");

            ShowText(string.Format("desiredTextureMemory {0}", HumanReadableSize(Texture.desiredTextureMemory)));
            ShowText(string.Format("nonStreamingTextureMemory {0}", HumanReadableSize(Texture.nonStreamingTextureMemory)));
            ShowText(string.Format("totalTextureMemory {0}", HumanReadableSize(Texture.totalTextureMemory)));
        }
    }
}

2.Game窗口下,查看哪位纹理使用了Streaming。显示绿的是使用了Streaming;蓝色的是未开启Streaming;红色的是还没使用Streaming。

using UnityEngine;

public class TextureStreamingDebug : MonoBehaviour
{
    public Camera m_MainCamera;
    public Shader m_ReplacementShader;
    public bool m_ActivateDebugShader;
    private bool m_DebugShaderActive = false;
    private RenderingPath originalRenderingPath;

    void Start()
    {
        // Grab camera from self if none set
        if (!m_MainCamera)
            m_MainCamera = GetComponent<Camera>();

        if (m_MainCamera)
            originalRenderingPath = m_MainCamera.renderingPath;
    }

    void Update()
    {
        if (!m_MainCamera || !m_ReplacementShader)
            return;

#if UNITY_STANDALONE_WIN || UNITY_EDITOR
        if (Input.GetKeyDown(KeyCode.Space))
            m_ActivateDebugShader ^= true;
#else
        if (Input.GetButtonDown("Fire1"))
            m_ActivateDebugShader ^= true;
#endif

        if (m_ActivateDebugShader != m_DebugShaderActive)
        {
            if (m_ActivateDebugShader)
            {
                m_MainCamera.renderingPath = RenderingPath.Forward;
                m_MainCamera.SetReplacementShader(m_ReplacementShader, "RenderType");
            }
            else
            {
                m_MainCamera.renderingPath = originalRenderingPath;
                m_MainCamera.ResetReplacementShader();
            }

            m_DebugShaderActive = m_ActivateDebugShader;
        }

        if (m_DebugShaderActive)
            Texture.SetStreamingTextureMaterialDebugProperties();
    }
}

对应Shader:

Shader "Hidden/Scene View 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 > 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
}

感谢牛头人不服@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5e986adb8cabe84a011afd27


Rendering

Q:如下图,使用Sprite Renderer做图片和TextMeshPro做文字,使用Unity帧调试器查看,发现第一排三个图片动态合批了,第二排三个图片动态合批了,第三排三个图片动态合批了,但是文字都合不了批,原因是动态批处理在Player Settings中被关闭或在当前环境中被暂时禁用,以避免Z-Fighting。能不能所有图片一个批次,所有文字一个批次?

1.png

2.png

A1:我的方法是设置Sorting Layer和Order in Layer:

SpriteRenderer -> Additional Settings;
TextMeshPro Text -> Extra Settings。

但这样的缺点是字永远会在图的上面,重叠的时候显示会有问题。

感谢小埃拉@UWA问答社区提供了回答

A2:1.对于普通的Mesh物体的绘制,也就是使用Mesh Renderer绘制的Mesh:在开启了动态合批的情况下,使用相同Material的两个物体,以相邻顺序绘制时,如果满足其他动态合批的条件,即可合批。

题主的文字部分没有合批,是因为没有开启动态合批。看Frame Debugger,题主的渲染管线使用的是SRP,开启动态合批的方式是在RenderPipelineAsset的Inspector面板的Advanced下勾选Dynamic Batching。

3.png

至于说为什么图片出现了合批现象,这是因为图片使用Sprite Renderer来绘制,其网格是Unity动态生成的,合批的行为也是Unity内部处理的,不受Dynamic Batching开启与否的影响。

2.其次是控制渲染顺序,使应该合批的物体以相邻顺序渲染,先绘制所有图片,再绘制所有文字。

方法一:按照楼上说的,设置Order in Layer,图片的Order in Layer设置为0,文字为1。

4.png
方法二:将所有图片的Render Queue都设置为3000,将所有文字的Render Queue都设置为3001。
5.png
6.png
效果如下:
7.png
8.png

感谢Prin@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/606ec4af6bb31032f9791252


Addressable

Q:请问有没有人知道Addressable 1.17.13中Asset Provider和Asset Bundle Provider这两个选项的意义?

9.png

A1:可以搜索下Addressables的源码。可以扩展默认的实现,来自定义自己的下载和加载流程。

[DisplayName(“Assets from Bundles Provider”)]
public class BundledAssetProvider : ResourceProviderBase
[DisplayName(“AssetBundle Provider”)]
public class AssetBundleProvider : ResourceProviderBase

感谢jim@UWA问答社区提供了回答

A2:这个是用来自定义加载AssetBundle和AssetBundle里面的资源的方式,题主可以看下Addressables.CN版本里面的AssetBundleProvider.cs的实现,在这个版本里面添加了解密AssetBundle的方法,算是另外一种加载AssetBundle的方式。

10.png

感谢Xuan@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/605d8334f2c8273bbbf2a5fd


Addressable

Q:问题描述如下:
使用场景:我正通过Addressables实现无需重启游戏的热更新。

问题复现:

  • Part1:当前发布的资源为A版本。
  • Part2:而后我发布了B版本的资源,打开游戏后,当前还未开始更新,所以加载的资源为A版本。我开始检查Catalog和资源更新,检测到需要更新后,则释放之前加载的所有Handle,开始热更新。再次加载资源,此时为B版本。重启游戏,再次加载资源也是B版本。(这里一切看起来正常)
  • Part3:我发布了C版本的资源,此时打开游戏,就出现问题了。我加载的资源是A版本,而不是B版本。(这一步,我认为有问题)我还是紧接着释放之前加载的所有Handle,开始热更新,再次加载资源。C版本的资源也可以被使用。
  • Part4:我后来开始怀疑是否是Addressable在联网状态下,自动会去请求Catalog(我已经勾选Disable Catalog Update on Startup)。如果检测到有资源更新,就会使用最初始版本,而不是热更新后的最新版本资源。于是经过我的实验,我把我的资源服务器关掉,它就自动使用热更新后的最新版本。而启动资源服务器后,加载的资源就会是最初始版本,只有在我热更新后,才恢复正常。

期望效果:我既然已经更新资源,不管是什么情况下,这个资源就应该是最新的。而不是莫名其妙地出现了最初始版本的资源。

Unity版本:2020.1.9f1c1
平台:Windows
相关代码:测试Addressables项目
https://gitee.com/dreamCirno/addressable-hotfix-test/tree/master

A1:不知道你用的是哪个版本,建议参考UWA问答中的信息:
https://answer.uwa4d.com/question/5e3b7fe686a39548919837e1

或者看看调用的顺序是不是有问题,我是按照这个顺序调用的:
InitializeAsync->CheckForCatalogUpdates->UpdateCatalogs->GetDownloadSizeAsync->DownloadDependenciesAsync

感谢jim@UWA问答社区提供了回答

A2:从你的描述上看,感觉你这个好像是没更新到B版本的Catalog导致的。

感谢Robot.Huang@UWA问答社区提供了回答

A3:参考如下代码:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.UI;
using static UnityEngine.AddressableAssets.Addressables;

public class GameLaunch : MonoBehaviour 
{

    public Action<string> OnStatusTipHasChanged;
    public Action<float> OnDownloadPercentHasChanged;
    public Action<bool> OnDownloadHasResult;

    AsyncOperationHandle<GameObject> handle;
    GameObject obj;

    /// <summary>
    /// 自定义指定待更新检测的 Label 集合
    /// </summary>
    private List<string> mKeys = new List<string>() { "default" };

    public IEnumerator CheckUpdate() 
    {
        bool isNeedUpdateCatalog = false;
        bool isNeedUpdateResources = false;

        // 初始化 Addressable
        var initHandle = Addressables.InitializeAsync();
        yield return initHandle;
        OnStatusTipHasChanged?.Invoke("开始检查更新");
        Debug.LogError("开始检查更新");

        // 检查本地 Catalog 是否为最新版本
        var checkHandle = Addressables.CheckForCatalogUpdates(false);
        yield return checkHandle;
        if (checkHandle.Status == AsyncOperationStatus.Succeeded) {
            OnDownloadPercentHasChanged?.Invoke(1);
            OnStatusTipHasChanged?.Invoke("目录检查完成");
            Debug.LogError("目录检查完成");
        }

        List<string> catalogs = checkHandle.Result;
        if (catalogs != null && catalogs.Count > 0) 
        {
            OnStatusTipHasChanged?.Invoke("检测到目录需要更新");
            Debug.LogError("检测到 Catalogs 需要更新");
            isNeedUpdateCatalog = true;
        } 
        else 
        {
            OnStatusTipHasChanged?.Invoke("检测到目录已是最新");
            Debug.LogError("检测到 Catalogs 已是最新");
        }

        var sizeHandle = Addressables.GetDownloadSizeAsync(mKeys);
        if (sizeHandle.Result > 0) 
        {
            Debug.LogError("检测到有更新资源包");
            OnStatusTipHasChanged?.Invoke("检测到有更新资源包");
            isNeedUpdateResources = true;
        } 
        else 
        {
            Debug.LogError("检测到没有资源更新");
            OnStatusTipHasChanged?.Invoke("检测到没有资源更新");
        }

        OnStatusTipHasChanged?.Invoke("准备进行下一步");

        if (isNeedUpdateCatalog || isNeedUpdateResources) 
        {
            if (isNeedUpdateCatalog) 
            {
                yield return UpdateCatalog(catalogs);
            }
            if (isNeedUpdateResources) 
            {
                yield return UpdateResources();
            }
            OnDownloadHasResult?.Invoke(true);
        } 
        else 
        {
            //StartGame();
            Debug.LogError("开始游戏");
        }
    }

    private void Update() 
    {
        if (Input.GetKeyDown(KeyCode.C)) 
        {
            StartCoroutine(CheckUpdate());
        }
        if (Input.GetKeyDown(KeyCode.L)) 
        {
            handle = Addressables.LoadAssetAsync<GameObject>("Image");
            handle.Completed += param => 
            {
                if (param.Status == AsyncOperationStatus.Succeeded) 
                {
                    Debug.LogError("预加载成功");
                } 
                else 
                {
                    Debug.LogError("预加载失败");
                }
                obj = param.Result;
            };
        }
        if (Input.GetKeyDown(KeyCode.R)) 
        {
            ReleaseCache();
        }
        if (Input.GetKeyDown(KeyCode.Space)) 
        {
            Instantiate(obj, new Vector2(UnityEngine.Random.Range(0, 400), UnityEngine.Random.Range(0, 400)), Quaternion.identity, GameObject.Find("Canvas").transform);
        }
    }

    private IEnumerator UpdateCatalog(List<string> catalogs) 
    {
        var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
        Debug.LogError("开始更新 Catalogs");
        yield return updateHandle;

        Addressables.Release(updateHandle);
    }

    private IEnumerator UpdateResources() 
    {
        ReleaseCache();
        // 需更新大小 > 0,表示需要下载更新
        // 清理旧资源
        //var clearHandle = Addressables.ClearDependencyCacheAsync(mKeys, false);
        //yield return clearHandle;

        // 下载待更新资源
        var downloadHandle = Addressables.DownloadDependenciesAsync(mKeys, MergeMode.Union, false);
        Debug.LogError("开始更新资源");
        while (!downloadHandle.IsDone) 
        {
            DownloadStatus downloadStatus = downloadHandle.GetDownloadStatus();
            OnDownloadPercentHasChanged?.Invoke(downloadStatus.Percent);
            OnStatusTipHasChanged?.Invoke($"下载进度: {downloadStatus.Percent * 100} %");
            Debug.LogError($"下载进度: {downloadStatus.Percent * 100} %");
            yield return null;
        }
        if (downloadHandle.Status == AsyncOperationStatus.Succeeded) 
        {
            OnStatusTipHasChanged?.Invoke($"下载情况:{(downloadHandle.IsDone ? "完成" : "未完成")}");
            Debug.LogError($"下载情况:{(downloadHandle.IsDone ? "完成" : "未完成")}");
        } 
        else 
        {
            OnStatusTipHasChanged?.Invoke($"下载更新包失败");
            Debug.LogError($"下载更新包失败,错误内容:{downloadHandle.OperationException.Message}");
        }
        Addressables.Release(downloadHandle);
    }

    private void ReleaseCache() 
    {
        try 
        {
            Addressables.Release(handle);
            Debug.LogError("释放资源成功");
        } 
        catch (Exception) 
        {
            Debug.LogError("释放资源失败");
        }
    }

}

感谢题主李传东@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/607152b46bb31032f9791298


Rendering

Q:使用法线描边算法,在一些比较薄的网格上有穿模现象。这是算法问题还是网格问题?应该如何解决?

11.png

Shader如图:

12.png

13.png

A1:比较怀疑两点:
1.是否要Cull Front而不是Cull Back?
2.如果是自己渲染的RenderTexture,是不是深度的精度给的不太够?

感谢王宇@UWA问答社区提供了回答

A2:是这种方法会产生的固有问题,渲染的Backfaces与原有模型发生深度冲突,遮挡模型,造成穿透问题。

解决方法:一种方法是给Backfaces设置Z-offset,让轮廓线埋没到临近的面里。另一种方法是修改Backfaces扩张的法线,使轮廓线(Backfaces)扁平化。

相关参考链接:https://blog.csdn.net/candycat1992/article/details/41120019

感谢Xuan@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/606ec44f6bb31032f9791251

封面图来源于网络


今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官方技术QQ群:793972859(原群已满员)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK