侑虎科技

侑虎科技 查看完整档案

上海编辑  |  填写毕业院校  |  填写所在公司/组织 www.uwa4d.com 编辑
编辑

UWA官网:https://www.uwa4d.com
微信公众号:侑虎科技

个人动态

侑虎科技 发布了文章 · 今天 15:10

UE4 Niagara粒子特效快速入门

01 文章简介

本课程比较全面和系统地介绍了UE4新版粒子——Niagara粒子系统。对比传统的粒子系统,Niagara的架构更加科学合理,采用全新的节点式模块化逻辑,可以兼容更多的资源,拥有更加灵活的组合,是未来UE5的发展方向。

课程从最基础开始讲解,从如何建立粒子到粒子如何碰撞,粒子事件如何发生等等,除此之外还有UE4的材质系统、材质节点、贴图以及外部资源的导入、定序器等相关内容,基本上涵盖了UE4的Niagara粒子全部模组和属性。

课程属于基础内容,适合对UE4感兴趣想入门的同学。以下为课程节选,完整课程可戳此查看。

https://www.bilibili.com/vide...

02 适合读者

  1. 对UE4特效感兴趣想入门的同学
  2. 想从事基于UE4 / UE5美术方面工作的同学

03 你将获得

  1. 快速掌握Niagara粒子系统的基础知识
  2. 快速掌握Niagara粒子系统的使用技巧
  3. 课程配套资源和工程文件
查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 4月14日

Texture Streaming的使用疑问

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问答社区提供了回答

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(原群已满员)

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 4月7日

Addressable编辑器相关开发问题

1)Addressable编辑器相关开发问题
​2)Addressable动态设置更新地址
3)Addressable在网络差的环境中下载资源
4)Android ETC2 Fallback的疑问
5)内置Shader中“Dependency”的定义


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

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

Addressable

Q:我想通过编辑器脚本来做自动导入下图这个选项并打钩,能自己设置相关的Key。有没有参考的工程或代码示例?谢谢!

A:用AssetDatabase.AssetPathToGUID和AddressableAssetSettings.CreateOrMoveEntry。

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

Addressable

Q:Addressable动态配置的热更新地址,使用了文档里面的静态变量 (放大括号里),Addressable启动后获取到的Catalogs.Count一直为零,怀疑是地址的问题,有没有要求这个类也是静态的?

如果我配死地址,是能更新下来的,比如这样:

这种更新已经测试通过,目前是想在生成首包后动态去改这个RemoteLoadPath。

A:我们是这样子的,RemoteLoadPath指向一个static的property,这个property从配置表里返回资源更新的地址。

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

Addressable

Q:我们使用Addressable来做资源的热更新。现在模拟了一个较差网络环境下的下载资源情况,使用Windows上模拟丢包的软件模拟了网络环境,丢包率设置为30%。

Addressable的下载会卡死在某个百分比,DownloadDependenciesAsync的Completed事件不会被触发,所以无法通过AsyncOperationHandle的status判断是失败还是成功。

Log显示的报错是:

请问这种情况有什么好的解决方法吗?

A:后来发现是有几个资源的Time Out值设置得太大了,导致一直在等待,所以进度条卡死。解决方法是将Time Out值设置的小一点,并增加retry次数。

我Time Out设置的是20。感觉不宜太长,否则超时的时候下载进度感觉会长时间卡死;太短的话,移动网络太差,也经常会超时导致超时下载失败。

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

Editor

Q:我们的游戏在模拟器上运行一会儿就会崩溃,怀疑是图片被解压成RGBA32导致内存过大产生的崩溃。Android ETC2 Fallback设置为16 bit是不是能够大幅降低在低端手机的内存占用呢?

另外我在Build Setting选择ETC2 Fallback后,Project Setting没有任何变化,这个值是保存在哪里的?应该怎么上传?(版本:Unity 2018.4.31)

A:模拟器一般内存都够的,如果怕是内存问题可以调整一下分配给模拟器的内存。最好还是连上Logcat看一下Log,是因为内存问题,还是32位或者64位应用的问题,又或者是64位LuaJit之类的问题。这个值存在LibraryEditorUserBuildSettings.asset文件中。

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

Shader

Q:正在尝试自定义Terrain的Shader,发现内置Shader的末尾有这样两行,想问问其中的“Dependency”是做什么用的,后面指定的Shader起什么作用?

Dependency “AddPassShader” = "Hidden/TerrainEngine/Splatmap/Diffuse-AddPass"
Dependency “BaseMapShader” = “Diffuse”

A:Dependency是一个未文档的关键词,其用于指定Shader依赖关系,实际作用有两个:

  • 打包的时候依赖不会遗漏,可以通过AssetBundle Browser验证。
  • Unity 2019之后可以通过C#接口Shader.GetDependency(等号左侧依赖名) 获取等号右侧Shader名。

地形系统那么做,是因为有三种光照模型,通过GetDependency(依赖名) 可以减少一些Shader名的硬编码,依赖名充当了接口的作用。

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

封面图来源于网络


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

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

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月31日

如何优化UI中大量使用SetActive的问题

1)如何优化UI中大量使用SetActive的问题
​2)ASTC压缩和ETC2压缩打出的APK包的问题
3)PNG图片格式与TGA图片格式问题
4)游戏运行的崩溃问题
5)关于AssetBundle加载方式的适用环境


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

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

UI

Q:在UI的日常开发中为了显示或者隐藏某些GO使用了大量的SetActive(true)/(false)代码。但是SetActive的开销过大,所以不想使用SetActive,是否有其他解决方法?

A1:这种情况下Active/Deactive的开销主要有几个方面:

1.C#层到Native层的穿梭调用速度比C#层内的速度慢。
2.UI元素的变化导致所在的Canvas变化,触发函数Canvas.SendWillRenderCanvases()与Canvas.BuildBatch()造成高耗时。
3.UI元素的网格顶点数组改变会造成堆内存分配,触发GC,导致耗时(不过对UI元素进行位置移动不会造成堆内存分配)。

因此,优化也可以从以下几点考虑:
1.在C#层设置变量来标识相应的GO处于Active还是非Active状态,避免对Active的对象进行SetActive(true),避免对非Active的对象进行SetActive(false)。

对Active进行SetActive(true)时,“底层”会进行判断,但调用的时候,就已经是从C#层调用底层,导致开销较高。在C#层判断好,就避免了让底层判断。

2.将要频繁变化的UI元素与不频繁变化的UI元素放在不同的Canvas中,减少UI元素变化时的耗时。

3.通过将UI元素的坐标移动到Canvas的范围之外的方法来显示与隐藏,避免SetActive的耗时以及SendWillRenderCanvases的耗时。

4.经测试,对Component进行enabled = false的操作比对GO进行SetActive(false)的操作耗时低。

5.通过添加CanvasGroup组件设置透明度的方式来进行显示与隐藏。

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

A2:最近做优化,也发现了这个问题,尤其是挂Image和TMPText的在SetActive的时候耗时更差。我准备从以下方面做优化:

1.对于挂UICanvas的直接修改Layer层。

2.对于不挂UICanvas的,改成挂CanvasGroup来控制Alpha.
这个地方有个麻烦的点。因为我们项目开了很久,让拼界面的同学去到需要控制的节点上挂CanvasGroup不太现实。在运行时动态挂组件,对性能有些担忧。所以打算改成运行时遇到修改Active的节点,到对应的Prefab下增加一个CanvasGroup控件,这样Prefab就补全CanvasGroup控件了。在正式上线之后,如果有遗漏不存在的,直接使用SetActive。

3.使用CanvasGroup方式有个缺点,只是改变了Alpha,依然会占有布局,所以父界面是Layout的,不能采用CanvasGroup。初步计划是对比SetScale0和SetActive的耗时,两者应该都会引起重绘。

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

Texture

Q:在一个空工程里面,我放了几张大图2048*2048的,采用ETC2 4bits的格式压缩的时候单张大小为2MB,采用ASTC 6X6压缩的时候单张大小是1.8MB。

按照我的理解:采用ASTC压缩格式打出的APK应该更小才对,可是事实和我预想的相反:采用ETC2压缩打出的APK为21.1MB;采用ASTC压缩打出的APK为25.7MB。

为什么采用ETC2压缩的包体反而更小呢?ASTC打出的APK更大呢?

A:占用包体的大小和在Editor下的Preview界面看到的大小是两回事。

Preview界面看到的大小是ASTC或者ETC2格式的资源的大小,而打包后,会对资源进行进一步的压缩(LZ4或LZMA)。只能说明ETC2压缩成LZ4后占用的包体大小确实比ASTC压缩成LZ4后占用的大小更小,至于原因就要看具体压缩算法的实现了。

可以用AssetBundle来验证,如果打AssetBundle包时选择NoCompression,那么确实ASTC格式的比ETC2格式的AssetBundle包更小。如果选择LZ4或LZMA压缩,那么ETC2格式的AssetBundle包比ASTC格式的AssetBundle包要小。

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

Texture

Q:美术说有一张带透明通道的图,必须出TGA格式,不能出PNG格式的图,缺少透明通道。请问为什么PNG格式在Unity中有些是有透明通道的?该怎么让美术在PhotoShop中出一张PNG格式图,又能满足效果的?可否详细说下这两者图在Unity中的区别原理?

A1:在PhotoShop中针对PNG并没有透明通道这一说。导出也只能导出RGB3个通道。想要修改PNG像素的透明信息需要用到蒙板。

你和美术同学说“这块的信息画在蒙版上,黑透白不透”,他就明白了。

感谢张首峰@UWA问答社区提供了回答

A2:选TGA美术比较好处理,也不用关心PNG那几种格式的区别,PhotoShop里也不用处理Alpha的事,毕竟在引擎里都要根据不同平台进行压缩处理。不在乎工程大小的情况下,流程更方便才是关键。

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

Android

Q:游戏运行过一段时间后某些机器会出现以下所示的崩溃,具体Log可戳原问答查看,请问有遇到过类似的问题吗?

JNI ERROR (app bug): global reference table overflow (max=51200)

我看Unity 2018.3里面有一个是相关内容,我们现在用的版本是2019.4.10,按说应该已经修好了,望各位大佬指导迷津,谢谢!

A1:是不是JNI调用次数太多了?我之前试过,有个腾讯语音的API一直在调用JNI,运行到一定时间后就崩溃了,日志好像跟你一样。

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

A2:应该是AndroidJavaClass和AndroidJavaObject只频繁New没调用Dispose导致的。

感谢上山打野@UWA问答社区提供了回答

A3:参考以下信息:

2019.4.21f1 Release Notes
Fixes

  • Android: Fixed Java local reference leaking when using AndroidJavaClass/Object. (1283209)

https://unity3d.com/unity/whats-new/2019.4.21 的Fixes里。

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

AssetBundle

Q:关于AssetBundle加载方式的适用环境中,AssetBundle.LoadFromMemory以及AssetBundle.LoadFromStream适用环境分别是什么?

A1:AssetBundle.LoadFromMemory
1.使用UnityWebRequest下载的AssetBundle资源并且使用后不存到本地;
2.有加密需求的AssetBundle资源。

AssetBundle.LoadFromStream
1.有加密需求的AssetBundle资源(内存值理论上比AssetBundle.LoadFromMemory小);
2.Android下需要Copy出StreamingAssets目录外,创建流。

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

A2:当资源有加密需求时,可先把AssetBundle读取到内存当中,进行解密后再调用该AssetBundle.LoadFromMemory进行加载。该方法消耗的最大内存量将至少是AssetBundle的两倍。可参考《AssetBundle的原理及最佳实践》

AssetBundle.LoadFromStream可进行流式加载,不需要将AssetBundle全部读到内存中再解密、加载,而是可以通过每次像Buffer中读一部分,解密一部分的方式进行加载,不会多占用一份很大的内存。如果使用该接口,需要自定义一个继承FileStream类,然后在Read和Write方法内对Byte数组进行异或加密解密处理。

具体用法可参考:https://www.xuanyusong.com/archives/4607

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

封面图来源:UiFaderPro
一组使Unity引擎中的UI(4.6b + uGUI)的淡入淡出变得容易的脚本。
https://lab.uwa4d.com/lab/5b5d2aaad7f10a201feadf62


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

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

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月24日

Addressable资源热更新疑问

1)Addressable资源热更新疑问
​2)如何解决远处网格线会花的问题
3)关于着色器中某些特殊图片的用途
4)Lightmap在内存中有重复加载
5)Unity Job System问题


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

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

Addressable

Q:Addressable资源热更新,提示CRC Mismatch,将需要热更新的资源已经上传至CDN对应目录,启动游戏时报错:

A:我们的项目之前也遇到了同样的问题,具体表现是打包增量后Bundle的Hash没有改变,但是CRC改变了,导致加载出错。

目前我们这里有两种处理方案,仅供参考:

  1. 就是按照Unity官方说的,在Schema上禁用掉CRCCheck;但是这种方式在校验Bundle下载的完整性和正确性上可能会有些问题。
  2. 热更新流程上加入Hash和CRC的双重检测,检测到其中任一值有变化就认定该Bundle需要更新,并重载最新的Catalog文件;同样这样做的问题是有可能会平白增加很多Bundle下载,即便这个Bundle的内容并无更新。

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

Texture

Q:想要实现网格线的效果,但是远处的线会花,是什么原因?这是一个正方形的Mesh,然后使用UV的缩放铺满n个小格子,图片设置成了Repeat,并且关闭了Mipmap。但是还是会出现远处的线花了,有什么解决方案吗?

A1:把Texture的Aniso Level值调高试试。

Aniso Level就是用于低角度看地面的。另外把Filter Mode设置成Trilinear,就不会出现一段一段的效果了,就是开销比较大。

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

A2:按下图调整后,问题解决了:

感谢题主halm@UWA问答社区提供了回答

Shader

Q:如下图所示,因为资源是从其它地方下载的,没有详细说明,想知道这种图在TA方面来说,是用来做什么的?T_M_Body_Msa_b.png、T_M_Body_Msa_g.png和T_M_Body_Msa_Mtl.png 分别代表哪一块着色器中用到的,给个关键字提示下,谢谢。

A:分别为b(Bump)、g(Glossness)和Mtl(Metallic)。

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

Rendering

Q:如下图,为什么Lightmap在Profile里会显示有重复的呢?我看每一个都是40MB,有什么办法优化吗?而且对应的引用脚本里,没有发现对Lightmap的引用。

A:看起来每张Lightmap都加载了两份。题主有没有写自己管理Lightmap的机制呢?是一次就加载两份还是在切换场景或者更换Lightmap的时候没有卸载掉?加载场景时有没有使用LoadSceneMode.Additive,导致场景累加式加载,不卸载之前的资源?

Lightmap的引用数很高,看看是被哪些脚本索引住了,相应的组件是不是一直驻留。在该卸载的时候要把索引置空。

这个窗口把GameObject和Monobehaviour互相引用关系都显示出来了,不是直接的引用,确实比较复杂不好找。可以用Unity给的专门的MemoryProfiler试试,相对清楚一点。

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

Script

Q1:Unity 2020.2.1+Hub下载的SDK、NDK、JDK和BuildTools打包错误。

相关错误提示中没有一条提到有价值的信息。

经过验证:streammingAssets文件有680(包含)个限制,超过就打不出包了,带不带后缀名都没关系。不清楚是不是个人版的限制?

A1:应该是不会的,上万文件都测试过,没问题。

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

A2:你可以勾选Custom Gradle properties template,在文件中把unityStreamingAssets=.unity3d*STREAMING_ASSETS*改为unityStreamingAssets=.unity3d,就可以了。

如果需要特殊文件类型不压缩,自行添加,以逗号“, ”隔开。如unityStreamingAssets=.unity3d, .bundle。

U3D从2020.2.3开始修改了aaptOptions 的打包参数,aaptOptions的数组最大是255。

aaptOptions 
{
    noCompress = [’.ress’, ‘.resource’, ‘.obb’] + unityStreamingAssets.tokenize(’, ')

    ignoreAssetsPattern = “!.svn:!.git:!.ds_store:!.scc:.:!CVS:!thumbs.db:!picasa.ini:!*~”
}

*PACKAGING_OPTIONS* 

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

Q2:请教下如果希望streamingAssets下的都不压缩,文件又都没后缀,要怎么写呢?

A:由于U3D 2020.2必须使用unityStreamingAssets变量,所以需要你把工程导出,然后修改aaptOptions,让全部文件不压缩。

参考:https://developer.android.google.cn/reference/tools/gradle-api/4.1/com/android/build/api/dsl/AaptOptions

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

封面图来源:Unity Interactive Grass
可用在Unity项目的可交互的草,会在进入场景时将Unity原生的草替换成自己生成的Mesh。
https://lab.uwa4d.com/lab/5b590fe1d7f10a201fe10467


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

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

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月19日

活动 | UWA DAY 2021 开启报名!

UWA DAY 2021 又要和大家见面啦!由侑虎科技主办,以游戏开发为主题的第五届UWA DAY技术大会将在上海隆重召开。本次大会的主题是“助力游戏研发迈入工业化时代”,UWA将带领行业开发者品鉴业界的卓越研发理念和实战心得,对“工业化”的定义和落地进行更深入的求知探新,并携手业内合作伙伴共同构建开放共赢的技术交流生态。

2020年是特殊的一年,随着疫情肆虐、版号限制常态化,游戏行业发生着翻天覆地的改变,增量打法已逐步退出市场,大家不再局限于把项目推上线,而是如何打磨出更精品的项目:更酷炫的特效、更庞大的世界观、更细腻的画面、更出挑的玩法,更出色的性能......我们也看到《原神》、《黑神话:悟空》、《天谕》等系列精品大作更是把研发流程推向了新的高度。同时,在这样的趋势下,研发团队对于开发流程的规范化、流程化、专业化和自动化需求也在不断增加。如何搭建科学的、高效的、自动化的工业级流水线?这是各大研发团队不断探索的方向,也是本次大会聚焦的内核。

本次大会第一日开设一个主会场,第二日开设六个分会场,共计约40个干货议题。内容将涵盖程序、美术、制作、策划、质量保障、前沿探索等多个方面,演讲嘉宾包含了UWA技术专家和业界精英,现场更有多个游戏作品展区供您体验交流。

我们的议题正在火热征集中,欢迎志同道合的朋友投稿:support@uwa4d.com

活动信息

主办方:侑虎科技
时 间:2021年8月21日~8月22日(周六、周日)
地 点:上海市静安区广中西路333号 上海宝华万豪酒店二楼
报 名:点击此处报名,或者扫描下方微信二维码(现早鸟优惠价,报名请抓紧时间哦~)

商务、媒体、展位申请 联系 Jessica@uwa4d.com 或者 QQ:2476215747

往期精彩盘点:

UWA DAY 2020:品往鉴来 质存高远

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月17日

下载AssetBundle的Mono内存问题

1)下载AssetBundle的Mono内存问题
​2)Unity 2019运行时获取Hierarchy上预制体资源路径
3)多个Submeshes模型合并后的显示问题
4)ToLua中访问Time.deltaTime为0
5)CacheServer莫名的断开连接


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

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

Memory

Q:使用协程+UnityWebRequest下载Bundle时分配的内存释放不掉。

下载步骤如下:
1. 使用协程+UnityWebRequest下载Bundle;
2. 使用BinaryWriter将UnityWebRequest.downloadHandler.data的数据写入机器;
3. 调用UnityWebRequest的Dispose函数;
4. 调用Resources.UnloadUnusedAssets(),UnityWebRequest.ClearCookieCache()和System.GC.Collect()。

下载每一个Bundle都会执行如上流程,但是Profiler调试真机发现,分配的内存无法释放,会把Mono内存越撑越大,请问大家遇到过这种情况吗?

A:之前遇到过类似的问题,题主可以参考一下,我们使用的Unity版本是2018.4.31。

不管是之前的WWW还是现在的WebRequest,都是使用的其成员DownloadHandler进行下载。当你访问“.data”属性时,其实访问的是一个“GetData()”函数的包装,这个函数返回的是一段native-memory data buffer的拷贝,这也是问题的根结所在。

第一个可以优化的点就是减少“.data”的使用,用临时变量缓存下来。
第二个就是替换DownloadHandler,Unity提供了多种DownloadHandler,我们当时是下载图片,所以用的是DownloadHandlerTexture,看你的问题,可以试试DownloadHandlerAssetBundle。

参考:https://docs.unity3d.com/ScriptReference/Networking.DownloadHandlerAssetBundle-ctor.html

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

Editor

Q:美术希望在编辑器运行的状态下获取场景GameObject实例对应预制体的路径,以前Unity 2017是可以通过var pRoot = PrefabUtility.GetPrefabParent(go); return AssetDataBase.GetAssetPath(pRoot),获取运行时预制路径的。

最近升级到2019发现接口已经更新,PrefabUtility.GetPrefabInstanceHandle(targetGameObject)、PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefab)等接口都不能在Unity运行的时候去获取Hierarchy里面的GameObject路径。

我问题里面的关键是运行状态,也就是点了播放,然后获取Hierarchy上预制的存储路径。PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefab)不点播放是可以获取,播放了只能获取Project上预制的存储路径。

A:PrefabUtility.GetNearestPrefabInstanceRoot:获取最近的预制体实例Root。
PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot:获取预制体路径。

if (PrefabUtility.IsPartOfPrefabInstance(seletedGo))
{
    string prefabAssetPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(seletedGo);
} 

GetPrefabAssetPathOfNearestInstanceRoot可以获取路径,“PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(prefab)” 这个接口传的不是Prefab部分,而是实例的。

如果不行,在Editor时,在它身上挂脚本或者别的方式建立映射,Runtime下再拿。

感谢静风霁@UWA问答社区提供了回答

Mesh

Q:请教下,我测试一个Skinmesh合并功能,发现当Mesh包含多个Submeshes时,合并成功后,无法渲染显示出来。

当测试的Mesh不包含Submeshes时,可以合并显示。难道CombineMeshes只能合并不带Submeshes的Mesh?不知道问题出在哪?有什么解决思路吗?

注:不管是使用“r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false); ”带上所有材质,还是“r.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false) ;”合并一张贴图材质,带多个Submeshes的模型只能合并无法渲染显示出来。不带多个Submeshes的模型能够正常合并显示。

A:测试发现,需要对每个CombineInstance重新指定三角面,才能正常显示带多个Submeshes的模型。

如这段代码:

 foreach (SkinnedMeshRenderer smr in allSkineMeshList)
    {
        for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
        {
            CombineInstance ci = new CombineInstance();
            ci.mesh = smr.sharedMesh;
            ci.mesh.triangles = smr.sharedMesh.triangles; //这里重新指定

            ci.subMeshIndex = sub;
            ci.transform = matrix * smr.transform.localToWorldMatrix;
            combineInstances.Add(ci);
        }

    } 

感谢题主牛头人不服@UWA问答社区提供了回答

Lua

Q:在ToLua中访问Unity的Time.deltaTime始终为nil,赋值给Lua变量也是0。
PS:在ToLua访问Unity API之前已经进行过静态绑定,其他API都可以被Lua正常访问。

为什么ToLua中访问Time.deltaTime为0?

A:Lua的绑定必须写在Lua虚拟机启动之后:

感谢题主yxy@UWA问答社区提供了回答

Editor

Q:客户端打开Cache Server面板测试Connection会发现连接不上,然后点击一下服务器的控制台,Enter一下会提示:

然后客户端才能重新连接,怎么感觉像是失去焦点的意思,这是什么原因呢?

A:Windows在服务器的控制台右击设置属性/默认值 “取消勾选快速编辑模式”。

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

封面图来源:VaSplineSnag
VaSplineSnag是提供给UE4关卡设计师的样条曲线工具。
https://lab.uwa4d.com/lab/5b8627da02004fb65977efaf


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

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

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月15日

RenderTexture导致UI花屏的问题

1)RenderTexture导致UI花屏的问题
​2)Unity 2019 ToLua导出ParticleSystem异常
3)UWA GOT在安卓10上的截图问题
4)Addressables.CheckForCatalogUpdates和Addressables.GetDownloadSizeAsync的问题
5)用RenderDoc抓不到华为手机的帧


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

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

Rendering

Q:UI花屏该怎么查?出现绿色,也有紫色在闪烁。如下图所示,黑色背景还有紫色在闪烁,截图截不了。只有在真机下才出现,模拟器上是没有的。

这黑色背景是一个RenderTexture的Blur效果,SRP实现的,使用的设备是小米K30。

A1:建议使用FrameDebugger定位一下是哪些Shader执行出现的问题,出现问题的很有可能是该Shader的渲染执行顺序有问题,看看RenderPassEvent设置是否正确。

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

A2:问题查出来了,用了RenderTexture.GetTemporary,depthBuffer参数传了16导致的,0不会。

感谢题主NG週@UWA问答社区提供了回答

Lua

Q:Unity 2019 Tolua导出ParticleSystem异常,如图:

由于这里Type为null,导致ParticleSystem无法导出。

A:用1.0.8.595处理Unity运行库[out]标记非out参数问题,去官网更新一下。
https://github.com/topameng/tolua/commits
topameng committed on 19 Jun 2020

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

Android

Q:UWA在安卓10上不能正常使用截图功能,已经按照文档配置了Manifest,如下图:

A:这个解决方案是针对UWA GOT Online打包的Write权限的问题的,并不能解决截图的问题。目前在安卓10系统的真机上,只有APK设置的TargetSDKVersion<=28时才能截图。TargetSDKVersion设置为<=28(28为安卓9),出的APK仍旧能在安卓10手机上跑的。

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

Addressable

Q:请问大佬,为什么Addressables.CheckForCatalogUpdates和 Addressables.GetDownloadSizeAsync永远返回AsyncOperationStatus.Succeeded?

我http服务器都没开,它还是返回成功。本来是我是通过Addressables.CheckForCatalogUpdates和Addressables.GetDownloadSizeAsync来判断是否需要更新资源,但是http服务器都没开,依然返回成功,检测为不需要更新了。请问如何处理?

Addressable版本1.16.15
Unity 2018.4.14f

A1:翻了代码,最后发现CheckCatalogsOperation中的Execute方法里直接 Complete(result, true, null);这样返回的,所以永远都是成功的。

感谢题主wu@UWA问答社区提供了回答

A2:Addressables.CheckForCatalogUpdates是通过返回一个“catalog ids”的列表来判断是否有更新的,就判断这个列表有没有就可以了。

服务器没开自然是不需要更新,也没法更新。GetDownloadSizeAsync则是通过最新的Catalog内AssetBundle的hash和已经下载的AssetBundle的hash对比来判断是否需要下载,需要下载则把Size累计上。既然catalog没有被更新掉,自然也就没它什么事情了。

怎么处理就看业务层想怎么设计了。比如:
更新服务器开不开都希望进游戏,那么就按现在CheckForCatalogUpdates返回列表为空就当成不需要更新,继续跑就行了。

如果必须在更新服务在线的情况下走,那么就由外部程序来判断服务器是否活着,然后再进到游戏更新逻辑。

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

Rendering

Q:最近遇到渲染深度不正确的问题。在骁龙的机型上渲染出来是错的(和编辑器不一致),在华为的机型和iOS上渲染出来是正确的。于是想用RenderDoc抓帧对比下两边的区别,看看到底哪一步不一样。

结果发现同样的包,在骁龙的机型上可以抓到帧(大约几十MB数据的.rdc文件),在华为的机型上抓出来是黑的(只有3MB左右数据的.rdc文件)。

去RenderDoc的Issue里翻了下,找到一条接近的,看作者的意思大概就是这问题没救。请问有没有人遇到过类似的问题,或者给一些建议。
https://github.com/baldurk/renderdoc/issues/2106

测试环境:
Unity 2018.4/Unity 2019.4
OpenGL ES3(如果用Vulkan,在华为手机上也可以正常抓帧)

1加手机抓帧结果:

华为手机抓帧结果:

A:后来发现某个用Mali Gpu的OPPO手机可以抓帧,就没用华为手机继续查这问题。

不过查资料过程中发现华为有一个自己改版的RenderDoc,可以用来在华为手机上抓帧:
https://developer.huawei.com/consumer/cn/forum/topic/0203337632069160300

感谢题主deviljz@UWA问答社区提供了回答


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

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

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月4日

URP从原理到应用——进阶篇

1.1 前言

在SRP中C++提供了最底层的渲染接口,URP和HDRP根据底层渲染接口构建出各自的渲染管线。如下图所示,整个帧渲染的每个Pass都是在C#中完成,只需要打开URP的源码就可以轻松进行调试,这在Built-in管线中是不可能做到的。管线开源还有个好处就是我们可以进一步优化性能,URP为了兼容性默认会经过4次RT拷贝,但其实完全可以节约掉这部分性能,只需要改改源码就可以实现。

image.png

Unity目前摄像机动态分辨率支持的不佳,只有在iOS和Android的Vulkan环境下才支持,由于目前大部分设备还只是OpenGL ES,所以无法做到3D降分辨率,UI不降分辨率。有了URP我们就可以通过修改源码的方式实现这种需求。如下图所示, 我们将3D摄像机的分辨率改成了0.5,这样渲染的压力大大减小。

image.png

如下图所示,后面UI是高清分辨率直接写到FrameBuffer中,从而实现性能与效果的双重兼容性。

image.png

本篇文章会提供一些经典案例,比如URP优化RT拷贝次数、UI一部分背景+3D模糊效果、干掉FinalBlit优化性能,3D部分与UI部分不同分辨率提高性能与效果。这些功能都离不开源码的定制以及修改,所以对URP源码我们一定要了然于胸。

戳此《URP从原理到应用——进阶篇》查看全文。


1.2 渲染管线与渲染技术

渲染管线和渲染技术可以说是两个完全不同的概念,渲染技术和平台引擎是无关的,学术界的大佬将渲染公式研究出来,才慢慢应用在工业界。

比如Phong光照模型,Bui Tuong Phong(裴祥风)1975年出版的博士论文 《Illumination for computer generated pictures(计算机生成图片光照)》中的计算光的反射向量有一定开销,Jim Blinn(吉姆·布林)就基于Phong光照模型的基础上于1977年提出使用光的向量+视向量算出中向量(计算中向量的效率高于计算反射向量),中向量与法向量做点乘计算高光强度,这就是我们现在手绘贴图最常用的Blinn–Phong光照模型。

现在手游中,PBR光照模型几乎已经成为标配,早在上世纪80年代由康奈尔大学就开始研究物理正确的渲染理论基础。迪士尼(Disney) 在总结前人经验的基础上,于2012年首先提出实时渲染的PBR方案。论文中提到,原本他们可以实现多个物理光照模型,让艺术家们选择和组合它们,但是无法避免参数过多的情况。所以他们将实时基于物理的渲染整合成一个模型。该光照模型由Epic Games首先应用于自家Unreal Engine的实时渲染中,上一章我们提到Unity也根据这个模型提出了一套精简版拟合方案。

如果大家对PBR的理论基础感兴趣,推荐阅读出版于2016年的这本旷世巨作《Physically Based Rendering(基于物理的渲染)》,作者是三位巨佬:包括Matt Pharr(NVIDIA的杰出研究科学家)、Wenzel Jakob(EPFL计算机与通信科学学院的助理教授)和Greg Humphreys(FanDuel的工程总监,之前曾在Google的Chrome图形团队和NVIDIA的OptiX GPU光线追踪引擎工作)。

image.png

这本书提供了在线免费阅读,推荐大家一定要看《Physically Based Rendering(基于物理的渲染)》

通过Phong光照模型和PBR光照模型我们可以看出,渲染技术与渲染引擎是无关的,时至今日渲染技术还有很多,实时图形渲染背后的都是学术界的大佬,并不受限于Unity引擎或者Unreal Engine引擎。再回到工业界游戏引擎会根据学术界的公式适当做一些拓展或者优化,并且引入一些通用渲染效果加入引擎中。目前比较火的岗位包括图形程序员和技术美术。我认为顶级的图形程序员并不仅仅是渲染公式的搬运工,一定要搞懂背后的原理,只有完全搞懂公式的原理才能有目的性地修改渲染公式,实现项目风格化的图形渲染。(在这条路上我愿与君共勉。)

1.2.1 普通光照计算

内置管线中将光照都封装在Surface Shaders中,不利于学习与修改,现在URP将Blinn-Phong和PBR的光照都封装在Lighting.hlsl中,直接引用即可。如果某些地方想修改也可以自己写一个Shader文件,引用一个自定义的Lighting.hlsl:

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

计算光照必须要得到每盏光的光方向、光颜色、光衰减、视线方向和着色点法线方向。

1. Blinn-Phong

在URP中直接使用SimpleLit.shader就可以直接实现Blinn-Phong光照,在顶点着色器中先得到视线方向:

Varyings LitPassVertexSimple(Attributes input)
{
    Varyings output = (Varyings)0;
    //...略
    output.viewDir = GetWorldSpaceViewDir(vertexInput.positionWS);
    //...略
    return output;
}

在片元着色器中计算光照颜色:

half4 LitPassFragmentSimple(Varyings input) : SV_Target
{
    //...略
    InputData inputData;
    InitializeInputData(input, normalTS, inputData);
    //...略
    half4 color = UniversalFragmentBlinnPhong(inputData, diffuse, specular, smoothness, emission, alpha);
    return color;
}

最终结果:

half4 UniversalFragmentBlinnPhong(InputData inputData, half3 diffuse, half4 specularGloss, half smoothness, half3 emission, half alpha)
{
    //主光
    Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, shadowMask);
    half3 attenuatedLightColor = mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);
    half3 diffuseColor = inputData.bakedGI + LightingLambert(attenuatedLightColor, mainLight.direction, inputData.normalWS);
    half3 specularColor = LightingSpecular(attenuatedLightColor, mainLight.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
   //点光
#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowMask);
        #if defined(_SCREEN_SPACE_OCCLUSION)
            light.color *= aoFactor.directAmbientOcclusion;
        #endif
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, inputData.normalWS);
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, inputData.normalWS, inputData.viewDirectionWS, specularGloss, smoothness);
    }
#endif
    //最终结果 环境光 + 漫反射 + 高光
    half3 finalColor = diffuseColor * diffuse + emission + specularColor;
    return half4(finalColor, alpha);
}

1.2.2 PBR光照计算

接着就是处理主光阴影的CBUFFER_START(MainLightShadows),URP的C#代码将阴影级联与阴影偏移等参数传递到GPU中,Shader根据这些参数就可以采样正确的阴影信息了。

Shadows.hlsl
#ifndef SHADER_API_GLES3
CBUFFER_START(MainLightShadows)
#endif
// Last cascade is initialized with a no-op matrix. It always transforms
// shadow coord to half3(0, 0, NEAR_PLANE). We use this trick to avoid
// branching since ComputeCascadeIndex can return cascade index = MAX_SHADOW_CASCADES
float4x4    _MainLightWorldToShadow[MAX_SHADOW_CASCADES + 1];
float4      _CascadeShadowSplitSpheres0;
float4      _CascadeShadowSplitSpheres1;
float4      _CascadeShadowSplitSpheres2;
float4      _CascadeShadowSplitSpheres3;
float4      _CascadeShadowSplitSphereRadii;
half4       _MainLightShadowOffset0;
half4       _MainLightShadowOffset1;
half4       _MainLightShadowOffset2;
half4       _MainLightShadowOffset3;
half4       _MainLightShadowParams;  // (x: shadowStrength, y: 1.0 if soft shadows, 0.0 otherwise, z: oneOverFadeDist, w: minusStartFade)
float4      _MainLightShadowmapSize; // (xy: 1/width and 1/height, zw: width and height)
#ifndef SHADER_API_GLES3
CBUFFER_END
#endif

URP的C#部分代码是如何传递以上信息的,请大家仔细阅读MainLightShadowCasterPass.cs。

image.png

非主光阴影参数CBUFFER_START(AdditionalLightShadows)同样是由C#传递进来的。

Shadows.hlsl
#ifndef SHADER_API_GLES3
CBUFFER_START(AdditionalLightShadows)
#endif
float4x4    _AdditionalLightsWorldToShadow[MAX_VISIBLE_LIGHTS];
half4       _AdditionalShadowParams[MAX_VISIBLE_LIGHTS];
half4       _AdditionalShadowOffset0;
half4       _AdditionalShadowOffset1;
half4       _AdditionalShadowOffset2;
half4       _AdditionalShadowOffset3;
float4      _AdditionalShadowmapSize; // (xy: 1/width and 1/height, zw: width and height)
#ifndef SHADER_API_GLES3
CBUFFER_END
#endif
#endif

URP的C#部分代码是如何传递以上信息的,请大家仔细阅读AdditionalLightsShadowCasterPass.cs。

image.png

有了阴影的参数还不够,还需要将1盏主光和8盏非主光的颜色、矩阵、LightProbe和灯光的衰减从URP的C#代码传入GPU中,这样Shader就能着色灯光和阴影了。如以下代码所示,每个物体都需要引用Input.hlsl并且C#中传递主光的信息。

Input.hlsl
float4 _MainLightPosition;
half4 _MainLightColor;
half4 _MainLightOcclusionProbes;

接着再传递点光的信息,它们保存在CBUFFER_START(AdditionalLights)中:

Input.hlsl
#ifndef SHADER_API_GLES3
CBUFFER_START(AdditionalLights)
#endif
float4 _AdditionalLightsPosition[MAX_VISIBLE_LIGHTS];
half4 _AdditionalLightsColor[MAX_VISIBLE_LIGHTS];
half4 _AdditionalLightsAttenuation[MAX_VISIBLE_LIGHTS];
half4 _AdditionalLightsSpotDir[MAX_VISIBLE_LIGHTS];
half4 _AdditionalLightsOcclusionProbes[MAX_VISIBLE_LIGHTS];
#ifndef SHADER_API_GLES3
CBUFFER_END
#endif
#endif

如下图所示,C#代码中请大家仔细阅读URP中ForwardLights.cs类文件,可以找到实际传递的地方。

image.png

光的数据已经全部传递到Shader中了,接着就可以计算光照了。请大家仔细阅读Lighting.hlsl文件,这里包含了物体表面主光和点光源的颜色计算。

Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, shadowMask);
//..部分略,mainLight计算物体表面的主光颜色
color += LightingPhysicallyBased(brdfData, brdfDataClearCoat,
                                 mainLight,
                                 inputData.normalWS, inputData.viewDirectionWS,
                                 surfaceData.clearCoatMask, specularHighlightsOff);
 
#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
 
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowMask);
        #if defined(_SCREEN_SPACE_OCCLUSION)
            light.color *= aoFactor.directAmbientOcclusion;
        #endif
        //light计算物体表明点光的颜色,最多8盏颜色进行相加
        color += LightingPhysicallyBased(brdfData, brdfDataClearCoat,
                                         light,
                                         inputData.normalWS, inputData.viewDirectionWS,
                                         surfaceData.clearCoatMask, specularHighlightsOff);
    }
#endif

最终物体表面颜色由BRDF乘以辐射率得出:

half3 LightingPhysicallyBased(BRDFData brdfData, BRDFData brdfDataClearCoat,
    half3 lightColor, half3 lightDirectionWS, half lightAttenuation,
    half3 normalWS, half3 viewDirectionWS,
    half clearCoatMask, bool specularHighlightsOff)
{
    //..部分代略
    half NdotL = saturate(dot(normalWS, lightDirectionWS));
    //请注意这里,光源颜色*光源衰减*(法线方向点乘光的方向)
    half3 radiance = lightColor * (lightAttenuation * NdotL);
 
    half3 brdf = brdfData.diffuse;
    brdf += brdfData.specular * DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);
 
    //最终将计算的辐射率*BRDF
    return brdf * radiance;
}

1.2.3 BRDF优化

Unity的工程师Renaldas Zioma在2015年发表了一篇在移动端优化PBR的论文《See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course》(强烈建议大家看看),对BRDF高光感兴趣的同学可以继续看这段代码。

half DirectBRDFSpecular(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS)
{
    float3 halfDir = SafeNormalize(float3(lightDirectionWS) + float3(viewDirectionWS));
    float NoH = saturate(dot(normalWS, halfDir));
    half LoH = saturate(dot(lightDirectionWS, halfDir));
 
    // GGX Distribution multiplied by combined approximation of Visibility and Fresnel
    // BRDFspec = (D * V * F) / 4.0
    // D = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2
    // V * F = 1.0 / ( LoH^2 * (roughness + 0.5) )
    // See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course
    // https://community.arm.com/events/1155
 
    // Final BRDFspec = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2 * (LoH^2 * (roughness + 0.5) * 4.0)
    // We further optimize a few light invariant terms
    // brdfData.normalizationTerm = (roughness + 0.5) * 4.0 rewritten as roughness * 4.0 + 2.0 to a fit a MAD.
    float d = NoH * NoH * brdfData.roughness2MinusOne + 1.00001f;
 
    half LoH2 = LoH * LoH;
    half specularTerm = brdfData.roughness2 / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm);
 
    // On platforms where half actually means something, the denominator has a risk of overflow
    // clamp below was added specifically to "fix" that, but dx compiler (we convert bytecode to metal/gles)
    // sees that specularTerm have only non-negative terms, so it skips max(0,..) in clamp (leaving only min(100,...))
#if defined (SHADER_API_MOBILE) || defined (SHADER_API_SWITCH)
    specularTerm = specularTerm - HALF_MIN;
    specularTerm = clamp(specularTerm, 0.0, 100.0); // Prevent FP16 overflow on mobiles
#endif
 
return specularTerm;
}

代码中的注释可以清晰地看到Unity是如何进行优化的,首先看看完整的BRDF公式。

D:微表面分部函数(注意这里的D就是GGX);

G:遮挡可见性函数(注意这里的G并不是GGX);

F:菲涅尔函数;

image.png

首先Unity先将G项进行拟合称为V项遮挡可见性函数,如下图所示,拟合结果 V=G/(4(N⋅V)(N⋅L)):

image.png

接着对V*F进行拟合:

image.png

最终BRDF = V F D (前面说到D就是GGX):

image.png

如下图所示,按照上面的公式大家可以自己乘一下看看最终是不是正确。

image.png

大家可以按照这个公式再对比一下前面函数DirectBRDFSpecular计算的是否正确。

戳此《URP从原理到应用——进阶篇》查看全文。

查看原文

赞 0 收藏 0 评论 0

侑虎科技 发布了文章 · 3月4日

如何定位Unity死循环导致的完全卡死

1)如何定位Unity死循环导致的完全卡死
​2)如何设定Unity AssetBundle单个包大小
3)MaterialPropertyBlock修改Stencil相关参数
4)线性空间中动作文件控制材质球颜色失真问题
5)DrawMeshInstancedIndirect在华为手机上失效


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

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

Script

Q:运行游戏时,在某些特定的情况下,Unity会突然卡死,看任务控制器里的Unity进程内存会持续飙升到很高的值。

由于目前游戏工程已经很大了,并且脚本众多(Lua+C#),出现这种情况时又没有异常日志,断点也断不到位置,也不知道是哪里出现的死循环异常了,这个问题卡了很久,没思路了,求解。

A1:经网友真木提示了这篇文章:https://www.cnblogs.com/lijiajia/p/10817407.html

通过debug.sethook这个函数来注册一个Hook的Handler,把每一行或者每个函数的调用都打印出来,就能知道死循环的位置了。这种方法可用于找Lua的死循环,假如是C#的死循环,需要其它方法。

感谢题主loy_liu@UWA问答社区提供了回答

A2:如果是C#的死循环,可以使用VS附加Unity调试,暂停整个游戏,然后切换到主线程,看看主线程当前运行到了哪一句。

感谢西元前的史莱姆@UWA问答社区提供了回答

AssetBundle

Q:Unity AssetBundle单个包大小多少比较合适?

A1:以前LZMA格式时,是建议小于1MB的,现在已经没有这个约束了。因为LoadFromFile+LZ4的加载速度已经非常快了。所以,文件大小更建议从热更新的角度出发,尽可能不要给热更新产生大麻烦即可。

该问答由UWA提供

A2:楼上说得对,读取速度已经不是问题,不要太小也不用太大,个人觉得1-10MB间差不多都可以,我个人是以使用的聚集性把每个包控制在几MB左右。

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

Material

Q:我在原生的Sprite-default Shader中加入了Stencil的相关设置,在编辑器状态下用MaterialPropertyBlock修改Stencil的Comp和Ref,在Inspector面板中该值已经被修改,但是在Frame Debugger中并未生效修改值,还是未修改前的设置,是不能用MaterialPropertyBlock改Stencil的相关参数吗?

A:可参考以下信息:

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

Rendering

Q:线性空间中动作文件控制材质球颜色失真问题。
以下是直接设置材质球的颜色显示:

以下是通过Animator动作文件设置材质球的颜色显示:

为什么不同呢?材质球是[HDR] Color有问题。(版本:Unity 2019.4.9f)

A:在线性空间中,写入到MaterialPropertyBlock的渲染颜色参数,在渲染时会转换到Gamma空间,即变成2.2次幂。

用动画修改材质的颜色,其实是向MaterialPropertyBlock中配置参数。所以在使用K材质球设计动画时,需要把颜色值的0.4545次幂写入到曲线中。

感谢张首峰@UWA问答社区提供了回答

Rendering

Q:Unity 2018.4.24的DrawMeshInstancedIndirect在华为手机上失效是为什么?我使用Testin的华为手机进行测试DrawMeshInstancedIndirect接口,试过的都是不行的,但是我用其他的手机就都可以。有遇到同样问题吗?

A1:贴上详细日志看看,然后把华为手机的设备信息贴上,最后问下是不是带麒麟CPU的华为机。

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

A2:由于驱动程序问题,对于仅具有OpenGL ES 3.0的Adreno GPU的Android设备禁用了GPU实例支持。

Graphics: GPU Instancing: Added support for Android with OpenGL ES 3.0 or newer. Note however that GPU instancing support is disabled for Android devices that have the Adreno GPU with only OpenGL ES 3.0, because of driver issues.

可以使用SystemInfo.supportsInstancing检测机器是否支持GPU Instancing,最保险的方式就是OpenGL ES 3.1及以上支持。

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

A3:最后发现是华为Mali不支持SSBO的原因。

感谢题主halm@UWA问答社区提供了回答

封面图来源于网络


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

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

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 4 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-03-17
个人主页被 2.3k 人浏览