【Shader】Unity批量更换Shader

在Unity开发中,因为渲染管线问题,经常会遇到需要大量更改shader的问题,本篇文章讲述批量更换Shader的工具

一、原理:

  • 拿到场景内、指定文件夹内所有模型或指定模型
  • 编辑获取其Renderer上的所有材质球
  • 记录原材质的颜色贴图
  • 更换新的shader
  • 将颜色与贴图赋值给新的shader

二、代码

  • 代码功能:(其他地方可自行更改)
    • 更换场景内所有物体的shader
    • 目标Shader为URP管线
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;

public class BathShaderTool : EditorWindow
{
    private Shader _targetShader;
    private int _selectedShaderIndex = 0;

    private static string[] _shaders = new string[]
    {
        // 可自定指定Shader名称
        //"Universal Render Pipeline/Lit",
    };

    private string _folderPath = "Assets/YourFolderPath"; // 指定你的文件夹路径

    [MenuItem("Tools/BathShaderTool")]
    public static void ShowWindow()
    {
        if (_shaders.Length == 0)
        {
            LoadAllShaders();
        }
        GetWindow<BathShaderTool>();
    }

    private void OnGUI()
    {
        GUILayout.Label("BathShader", EditorStyles.boldLabel);
        _folderPath = GUILayout.TextField(_folderPath);

        _selectedShaderIndex = EditorGUILayout.Popup("Target Shader", _selectedShaderIndex, _shaders);
        _targetShader = Shader.Find(_shaders[_selectedShaderIndex]);

        if (GUILayout.Button("设置场景中所有对象的shader"))
        {
            if (_targetShader != null)
            {
                SetShaderForAllObjectsInScene(_targetShader);
            }
            else
            {
                Debug.LogWarning("Please select a shader.");
            }
        }

        if (GUILayout.Button("设置文件夹中所有对象的shader"))
        {
            if (_targetShader != null)
            {
                SetShader<GameObject>("t:GameObject", _targetShader,
                    (mat, shader) => TraverseAndSetShader((GameObject)mat, shader));
            }
            else
            {
                Debug.LogWarning("Please select a shader.");
            }
        }

        if (GUILayout.Button("设置文件夹中所有材质球的shader"))
        {
            if (_targetShader != null)
            {
                SetShader<Material>("t:Material", _targetShader,
                    (mat, shader) => SetShaderForMat((Material)mat, shader));
            }
            else
            {
                Debug.LogWarning("Please select a shader.");
            }
        }
    }
    
    /// <summary>
    /// 设置场景中所有对象的Shader
    /// </summary>
    /// <param name="shader"></param>
    private void SetShaderForAllObjectsInScene(Shader shader)
    {
        GameObject[] allObjects = SceneManager.GetActiveScene().GetRootGameObjects();

        foreach (GameObject obj in allObjects)
        {
            TraverseAndSetShader(obj, shader);
        }

        Debug.Log("Shader has been set for all objects in the scene.");
    }

    /// <summary>
    /// 遍历对象
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="shader"></param>
    private void TraverseAndSetShader(GameObject obj, Shader shader)
    {
        SetShaderForAllRenderers(obj, shader);

        foreach (Transform child in obj.transform)
        {
            TraverseAndSetShader(child.gameObject, shader);
        }
    }

    /// <summary>
    /// 遍历Renderer并设置Shader
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="shader"></param>
    private void SetShaderForAllRenderers(GameObject obj, Shader shader)
    {
        Renderer[] renderers = obj.GetComponents<Renderer>();

        foreach (Renderer renderer in renderers)
        {
            if (renderer != null)
            {
                foreach (Material material in renderer.sharedMaterials)
                {
                    SetShaderForMat(material, shader);
                }
            }
        }
    }

    /// <summary>
    /// 设置材质球的Shader
    /// </summary>
    /// <param name="material"></param>
    /// <param name="shader"></param>
    private void SetShaderForMat(Material material, Shader shader)
    {
        if (material != null)
        {
            Color albedoColor = material.color;
            Texture albedoTexture = material.mainTexture;

            material.shader = shader;

            material.color = albedoColor;
            material.mainTexture = albedoTexture;
        }
    }
    
    /// <summary>
    /// 设置文件夹中所有指定类型的资源的Shader
    /// </summary>
    /// <param name="type"></param>
    /// <param name="shader"></param>
    /// <param name="action"></param>
    /// <typeparam name="T"></typeparam>
    private void SetShader<T>(string type,Shader shader,Action<object,Shader> action) where T : Object
    {
        string[] guids = AssetDatabase.FindAssets(type, new[] { _folderPath });
        foreach (string guid in guids)
        {
            string assetPath = AssetDatabase.GUIDToAssetPath(guid);
            T assetAt = AssetDatabase.LoadAssetAtPath<T>(assetPath);

            if (assetAt != null)
            {
                action?.Invoke(assetAt,shader);
                EditorUtility.SetDirty(assetAt);
            }
        }

        AssetDatabase.SaveAssets();
    }
    
    /// <summary>
    /// 加载所有Shader名称
    /// </summary>
    private static void LoadAllShaders()
    {
        var shaders = Resources.FindObjectsOfTypeAll<Shader>();
        _shaders = new string[shaders.Length];

        for (int i = 0; i < _shaders.Length; i++)
        {
            Debug.Log(shaders[i].name);
            _shaders[i] = shaders[i].name;
        }
    }
}
### UnityShader 的 RenderType 属性及其用法 `RenderType` 是 Unity Shader 中的一个重要属性,用于定义渲染队列中的特定行为。它主要用于控制如何以及何时绘制材质或对象。通过设置 `RenderType`,可以影响透明度排序、光照计算以及其他渲染阶段的行为。 #### 渲染队列概述 在 Unity 中,所有的几何体都按照一定的顺序进行渲染,这个顺序由 **渲染队列 (Rendering Queue)** 控制。默认情况下,Unity 将对象分为几个主要的渲染队列: - 背景 (`Background`):通常用于天空盒。 - 不透明 (`Geometry`):大多数不透明对象属于此队列。 - 透明 (`Transparent`):半透明对象在此队列中按距离相机远近排序并渲染。 `RenderType` 提供了一种更细粒度的方式来指定某些类型的材质应归属于哪个渲染队列,从而实现更好的性能和视觉效果[^1]。 #### 常见的 RenderType 类型及用途 以下是几种常见的 `RenderType` 设置及其典型应用场景: 1. **Opaque** - 表示完全不透明的对象。 - 这些对象会在早期阶段被渲染到屏幕上,以便后续的透明对象能够基于它们的颜色进行混合操作。 2. **Transparent** - 指定该材质为透明材质。 - 此类材质会延迟至最后阶段渲染,并依据其与摄像机之间的相对位置重新排列以获得正确的视觉层次感。 3. **TransparentCutout** - 使用 Alpha 测试技术来创建具有硬边裁剪效果的材质。 - 它既不是纯粹的不透明也不是真正的透明,而是介于两者之间的一种形式,在保持一定效率的同时允许部分区域不可见。 4. **Background / Overlay** - 特殊类别分别对应背景层(如 Skybox)和其他覆盖物(UI 元素等),这些都需要特别处理才能正确显示在整个场景之上或之下。 #### 实际应用案例分析 下面给出一些具体的例子说明如何配置不同种类的 shader 并利用 render type 参数达到预期目标: ##### 示例一: 创建简单的平面反射效果 当构建水面或其他镜面时,可能需要用到额外 pass 来捕捉周围环境作为倒影贴图的一部分。此时可以通过调整相应 material 的 properties block 添加如下字段: ```csharp Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader{ Tags { "Queue"="Transparent" "RenderType"="Transparent"} Pass{} } ``` 此处我们将 queue 设定为了 Transparent 同时也指定了对应的 render type ,这样就能确保我们的自定义逻辑能够在恰当的时间被执行出来形成最终的画面表现. ##### 示例二: 批量管理静态模型组提升效能 对于那些频繁重复出现的小物件比如草地上的石头碎片等等来说单独实例化每一个都会带来不小的开销 。因此我们可以考虑采用 draw call batching 技术将其打包成单次调用来减少 CPU/GPU交互次数进而加快帧率响应速度 [^2]. 具体做法包括但不限于以下几个方面 : - 组合相近特性的实体成为一个整体 ; - 减少使用的 materials 数目 ,尽可能统一共用一套 textures atlas; - 避免过多复杂特效叠加导致反复重绘同一个 surface. 以上措施结合起来往往能显著改善大型项目里的运行状况 . #### 总结 合理运用 Shader 的 RenderType 可以为开发者提供更多灵活性去定制专属需求下的图形管线结构 .无论是追求极致真实的物理模拟还是高效简洁的游戏体验设计都可以从中受益匪浅.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

憨辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值