Unity引擎shader相关概念问与答

ShaderLab最终编译之后是什么状态

ShaderLab是Unity的一种着色器脚本语言,它用于定义着色器的属性、渲染状态、子着色器等。当你在Unity中编写ShaderLab脚本并保存时,Unity会自动将其编译为一种或多种底层的图形API着色器语言,如HLSL(用于DirectX)或GLSL(用于OpenGL和Vulkan)等。
编译过程中,ShaderLab脚本中的各种属性和渲染状态会被转换为相应的API调用,而ShaderLab脚本中的CGPROGRAM或HLSLPROGRAM部分(这是实际的着色器程序,通常用HLSL或GLSL编写)则会被直接编译为相应的机器代码。
编译后的着色器代码会被Unity嵌入到游戏的运行时环境中,然后在游戏运行时,根据需要,这些编译后的着色器代码会被发送到图形硬件(如GPU)上执行,以渲染出各种图形效果。
需要注意的是,由于不同的图形API和不同的硬件可能支持不同的着色器语言和功能,因此Unity会为每个目标平台生成一组适当的着色器代码。这就是为什么在ShaderLab脚本中,我们通常需要定义多个SubShader,每个SubShader对应一种特定的硬件或API能力。Unity会自动选择最合适的SubShader来使用。

使用多通道渲染的目的是什么,有什么性能问题

多通道渲染(Multi-Pass Rendering)是一种常见的渲染技术,它的主要目的是为了实现一些复杂的渲染效果,这些效果可能无法在单一的渲染通道(Pass)中完成。例如,一些复杂的光照模型、阴影效果、后处理效果等,都可能需要使用多通道渲染来实现。
在多通道渲染中,每个通道(Pass)都会对场景中的物体进行一次渲染,然后将渲染结果存储在一个或多个渲染目标(如纹理或帧缓冲区)中。然后,在后续的通道中,这些渲染结果会被用作输入,以生成最终的渲染效果。
多通道渲染的主要优点是它可以实现非常复杂和详细的渲染效果。然而,这种技术也有一些性能问题需要注意:

带宽消耗:每个通道都需要读写渲染目标,这可能会消耗大量的内存带宽。如果你的渲染目标很大,或者你有很多的通道,那么这可能会成为一个问题。

渲染次数:每个通道都需要对场景中的物体进行一次渲染,这意味着你的GPU需要做更多的工作。如果你的场景很复杂,或者你有很多的通道,那么这可能会导致性能下降。

状态切换:每个通道可能需要使用不同的渲染状态和着色器。频繁的状态切换可能会导致性能下降。

因此,在使用多通道渲染时,你需要仔细考虑你的渲染需求和硬件能力,以找到最佳的平衡点。在可能的情况下,尽量减少通道的数量和渲染目标的大小,以提高性能。

在使用多通道渲染时,还有一些优化策略可以考虑:

尽量减少通道数量:每个额外的通道都会增加GPU的工作量和内存带宽的消耗。因此,如果可能,尽量减少通道的数量。例如,你可以尝试合并一些通道,或者使用更高效的算法来替代需要多通道的效果。

尽量减少渲染目标的大小:渲染目标的大小直接影响到内存带宽的消耗。如果可能,尽量减少渲染目标的大小。例如,你可以使用更小的纹理,或者使用压缩纹理格式。

尽量减少状态切换:状态切换是一种昂贵的操作,尤其是当你需要切换着色器或渲染目标时。如果可能,尽量减少状态切换。例如,你可以尝试将一些通道合并到一个着色器中,或者使用多渲染目标(MRT)来一次性渲染到多个渲染目标。

使用延迟渲染或者前向+渲染:这些渲染路径可以有效地减少渲染次数,特别是在处理复杂光照和阴影时。但是,它们也有自己的限制和问题,比如延迟渲染不支持透明物体,前向+渲染可能需要更多的内存等。

使用硬件支持的特性:一些现代的图形硬件提供了一些特性,可以帮助优化多通道渲染,比如硬件压缩、异步计算、多渲染目标等。如果可能,尽量利用这些特性。

总的来说,多通道渲染是一种强大但复杂的技术,需要仔细地权衡其优点和缺点。在设计和实现你的渲染管线时,你需要考虑到你的具体需求、硬件能力、性能预算等因素,以找到最佳的解决方案。

在多通道渲染的上下文中,还有一些其他的概念和技术可以进一步探讨:

后处理:后处理是多通道渲染的一个常见应用。在后处理阶段,可以应用各种效果,如模糊、色彩校正、光晕、深度感知模糊等。这些效果通常在渲染场景的所有其他元素之后应用,因此被称为“后处理”。后处理通常需要额外的渲染通道,并且可能需要访问之前通道的渲染结果。

遮挡查询:遮挡查询是一种可以优化多通道渲染的技术。通过使用遮挡查询,可以在渲染一个物体之前检查它是否被其他物体遮挡。如果一个物体被完全遮挡,那么就没有必要在后续的通道中渲染它,从而节省了GPU的工作量。

实例化:实例化是一种可以减少渲染次数的技术。通过使用实例化,可以一次性渲染多个相同的物体,而不是分别渲染每一个。这可以大大减少渲染次数,特别是在渲染大量相同物体的场景中。

级联阴影映射:级联阴影映射是一种用于实现大范围阴影的技术。它使用多个阴影映射(每个映射覆盖不同的距离范围),并在渲染时选择合适的映射,以实现在不同距离都有正确阴影的效果。这种技术通常需要多个渲染通道来实现。

这些只是多通道渲染中可能会遇到的一些概念和技术。在实际应用中,可能还会有更多的技术和策略可以用来优化多通道渲染。

渲染目标的大小是什么

渲染目标的大小通常指的是渲染到的纹理或帧缓冲区的分辨率,即其宽度和高度的像素数。例如,一个1920x1080的渲染目标就是一个宽度为1920像素,高度为1080像素的纹理或帧缓冲区。
渲染目标的大小对渲染性能有很大影响。更大的渲染目标需要更多的内存来存储,同时也需要更多的GPU计算资源来处理。因此,如果渲染目标的大小过大,可能会导致内存不足或者渲染速度下降。
另一方面,渲染目标的大小也会影响渲染的质量。更大的渲染目标可以提供更高的分辨率和更详细的图像,但是也会增加渲染的复杂性和成本。因此,选择合适的渲染目标大小是一个需要权衡的问题。
在实际应用中,渲染目标的大小通常会根据需要的图像质量、可用的硬件资源、以及特定的性能需求来选择。在某些情况下,也可能会动态调整渲染目标的大小,以适应不同的情况和需求。

渲染目标的大小为什么会影响性能

渲染目标的大小会影响性能,主要是因为以下几个原因:

内存使用:更大的渲染目标需要更多的内存来存储像素数据。例如,一个1920x1080的渲染目标需要存储超过两百万个像素的数据。如果你的渲染目标非常大,或者你有很多渲染目标,那么这可能会占用大量的内存,甚至超过你的硬件的内存限制。

带宽:当你渲染到一个大的渲染目标时,你需要在GPU和内存之间传输大量的数据。这可能会占用大量的内存带宽,从而影响其他需要内存带宽的操作的性能。

计算负载:更大的渲染目标意味着更多的像素需要处理。每个像素都需要进行一系列的计算,包括顶点变换、光照计算、纹理采样等。因此,更大的渲染目标会增加GPU的计算负载,从而降低渲染速度。

填充率:填充率是指GPU每秒可以处理的像素数量。如果你的渲染目标非常大,那么你可能会达到你的GPU的填充率限制,从而降低渲染速度。

因此,选择合适的渲染目标大小是一个重要的优化策略。在实际应用中,你可能需要根据你的具体需求和硬件能力,以及你的性能预算,来选择最佳的渲染目标大小。

在讨论渲染目标大小对性能影响的基础上,我们可以进一步探讨一些相关的优化策略:

动态分辨率:动态分辨率是一种常见的优化技术,它可以根据当前的性能需求动态调整渲染目标的大小。例如,如果检测到性能下降,可以降低渲染目标的大小以提高帧率;反之,如果性能充足,可以提高渲染目标的大小以提高图像质量。

多分辨率渲染:在某些情况下,可能不需要对整个场景使用相同的分辨率。例如,对于远离摄像机的物体,或者在视野边缘的物体,可以使用较低的分辨率进行渲染。这种技术可以有效地减少不必要的渲染负载,从而提高性能。

延迟渲染和前向渲染:延迟渲染和前向渲染是两种常见的渲染策略,它们对渲染目标的大小有不同的需求和影响。延迟渲染通常需要更大的渲染目标来存储中间结果,但可以处理大量的光源和复杂的材质。前向渲染则对渲染目标的大小要求较低,但处理大量光源时可能会遇到性能问题。

层次化渲染:层次化渲染是一种可以减少渲染负载的技术。通过使用层次化的数据结构(如四叉树或八叉树),可以快速剔除不在视野内的物体,从而减少需要渲染的像素数量。

这些只是一些可能的优化策略,实际上可能还有更多的方法可以用来处理渲染目标大小对性能的影响。在实际应用中,可能需要根据具体的需求和环境,以及硬件的能力,来选择最合适的策略。

渲染通道

在计算机图形学中,一个"pass"或者"渲染通道"通常指的是一次完整的渲染操作,包括一系列的步骤,从准备数据到最终输出图像。一个pass通道可能包括以下几个部分:

准备数据:这包括从CPU发送数据到GPU,设置渲染状态,以及其他的预处理步骤。

顶点处理:这是在GPU上执行的第一个步骤,包括顶点着色器,几何着色器,和曲面细分着色器。这些着色器处理3D模型的顶点数据,进行变换,光照计算,以及其他的操作。

光栅化:光栅化是将3D模型转换为2D像素的过程。这包括裁剪,透视除法,扫描转换,以及其他的步骤。

片元处理:片元处理包括片元着色器和深度测试。片元着色器计算每个像素的颜色,深度测试则决定哪些像素应该被绘制到最终的图像中。

输出:最后,渲染的结果被输出到一个渲染目标,这通常是一个帧缓冲区或者一个纹理。

在实际应用中,一个渲染操作可能包括多个pass。例如,一个常见的渲染流程可能包括一个几何pass来渲染场景的几何信息,一个光照pass来计算光照效果,以及一个后处理pass来添加特效。每个pass都有自己的输入和输出,以及一系列的渲染步骤。

多个pass可以同时执行吗

在大多数情况下,多个渲染通道(pass)是顺序执行的,而不是同时执行。这是因为每个pass通常依赖于前一个pass的结果。例如,一个光照pass可能需要使用几何pass生成的深度信息,一个后处理pass可能需要使用光照pass生成的图像。因此,这些pass必须按照正确的顺序执行。
然而,有一些情况下,可以并行执行多个pass。例如,如果两个pass处理的数据是独立的,或者一个pass的输出不会影响另一个pass的输入,那么这两个pass可能可以同时执行。这通常需要硬件支持,并行执行的能力,例如多个GPU或者GPU的多个执行单元。
此外,一些现代的渲染技术,如异步计算和多重渲染目标(MRT),可以在一定程度上并行执行多个pass。异步计算可以让GPU同时执行图形和计算任务,而MRT可以让一个pass同时输出到多个渲染目标。
总的来说,是否可以同时执行多个pass取决于具体的渲染流程和硬件能力。在实际应用中,通常需要根据具体的需求和环境,以及硬件的能力,来选择最合适的渲染策略。

unity中材质包括哪些内容

在Unity中,材质(Material)是用来定义物体表面外观的资源。它决定了物体在光照和相机下的表现。一个材质主要包括以下内容:

Shader:Shader是一段运行在图形处理器(GPU)上的程序,用来计算物体的颜色和其他渲染效果。Unity内置了许多常用的Shader,例如Standard Shader用于实现物理基础渲染(PBR),Skybox Shader用于渲染天空盒等。你也可以编写自定义Shader来实现特殊的渲染效果。

纹理:纹理是一种图像资源,用来给物体添加详细的表面特征。例如,漫反射纹理(Albedo Texture)用来定义物体的基础颜色,法线纹理(Normal Map)用来模拟表面的凹凸细节,金属光滑度纹理(Metallic Smoothness Texture)用来定义物体的金属度和光滑度等。

属性:除了Shader和纹理,材质还包括一些其他的属性,例如颜色(Color)、金属度(Metallic)、光滑度(Smoothness)、透明度(Transparency)等。这些属性可以在Unity的材质编辑器中直接设置。

渲染设置:材质还包括一些渲染设置,例如渲染模式(Rendering Mode)、光照模式(Lighting Mode)、深度测试(Depth Test)、混合模式(Blend Mode)等。这些设置决定了材质在渲染过程中的行为。

总的来说,Unity中的材质是一个复杂的资源,包含了许多用于定义物体表面外观的信息。通过调整材质的Shader、纹理、属性和渲染设置,你可以创建各种各样的视觉效果。

当然,我可以继续解释Unity中材质的其他方面。

Tiling 和 Offset:这两个参数允许你控制纹理在物体表面上的分布。Tiling决定了纹理在物体表面上的重复次数,Offset则决定了纹理的起始位置。

Emission:这个属性允许你设置物体的自发光效果。你可以通过Emission颜色和强度来控制自发光的颜色和亮度,也可以使用Emission纹理来定义更复杂的自发光效果。

Detail Mask:这个属性允许你在物体表面上添加更多的细节。你可以使用Detail Mask来控制Detail Albedo Map和Detail Normal Map的分布。

Secondary Maps:这个属性允许你添加第二层的纹理,以增加物体表面的细节。你可以使用Secondary Maps来添加更复杂的纹理效果,例如微小的表面细节,或者复杂的光照效果。

Render Queue:这个属性决定了物体在渲染过程中的顺序。你可以通过调整Render Queue来解决一些渲染问题,例如透明物体的渲染顺序问题。

Specular Highlights 和 Reflections:这两个属性允许你控制物体的高光和反射效果。你可以通过关闭这些属性来优化性能,或者创建特殊的视觉效果。

以上就是Unity中材质的主要内容。通过调整这些参数,你可以创建各种各样的材质效果,从简单的单色材质,到复杂的物理基础渲染材质,都可以在Unity中实现。

材质(Material)是如何应用到游戏物体上的

在Unity中,将材质应用到游戏物体上的过程相当直接。以下是一般步骤:

创建材质:首先,你需要在项目面板中创建一个新的材质。你可以右键点击项目面板,选择"Create > Material",然后给新的材质命名。

设置材质:创建好材质后,你可以在检查器面板中设置材质的属性。例如,你可以选择一个Shader,然后设置Shader的参数,如颜色、纹理等。

应用材质:设置好材质后,你可以将它应用到游戏物体上。你可以直接将材质拖拽到场景中的物体上,或者在检查器面板中选择物体的渲染器组件(如Mesh Renderer),然后将材质拖拽到渲染器的Materials列表中。

以上就是在Unity中将材质应用到游戏物体上的基本步骤。需要注意的是,一个物体可以有多个材质,这通常用于物体有多个子网格的情况。在这种情况下,你可以在渲染器的Materials列表中添加多个材质,每个材质对应一个子网格。
此外,你也可以通过脚本动态地将材质应用到物体上。例如,你可以使用Renderer.material或Renderer.materials属性来设置物体的材质。这种方法通常用于实现动态的视觉效果,如换肤、闪烁等。

材质(Material)太多会导致什么结果

在Unity中,过多的材质可能会导致以下几个问题:

渲染性能下降:每个材质都需要单独的渲染调用(Draw Call)。渲染调用是CPU告诉GPU渲染一个物体的过程,这个过程需要一定的CPU时间。如果你的场景中有大量的材质,那么渲染调用的数量就会增加,从而导致CPU负载增加,渲染性能下降。

内存占用增加:每个材质都会占用一定的内存空间,用于存储Shader参数、纹理等数据。如果你的场景中有大量的材质,那么内存占用就会增加,可能会导致内存不足的问题。

资源管理复杂:如果你的项目中有大量的材质,那么资源管理就会变得复杂。你需要花费更多的时间来管理和优化这些材质,例如合并相似的材质,优化材质的设置等。

因此,尽管Unity允许你创建大量的材质,但在实际开发中,你应该尽量减少材质的数量,以提高渲染性能和简化资源管理。你可以通过以下几种方法来减少材质的数量:

使用纹理贴图:你可以使用纹理贴图(Texture Atlas)来合并多个小纹理到一个大纹理中,然后使用一个材质来渲染多个物体。

使用材质变体:你可以使用材质变体(Material Variant)来创建具有不同参数的材质,而不是创建多个不同的材质。

使用Shader特性:你可以使用Shader特性(Shader Feature)来在一个Shader中实现多种效果,然后使用一个材质来渲染多种效果。

为什么每个材质都需要单独的渲染调用

每个材质都需要单独的渲染调用(Draw Call)的原因主要是因为每个材质都有自己独特的属性和纹理。这些属性和纹理需要被上传到图形处理器(GPU)以便在渲染过程中使用。
当你在Unity中创建一个材质并应用到一个物体上时,这个材质的所有属性(例如颜色、纹理、光照模型等)都需要被设置到GPU中。这个过程就是一个渲染调用。当你需要渲染另一个使用不同材质的物体时,你需要再次进行一个渲染调用,将新的材质属性设置到GPU中。
因此,每个材质都需要一个单独的渲染调用。如果你的场景中有大量的材质,那么渲染调用的数量就会增加,从而可能导致渲染性能下降。
然而,Unity和其他游戏引擎通常会使用一些优化技术来减少渲染调用的数量。例如,批处理(Batching)是一种常见的优化技术,它可以将多个使用相同材质的物体合并为一个渲染调用。这样,即使你的场景中有大量的物体,只要它们使用的是相同的材质,那么渲染调用的数量就可以大大减少。

继续上述讨论,虽然批处理(Batching)是一种有效的优化技术,但它也有一些限制。例如,动态批处理只能用于较小的物体,而静态批处理只能用于不会移动的物体。此外,批处理也不能用于使用不同材质的物体。
因此,除了使用批处理,还有其他一些方法可以帮助你减少渲染调用的数量:

使用纹理贴图(Texture Atlas):纹理贴图可以将多个小纹理合并到一个大纹理中,这样你就可以使用一个材质来渲染多个物体,从而减少渲染调用的数量。

使用材质变体(Material Variants):材质变体可以让你在一个材质中设置多种不同的属性,这样你就可以使用一个材质来渲染多种不同的效果,从而减少渲染调用的数量。

使用LOD(Level of Detail):LOD可以让你根据物体与摄像机的距离来选择不同的渲染质量。例如,你可以为远离摄像机的物体使用较低质量的材质,从而减少渲染调用的数量。

使用GPU Instancing:GPU Instancing可以让你在一个渲染调用中渲染多个使用相同材质但具有不同属性(如位置、旋转、缩放等)的物体。

以上就是一些常见的优化技术,你可以根据你的项目需求来选择合适的技术。需要注意的是,优化是一个持续的过程,你需要不断地测试和调整你的设置,以达到最佳的性能。

一个pass需要一次渲染调用吗

是的,通常情况下,一个Pass对应一次渲染调用(Draw Call)。在图形渲染中,一个Pass可以被理解为一次渲染操作或者一次渲染阶段。
例如,在一个典型的前向渲染管线中,可能会有多个Pass,包括深度预Pass(Depth Pre-Pass)、阴影Pass(Shadow Pass)、光照Pass(Lighting Pass)等。每个Pass都需要一次渲染调用,因为每个Pass都需要将不同的数据(例如深度信息、阴影信息、光照信息等)上传到GPU。
然而,有一些优化技术可以减少渲染调用的数量。例如,多Pass渲染(Multi-Pass Rendering)可以在一个渲染调用中完成多个Pass,从而减少渲染调用的数量。但这种技术通常需要更复杂的Shader和更高的GPU性能。
此外,还有一些特殊的情况下,一个Pass可能不需要一次渲染调用。例如,如果你在Shader中使用了多个SubPass(子Pass),那么这些SubPass可能会在一个渲染调用中完成。但这种情况通常需要特定的硬件支持,并且在实际开发中并不常见。

一个Pass的数据在哪里存的

一个Pass的数据主要存储在图形处理器(GPU)的内存中。当一个Pass被执行时,相关的数据(如顶点数据、纹理数据、光照数据等)会被上传到GPU的内存中,然后由GPU进行处理。
这些数据主要包括以下几类:

顶点数据:这包括顶点的位置、颜色、法线、纹理坐标等信息。这些数据通常存储在顶点缓冲区(Vertex Buffer)中。

索引数据:这是用于描述如何从顶点数据中构建三角形的数据。这些数据通常存储在索引缓冲区(Index Buffer)中。

常量数据:这包括一些在整个Pass中不会改变的数据,如光照参数、摄像机参数等。这些数据通常存储在常量缓冲区(Constant Buffer)或Uniform Buffer中。

纹理数据:这包括用于渲染物体表面的纹理图像。这些数据通常存储在纹理内存中。

渲染目标数据:这包括渲染结果的颜色数据和深度数据。这些数据通常存储在帧缓冲区(Frame Buffer)中。

以上就是一个Pass的数据主要存储的地方。需要注意的是,这些数据的存储和处理方式可能会根据不同的渲染管线和硬件平台有所不同。

shader中一个Pass通道是什么意思

在Shader编程中,一个Pass通道指的是渲染管线中的一次完整的渲染过程。在这个过程中,Shader程序会对渲染对象的每个像素或顶点执行一系列操作,以实现特定的视觉效果。一个Shader可以包含多个Pass,每个Pass都可以执行不同的渲染任务。
为什么需要多个Pass?

复杂效果的分层:有些视觉效果可能非常复杂,需要多个步骤来完成。通过将这些步骤分解到不同的Pass中,可以更容易地管理和维护。
性能优化:某些效果可能只需要在特定的条件下渲染,例如只有在物体被光照照射时才需要高光效果。通过将这些效果放在单独的Pass中,可以避免不必要的渲染计算,从而提高性能。
兼容性:不同的硬件可能对Shader的复杂度有不同的限制。通过使用多个Pass,可以为不同性能的硬件提供不同级别的视觉效果。

Pass的基本结构
在Unity的ShaderLab语法中,一个Pass的基本结构通常如下所示:
Pass {
// State settings (e.g., blending, depth test)
Blend SrcAlpha OneMinusSrcAlpha
ZWrite On
Cull Back

// Shader programs (e.g., vertex and fragment shaders)
CGPROGRAM
// ... Shader code here ...
ENDCG

}

在这个结构中,你可以设置一些状态,比如混合模式(Blend)、深度写入(ZWrite)、剔除模式(Cull)等,然后是实际的Shader代码,通常包括顶点和片元程序。
Pass的类型

Forward Pass:用于前向渲染的Pass,通常包括基础的光照和材质效果。
Shadow Pass:用于生成阴影贴图的Pass。
Deferred Pass:在延迟渲染中使用,负责将材质属性写入G-buffer。
Light Pass:在延迟渲染中,用于计算光照效果。

示例
以下是一个简单的Shader,它包含两个Pass:第一个Pass负责基础的漫反射光照,第二个Pass添加了一个简单的轮廓效果。
Shader “Custom/TwoPassShader” {
SubShader {
// First pass (diffuse lighting)
Pass {
CGPROGRAM
// Vertex and fragment shaders for diffuse lighting
ENDCG
}
// Second pass (outline effect)
Pass {
CGPROGRAM
// Vertex and fragment shaders for outline effect
ENDCG
}
}
}

在这个例子中,物体首先会经过第一个Pass的渲染,然后再经过第二个Pass的渲染。每个Pass都可以有自己的独立Shader代码,用于实现不同的效果。
总的来说,Pass在Shader中是一个非常重要的概念,它允许开发者以灵活的方式控制渲染过程,实现各种复杂和高效的视觉效果。

在深入了解Pass的更多细节之前,重要的是要理解,每个Pass都会增加渲染的负担。因此,虽然多个Pass可以实现复杂的效果,但它们也可能对性能产生负面影响,特别是在渲染大量物体时。因此,Shader的编写者需要在视觉效果和性能之间找到平衡。
Pass的进阶用法

多Pass渲染技术:在一些高级的Shader中,可能会使用多个Pass来实现特殊的渲染技术,比如描边、泛光、屏幕空间的反射等。
条件Pass:可以在Shader中使用关键字来启用或禁用特定的Pass,这样可以根据不同的硬件或用户设置来调整Shader的复杂度。
GrabPass:这是一个特殊的Pass,它可以捕获当前屏幕的内容到一个纹理中,然后在后续的Pass中使用这个纹理来实现各种后处理效果。

Pass的性能考量

批处理:Unity会尝试将多个相同材质的渲染对象批处理在一起,以减少绘制调用的数量。但是,如果Shader包含多个Pass,这可能会打破批处理,因为每个对象需要被多次绘制。
过度绘制:在多Pass渲染中,同一个像素可能会被多次处理,这会导致过度绘制,特别是在复杂场景中。过度绘制会增加GPU的工作负担,降低性能。

优化技巧

减少Pass数量:尽可能地减少Pass的数量,特别是在移动设备或性能受限的平台上。
合并效果:如果可能,尝试在一个Pass中合并多个效果,以减少渲染次数。
使用LOD:为Shader编写不同的细节级别,并在远距离时使用更简单的Shader变体,这样可以在不牺牲视觉质量的情况下提高性能。

实例
让我们看一个具体的例子,这个Shader包含一个用于渲染基础颜色的Pass和一个用于添加光泽效果的Pass:
Shader “Custom/SimpleTwoPass” {
Properties {
_Color (“Color”, Color) = (1,1,1,1)
_Glossiness (“Glossiness”, Range(0, 1)) = 0.5
}
SubShader {
// First pass (base color)
Pass {
CGPROGRAM
// Vertex and fragment shaders for base color
ENDCG
}
// Second pass (gloss effect)
Pass {
CGPROGRAM
// Vertex and fragment shaders for gloss effect
ENDCG
}
}
}

在这个Shader中,第一个Pass负责渲染物体的基础颜色,而第二个Pass则添加了一个光泽效果,这个效果可能会模拟光线在物体表面的反射。每个Pass都可以访问Shader属性(如_Color和_Glossiness),这些属性可以在Unity的材质编辑器中设置。
通过这种方式,Shader的编写者可以创造出丰富的视觉效果,同时也需要注意管理每个Pass对性能的影响。在实际的游戏开发中,Shader的优化是一个持续的过程,需要不断地测试和调整,以确保在不同的设备上都能获得最佳的性能和视觉效

继续深入了解Pass在Shader中的应用,我们可以探讨一些更高级的概念和技术,这些可以帮助开发者进一步提升渲染效果和性能。
高级Pass技术

Stencil Buffer Operations:在某些Pass中,可以使用模板缓冲区来控制渲染的区域。这可以用于实现各种效果,如镜面反射、遮罩和其他基于屏幕的技术。
Tessellation and Geometry Shaders:在支持的平台上,可以使用细分(Tessellation)和几何(Geometry)Shader来在GPU上动态生成更复杂的几何形状,这可以用于实现细节级别非常高的表面或其他复杂的几何效果。

Shader Pass的调试

Frame Debugging Tools:使用Unity的帧调试工具可以逐步查看每个Pass的效果,这对于理解和优化复杂的Shader非常有用。
Custom Inspector:为Shader编写自定义的材质编辑器,可以在Unity编辑器中提供更多的调试信息和控制。

Shader Pass的最佳实践

避免不必要的Pass:在设计Shader时,应该避免添加不必要的Pass。每个额外的Pass都会增加渲染时间,尤其是在复杂场景或低端硬件上。
使用Shader预编译指令:通过使用预编译指令(如#pragma multi_compile和#pragma shader_feature),可以为Shader创建多个变体,这样可以在运行时根据需要启用或禁用特定的功能,而不是使用多个Pass。
利用Unity的Shader性能分析工具:Unity提供了一些工具来分析Shader的性能,如Shader Profiler。这些工具可以帮助你识别哪些Pass或Shader代码片段可能会导致性能瓶颈。

实际案例
假设我们有一个Shader,它需要在物体上添加一个动态阴影效果。这个效果可能需要一个额外的Pass来渲染阴影贴图:
Shader “Custom/ShadowCastingShader” {
// Properties, subshaders, etc.

SubShader {
    // Regular rendering pass
    Pass {
        // ...
    }

    // Shadow casting pass
    Pass {
        Name "SHADOWCASTER"
        Tags { "LightMode" = "ShadowCaster" }

        CGPROGRAM
        // Shader code for casting shadows
        ENDCG
    }
}

}

在这个例子中,第一个Pass处理常规的渲染,而第二个Pass专门用于阴影投射。通过将阴影投射逻辑放在一个独立的Pass中,我们可以确保只有在需要时才执行这些额外的计算,从而优化性能。
结论
Shader的Pass是一个强大的工具,它允许开发者以精细的控制来实现复杂的渲染效果。然而,随着这种能力的增加,也需要更多的责任来确保性能不会受到影响。通过仔细地规划Shader的结构,使用高级技术,并且不断地测试和优化,可以在保持高性能的同时实现视觉上令人印象深刻的结果。

一个Pass的执行流程是什么

在Unity中,Shader是用来控制物体如何被渲染的程序,而一个Pass在Shader中代表了一次完整的渲染过程。一个Shader可以包含多个Pass,每个Pass可以用来实现不同的效果,比如光照、阴影、反射等。以下是一个Pass在Unity Shader中的典型执行流程:

设置渲染状态:在Pass开始之前,Unity会根据Shader中定义的状态设置渲染管线的状态。这包括混合模式、剔除模式、深度测试等。

顶点着色器(Vertex Shader):这是Pass的第一个阶段,Unity会传递顶点数据到顶点着色器。顶点着色器负责处理每个顶点的位置、法线、纹理坐标等信息,并进行变换,如模型变换、视图变换和投影变换。

曲面着色器(Surface Shader):在Unity中,曲面着色器是一个可选的高级特性,它允许开发者以更简单的方式处理光照和材质属性。如果Shader中包含曲面着色器,它会在顶点着色器之后执行,负责计算光照模型。

光栅化(Rasterization):经过顶点着色器处理后,顶点数据会被光栅化成片元(fragments)。这个过程是由GPU自动完成的,不需要开发者编写代码。

片元着色器(Fragment Shader):片元着色器处理每个片元的颜色和纹理。它会接收顶点着色器或曲面着色器传递的数据,并根据这些数据计算最终的像素颜色。

深度和模板测试(Depth and Stencil Testing):在片元着色器之后,会进行深度测试和模板测试,以确定片元是否应该被丢弃或写入到帧缓冲区。

混合(Blending):如果启用了混合,GPU会根据定义的混合模式将当前片元的颜色与帧缓冲区中已有的颜色混合。

写入帧缓冲区(Writing to Framebuffer):通过上述所有测试的片元最终会被写入帧缓冲区,这个缓冲区最终会被显示到屏幕上。

每个Pass都可以通过Shader代码中的Pass块来定义,例如:
Shader “MyShader”
{
SubShader
{
// … 其他状态设置 …

    Pass
    {
        // ... Pass特定的状态设置 ...

        CGPROGRAM
        // 顶点着色器和片元着色器的定义
        ENDCG
    }

    // ... 可能的其他Pass ...
}

}

在Unity中,Shader的编写通常使用CG/HLSL语言,并通过Unity的ShaderLab语法来组织和设置渲染状态。一个好的Shader应该尽可能地减少Pass的数量,因为每个额外的Pass都会增加渲染的负担。

在Unity Shader中,每个Pass的执行流程是由GPU在渲染管线中顺序执行的。继续上述步骤,这里是一个Pass执行流程的更详细解释:

设置渲染状态:在Pass块的开始,可以通过ShaderLab命令设置渲染状态,如ZWrite On开启深度写入,Blend SrcAlpha OneMinusSrcAlpha设置透明混合模式等。

顶点着色器(Vertex Shader):在这个阶段,顶点着色器接收顶点数据(如位置、法线、纹理坐标等),并执行必要的数学变换,将顶点从模型空间变换到裁剪空间。这通常涉及模型-视图-投影矩阵的乘法。

曲面着色器(Surface Shader):如果使用了曲面着色器,Unity会自动生成顶点着色器和片元着色器的代码。曲面着色器允许开发者专注于描述材质的外观和光照交互,而不必处理低层次的顶点和片元操作。

光栅化(Rasterization):这是一个固定的硬件阶段,顶点着色器输出的顶点被组成图元(如三角形),然后转换成屏幕空间的像素片元。

片元着色器(Fragment Shader):片元着色器处理每个片元的颜色和纹理,执行如纹理采样、颜色计算等操作。这个阶段是实现各种视觉效果的关键,如漫反射、镜面反射、法线映射等。

深度和模板测试(Depth and Stencil Testing):这些测试决定了片元是否应该被丢弃。深度测试确保更近的物体遮挡更远的物体,模板测试可以用于实现各种特殊效果,如镜面反射、阴影等。

混合(Blending):如果启用,混合阶段会根据设置的混合模式将片元的颜色与帧缓冲区中的颜色结合起来,用于实现透明度、光晕等效果。

写入帧缓冲区(Writing to Framebuffer):最终,片元的颜色值会写入帧缓冲区。如果有多个Pass,之后的Pass会在这个基础上继续渲染。

在Unity Shader中,可以通过多个Pass来实现复杂的渲染效果。例如,第一个Pass可以用来渲染漫反射和环境光,第二个Pass可以添加镜面高光,第三个Pass可能用于添加阴影效果。每个Pass都可以有自己的顶点和片元着色器代码,以及特定的渲染状态设置。
为了优化性能,应该尽量减少Pass的数量和复杂度,因为每个额外的Pass都会增加GPU的工作负担。此外,Unity提供了一些优化技术,如批处理(Batching),它可以减少渲染调用的数量,从而提高性能。

对象使用相同的材质意味着他们的shader也相同吗

是的,在Unity中,如果多个对象使用相同的材质,那么它们也会使用相同的Shader。材质(Material)是一个包含了Shader以及用于该Shader的各种参数(如颜色、纹理、光照属性等)的资源。当你为一个对象分配一个材质时,实际上是在告诉渲染引擎使用该材质指定的Shader来渲染该对象,并应用材质中设置的参数。
在Unity中,Shader定义了物体表面的渲染方式,包括光照、颜色混合、纹理映射等。而材质则是Shader的一个实例,它使用了Shader中定义的属性,并为这些属性提供了具体的值。
因此,当多个对象使用相同的材质时,它们不仅会使用相同的Shader,还会共享相同的参数设置。这使得这些对象可以被一起批处理,因为它们的渲染状态是相同的,这样可以减少渲染调用的次数,提高渲染效率。
要注意的是,即使两个材质使用了相同的Shader,但如果它们的参数不同(例如,不同的纹理或颜色),那么它们仍然被视为不同的材质,这可能会阻止这些对象被一起批处理。为了实现批处理,需要确保对象不仅使用相同的Shader,还要使用完全相同的材质实例。
为了确保对象能够被有效地批处理,除了使用相同的材质实例之外,还有一些其他的最佳实践和注意事项:

合理组织场景中的对象:将使用相同材质的对象放置在场景中靠近的位置,这样可以提高批处理的可能性。

避免不必要的材质属性更改:频繁更改对象的材质属性(如颜色、纹理等)会导致批处理中断。如果需要在游戏中动态更改这些属性,考虑使用材质属性块(MaterialPropertyBlock)来进行更改,这样可以避免创建新的材质实例。

使用GPU Instancing:对于需要大量重复渲染的相同对象(如树木、草等),可以使用GPU Instancing技术。这种技术允许Unity在一个渲染调用中绘制多个对象的实例,即使它们的位置、旋转或缩放不同。这需要Shader支持Instancing,并且在材质上启用Instancing选项。

优化Shader代码:简化Shader代码可以减少渲染时间,特别是在移动设备上。避免在Shader中使用复杂的计算和不必要的纹理采样。

减少Draw Calls:尽量减少渲染调用的数量。这可以通过合并网格、使用较少的材质和优化场景层次结构来实现。

使用LOD(Level of Detail)系统:对于远距离的对象,使用较低详细等级的模型和材质,这样可以减少渲染负担。

避免过度使用透明度:透明对象通常不能很好地批处理,因为它们需要特殊的排序和混合操作。尽量减少场景中透明物体的数量,或者使用更高效的透明度技术,如Alpha Test。

监控Profiler:使用Unity的Profiler工具来监控游戏的性能,特别是渲染部分。Profiler可以帮助你识别渲染瓶颈和优化的机会。

通过遵循这些最佳实践,你可以确保你的游戏或应用在渲染时能够尽可能高效,从而提供更流畅的用户体验,特别是在资源受限的平台上,如移动设备或VR头显。

渲染管线是如何识别游戏物体使用了相同的材质实例的

在Unity中,渲染管线通过比较游戏物体的材质实例来识别它们是否使用了相同的材质。这个过程通常涉及以下几个步骤:

材质实例的引用:当你将一个材质分配给一个游戏物体时,该物体持有对该材质实例的引用。如果多个游戏物体引用了同一个材质实例,那么它们实际上是共享同一个材质。

渲染队列排序:在渲染过程中,Unity会根据材质的渲染队列属性对所有待渲染的物体进行排序。渲染队列决定了物体的渲染顺序,相同材质的物体会被放在一起排序,这有助于批处理。

批处理准备:在渲染前,Unity会检查哪些物体可以被批处理。对于静态批处理,Unity在编辑时就会合并静态物体的网格和材质。对于动态批处理,Unity在运行时检查使用相同材质的物体,并尝试将它们的渲染合并为一个批处理调用。

实例ID检查:Unity使用实例ID来标识不同的材质实例。如果两个物体的材质实例ID相同,那么它们使用的是同一个材质实例,因此可以被视为批处理的候选对象。

状态变化最小化:在渲染过程中,Unity尽量减少状态变化,如切换Shader、改变材质属性等。如果连续渲染的物体使用相同的材质实例,那么这些状态变化就不需要发生,从而可以一起批处理。

GPU Instancing:对于支持GPU Instancing的材质,Unity会检查物体的材质是否启用了Instancing,并且Shader是否支持。如果条件满足,Unity会将这些物体的数据(如变换矩阵)传递给GPU,由GPU来处理实例化渲染。

Unity的渲染管线通过这些机制来识别和优化使用相同材质实例的物体的渲染。开发者可以通过合理的资源管理和场景设置来协助Unity更高效地进行批处理,从而提高游戏的渲染性能。

渲染管线是如何识别游戏物体使用了相同的材质实例但是材质参数不同的

在Unity中,即使多个游戏物体使用了相同的材质实例,如果它们的材质参数不同,那么它们在渲染时通常不能被批处理在一起。这是因为不同的材质参数会导致不同的渲染状态,这需要单独的渲染调用。Unity的渲染管线通过以下方式处理这种情况:

材质属性检查:在准备渲染时,Unity会检查每个游戏物体的材质属性,如颜色、纹理、光照参数等。如果两个物体的材质属性完全相同,它们可以被考虑进行批处理。

MaterialPropertyBlock:如果你想为相同的材质实例设置不同的参数,而又不想破坏批处理,你可以使用MaterialPropertyBlock。这是一个Unity提供的机制,允许你为单个渲染调用设置不同的材质属性,而不需要为每个物体创建不同的材质实例。这样,即使物体的材质参数不同,它们仍然可以被批处理。

GPU Instancing:对于支持GPU Instancing的材质和Shader,Unity可以将不同的材质参数(如颜色或其他可变参数)作为实例数据传递给GPU。这样,即使每个实例的参数不同,它们仍然可以在一个渲染调用中被处理。

动态批处理限制:Unity的动态批处理有一定的限制,比如只能批处理顶点数、材质属性等在一定范围内的物体。如果物体的材质参数变化太大,或者超出了动态批处理的限制,那么它们将不会被批处理。

Profiler和Frame Debugger:Unity提供了Profiler和Frame Debugger工具,允许开发者检查渲染过程中的批处理情况。通过这些工具,开发者可以看到哪些物体被批处理,哪些没有,以及为什么。

总的来说,Unity的渲染管线通过检查材质属性和使用高级技术(如MaterialPropertyBlock和GPU Instancing)来处理使用相同材质实例但参数不同的物体。这些机制允许开发者在保持高效渲染的同时,为每个物体定制材质的外观。
为了确保即使在游戏物体使用相同材质实例但材质参数不同时,也能尽可能高效地渲染,Unity采取了一些策略和优化技术:

Shader变体:Unity的Shader编译系统会为不同的材质参数创建不同的Shader变体。这些变体可以在运行时根据需要动态加载,以确保即使材质参数不同,也能使用最适合当前渲染情况的Shader代码。

关键字:Unity Shader可以使用关键字来启用或禁用特定的代码路径。这允许Shader根据材质属性的不同来调整其行为。在运行时,Unity会根据当前的材质属性设置相应的关键字,以选择正确的Shader变体。

材质属性动画:如果游戏物体的材质属性随时间变化(例如,通过动画或脚本),Unity会在每一帧更新这些属性。这可能会影响批处理,因为每个物体可能需要单独的渲染调用。为了优化这种情况,可以使用GPU Instancing或者MaterialPropertyBlock来减少对批处理的影响。

渲染路径:Unity支持不同的渲染路径(如Forward Rendering和Deferred Rendering),每种路径对材质属性的处理方式不同。选择合适的渲染路径可以根据游戏的需求来优化性能。

自定义渲染管线:对于需要更高级渲染优化的项目,Unity允许开发者创建自定义渲染管线(SRP)。通过SRP,开发者可以完全控制渲染过程,包括如何处理使用相同材质实例但参数不同的物体。

材质优化:在设计材质时,应尽量减少不必要的属性和纹理,以减少对性能的影响。同时,合理使用材质属性的默认值可以减少需要动态更改的参数数量。

批处理覆盖:在某些情况下,如果你知道改变某些材质参数不会对视觉效果产生重大影响,你可以选择覆盖这些参数,以便物体可以被批处理。这需要在性能和视觉质量之间做出权衡。

通过这些策略和技术,Unity的渲染管线能够在保持渲染效率的同时,为游戏物体提供丰富的视觉效果。开发者需要根据具体的项目需求和性能目标,选择合适的方法来优化渲染性能。

LOD(Level of Detail)系统:Unity提供了LOD系统,允许根据相机与物体的距离来选择不同级别的细节。这可以包括使用不同的材质或简化的Shader,从而在远距离时减少渲染负担,即使物体使用了相同的材质实例但参数不同。

避免过度绘制:通过合理设计场景和使用遮挡剔除(Occlusion Culling),可以避免渲染那些被其他物体遮挡的物体。这样,即使这些物体使用了相同的材质实例但参数不同,它们也不会对性能产生影响,因为它们根本不会被渲染。

合理使用静态和动态物体:将不会移动的物体标记为静态,可以让Unity在编辑时进行优化,如合并网格和材质。对于动态物体,可以通过MaterialPropertyBlock来动态改变材质参数,而不影响批处理。

使用Command Buffers:高级用户可以使用Command Buffers来手动控制渲染命令的执行。这允许开发者在渲染管线的特定点插入自定义的渲染命令,可以用来优化特定情况下的渲染性能。

材质变体集中管理:通过集中管理材质变体,可以确保不会创建过多不必要的材质实例。这可以通过使用材质管理器或者在资源加载时进行控制来实现。

使用Shader预编译:通过预编译Shader变体,可以减少运行时编译带来的性能开销。这需要在项目设置中合理配置Shader变体,以确保运行时需要的变体都已经编译好。

性能分析和优化:定期使用Unity Profiler和其他性能分析工具来检查游戏的性能瓶颈。这可以帮助开发者识别和优化那些使用相同材质实例但参数不同的物体的渲染性能问题。

教育和最佳实践:最后,确保团队成员了解Unity渲染管线的工作原理和最佳实践是非常重要的。通过教育和代码审查,可以确保项目中的材质使用得当,从而提高整体性能。

总结来说,Unity提供了多种工具和技术来帮助开发者优化使用相同材质实例但参数不同的物体的渲染。这些优化需要在项目的整个生命周期中不断进行,以确保最终产品能够在各种硬件上提供流畅的游戏体验。

shader程序是每个游戏对象都有一份实例吗

不是的。在Unity中,Shader程序是独立于游戏对象的。Shader是一段运行在图形处理器(GPU)上的程序,用于处理顶点和像素的渲染。Shader程序通常被编译成一份代码,然后被所有使用该Shader的材质共享。
当你为游戏对象分配一个材质时,该材质会引用一个Shader。如果多个游戏对象使用相同的材质,它们实际上是共享同一个Shader实例。这意味着Shader代码本身不会为每个游戏对象复制一份,而是所有引用该Shader的材质都会使用同一份编译后的Shader代码。
这种设计允许Unity更高效地管理资源和渲染过程,因为它减少了内存占用,并且在渲染时可以更容易地批处理使用相同Shader的游戏对象。
然而,尽管Shader程序本身是共享的,但是每个游戏对象可以通过其材质有不同的参数设置,如不同的颜色、纹理等。这些参数是通过材质属性来设置的,而材质属性可以为每个游戏对象独立设置,即使它们使用的是相同的Shader。这就是为什么即使多个游戏对象使用相同的Shader,它们看起来也可以完全不同的原因。
为了进一步解释Shader和材质在Unity中的工作方式,我们可以将其与现实世界中的物体和涂料进行类比:

Shader:可以想象Shader是一种“涂料配方”,它定义了涂料的基本特性,比如它是油漆还是乳胶漆,是哑光还是有光泽。这个配方是固定的,不管你要涂多少个墙面,配方都是一样的。

材质:材质则相当于根据这个配方调制出来的一桶涂料。你可以根据需要添加不同的颜色素或其他添加剂来改变这桶涂料的外观。如果你有多个房间要涂相同的颜色,你可以用同一桶涂料来涂。

游戏对象:游戏对象则相当于房间的墙面。你可以用同一桶涂料(材质)来涂不同的墙面(游戏对象),每个墙面都会显示出相同的颜色和质感。

在Unity中,当多个游戏对象使用相同的材质时,它们实际上是共享同一份Shader配方和材质设置。但是,Unity也允许你为每个游戏对象的材质实例化,这样你就可以在保持相同Shader的情况下,为每个游戏对象设置不同的材质属性,比如不同的颜色或纹理映射。
这种设计的优势在于:

内存效率:因为Shader代码是共享的,所以不需要为每个游戏对象存储额外的Shader代码,这样可以节省大量内存。

渲染效率:Unity可以批处理那些使用相同Shader和相似材质属性的游戏对象,这样可以减少渲染调用的次数,提高渲染效率。

灵活性:开发者可以通过改变材质属性来调整游戏对象的外观,而不需要编写新的Shader代码。

总之,Shader在Unity中是一种高效且灵活的资源,它允许开发者通过材质来控制游戏对象的视觉表现,同时保持了渲染过程的高效性。

继续深入了解Unity中的Shader和材质管理,我们可以探讨一些更高级的概念和实践:
材质实例化:在Unity中,如果你在运行时更改了一个游戏对象的材质属性(例如颜色),Unity会自动为该游戏对象创建一个材质的实例(Instance)。这意味着该游戏对象现在有了一个独特的材质副本,不再与其他游戏对象共享相同的材质。这对于创建动态和互动的环境非常有用,但也要注意,过度实例化可能会影响性能,因为它减少了批处理的机会。
材质属性块:为了避免不必要的材质实例化,Unity提供了MaterialPropertyBlock。这是一种高效的方式来改变游戏对象的材质属性,而不会创建新的材质实例。MaterialPropertyBlock可以用来批量设置多个游戏对象的材质属性,而不会影响它们共享的材质。
Shader变体:Shader通常包含多个“变体”(Variant),这些变体可能包含不同的功能,如光照模型、阴影类型或特殊效果。Unity在编译时会为每个Shader生成这些变体。为了优化性能,你可以通过Shader变体集(Shader Variant Collection)来预编译和包含游戏中实际使用的变体,这样可以减少运行时的编译时间和避免潜在的性能问题。
GPU Instancing:GPU Instancing是一种技术,它允许Unity在单个渲染调用中绘制多个具有相同材质的游戏对象。这可以显著提高渲染效率,特别是在渲染大量相似对象时,如森林、草地或军队。为了使用GPU Instancing,你的Shader需要被写成支持这项技术,而且游戏对象的材质需要启用Instancing选项。
自定义Shader编写:虽然Unity提供了一系列内置的Shader,但有时你可能需要编写自定义Shader来实现特定的视觉效果。Unity的ShaderLab语言和HLSL/Cg语言允许你创建高度定制的Shader。自定义Shader编写需要深入理解渲染管线和GPU编程。
性能分析:使用Unity Profiler和Frame Debugger等工具定期检查游戏的渲染性能至关重要。这些工具可以帮助你识别性能瓶颈,如过多的材质实例化、Shader变体过多或不必要的渲染调用。
通过这些高级技术和最佳实践,你可以更有效地管理和优化Unity中的Shader和材质,确保游戏在各种硬件上都能提供最佳的性能和视觉体验。

Shader性能优化:在Unity中,Shader的性能优化是一个重要的话题。Shader的复杂性直接影响到渲染性能,因此在编写Shader时需要考虑到其对GPU的负载。优化技巧包括减少纹理采样次数、使用更简单的数学运算、避免在Shader中使用分支语句(if-else),以及尽可能地减少Shader的指令数量。
多通道渲染(Multi-Pass Rendering):有些效果,如复杂的光照模型或阴影,可能需要多个渲染通道来实现。在Unity中,你可以编写多通道Shader,这些Shader在一个渲染操作中执行多次,每次处理不同的渲染阶段。然而,多通道渲染会增加GPU的工作负载,因此应当谨慎使用。
渲染路径(Rendering Path):Unity支持不同的渲染路径,包括Forward Rendering和Deferred Rendering。每种渲染路径都有其优势和限制,选择合适的渲染路径可以根据游戏的需求来优化性能。例如,Deferred Rendering非常适合场景中有大量光源的情况,而Forward Rendering则更适合移动设备或需要兼容较老硬件的游戏。
Shader LOD(Level of Detail):为了进一步优化性能,可以为Shader实现不同的细节级别(LOD)。这样,根据游戏对象与摄像机的距离,可以使用不同复杂度的Shader变体来渲染。远距离的对象可以使用更简单的Shader,以减少GPU的负载。
使用内置Shader函数:Unity提供了许多内置的Shader函数,这些函数经过优化,可以帮助你更高效地实现常见的渲染效果。在可能的情况下,使用这些内置函数而不是编写自己的实现,可以提高Shader的性能和兼容性。
避免过度绘制(Overdraw):过度绘制发生在多个对象在屏幕上的相同像素位置上重叠渲染时。这意味着GPU需要进行额外的工作来渲染不可见的像素。通过使用遮挡剔除(Occlusion Culling)、合理的场景布局和摄像机设置,可以减少过度绘制,从而提高渲染性能。
使用Shader预编译:为了避免游戏运行时的编译延迟,可以在游戏打包时预编译Shader。这可以通过在Unity编辑器中使用Shader变体集合来实现,确保游戏中使用的所有Shader变体都被预先编译并包含在游戏构建中。
通过这些策略和技术,你可以确保你的Unity游戏在各种设备上都能提供流畅的渲染性能,同时保持高质量的视觉效果。Shader优化是一个持续的过程,需要不断地测试、分析和调整,以找到最佳的平衡点。

多个游戏物体可以同时进行渲染吗 还是要按顺序渲染

在Unity中,多个游戏物体确实可以同时进行渲染,这是通过图形处理单元(GPU)的并行处理能力实现的。GPU设计用于高效地处理大量的并行操作,这使得它非常适合同时渲染多个游戏物体。
然而,从逻辑上讲,渲染过程仍然涉及到一定的顺序,这是由渲染管线定义的。渲染管线是一系列顺序执行的步骤,用于将3D场景转换为2D屏幕上的图像。这些步骤包括应用程序阶段、几何处理阶段、光栅化阶段和像素处理阶段。
在Unity中,游戏物体通常按照以下顺序进行渲染:

遮挡剔除(Occlusion Culling):Unity首先确定哪些物体实际上在摄像机视野内,并且没有被其他物体遮挡。不在视野内或被遮挡的物体可以被剔除,不参与后续的渲染过程。

排序和批处理(Sorting and Batching):接下来,Unity会对可见的游戏物体进行排序,以优化渲染过程。它会尝试将使用相同材质和Shader的物体批处理在一起,以减少对GPU的状态改变,从而提高效率。

渲染调用(Render Calls):排序和批处理后,Unity会发出渲染调用,指示GPU渲染一批游戏物体。这些调用可以并行执行,因为GPU可以同时处理多个渲染任务。

渲染队列(Render Queues):Unity中的材质可以指定它们在渲染队列中的位置,这决定了它们的渲染顺序。例如,透明物体通常在不透明物体之后渲染,以确保正确的混合和透明度效果。

后处理(Post-Processing):所有游戏物体渲染完成后,Unity可以应用后处理效果,如色彩校正、模糊和光晕效果,这些通常在屏幕空间中进行,并且是按顺序执行的。

尽管渲染管线有其固有的顺序,但GPU的并行处理能力意味着在任何给定的时刻,都可能有多个游戏物体同时处于不同渲染阶段。例如,一些物体可能正在进行顶点着色,而另一些物体可能正在进行片元着色。
总的来说,虽然渲染过程在逻辑上是有序的,但在物理上,多个游戏物体确实可以同时进行渲染,这得益于现代GPU的并行计算能力。
当游戏物体的渲染状态发生改变时,并不一定意味着整个渲染流程都要重新开始。渲染状态的改变通常指的是物体的材质、Shader、纹理或其他渲染相关属性的变化。这些改变会影响到该物体的渲染,但不一定会影响到整个场景的渲染流程。
以下是一些常见的渲染状态改变及其影响:

材质属性改变:如果一个游戏物体的材质属性(如颜色、光泽度等)发生改变,只有该物体的渲染状态会被更新。这种情况下,只有该物体需要重新渲染,而不会影响其他物体。

Shader更换:如果更换了游戏物体的Shader,那么该物体需要重新编译新的Shader并应用。这通常只影响到使用新Shader的物体,而不会影响到其他使用不同Shader的物体。

纹理更新:如果更新了游戏物体的纹理,只有使用该纹理的物体需要重新渲染以反映新的纹理。这不会影响到使用其他纹理的物体。

光照和阴影变化:如果场景中的光照或阴影发生变化,可能需要重新计算受影响的物体的光照和阴影。这可能会影响到多个物体,但通常不需要重新执行整个渲染流程。

几何形状变化:如果游戏物体的几何形状(如网格)发生变化,那么需要重新计算该物体的顶点数据和可能的碰撞数据。这只影响到该物体,而不会影响到其他物体。

视图或投影变化:如果摄像机的视图或投影矩阵发生变化(例如,摄像机移动或旋转),则需要重新计算所有可见物体的渲染状态。这种情况下,整个场景的渲染流程可能需要更新,但并不是从头开始,而是更新相关的变换和视图依赖项。

Unity的渲染引擎设计得足够智能,能够最小化不必要的计算。它会尽可能地只更新必要的部分,而不是重新执行整个渲染流程。这种优化是为了确保渲染性能不会因为频繁的状态变化而受到影响。
Unity的渲染引擎通过各种优化技术来确保即使在频繁的状态变化下也能保持高效的渲染性能。以下是一些Unity中用于优化渲染流程的技术和概念:

批处理(Batching):Unity会尝试将多个渲染调用合并为一个批处理调用,以减少CPU到GPU的通信开销。这通常发生在使用相同材质和Shader的游戏物体之间。当这些物体的渲染状态发生变化时,Unity会重新评估批处理条件并进行必要的更新。

动态批处理(Dynamic Batching):对于小型物体,Unity可以在运行时动态地将它们合并为单个批处理调用,这有助于减少渲染开销。

静态批处理(Static Batching):对于不会移动的游戏物体,可以在编辑时将它们合并为单个批处理调用。这样,即使其中一个物体的渲染状态发生变化,也不会影响到整个批处理的其他物体。

GPU Instancing:当需要渲染许多外观相同的物体时,GPU Instancing可以用来减少渲染开销。这种技术允许单个渲染调用同时渲染多个物体实例。

遮挡剔除(Occlusion Culling):Unity会自动剔除那些被其他物体遮挡的物体,这样它们就不会进入渲染流程,从而节省资源。

延迟渲染(Deferred Rendering):在延迟渲染路径中,Unity会将渲染过程分为两个阶段:几何阶段和光照阶段。这样,即使物体的渲染状态发生变化,也只需要更新受影响的阶段,而不是整个渲染流程。

材质属性块(Material Property Blocks):通过使用材质属性块,可以在不更改材质实例的情况下,为单个渲染调用中的多个物体设置不同的材质属性。这有助于减少因状态变化而导致的渲染开销。

渲染队列和层级(Render Queues and Layers):通过合理地使用渲染队列和层级,可以控制渲染顺序并优化渲染流程。这样,即使某些物体的状态发生变化,也不会影响到整个场景的渲染顺序。

资源管理:Unity提供了资源管理系统,允许开发者控制资源的加载和卸载。通过有效地管理资源,可以确保渲染流程中只包含必要的资源,从而提高性能。

通过这些技术,Unity能够在游戏物体的渲染状态发生变化时,只更新必要的部分,而不是重新执行整个渲染流程。这种优化确保了游戏的流畅运行,即使在复杂场景和动态环境中也能保持高效的渲染性能。

游戏物体的渲染状态发送改变了是不是意味着当前游戏物体的整个渲染流程要重新跑一遍

当游戏物体的渲染状态发生改变时,通常不需要重新跑整个渲染流程。渲染状态的改变可能涉及材质、纹理、Shader参数等方面的更新。Unity会在下一个渲染帧中考虑这些变化,并在必要时只更新受影响的部分。
例如,如果你更改了一个物体的材质颜色,Unity只需要在下一帧中使用新的颜色值来渲染该物体,而不需要重新执行整个渲染流程。同样,如果你更改了物体的Shader或纹理,Unity会在下一帧中使用新的Shader或纹理来渲染该物体。
然而,某些类型的改变可能需要更多的更新。例如,如果你更改了物体的网格(Mesh),这可能需要重新计算光照、阴影和其他可能受网格形状影响的效果。即便如此,这通常也只影响到该物体及其直接相关的渲染过程,而不是整个场景的渲染流程。
Unity的渲染引擎设计得足够智能,能够最小化不必要的计算。它会尽可能地只更新必要的部分,而不是重新执行整个渲染流程。这种优化是为了确保渲染性能不会因为频繁的状态变化而受到影响。
总结来说,游戏物体的渲染状态发生改变时,通常只需要更新该物体的特定渲染状态,而不需要重新跑整个渲染流程。这样的设计使得Unity能够在保持高性能的同时,也能够灵活地处理游戏物体状态的动态变化。

Unity中的渲染流程是高度优化的,以便在游戏运行时处理大量的状态变化,而不会显著影响性能。当游戏物体的渲染状态发生改变时,Unity会采取以下步骤来确保高效渲染:

状态变化检测:Unity会检测到游戏物体的渲染状态发生了哪些具体的改变,比如材质属性、Shader参数、纹理更换等。

脏标记(Dirty Flagging):一旦检测到状态变化,Unity会将相关的游戏物体或组件标记为“脏”(dirty),这意味着它们需要在下一次渲染时更新。

局部更新:在下一个渲染帧中,Unity会根据脏标记来更新那些状态发生变化的游戏物体。这通常涉及到更新GPU的状态机或上传新的参数到Shader。

渲染队列调整:如果必要,Unity会调整受影响物体在渲染队列中的位置,以确保正确的渲染顺序,比如透明物体通常在不透明物体之后渲染。

批处理和实例化:Unity会尝试将状态相似的物体进行批处理或实例化,以减少渲染调用的数量,从而提高效率。

光照和阴影更新:如果物体的变化影响到了光照或阴影(例如,改变了物体的位置或网格),Unity会重新计算受影响的光照和阴影。

遮挡剔除和视锥剔除:Unity会进行遮挡剔除和视锥剔除,以确保只渲染摄像机视野内的和未被遮挡的物体。

最终渲染:经过以上所有必要的更新后,Unity会将最终的渲染结果呈现到屏幕上。

这个过程是高度优化的,因为Unity会尽量避免全局更新,而是只更新必要的部分。这意味着即使单个游戏物体的渲染状态发生了改变,也不会导致整个场景重新渲染,而是只会影响到那些“脏”的物体。这种优化确保了即使在复杂场景中,游戏也能保持流畅的帧率和响应速度。

资源管理:Unity会监控资源的使用情况,如纹理和模型数据。如果游戏物体的状态变化涉及到新资源的加载,Unity会通过其资源管理系统来异步加载资源,以避免造成帧率下降。

Shader变体编译:如果状态变化需要新的Shader变体(例如,启用或禁用了某个Shader关键字),Unity可能需要编译新的Shader变体。这通常在游戏开发阶段预先完成,以避免运行时编译带来的性能开销。

内存和缓存管理:Unity会管理GPU和CPU内存,确保数据高效地传输。例如,如果一个物体的纹理被更新,Unity会确保新纹理被上传到GPU内存中,并且旧纹理被适当地释放或缓存。

多线程渲染:Unity支持多线程渲染,这意味着渲染过程可以在不同的CPU核心上并行处理,从而提高性能。当游戏物体的状态发生变化时,相关的更新可以在不同的线程上进行,减少主线程的负担。

性能分析和调试:Unity提供了一系列的性能分析工具,如Profiler,帮助开发者识别和优化可能的性能瓶颈。如果状态变化导致性能下降,开发者可以使用这些工具来诊断问题并进行优化。

用户自定义优化:开发者可以通过编写自定义脚本和Shader来进一步优化渲染流程。例如,可以创建自定义的材质属性更新逻辑,或者编写特定的Shader代码来处理特殊的渲染效果。

平台特定优化:Unity允许开发者针对不同的硬件平台进行优化。这意味着当游戏物体的渲染状态发生变化时,Unity可以利用特定平台的硬件特性来优化渲染流程。

通过这些连贯的步骤,Unity确保了即使在游戏物体频繁变化的情况下,也能够维持高效的渲染性能。这些优化措施的目的是为了减少不必要的计算,确保只有必要的数据被处理和传输,从而使游戏运行更加流畅。

unity shader中pass通道是如何做性能优化的

在Unity中,对Shader中的Pass进行性能优化是一个多方面的过程,涉及到减少Pass的数量、优化Shader代码、合理使用资源和技术等。以下是一些具体的优化策略:

减少Pass数量:

尽量在一个Pass内完成所有必要的渲染工作,避免不必要的多Pass渲染。
对于可以预计算的效果,如静态光照,使用光照贴图代替实时光照计算。

合并Shader效果:

如果多个物体可以共享相同的视觉效果,考虑将它们的Shader合并,以便可以一次性渲染多个物体。
使用Shader LOD(Level of Detail)系统来为不同的视图距离提供不同复杂度的Shader变体。

优化Shader代码:

移除不必要的Shader指令,如未使用的变量和函数。
使用更高效的数学运算和算法,例如使用fma指令代替单独的乘法和加法操作。
避免在片元Shader中使用昂贵的操作,如循环和条件分支,特别是那些依赖于动态条件的。

使用Shader预编译指令:

利用#pragma multi_compile和#pragma shader_feature来为不同的渲染路径创建Shader变体,这样可以在运行时根据需要切换Shader功能,而不是增加额外的Pass。

减少过度绘制:

使用剔除技术(如背面剔除)来减少不必要的渲染。
对于透明物体,正确排序渲染顺序以减少混合操作。

优化资源使用:

使用合适的纹理尺寸和压缩格式来减少内存占用和提高纹理采样效率。
使用纹理贴图的mipmap层级来减少在远距离渲染时的纹理带宽消耗。

使用GPU Profiling工具:

利用Unity的Profiler和其他GPU分析工具来监控Shader的性能,找出瓶颈。
分析Draw Calls和批处理情况,确保渲染是高效的。

使用更高效的数据结构:

使用结构体和数组来优化数据访问,减少Shader中的全局变量数量。

利用现代图形API特性:

如果目标平台支持,考虑使用如Compute Shaders、Tessellation等现代图形API特性来进行更高效的渲染。

良好的Shader书写习惯:

使用描述性强的变量名和注释来保持代码的可读性,这有助于未来的优化工作。
组织代码结构,使得常用的代码片段易于重用和维护。

通过这些策略,你可以显著提高Shader的性能,减少渲染时间,同时保持或甚至提升渲染效果的质量。记住,优化是一个持续的过程,需要不断地测试、分析和调整。

需要使用多pass的应用场景是什么,具体原因是什么

在Unity中,使用多Pass渲染的场景通常是为了实现复杂的视觉效果,这些效果不能在单个Pass中完成。以下是一些需要使用多Pass的典型应用场景和具体原因:

复杂的光照模型:

当一个物体需要多种类型的光照效果(如漫反射、高光、环境光遮蔽)时,可能需要多个Pass来分别计算这些效果。
例如,实现Blinn-Phong光照模型时,可能需要一个Pass来计算漫反射,另一个Pass来添加高光效果。

描边和轮廓效果:

为了在物体周围创建一个轮廓或描边效果,通常需要一个额外的Pass来渲染扩大的版本的物体,然后与原始物体进行混合。

透明和半透明材质:

对于需要渲染透明或半透明物体的场景,可能需要一个Pass来渲染不透明部分,另一个Pass来处理透明效果,以确保正确的混合和深度排序。

特殊效果:

一些特殊效果,如反射、折射、光晕或者屏幕空间效果,可能需要额外的Pass来捕获场景的状态或渲染特殊的图案。

阴影生成:

在实现阴影时,通常需要一个Pass来渲染阴影贴图(即从光源视角渲染场景),然后在另一个Pass中将这些阴影应用到场景中。

后处理效果:

后处理效果如模糊、色彩校正、HDR效果等,通常在渲染管线的最后阶段以多个Pass的形式应用。

细节层次(LOD)系统:

在实现LOD系统时,不同的细节层次可能需要不同的渲染技术,这可能导致在远距离使用一个简化的Pass,而在近距离使用一个或多个高质量的Pass。

顶点动画和形变:

对于需要在顶点着色器中进行复杂计算的动画或形变效果,可能需要一个Pass来处理形变,另一个Pass来进行常规渲染。

具体原因:

技术限制:某些效果在技术上无法在单个Pass中实现,或者实现起来效率低下。
资源管理:分离Pass可以更好地管理GPU资源,如纹理和渲染目标。
灵活性和可维护性:多Pass结构允许开发者更灵活地控制渲染流程,易于调试和维护。

尽管多Pass可以实现复杂和高质量的效果,但它们也会增加GPU的工作负担,因此在使用时需要权衡性能和视觉效果。在移动和性能受限的平台上,通常会尽量避免使用多Pass渲染,或者寻找可以模拟类似效果的单Pass解决方案。

在Unity中,继续优化多Pass Shader的性能和质量,开发者可以采取以下策略:

  1. 选择合适的渲染路径
    Unity提供了多种渲染路径(如Forward, Deferred, Legacy等),每种路径对Pass的处理方式不同。例如,Deferred渲染路径可以减少光照计算的Pass数量,因为它在G-Buffer中统一处理光照。
  2. 使用条件编译
    通过在Shader中使用条件编译指令,可以根据材质属性或平台特性来启用或禁用特定的Pass,从而避免在不需要的情况下执行某些Pass。
  3. 优化渲染顺序
    合理安排Pass的渲染顺序可以减少状态切换和过度绘制。例如,先渲染所有不透明物体,再渲染透明物体,可以提高渲染效率。
  4. 使用替代技术
    在可能的情况下,使用替代技术来模拟多Pass效果。例如,使用屏幕空间的后处理效果来模拟复杂的光照效果,或者使用延迟渲染来处理多光源。
  5. 利用硬件特性
    现代GPU提供了许多高级特性,如实例化渲染、计算着色器等,可以用来提高多Pass渲染的效率。
  6. 精简Shader变体
    通过精简Shader变体,减少编译和加载时间,同时降低运行时的资源消耗。可以通过移除不必要的预编译指令和优化Shader代码来实现。
  7. 使用LOD系统
    对Shader进行细节层次划分,为不同的视图距离提供不同复杂度的Shader变体,从而在远距离时减少渲染负担。
  8. 动态调整质量
    根据当前的性能指标动态调整Shader的质量,例如,在帧率下降时降低某些效果的质量或关闭某些Pass。
  9. 缓存和重用渲染结果
    对于某些效果,可以将一个Pass的渲染结果缓存起来,供其他Pass使用,避免重复计算。
  10. 使用专业工具进行分析
    使用Unity Profiler、RenderDoc、GPUView等工具来分析Shader的性能,找出并优化瓶颈。
  11. 良好的编码实践
    保持代码整洁,避免冗余计算,合理组织Shader代码,使得维护和优化更加容易。
  12. 社区和插件资源
    利用Unity社区和市场中的资源,如高效的Shader库和优化工具,可以帮助快速实现和优化多Pass效果。
    结论
    多Pass Shader的优化是一个不断迭代的过程,需要结合项目的具体需求和目标平台的特性来进行。通过上述策略,可以在保持视觉效果的同时,尽可能地提高渲染性能。在实际开发中,应该根据性能分析的结果来决定哪些优化措施是最有效的。

unity引擎中shader程序和材质的关系

在Unity引擎中,Shader程序和材质(Material)是渲染管线中的两个核心概念,它们共同决定了游戏对象的外观。它们之间的关系可以用以下方式来描述:
Shader(着色器):

定义:Shader是一段运行在图形处理单元(GPU)上的程序,它告诉GPU如何渲染物体的表面。Shader包含了一系列的指令,用于处理顶点数据和像素数据,以及如何计算光照、颜色、纹理等。
作用:Shader决定了渲染的算法和技术,比如光照模型、阴影的生成、透明度处理、顶点动画等。
通用性:一个Shader可以被多个材质使用,因为它定义了一种渲染方式,而不是具体的颜色或纹理。

Material(材质):

定义:材质是Shader的一个实例,它包含了一组参数和资源(如纹理、颜色值、透明度等),这些参数和资源会被Shader程序使用来渲染物体。
作用:材质决定了物体的具体外观,比如它的颜色、光泽度、粗糙度等。通过改变材质的参数,可以在不修改Shader代码的情况下调整物体的视觉效果。
个性化:每个游戏对象可以有自己的材质,即使它们使用相同的Shader,通过不同的材质参数,它们也可以呈现出不同的外观。

它们之间的关系:

依赖性:材质依赖于Shader来定义它的渲染逻辑。没有Shader,材质就没有“指令”来告诉GPU如何渲染物体。
参数化:Shader定义了一系列可以被材质设置的参数(比如颜色、纹理等)。这些参数在Shader中通常是以属性(Properties)的形式出现,而在材质中则是具体的值。
灵活性:通过改变材质的参数,可以在同一个Shader的基础上创建出外观各异的物体,这提供了极大的灵活性和重用性。

简单来说,Shader是“配方”,它定义了渲染的规则和步骤;而材质则是根据这个“配方”制作出来的“菜肴”,它提供了具体的“食材”和“调料”。通过不同的材质,同一个Shader可以用来制作出视觉上截然不同的效果。

继续深入探讨Unity中Shader和材质的关系,我们可以从它们在实际开发中的应用和互动来进一步理解:
Shader的灵活性和重用性
一个Shader可以设计得非常灵活,通过内部的逻辑判断和参数配置,它可以支持多种不同的渲染效果。例如,一个Shader可能包含代码来处理金属和非金属表面,通过材质中的参数来选择不同的渲染路径。
材质的具体化
材质是将Shader中的抽象参数具体化的过程。开发者在材质编辑器中为Shader中定义的每个属性赋予具体的值,这些值在运行时被传递给Shader,由Shader来实际执行渲染操作。
ShaderLab和CG/HLSL
Unity中的Shader通常由两部分组成:ShaderLab代码和CG/HLSL代码。ShaderLab是Unity特有的,用于定义Shader的属性、Pass等;CG/HLSL则是实际的着色器语言,用于编写GPU执行的代码。材质与ShaderLab定义的属性直接交互,而CG/HLSL代码则处理这些属性的渲染逻辑。
材质属性的动态更改
在游戏运行时,可以通过脚本动态更改材质的属性,从而实现动态的视觉效果,如改变物体的颜色、增加发光效果等。这种动态更改是即时的,不需要重新编译Shader,因为材质已经包含了Shader的所有可能性。
Shader的性能考量
Shader的复杂性直接影响渲染性能。在设计Shader时,需要考虑其对性能的影响。而材质作为Shader的实例,通过启用或禁用某些特性(比如光照、纹理映射等),可以在不同的性能需求下进行调整。
材质实例
在Unity中,每个游戏对象可以有自己的材质实例,即使它们共享同一个Shader。这意味着,即使两个物体看起来有相同的材质,它们也可以独立地更改属性,而不会影响到彼此。
Shader变体和关键字
Unity中的Shader可能会有多个变体(Variant),这些变体是由预处理器指令(如#pragma multi_compile和#pragma shader_feature)生成的。材质中的某些属性设置可能会触发不同的Shader变体,这允许同一个Shader根据需要编译出不同的代码路径。
总结
Shader和材质在Unity中是紧密相连的,Shader提供了渲染的框架和可能性,而材质则填充了这个框架,给予了具体的外观。理解它们之间的关系对于开发高效和高质量的游戏至关重要。通过合理设计Shader并智能地使用材质,可以在保持游戏美观的同时,优化性能和资源使用。

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

牛掰是怎么形成的

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

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

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

打赏作者

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

抵扣说明:

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

余额充值