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 OffCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma target 2.0#pragma exclude_renderers glesENDCG    }} SubShader {    Tags { "ForceSupported" = "True" "RenderType"="TransparentCutout" }    Pass     {        AlphaTest Greater [_Cutoff]CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma target 2.0#pragma exclude_renderers glesENDCG    }} 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 OffCGPROGRAM#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 OffCGPROGRAM#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 OffCGPROGRAM#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 OffCGPROGRAM#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问答社区提供了答复

Rendering

Q:如下图,应用Sprite Renderer做图片和TextMeshPro做文字,应用Unity帧调试器查看,发现第一排三个图片动静合批了,第二排三个图片动静合批了,第三排三个图片动静合批了,然而文字都合不了批,起因是动静批处理在Player Settings中被敞开或在以后环境中被临时禁用,以防止Z-Fighting。能不能所有图片一个批次,所有文字一个批次?

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。

至于说为什么图片呈现了合批景象,这是因为图片应用Sprite Renderer来绘制,其网格是Unity动静生成的,合批的行为也是Unity外部解决的,不受Dynamic Batching开启与否的影响。

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

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

办法二:将所有图片的Render Queue都设置为3000,将所有文字的Render Queue都设置为3001。


成果如下:

感激Prin@UWA问答社区提供了答复

Addressable

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

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的形式。

感激Xuan@UWA问答社区提供了答复

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/...

A1:不晓得你用的是哪个版本,倡议参考UWA问答中的信息:
https://answer.uwa4d.com/ques...

或者看看调用的程序是不是有问题,我是依照这个顺序调用的:
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问答社区提供了答复

Rendering

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

Shader如图:

A1:比拟狐疑两点:
1.是否要Cull Front而不是Cull Back?
2.如果是本人渲染的RenderTexture,是不是深度的精度给的不太够?

感激王宇@UWA问答社区提供了答复

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

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

相干参考链接:https://blog.csdn.net/candyca...

感激Xuan@UWA问答社区提供了答复

封面图来源于网络


明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。

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