合并Shader系列 | 如何合并渲染状态

Shader叠加网格效果 GitHub项目地址 叠加网格效果,其实就是简单的叠加一层网格纹理的效果,但是项目中需要把网格往视觉方向正面叠加,所以我这里就实现了两种叠加效果。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEDTR3ts-1591966841166)(https://upload-images.jianshu.io/upload_images/15470347-13bdc7c1322adb04.png?imageMogr2/auto-orient/strip%7CimageView2. 阅读详情

《合并Shader》系列旨在介绍一些在保证功能不打折的情况下精简Shader数量的方法,其遵循的原理就是把相似功能的Shader文件合并在一个文件里。在掌握了这些技能后,研发团队能在极致情况下做到把所有的Shader文件合并成一个,如Unity 5.x的Standard着色器。本系列会从简到繁的方式来依次遍历林林总总的合并方法,希望大家有所收获。

这是侑虎科技第179篇原创文章,感谢作者唐建伟(网名:七火,QQ:383684387)供稿。欢迎转发分享,未经作者授权请勿转载。作者博客: http://www.7fires.cn/。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U Sparkle开发者计划,这个舞台有你更精彩!


在数学中我们学习过:把多项式中的同类项合并成一项叫做合并同类项。同理,提取Shader的相似部分,把多个Shader合并成一个就叫做Shader的合并,也叫合并Shader,偶尔也会引用数学的名词来称呼他为Shader的合并同类项。

Shader的合并方式有很多,根据不同的合并技能和方法可以划分为不同的派系。今天优先介绍一种不太常见、但又很实用的派系,往下看。

在Shader的合并方法中,MaterialPropertyDrawer(属性定义)可以说是自成一派,但又与其他派系有着千丝万缕的关系,今天我们就先拿它来开刀。

在此之前我想补充一点:对于Shader的合并,首先让人想到的应该是宏定义,相信宏定义也是大家应用最广、最先接触的。(毕竟由于GPU的特殊性,Shader里常常通篇都充满了各种宏定义。)当然,该系列会对宏定义有所介绍,它可是Shader合并里功高盖世的重要角色,很多地方都会有它的身影。但对它的介绍不在这一篇,也许会是下一篇。因为我认为在合并Shader的众多方法中,最简单的不是它,而是使用Unity已经预先定制好的几种MaterialPropertyDrawer的方式。只用修改两行代码,就可以搞定一类Shader的合并,该方法主要用来合并那些只是渲染状态不一样的Shader。


◆◆◆
初次简单使用

一个完整的Shader,它的渲染状态变量有很多种,由于不是每一种状态的改变都能很明显地看到结果。那么作为初次使用,我们就优先选择一种最明显的、最易懂的状态作为测试用例—ZTest,即深度比较。对ZTest不太了解的朋友,可以看看官方的学习文档或者查看一下相关技术书籍,我就不在这里具体介绍这个状态了。

1.1 首先我们选用的是Unity官方提供的一个最常用Shader

Normal-Diffuse.shader(Legacy Shaders/Diffuse)

1.2 在属性列表(Properties)中添加一行

[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2

1.3 在SubShader中添加一行

ZTest [_ZTest]

1.4 完整的Shader

Shader "ShaderCombine/01.ShaderCombineSimpleZTest"
{
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        ZTest [_ZTest]

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        fixed4 _Color;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }

    Fallback "Legacy Shaders/VertexLit"
}

只用添加这两行代码,我们就可以在Inspector面板中控制使用该Shader物体的深度测试方法。

1.5 直观展示
在Unity中的测试示例是这样的:
请输入图片描述
当然大家也可以通过这个示例试验下每一种深度测试的方法是否与你心中所想、或之前所学的有所冲突。


◆◆◆
再次深入使用

在上面的例子中,我们只使用了深度测试。但单单一个深度测试肯定满足不了我的需求,我们还需要更多、更多的状态,比如背面剔除、混合模式等等。

在这一节中,我列举出了一些常用的状态控制量,对于一些不常用的模板等,就不在这里列举。大家可以依葫芦画瓢,因为大部分从理论上来说都是可行的。

2.1 一个大而全的简单示例Shader如下:

Shader "ShaderCombine/02.ShaderCombineCommonState"
{
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 1
        [Enum(Off,0,On,1)] _ZWrite ("ZWrite", Float) = 1
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 1
        [Enum(UnityEngine.Rendering.BlendMode)] _SourceBlend ("Source Blend Mode", Float) = 2        
        [Enum(UnityEngine.Rendering.BlendMode)] _DestBlend ("Dest Blend Mode", Float) = 2
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        ZTest [_ZTest]
        Cull [_Cull]
        ZWrite [_ZWrite]
        ZTest [_ZTest]
        Blend [_SourceBlend] [_DestBlend]

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        fixed4 _Color;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }

    Fallback "Legacy Shaders/VertexLit"
}

2.2 直观展示
应用效果:
请输入图片描述


◆◆◆
有限自定义

在上面的示例中,我们都是使用Unity预先定义好的一些枚举类型,比如UnityEngine.Rendering.CompareFunction、UnityEngine.Rendering.BlendMode等。这些定义好的类型把每个状态可能的选项都一一列举了,但有时候我们并不需要这么多选项,或者说我们并不希望给美术列举出所有的可选项,毕竟很多选项我们可能做完整个项目或者几个项目都不会使用到,而选项过多也会带来很的麻烦。或者换一个说法,我希望我们的功能使用起来简单易懂、不易出错并且可控,那就需要我们开发做更多的工作,去掉那些"无用"的选项。其实说到底无非就是:我们能否自定义每个状态的选项呢?答案当然是可以的。

在给出自定义方式前,我们先来熟悉一下Unity给我们提供的这几个枚举类型。

3.1 剔除模式

UnityEngine.Rendering.CullMode:
public enum CullMode
{       
    Off   = 0,    //Disable culling.       
    Front = 1,    //Cull front-facing geometry.       
    Back  = 2     //Cull back-facing geometry.
}

3.2 比较方式
该比较方式通用与深度比较和模板比较


UnityEngine.Rendering.CompareFunction
//Depth or stencil comparison function.
public enum CompareFunction
{
    Disabled     = 0,   //Depth or stencil test is disabled.
    Never        = 1,   //Never pass depth or stencil test.
    Less         = 2,   //Pass depth or stencil test when new value is less than old one.
    Equal        = 3,   //Pass depth or stencil test when values are equal.
    LessEqual    = 4,   //Pass depth or stencil test when new value is less or equal than old one.
    Greater      = 5,   //Pass depth or stencil test when new value is greater than old one.
    NotEqual     = 6,   //Pass depth or stencil test when values are different.
    GreaterEqual = 7,   //Pass depth or stencil test when new value is greater or equal than old one.
    Always       = 8    //Always pass depth or stencil test.
}

3.3 混合模式

UnityEngine.Rendering.BlendMode
//Blend mode for controlling the blending.
public enum BlendMode
{
    Zero             = 0,   //Blend factor is (0, 0, 0, 0).
    One              = 1,   //Blend factor is (1, 1, 1, 1).
    DstColor         = 2,   //Blend factor is (Rd, Gd, Bd, Ad).
    SrcColor         = 3,   //Blend factor is (Rs, Gs, Bs, As).
    OneMinusDstColor = 4,   //Blend factor is (1 - Rd, 1 - Gd, 1 - Bd, 1 - Ad).
    SrcAlpha         = 5,   //Blend factor is (As, As, As, As).
    OneMinusSrcColor = 6,   //Blend factor is (1 - Rs, 1 - Gs, 1 - Bs, 1 - As).
    DstAlpha         = 7,   //Blend factor is (Ad, Ad, Ad, Ad).
    OneMinusDstAlpha = 8,   //Blend factor is (1 - Ad, 1 - Ad, 1 - Ad, 1 - Ad).
    SrcAlphaSaturate = 9,   //Blend factor is (f, f, f, 1); where f = min(As, 1 - Ad).
    OneMinusSrcAlpha = 10   //Blend factor is (1 - As, 1 - As, 1 - As, 1 - As).
}

3.4 有限的自定义
在上面三个小小节中,我们了解了Unity自身提供的状态选项,而且每一个状态选项后都强制赋上了相应的数值,这是有原因的。因为我们写好的Shader不管怎样,都要首先经过Unity的编译等处理转换为目标平台的着色器语言。而Unity自己的Shader编译器,在没有源码的情况下我们是无法修改的,也就是说我们不能随意更改这些状态的数值。其实我们修改的这些状态值都是给Unity的Shader编译器看的,而编译器对状态的数值理解是固化好的。因此,虽然我们可以自定义这些状态选项,但编译器也不会任由我们随意定义,这就好比戴着镣铐跳舞,虽然有限制,但我们依然可以跳出优美的舞蹈。

自定义非常的简单,我们可以减少选项的数量,但是不能改变每一项的值,这就要求我们强行给每一个值赋上对应的值,依然还是用深度测试实验,如下所示:

这是之前的:

[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 2

这是自定后的:

[Enum(Less,2,Greater,5)] _ZTest ("ZTest", Float) = 2

选项与数值全部使用逗号分隔,该示例中我只给出了两个选项,小于和大于,便于直观查看。

完整Shader如下:

Shader "ShaderCombine/03.ShaderCombineCustomState"
{
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        [Enum(Less,2,Greater,5)] _ZTest ("ZTest", Float) = 2
    }
    SubShader {
        Tags { "RenderType"="Opaque"}
        LOD 200
        ZTest [_ZTest]

        CGPROGRAM
        #pragma surface surf Lambert

        sampler2D _MainTex;
        fixed4 _Color;

        struct Input {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }

    Fallback "Legacy Shaders/VertexLit"
}

3.5 直观展示
在Unity中的样子是这样的:

请输入图片描述

细心的读者可能已经发现我们在第二章节中的完整示例中就有使用自定义,就是里面的那个写深度_ZWrite选项,因为没有在Unity里找到相应的枚举值,就直接使用了自定义,反正只要保证数值正确就可以任意发挥使用。更多的使用和应用场景就等你们去发现了,我这只是抛砖引玉。

PS:其实最初是想花一整章篇幅来讲解MaterialPropertyDrawer的各种使用,但其内部的扩展空间还比较广泛。写下来篇幅太长,而太长的篇幅,阅读起来也比较麻烦,所以还是拆成几章篇幅来慢慢絮叨吧,同时也遵从一次只讲一个问题。

以上便是MaterialPropertyDrawer的应用场景之一,对于MaterialPropertyDrawer的应用,在后续的篇章中也还会陆续出现。笔者计划把MaterialPropertyDrawer应用当成《合并Shader》系列中的一个分支,当然《合并Shader》系列不会仅且只有这一个分支的,O(∩_∩)O哈哈~,欢迎大家持续关注!

Unity Shader 学习17:合批渲染 合批主要是针对这三个概念进行优化减少: ① SetPass Call:一次渲染状态切换,也就是每次切换 材质/Pass 时,就会触发一次SetPass Call ② Draw Call:cpu 调用一次 gpu 绘制函数 ③ Batch:表示一组可以一起交给gpu的Draw Call 优化结果可以在 Game窗口-Stats 或 FrameDebugger 中查看。 阅读详情

相关推荐

Unity内置渲染管线Shader深度解析:从源码解读到性能优化实战

在实时图形渲染领域,着色器(Shader)是连接美术资源与GPU硬件的核心技术桥梁,它定义了三维模型表面每个像素的最终颜色与视觉效果。其工作原理基于可编程渲染管线,通过顶点着色器和片元着色器两个主要阶段,分别处理几何变换与像素着色,实现了从模型空间到屏幕空间的复杂映射。这项技术的核心价值在于,它让开发者能够以极高的灵活性控制渲染流程,从而在性能与视觉质量之间取得最佳平衡,广泛应用于游戏开发、虚拟现实、建筑可视化等对图形表现有高要求的场景。对于使用Unity引擎的开发者而言,深入理解其内置渲染管线(Built

weixin_30918633的博客 458

Unity中Shader的Pass的复用

Pass的复用一般用于,已经写好了一个Pass,在另一个Shader中有相同的效果的Pass,可以直接引用。

楠溪泽岸的博客 1007

Unity Shader学习1 预备知识:渲染管线

​一、GPU是什么 1. GPU与CPU GPU是显卡上的一块芯片,就像CPU是主板上的一块芯片。 2. CPU 可以理解为颜料一个点一个点地先后发射,连接在一起,给人呈现以笑脸的图像,一步步迭代计算,花费时间较长。 3.GPU 多管颜料一瞬间同时发射,直接绘制成一幅完整的画像,大量高并行计算,花费时间是在一瞬间。 比起用三五个强大的微处理器(或者说“管道”)来处理这些信息,用一大堆小的微处理器来并行计算,快得多。这就是图形处理器(GPU : Graphic Processor Unit)的来

weixin_63623077的博客 1637

【unity】通过两个shader实现物体轮廓描边高亮效果

最终效果(自己忘记截图了,来源于别人的blog) 准备两个shader:一个shader是物体自身的shader(origin.shader),一个shader实现描边高亮(highlight.shader) highlight.shader代码如下:(代码来源:https://www.cnblogs.com/alps/p/7606028.html) Shader "Custom/hi...

⚡ 7322

UE4.27渲染管线实战:从Global Shader到Mesh Draw Command的完整解析

在游戏开发与实时图形渲染领域,渲染管线是决定最终画面呈现与性能的核心架构。其本质是CPU与GPU协同工作,将三维场景数据转换为屏幕像素的标准化流程。现代引擎普遍采用保留模式渲染,其核心原理在于将绘制状态提前封装为可复用的绘制指令,从而大幅减少CPU开销并实现高效的Draw Call合并,这是提升渲染性能的关键技术。理解并定制渲染管线,对于实现高级视觉效果、优化渲染性能具有极高的技术价值,尤其在需要实时绘制海量自定义几何体或实现独特风格化渲染等应用场景中至关重要。本文以UE4.27为例,深入剖析了从编写Glo

weixin_34353714的博客 717

Unity Shader多Pass渲染:从Standard Shader源码解析到自定义AddPass实战

多Pass渲染是计算机图形学中实现复杂视觉效果的核心技术之一,其原理在于通过多个渲染通道叠加不同层次的图像信息。在实时渲染领域,这一技术允许开发者在单个绘制调用中组合多种材质属性与光照模型,从而高效模拟丰富的表面细节与光学现象。从技术价值看,多Pass机制平衡了渲染质量与性能开销,尤其适用于需要叠加独立视觉特征(如边缘光、自发光、细节纹理)的场景,是构建模块化、可配置着色器系统的关键。在Unity引擎的Standard Shader中,多Pass通过#pragma multi_compile等指令实现按需编

weixin_30243533的博客 464

多个SubShader和单个SubShader之间的优缺点

选择使用多个SubShader还是单个SubShader多个SubShader:适合需要实现多种效果、优化性能和处理复杂场景的项目。虽然增加了复杂性和开发时间,但提供了更高的灵活性和功能性。单个SubShader:适合简单的Shader需求,易于管理和快速开发。虽然灵活性和性能优化能力有限,但在简单场景中可以有效减少开发工作量。理解这两者的优缺点,有助于开发者在Shader编写时做出更明智的决策。如果你有更多问题或需要进一步的讨论,请随时告诉我!

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 234

U3D_Shader编程(第一篇:快速入门篇)

《U3D_Shader编程》##《U3D_Shader编程》发布说明:++++Shader一个高大上的领域,不管怎么样,我来了。++++立钻哥哥从2018年开始正式对Shader进行战略布局。++++《U3D_Shader编程》将从零开始,循序渐进探索,还是先探索一段时间吧,后期根据需要再推出一个精品博文,这篇就算一个杂谈吧。##《U3D_Shader编程》目录:#第一篇:快速入门篇#第二篇:基础...

VRunSoftYanlz的博客 1万+

【合并Shader】合并渲染状态

http://www.7fires.cn/archives/203        年关将至,整理今年的大小事务,看看哪些是可以拖到年后的。整理发现今年一直想写,却迟迟未动手写的《合并Shader》还没有开工。是的,如你所见这是在开一个新坑,不多挖点坑,将来怎么会有意愿去填坑呢!!!当然更早的《USequencer系列》也会一直更新,毕竟USequencer最大的问题还没有解决,对

qq_14939027的博客 985

避坑指南:Laya Shader多Pass渲染性能分析与优化实战

本文深入探讨了Laya Shader多Pass渲染的性能优化策略,分析了多Pass渲染在游戏开发中的适用场景与性能影响。通过实战案例和代码示例,提供了Pass合并技术、渲染状态智能复用等优化方案,帮助开发者在实现复杂视觉效果的同时保持高性能。特别针对Shader编程中的性能瓶颈提出了具体解决方案。

weixin_31423955的博客 266

Shader实现多种效果

用Shader实现水波纹,霓虹文字,雷达等效果

Unity3D Shader系列之渲染流水线

1 引言 硬盘上的一个3D模型模型是怎么渲染到我们的屏幕上来的呢? 答案是经过渲染流水线。完整的渲染流水线包括3个大阶段,应用阶段→几何阶段→光栅化阶段。每个大阶段又分为几个小阶段。下面我们来看看整个渲染流水线过程。 2 应用阶段 应用阶段分为3个小步骤: ①加载模型数据到显存 ②设置渲染状态 ③调用Draw Call 2.1 加载模型数据到显存 先是CPU将模型数据加载到内存中,模型数据包括模型的网格数据(包括顶点的局部坐标、顶点的法线、顶点颜色以及顶点的纹理坐标等)、法线、材质、贴图等。 然后GPU将这

老王的博客 5056

sqoop抽取mysql的数据到hive表里面,字段内容被自动截取,导致字段不全的问题解决

今天央视重新转播NBA,写篇博客纪念一下这个重要的日子,湖人的表现就呵呵了!!!! 强行转到博客的主题来 mysql的字段属性:该字段能存下的值比如:“山东” `province` varchar(3) NOT NULL COMMENT '省份' 用sqoop sqoop import --connect "jdbc:mysql://xxxxxx:3306/xxxxxx?tinyInt1isBit=false&zeroDateTimeBehavior=convertToNull..

放肆桀骜 966

Shader 渲染流程概述

各种测试(模板、深度)的顺序并不是唯一的,虽然从逻辑上来说这些测试是在片元着色器之后进行的,但是对于大多数GPU来说,会尽可能在执行片元着色器之前进行这些测试,避免因为测试没通过导致浪费大量的计算成本。Unity的渲染流水中,深度测试就是在片元着色器之前。固定函数的流水线(Fixed-Function Pipeline),简称固定管线,通常是指在较旧的GPU上实现的渲染流水线。这种流水线只给开发者提供一些配置操作,但开发者没有对流水线阶段的完全控制权。

JYC020305的博客 995

一篇NS SAR ADC的学习记录

一篇NS SAR ADC的学习记录 内容包含: OverSampling Noise Shaping Dynamic Amplifier DAC with Redundancy 最近看了些有意思的SAR ADC结构,但是因为确实对模拟这个领域不太熟,导致了一系列的级联查找,最终达到了一个大概能懂的状态,为了理清思路,顺便别让看了这么些天的东西浪费了,还是计划写篇文章。 ps:由于不是一篇特别正紧的报告,参考文献就写的比较随便,且我还是个做数字的 A 13.5-ENOB, 107-μW Noise-Sha

weixin_45669724的博客 8714

基于SOCKET的***网络软件的设计与实现

二、基于SOCKET的网络软件的需求。2、概要设计:画出软件系统结构图。3、详细设计:软件的详细设计,如类图、序列图、重要的算法流程图或伪代码,等等。4、实现:软件基本功能相关的截图和关键代码。

Recursions的博客 1373

Shader的合并同类项

在数学中我们学习过:把多项式中的同类项合并成一项叫做合并同类项。同理,提取Shader的相似部分,把多个Shader合并成一个就叫做Shader的合并,也叫合并Shader,偶尔也会引用数学的名词来称呼他为Shader的合并同类项。 Shader的合并方式有很多,根据不同的合并技能和方法可以划分为不同的派系。今天优先介绍一种不太常见、但又很实用的派系,往下看。 在Shader的合并方法中,MaterialPropertyDrawer(属性定义)可以说是自成一派,但又与其他派系有着千丝万缕的关系,今天我们

Peter的博客 556

Windows服务器渗透必备:5款杀软在线识别平台实测对比(附避坑指南)

本文针对Windows服务器渗透测试中的关键环节,实测对比了五款主流的在线杀软识别平台。文章从实战场景出发,深入分析了各平台在识别准确率、响应速度、隐匿性及使用风险上的差异,并提供了详尽的避坑指南与高级隐匿技巧,旨在帮助安全人员快速、精准地识别目标环境安全防护,为后续行动奠定情报基础。

weixin_29322553的博客 434
上一篇: 性能诊断与优化工具(Q&A)
下一篇: [Unity Editor]10行代码搞定Hierarchy排序
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值