Unity ShaderVariant 变体收集方案分析

news2024/11/20 12:26:27

最近遇到一个问题,在editor中场景渲染正确,打包android之后,渲染异常。

经过排查得出原因:工程把所有shader单独打包Assetbundle,editor打包ab包的时候,未收集到正确的shader变体,未将场景中使用的shader变体打包到ab包中,所以发布apk之后,渲染异常。

什么是shader变体:ShaderVariant变体_Sevol_Y的博客-CSDN博客

在网上猛搜了一天,找到很多解决方法,也测试了很多方法:

  1. 方案1,测试可行。将shader加入到editor设置中,ProjectSetting-->Graphics-->AlwaysIncludedShaders。所有加入设置的shader都将编译全部shader变体。如果你知道是哪一个shader出现异常,加入进设置列表中,应该能解决问题。注意注意!:如果你的shader变体有很多,类似unity内置的standard,那么千万不要加入,这会导致你的出包巨慢,而且包内有很多用不到的shader变体。还有就是我出现问题的shader,包含的总变体有6M+个(百万个),这时候editor就会提醒你,亲,你的变体个数实在太多,这边不建议你加入列表,直接阻止你的操作。
  2. 方案2,未测试。将shader放入Resources文件夹下,等同方案一效果。
  3. 方案3,测试可行,将出现异常的mat和shader放在同一文件夹下,效果正常。
  4. 方案4,测试可行。使用unity自动收集shader变体文件功能。先点击Clear,清除已经收集到的变体,然后直接运行游戏,将游戏的犄角旮哪都跑一遍,或者你也可以运行到出现异常的地方(不过这样不全,只会解决你现目前发现问题的地方),然后点击SaveToAsset,保存shader变体文件到Shader  assetbundle包内,效果正常。
  5. 方案5,自动收集变体代码1,测试,未解决我的问题。自动收集变体文件ShaderVariant ,网上流传的脚本,放入工程测试了,不过未解决我的问题,不知道什么原因,这是BDFramework的框架内容,大家可以放到自己工程试试。GitHub - yimengfan/BDFramework.Core: Simple and powerful Unity3d game workflow! 简单、高效、高度工业化的商业级unity3d 工作流。
    using System.Collections;
    using System.Collections.Generic;
    using UnityEditor;
    using UnityEngine;
    using System.IO;
    using System.Reflection;
    using System;
    using UnityEngine.Rendering;
    using System.Linq;
    
    public class ShaderCollection : EditorWindow
    {
        static Dictionary<string, List<ShaderVariantCollection.ShaderVariant>> ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
        public static List<string> GetAllRuntimeDirects()
        {
            //搜索所有资源
            List<string> directories = new List<string>();
            directories.Add("Assets");
            return directories;
        }
        private ShaderVariantCollection svc;
        readonly public static string ALL_SHADER_VARAINT_PATH = "Assets/AllShaders.shadervariants";
    
        static List<string> allShaderNameList = new List<string>();
    
        [MenuItem("ShaderTool/AutoShaderVariants")]
        public static void GenShaderVariant()
        {
            ShaderVariantDict = new Dictionary<string, List<ShaderVariantCollection.ShaderVariant>>();
            //先搜集所有keyword到工具类SVC
            toolSVC = new ShaderVariantCollection();
            var shaders = AssetDatabase.FindAssets("t:Shader", new string[] { "Assets", "Packages" }).ToList();
            foreach (var shader in shaders)
            {
                ShaderVariantCollection.ShaderVariant sv = new ShaderVariantCollection.ShaderVariant();
                var shaderPath = AssetDatabase.GUIDToAssetPath(shader);
                sv.shader = AssetDatabase.LoadAssetAtPath<Shader>(shaderPath);
                toolSVC.Add(sv);
                //
                allShaderNameList.Add(shaderPath);
            }
    
            var toolsSVCpath = "Assets/Tools.shadervariants";
            //防空
    
            File.WriteAllText(toolsSVCpath, "");
            AssetDatabase.DeleteAsset(toolsSVCpath);
            AssetDatabase.CreateAsset(toolSVC, toolsSVCpath);
    
            //搜索所有Mat
            var paths = GetAllRuntimeDirects().ToArray();
            var assets = AssetDatabase.FindAssets("t:Prefab", paths).ToList();
            var assets2 = AssetDatabase.FindAssets("t:Material", paths);
            assets.AddRange(assets2);
            List<string> allMats = new List<string>();
    
            //GUID to assetPath
            for (int i = 0; i < assets.Count; i++)
            {
                var p = AssetDatabase.GUIDToAssetPath(assets[i]);
                //获取依赖中的mat
                var dependenciesPath = AssetDatabase.GetDependencies(p, true);
                var mats = dependenciesPath.ToList().FindAll((dp) => dp.EndsWith(".mat"));
                allMats.AddRange(mats);
            }
    
            //处理所有的 material
            allMats = allMats.Distinct().ToList();
    
            float count = 1;
            foreach (var mat in allMats)
            {
                var obj = AssetDatabase.LoadMainAssetAtPath(mat);
                if (obj is Material)
                {
                    var _mat = obj as Material;
                    EditorUtility.DisplayProgressBar("处理mat", string.Format("处理:{0} - {1}", Path.GetFileName(mat), _mat.shader.name), count / allMats.Count);
                    AddToDict(_mat);
                }
    
                count++;
            }
    
            EditorUtility.ClearProgressBar();
            //所有的svc
            ShaderVariantCollection svc = new ShaderVariantCollection();
            foreach (var item in ShaderVariantDict)
            {
                foreach (var _sv in item.Value)
                {
                    svc.Add(_sv);
                }
            }
    
            AssetDatabase.DeleteAsset(ALL_SHADER_VARAINT_PATH);
            AssetDatabase.CreateAsset(svc, ALL_SHADER_VARAINT_PATH);
            AssetDatabase.Refresh();
    
        }
        public class ShaderData
        {
            public int[] PassTypes = new int[] { };
            public string[][] KeyWords = new string[][] { };
            public string[] ReMainingKeyWords = new string[] { };
        }
    
        //shader数据的缓存
        static Dictionary<string, ShaderData> ShaderDataDict = new Dictionary<string, ShaderData>();
    
    
    
        //添加Material计算
        static List<string> passShaderList = new List<string>();
    
        /// <summary>
        /// 添加到Dictionary
        /// </summary>
        /// <param name="curMat"></param>
        static void AddToDict(Material curMat)
        {
            if (!curMat || !curMat.shader) return;
    
            var path = AssetDatabase.GetAssetPath(curMat.shader);
            if (!allShaderNameList.Contains(path))
            {
                Debug.LogError("不存在shader:" + curMat.shader.name);
                Debug.Log(path);
                return;
            }
    
            ShaderData sd = null;
            ShaderDataDict.TryGetValue(curMat.shader.name, out sd);
            if (sd == null)
            {
                //一次性取出所有的 passtypes 和  keywords
                sd = GetShaderKeywords(curMat.shader);
                ShaderDataDict[curMat.shader.name] = sd;
            }
    
            var kwCount = sd.PassTypes.Length;
            if (kwCount > 2000)
            {
                if (!passShaderList.Contains(curMat.shader.name))
                {
                    Debug.LogFormat("Shader【{0}】,变体数量:{1},不建议继续分析,后续也会跳过!", curMat.shader.name, kwCount);
                    passShaderList.Add(curMat.shader.name);
                }
                else
                {
                    Debug.LogFormat("mat:{0} , shader:{1} ,keywordCount:{2}", curMat.name, curMat.shader.name, kwCount);
                }
    
                return;
            }
    
    
            List<ShaderVariantCollection.ShaderVariant> svlist = null;
            if (!ShaderVariantDict.TryGetValue(curMat.shader.name, out svlist))
            {
                svlist = new List<ShaderVariantCollection.ShaderVariant>();
                ShaderVariantDict[curMat.shader.name] = svlist;
            }
    
            //求所有mat的kw
            for (int i = 0; i < sd.PassTypes.Length; i++)
            {
                //
                var pt = (PassType)sd.PassTypes[i];
                ShaderVariantCollection.ShaderVariant? sv = null;
                try
                {
                    string[] key_worlds = sd.KeyWords[i];
    
                    //变体交集 大于0 ,添加到 svcList
                    sv = new ShaderVariantCollection.ShaderVariant(curMat.shader, pt, key_worlds);
                    SetShaderVariantKeyWorld(svlist, sv);
                }
                catch (Exception e)
                {
                    Debug.LogErrorFormat("{0}-当前shader不存在变体(可以无视):{1}-{2}", curMat.name, pt, curMat.shaderKeywords.ToString());
                    continue;
                }
    
    
            }
        }
    
        static void SetShaderVariantKeyWorld(List<ShaderVariantCollection.ShaderVariant> svlist, ShaderVariantCollection.ShaderVariant? sv)
        {
            //判断sv 是否存在,不存在则添加
            if (sv != null)
            {
                bool isContain = false;
                var _sv = (ShaderVariantCollection.ShaderVariant)sv;
                foreach (var val in svlist)
                {
                    if (val.passType == _sv.passType && System.Linq.Enumerable.SequenceEqual(val.keywords, _sv.keywords))
                    {
                        isContain = true;
                        break;
                    }
                }
    
                if (!isContain)
                {
                    svlist.Add(_sv);
                }
            }
        }
    
    
        static MethodInfo GetShaderVariantEntries = null;
    
        static ShaderVariantCollection toolSVC = null;
    
        //获取shader的 keywords
        public static ShaderData GetShaderKeywords(Shader shader)
        {
            ShaderData sd = new ShaderData();
            GetShaderVariantEntriesFiltered(shader, new string[] { }, out sd.PassTypes, out sd.KeyWords, out sd.ReMainingKeyWords);
            return sd;
        }
    
        /// <summary>
        /// 获取keyword
        /// </summary>
        /// <param name="shader"></param>
        /// <param name="filterKeywords"></param>
        /// <param name="passTypes"></param>
        /// <param name="keywordLists"></param>
        /// <param name="remainingKeywords"></param>
        static void GetShaderVariantEntriesFiltered(Shader shader, string[] filterKeywords, out int[] passTypes, out string[][] keywordLists, out string[] remainingKeywords)
        {
            //2019.3接口
            //            internal static void GetShaderVariantEntriesFiltered(
            //                Shader                  shader,                     0
            //                int                     maxEntries,                 1
            //                string[]                filterKeywords,             2
            //                ShaderVariantCollection excludeCollection,          3
            //                out int[]               passTypes,                  4
            //                out string[]            keywordLists,               5
            //                out string[]            remainingKeywords)          6
            if (GetShaderVariantEntries == null)
            {
                GetShaderVariantEntries = typeof(ShaderUtil).GetMethod("GetShaderVariantEntriesFiltered", BindingFlags.NonPublic | BindingFlags.Static);
            }
    
            passTypes = new int[] { };
            keywordLists = new string[][] { };
            remainingKeywords = new string[] { };
            if (toolSVC != null)
            {
                var _passtypes = new int[] { };
                var _keywords = new string[] { };
                var _remainingKeywords = new string[] { };
                object[] args = new object[] { shader, 256, filterKeywords, toolSVC, _passtypes, _keywords, _remainingKeywords };
                GetShaderVariantEntries.Invoke(null, args);
    
                var passtypes = args[4] as int[];
                passTypes = passtypes;
                //key word
                keywordLists = new string[passtypes.Length][];
                var kws = args[5] as string[];
                for (int i = 0; i < passtypes.Length; i++)
                {
                    keywordLists[i] = kws[i].Split(' ');
                }
    
                //Remaning key word
                var rnkws = args[6] as string[];
                remainingKeywords = rnkws;
            }
        }
    }
  6. 方案6,自动收集变体代码2,测试。这里留一篇文章,我觉得很对,思路很清晰,易懂,可以看看GitHub - lujian101/ShaderVariantCollector
  7. 方案7,手撸Shader Variant Collection文件,同样不全,还不咋好用。

 最后贴几篇参考:

【Unity3D】Shader变体管理流程2-变体收集 - 简书

GitHub - lujian101/ShaderVariantCollector

爬坑篇(3)之 Unity2019 keyword坑(又名:性感程序化身名侦探) - 知乎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/428356.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

go语言并发编程

并发编程1.并发介绍1.1进程和线程1.2并发和并行1.3协程和线程1.协程2.线程1.4goroutine只是由官方实现的超级"线程池"2.Goroutine2.1使用Goroutine1.启动单个goroutine2.启动多个goroutine3.goroutine与线程3.1可增长的栈3.2goroutine调度3.runtime包3.1runtime.Gosc…

人员玩手机离岗识别检测系统 yolov5

人员玩手机离岗识别检测系统根通过pythonyolov5网络模型识别算法技术&#xff0c;人员玩手机离岗识别检测算法可以对画面中人员睡岗离岗、玩手机打电话、脱岗睡岗情况进行全天候不间断进行识别检测报警提醒。Python是一种由Guido van Rossum开发的通用编程语言&#xff0c;它很…

Nginx 的docker部署及宿主机配置文件修改重启

Nginx是一款高性能的Web服务器&#xff0c;用于反向代理、负载均衡、HTTP缓存等。在docker中部署Nginx可以更加方便地管理和配置。下面是部署Nginx的步骤&#xff1a; 步骤一&#xff1a;拉取Nginx镜像 首先需要从Docker Hub上拉取Nginx镜像&#xff0c;可以使用以下命令&…

windows11系统关闭右键【显示更多选项】

在新的Win11操作系统之中&#xff0c;微软为了美化界面&#xff0c;将右键菜单进行了整合更改&#xff0c;但是实用性却大幅度下降&#xff0c;引起了很多用户的反感&#xff0c;并迫切希望能够将Win11显示更多选项这个反人类的设置恢复成Win10的状态。 方法一&#xff1a;更改…

学成在线笔记0-面试问题

【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线牛客面试题 目录 介绍你的项目 项目难点 CDN是什么&#xff1f; 负载均衡是怎么做的&#xff1f; git使用了什么仓库&#xff1f; git代码冲突怎么处理&#xff1f; 你…

linux入门---文件系统

目录标题为什么会有文件系统磁盘的物理结构磁盘的存储结构磁盘的逻辑结构文件系统为什么会有文件系统 在我们的云服务器上存在着很多的文件&#xff0c;但并不是所有的文件都是被打开的&#xff0c;操作系统得管理好已经被打开的文件&#xff0c;那么同样的道理在磁盘中没有被…

Redis如何避免数据丢失?

Redis的持久化主要有两大机制 即 AOF(Append Only File)日志, RDB(Redis DataBase)快照。 AOF 日志是如何实现的&#xff1f; AOF是写后日志&#xff0c;就是Redis限制性命令&#xff0c;数据写入内存&#xff0c;然后才记录日志。AOF里记录的是Redis收到的每一条命令&#x…

uni-app--》如何制作一个APP并使用?

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

大数据应用——hbase shell操作

HBase 逻辑结构 HBase 物理存储结构 数据模型 1&#xff09;Name Space 命名空间&#xff0c;类似于关系型数据库的 DatabBase概念&#xff0c;每个命名空间下有多个表。HBase 有两个自带的命名空间&#xff0c;分别是 hbase 和 default hbase 中存放的是 HBase 内置的表&…

Android导入第三方SO库,上层Java调用(1)

Android导入第三方SO库&#xff0c;上层Java调用&#xff08;1&#xff09; &#xff08;1&#xff09;在module的build.gradle文件的android块中配置sourceSet&#xff1a; sourceSets {main {jniLibs.srcDir libs}} &#xff08;2&#xff09;在module的路径下&#xff0c;比…

[架构之路-166]-《软考-系统分析师》-4-据通信与计算机网络-2- 网络体系结构、协议

目录 4.2 网络体系结构与协议 4.2.1 网络互联模型 1. OSI / RM 各层的功能 2. TCP / IP 结构模型 4.2.2常见的网络协议 1 . 应用层协议 2 . 传输层协议 3 . 网络层协议 4.2.3网络地址与分配 4.2 网络体系结构与协议 网络体系结构是指计算机网络络的各层及其协议的集…

【k8s完整实战教程2】腾讯云搭建k8s托管集群

系列文章&#xff1a;这个系列已完结&#xff0c;如对您有帮助&#xff0c;求点赞收藏评论。 读者寄语&#xff1a;再小的帆&#xff0c;也能远航&#xff01; 【k8s完整实战教程0】前言【k8s完整实战教程1】源码管理-Coding【k8s完整实战教程2】腾讯云搭建k8s托管集群【k8s完…

Linux实战案列-发送告警邮件

发送告警邮件 准备 外部邮件服务器 首发在雪月书韵茶香 原因 本地自带邮箱容易被过滤&#xff0c;需要延迟性低的邮箱发送 配置docker 配置环境变量 主机版本&#xff1a;macOS 12.6.4 open .bash_profile export DOCKER_PATH"/Applications/Docker.app/Contents/Re…

threejs-效果合成器(EffectComposer)

文章目录前言EffectComposer 使用流程场景初始化&#xff1a;自转的地球创建THREE.EffectComposer添加后期处理通道并更新渲染EffectComposer 使用示例示例一&#xff1a;FilmPass 添加电视效果示例二&#xff1a;OutlinePass 添加闪烁效果总结前言 threejs中的效果合成器 Eff…

Python的基础

这是我自己学习Python的三个星期的小总结&#xff0c;内容包含了规范、数据类型、函数、类和捕捉异常&#xff0c;做了一个简单的梳理&#xff0c;希望可以帮助到和我一样开始学习Python的小伙伴&#xff0c;也希望多多支持&#xff0c;相互进步&#xff0c;下面步入正题。 基…

记一次 .NET某医疗器械清洗系统 卡死分析

一&#xff1a;背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题&#xff0c;回过头来看这个案例比较经典&#xff0c;这篇稍微整理一下供后来者少踩坑吧。 二&#xff1a;WinDbg 分析 1. 为什么会卡死 因为是窗体程序&#xff0c;理所当然就是看主…

MySQL全局锁、表级锁、行级锁介绍演示(详细)

目录 介绍 分类 1、全局锁 1.1介绍 1.2场景 1.3语法 1.4演示 2、表级锁 2.1介绍 2.2分类 2.3语法 2.4演示 3、行级锁 3.1介绍 3.2分类 3.3场景 介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;…

linux下搭建Hbase分布式数据库

文章目录Hbase概念1.安装Hbase1.jdk的配置2.安装hbase2.启动和操作1.启动服务2 **web-ui访问地址:http://node01:16010/master-status**3.简单的操作1.连接 HBase2.帮助命令3.创建一张表 create a table4.使用查看表是否存在5.describe 查看表描述6.put命令插入数据到表7. scan…

jmeter插件的安装

前言 jmeter常用的插件有很多&#xff0c;本身安装的jmeter是没有安装插件的工具&#xff0c;需要下载一个jar包&#xff0c;通过插件安装工具去安装jmeter插件plugins-manager.jar这个jar包就是用来安装jmeter插件的jar把这个jar包下载后放到jmeter的lib/ext目录下重启jmeter…

C++语法(16)---- 多态

https://blog.csdn.net/m0_63488627/article/details/130106690?spm1001.2014.3001.5501https://blog.csdn.net/m0_63488627/article/details/130106690?spm1001.2014.3001.5501 目录 1. 多态的概念 2.多态的实现 1.虚函数 2.多态条件 得到的多态条件 特殊条件 3.虚函…