【从UnityURP开始探索游戏渲染】专栏-直达

SRP提供的核心功能与架构‌

可编程管线基础

RenderPipeline基类

  • 通过继承该类并重写Render()方法,开发者可自定义渲染流程调度逻辑,替代传统固定管线。(URP中UniversalRenderPipeline : RenderPipeline 继承并重写Render方法。)

ScriptableRenderContext

  • 作为C#脚本与底层图形API的桥梁,允许通过代码调度渲染命令(如剔除、绘制)。public struct ScriptableRenderContext : IEquatable<ScriptableRenderContext>定义自定义渲染管道使用的状态绘图命令。)

管线资源分离机制

  • RenderPipelineAsset:存储配置数据(如材质、Shader参数)(URP中是

    public partial class UniversalRenderPipelineAsset : RenderPipelineAsset, ISerializationCallbackReceiver
  • RenderPipelineInstance:执行实际渲染逻辑的实例类。(在URP中,上面的RenderPipelineAsset资产类中重写protected override RenderPipeline CreatePipeline()方法,在其中创建渲染管线实例var pipeline = new UniversalRenderPipeline(this);

关键扩展点

事件回调

  • 通过RenderPipelineManager订阅渲染生命周期事件(如beginContextRendering),在特定阶段注入自定义逻辑。

动态渲染策略

  • 支持运行时切换渲染路径(如正向/延迟渲染),适应不同硬件性能需求(这里的渲染路径是URP或HDRP自己实现的,例如Forward+也是,所以这个渲染路径只是实现管线时的自定义渲染策略,所以运行时能切换。但是一般不建议切换,因为各种shader实现时都会根据渲染路径实现相应执行的Pass,如果随便切换,会导致部分shader可能因为只适配某种渲染路径,对其他渲染路径显示渲染异常。)。

URP在SRP上的具体实现‌

资源与实例初始化

URP管线资源‌(UniversalRenderPipelineAsset):

  • 定义默认Shader、光照模型、后处理栈等参数。

实例化流程‌:

  • 资源创建时调用CreatePipeline()生成UniversalRenderPipeline实例,接管Unity渲染循环。
  • 其中的渲染器基类ScriptableRenderer作为 渲染器可以用于所有摄像机,也可以在每个摄像机的基础上重写。它将实现光剔除和设置,并描述要在帧中执行的ScriptableRenderPass列表。渲染器可以通过额外的scriptablerendererfeature进行扩展,以支持更多的效果。渲染器的资源在ScriptableRendererData中序列化(编辑器中就在这个资源上挂载设置RendererFeature)。
    • UniversalRenderer 默认的3D渲染器继承自ScriptableRenderer。在其构造函数中

      public UniversalRenderer(UniversalRendererData data) : base(data) 根据上述序列化的Data数据,创建默认的渲染Pass逻辑。渲染路径就是在这个类文件中一同定义的,在构造函数中根据不同路径,给出不同策略执行Pass。

    • 定义渲染路径

      namespace UnityEngine.Rendering.Universal
      {
      /// <summary>
      /// Rendering modes for Universal renderer.
      /// </summary>
      public enum RenderingMode
      {
      /// <summary>Render all objects and lighting in one pass, with a hard limit on the number of lights that can be applied on an object.</summary>
      Forward = 0,
      /// <summary>Render all objects and lighting in one pass using a clustered data structure to access lighting data.</summary>
      [InspectorName("Forward+")]
      ForwardPlus = 2,
      /// <summary>Render all objects first in a g-buffer pass, then apply all lighting in a separate pass using deferred shading.</summary>
      Deferred = 1
      };
      // 省略下面代码。。。
      }
    • 构造函数根据渲染路径给出不同Pass执行策略

      /// <summary>
      /// Constructor for the Universal Renderer.
      /// </summary>
      /// <param name="data">The settings to create the renderer with.</param>
      public UniversalRenderer(UniversalRendererData data) : base(data)
      {
      // Query and cache runtime platform info first before setting up URP.
      PlatformAutoDetect.Initialize();
      ...
      • 设置各种材质和状态

        // 设置各种材质和状态
        
        #if ENABLE_VR && ENABLE_XR_MODULE
        Experimental.Rendering.XRSystem.Initialize(XRPassUniversal.Create, data.xrSystemData.shaders.xrOcclusionMeshPS, data.xrSystemData.shaders.xrMirrorViewPS);
        #endif
        m_BlitMaterial = CoreUtils.CreateEngineMaterial(data.shaders.coreBlitPS);
        m_BlitHDRMaterial = CoreUtils.CreateEngineMaterial(data.shaders.blitHDROverlay);
        m_CopyDepthMaterial = CoreUtils.CreateEngineMaterial(data.shaders.copyDepthPS);
        m_SamplingMaterial = CoreUtils.CreateEngineMaterial(data.shaders.samplingPS);
        m_StencilDeferredMaterial = CoreUtils.CreateEngineMaterial(data.shaders.stencilDeferredPS);
        m_CameraMotionVecMaterial = CoreUtils.CreateEngineMaterial(data.shaders.cameraMotionVector);
        m_ObjectMotionVecMaterial = CoreUtils.CreateEngineMaterial(data.shaders.objectMotionVector); StencilStateData stencilData = data.defaultStencilState;
        m_DefaultStencilState = StencilState.defaultValue;
        m_DefaultStencilState.enabled = stencilData.overrideStencilState;
        m_DefaultStencilState.SetCompareFunction(stencilData.stencilCompareFunction);
        m_DefaultStencilState.SetPassOperation(stencilData.passOperation);
        m_DefaultStencilState.SetFailOperation(stencilData.failOperation);
        m_DefaultStencilState.SetZFailOperation(stencilData.zFailOperation); m_IntermediateTextureMode = data.intermediateTextureMode; if (UniversalRenderPipeline.asset?.supportsLightCookies ?? false)
        {
        var settings = LightCookieManager.Settings.Create();
        var asset = UniversalRenderPipeline.asset;
        if (asset)
        {
        settings.atlas.format = asset.additionalLightsCookieFormat;
        settings.atlas.resolution = asset.additionalLightsCookieResolution;
        } m_LightCookieManager = new LightCookieManager(ref settings);
        } this.stripShadowsOffVariants = true;
        this.stripAdditionalLightOffVariants = true;
        #if ENABLE_VR && ENABLE_VR_MODULE
        #if PLATFORM_WINRT || PLATFORM_ANDROID
        // AdditionalLightOff variant is available on HL&Quest platform due to performance consideration.
        this.stripAdditionalLightOffVariants = !PlatformAutoDetect.isXRMobile;
        #endif
        #endif
      • Forward和Forward+灯光准备,深度预处理、深度拷贝模式等设置。

                    ForwardLights.InitParams forwardInitParams;
        forwardInitParams.lightCookieManager = m_LightCookieManager;
        forwardInitParams.forwardPlus = data.renderingMode == RenderingMode.ForwardPlus;
        m_Clustering = data.renderingMode == RenderingMode.ForwardPlus;
        m_ForwardLights = new ForwardLights(forwardInitParams);
        //m_DeferredLights.LightCulling = data.lightCulling;
        this.m_RenderingMode = data.renderingMode;
        this.m_DepthPrimingMode = data.depthPrimingMode;
        this.m_CopyDepthMode = data.copyDepthMode; #if UNITY_ANDROID || UNITY_IOS || UNITY_TVOS
        this.m_DepthPrimingRecommended = false;
        #else
        this.m_DepthPrimingRecommended = true;
        #endif
      • 关键来了!URP定制的流程在这里(从灯光阴影投射、深度和深度法线预渲染、到深度拷贝、延迟渲染中的特别执行的LightMode:”UniversalForwardOnly”、再到延迟渲染的GBuffer及其对GBuffer的屏幕空间的光照处理、不透明阶段Pass、深度拷贝、运动向量Pass、天空盒Pass、透明物体Pass、离屏UIPass、覆盖UIPass、最后的混合、深度拷贝、输出到缓冲区“_CameraColorAttachment”)

                    // Note: Since all custom render passes inject first and we have stable sort,
        // we inject the builtin passes in the before events.
        m_MainLightShadowCasterPass = new MainLightShadowCasterPass(RenderPassEvent.BeforeRenderingShadows);
        m_AdditionalLightsShadowCasterPass = new AdditionalLightsShadowCasterPass(RenderPassEvent.BeforeRenderingShadows); #if ENABLE_VR && ENABLE_XR_MODULE
        m_XROcclusionMeshPass = new XROcclusionMeshPass(RenderPassEvent.BeforeRenderingOpaques);
        // Schedule XR copydepth right after m_FinalBlitPass
        m_XRCopyDepthPass = new CopyDepthPass(RenderPassEvent.AfterRendering + k_AfterFinalBlitPassQueueOffset, m_CopyDepthMaterial);
        #endif
        m_DepthPrepass = new DepthOnlyPass(RenderPassEvent.BeforeRenderingPrePasses, RenderQueueRange.opaque, data.opaqueLayerMask);
        m_DepthNormalPrepass = new DepthNormalOnlyPass(RenderPassEvent.BeforeRenderingPrePasses, RenderQueueRange.opaque, data.opaqueLayerMask); if (renderingModeRequested == RenderingMode.Forward || renderingModeRequested == RenderingMode.ForwardPlus)
        {
        m_PrimedDepthCopyPass = new CopyDepthPass(RenderPassEvent.AfterRenderingPrePasses, m_CopyDepthMaterial, true);
        } if (this.renderingModeRequested == RenderingMode.Deferred)
        {
        var deferredInitParams = new DeferredLights.InitParams();
        deferredInitParams.stencilDeferredMaterial = m_StencilDeferredMaterial;
        deferredInitParams.lightCookieManager = m_LightCookieManager;
        m_DeferredLights = new DeferredLights(deferredInitParams, useRenderPassEnabled);
        m_DeferredLights.AccurateGbufferNormals = data.accurateGbufferNormals; m_GBufferPass = new GBufferPass(RenderPassEvent.BeforeRenderingGbuffer, RenderQueueRange.opaque, data.opaqueLayerMask, m_DefaultStencilState, stencilData.stencilReference, m_DeferredLights);
        // Forward-only pass only runs if deferred renderer is enabled.
        // It allows specific materials to be rendered in a forward-like pass.
        // We render both gbuffer pass and forward-only pass before the deferred lighting pass so we can minimize copies of depth buffer and
        // benefits from some depth rejection.
        // - If a material can be rendered either forward or deferred, then it should declare a UniversalForward and a UniversalGBuffer pass.
        // - If a material cannot be lit in deferred (unlit, bakedLit, special material such as hair, skin shader), then it should declare UniversalForwardOnly pass
        // - Legacy materials have unamed pass, which is implicitely renamed as SRPDefaultUnlit. In that case, they are considered forward-only too.
        // TO declare a material with unnamed pass and UniversalForward/UniversalForwardOnly pass is an ERROR, as the material will be rendered twice.
        StencilState forwardOnlyStencilState = DeferredLights.OverwriteStencil(m_DefaultStencilState, (int)StencilUsage.MaterialMask);
        ShaderTagId[] forwardOnlyShaderTagIds = new ShaderTagId[]
        {
        new ShaderTagId("UniversalForwardOnly"),
        new ShaderTagId("SRPDefaultUnlit"), // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
        new ShaderTagId("LightweightForward") // Legacy shaders (do not have a gbuffer pass) are considered forward-only for backward compatibility
        };
        int forwardOnlyStencilRef = stencilData.stencilReference | (int)StencilUsage.MaterialUnlit;
        m_GBufferCopyDepthPass = new CopyDepthPass(RenderPassEvent.BeforeRenderingGbuffer + 1, m_CopyDepthMaterial, true);
        m_DeferredPass = new DeferredPass(RenderPassEvent.BeforeRenderingDeferredLights, m_DeferredLights);
        m_RenderOpaqueForwardOnlyPass = new DrawObjectsPass("Render Opaques Forward Only", forwardOnlyShaderTagIds, true, RenderPassEvent.BeforeRenderingOpaques, RenderQueueRange.opaque, data.opaqueLayerMask, forwardOnlyStencilState, forwardOnlyStencilRef);
        } // Always create this pass even in deferred because we use it for wireframe rendering in the Editor or offscreen depth texture rendering.
        m_RenderOpaqueForwardPass = new DrawObjectsPass(URPProfileId.DrawOpaqueObjects, true, RenderPassEvent.BeforeRenderingOpaques, RenderQueueRange.opaque, data.opaqueLayerMask, m_DefaultStencilState, stencilData.stencilReference);
        m_RenderOpaqueForwardWithRenderingLayersPass = new DrawObjectsWithRenderingLayersPass(URPProfileId.DrawOpaqueObjects, true, RenderPassEvent.BeforeRenderingOpaques, RenderQueueRange.opaque, data.opaqueLayerMask, m_DefaultStencilState, stencilData.stencilReference); bool copyDepthAfterTransparents = m_CopyDepthMode == CopyDepthMode.AfterTransparents;
        RenderPassEvent copyDepthEvent = copyDepthAfterTransparents ? RenderPassEvent.AfterRenderingTransparents : RenderPassEvent.AfterRenderingSkybox; m_CopyDepthPass = new CopyDepthPass(
        copyDepthEvent,
        m_CopyDepthMaterial,
        shouldClear: true,
        copyResolvedDepth: RenderingUtils.MultisampleDepthResolveSupported() && SystemInfo.supportsMultisampleAutoResolve && copyDepthAfterTransparents); // Motion vectors depend on the (copy) depth texture. Depth is reprojected to calculate motion vectors.
        m_MotionVectorPass = new MotionVectorRenderPass(copyDepthEvent + 1, m_CameraMotionVecMaterial, m_ObjectMotionVecMaterial, data.opaqueLayerMask); m_DrawSkyboxPass = new DrawSkyboxPass(RenderPassEvent.BeforeRenderingSkybox);
        m_CopyColorPass = new CopyColorPass(RenderPassEvent.AfterRenderingSkybox, m_SamplingMaterial, m_BlitMaterial);
        #if ADAPTIVE_PERFORMANCE_2_1_0_OR_NEWER
        if (needTransparencyPass)
        #endif
        {
        m_TransparentSettingsPass = new TransparentSettingsPass(RenderPassEvent.BeforeRenderingTransparents, data.shadowTransparentReceive);
        m_RenderTransparentForwardPass = new DrawObjectsPass(URPProfileId.DrawTransparentObjects, false, RenderPassEvent.BeforeRenderingTransparents, RenderQueueRange.transparent, data.transparentLayerMask, m_DefaultStencilState, stencilData.stencilReference);
        }
        m_OnRenderObjectCallbackPass = new InvokeOnRenderObjectCallbackPass(RenderPassEvent.BeforeRenderingPostProcessing); m_DrawOffscreenUIPass = new DrawScreenSpaceUIPass(RenderPassEvent.BeforeRenderingPostProcessing, true);
        m_DrawOverlayUIPass = new DrawScreenSpaceUIPass(RenderPassEvent.AfterRendering + k_AfterFinalBlitPassQueueOffset, false); // after m_FinalBlitPass {
        var postProcessParams = PostProcessParams.Create();
        postProcessParams.blitMaterial = m_BlitMaterial;
        postProcessParams.requestHDRFormat = GraphicsFormat.B10G11R11_UFloatPack32;
        var asset = UniversalRenderPipeline.asset;
        if (asset)
        postProcessParams.requestHDRFormat = UniversalRenderPipeline.MakeRenderTextureGraphicsFormat(asset.supportsHDR, asset.hdrColorBufferPrecision, false); m_PostProcessPasses = new PostProcessPasses(data.postProcessData, ref postProcessParams);
        } m_CapturePass = new CapturePass(RenderPassEvent.AfterRendering);
        m_FinalBlitPass = new FinalBlitPass(RenderPassEvent.AfterRendering + k_FinalBlitPassQueueOffset, m_BlitMaterial, m_BlitHDRMaterial); #if UNITY_EDITOR
        m_FinalDepthCopyPass = new CopyDepthPass(RenderPassEvent.AfterRendering + 9, m_CopyDepthMaterial);
        #endif // RenderTexture format depends on camera and pipeline (HDR, non HDR, etc)
        // Samples (MSAA) depend on camera and pipeline
        m_ColorBufferSystem = new RenderTargetBufferSystem("_CameraColorAttachment");
      • 最后最一些兼容操作,结束构造。完成URP基本管线的主体流程。

        supportedRenderingFeatures = new RenderingFeatures();
        
                    if (this.renderingModeRequested == RenderingMode.Deferred)
        {
        // Deferred rendering does not support MSAA.
        this.supportedRenderingFeatures.msaa = false; // Avoid legacy platforms: use vulkan instead.
        unsupportedGraphicsDeviceTypes = new GraphicsDeviceType[]
        {
        GraphicsDeviceType.OpenGLCore,
        GraphicsDeviceType.OpenGLES2,
        GraphicsDeviceType.OpenGLES3
        };
        } LensFlareCommonSRP.mergeNeeded = 0;
        LensFlareCommonSRP.maxLensFlareWithOcclusionTemporalSample = 1;
        LensFlareCommonSRP.Initialize(); m_VulkanEnablePreTransform = GraphicsSettings.HasShaderDefine(BuiltinShaderDefine.UNITY_PRETRANSFORM_TO_DISPLAY_ORIENTATION);
        }

核心渲染流程分解

URP将渲染分为五个阶段,均在Render()方法中调度:

阶段 URP实现细节
准备阶段 收集场景渲染对象与光源数据,配置相机参数与目标纹理。
几何阶段 执行视锥剔除,生成GPU顶点数据;通过ScriptableRenderContext.DrawRenderers提交绘制命令。
光照阶段 采用简化PBR模型:计算实时光源贡献,支持烘焙光照混合;动态光源采用Tile-Based优化策略(Forward+路径时)。
光栅化阶段 执行深度预通道(Depth Prepass)减少过度绘制,结合GPU Instancing优化批次处理。
后处理阶段 在独立Pass中应用抗锯齿(FXAA/TAA)、Bloom等效果,支持自定义RendererFeature扩展

性能优化关键技术

  • SRP Batcher‌:对相同Shader变体但不同材质的物体进行动态合批,显著降低SetPass Call。
  • 光照剔除优化‌:按层级控制剔除距离(layerCullDistances),对静态物体预计算遮挡数据。

URP对SRP的扩展与简化‌

  • 标准化功能封装

    • 内置轻量级PBR光照模型,取代HDRP的复杂物理模拟以提升跨平台性能。
    • 集成‌Shader Graph‌可视化工具链,降低着色器开发门槛。(后期版本内置管线也可用ShaderGraph了)
  • 跨平台适配策略
    • 动态切换渲染精度(如移动端禁用实时阴影),通过QualitySettings分级配置。
    • 资源包精简:剔除HDRP的高精度贴图与计算密集型特效,缩小运行时内存占用。

对URP的扩展

  • URP基本管线流程在UniversalRenderer的构造函数中已经定义完整。并且在其中每个阶段都给出了插入点。那么只需要在这些插入点用创建RendererFeature的方式插入自定义的Pass来影响和扩充基本的URP管线。
  • 还有一种方式用RenderPipelineManager 提供的点位插入自定义Pass。

【从UnityURP开始探索游戏渲染】专栏-直达

(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,)

【渲染管线】UnityURP[渲染路径]底层源码解析的更多相关文章

  1. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  2. Java泛型底层源码解析--ConcurrentHashMap(JDK1.7)

    1. Concurrent相关历史 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全 ...

  3. ajax 底层源码解析

    对象: XMLHttpRequest 属性:readyState请求状态,开始请求时值为0直到请求完成这个值增长到4 responseText目前为止接收到的响应体,readyState<3此属 ...

  4. Disruptor底层源码解析(九)

    架构图: 性能为什么这么牛逼: public void sendData(ByteBuffer data) { //1 在生产者发送消息的时候, 首先 需要从我们的ringBuffer里面 获取一个可 ...

  5. Maven 依赖调解源码解析(三):传递依赖,路径最近者优先

    本文是系列文章<Maven 源码解析:依赖调解是如何实现的?>第三篇,主要介绍依赖调解的第一条原则:传递依赖,路径最近者优先.本篇内容较多,也是开始源码分析的第一篇,请务必仔细阅读,否则后 ...

  6. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  7. 给jdk写注释系列之jdk1.6容器(7)-TreeMap源码解析

    TreeMap是基于红黑树结构实现的一种Map,要分析TreeMap的实现首先就要对红黑树有所了解.      要了解什么是红黑树,就要了解它的存在主要是为了解决什么问题,对比其他数据结构比如数组,链 ...

  8. jQuery2.x源码解析(DOM操作篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) jQuery这个类库最为核心重要的功能就是DOM ...

  9. FileZilla客户端源码解析

    FileZilla客户端源码解析 FTP是TCP/IP协议组的协议,有指令通路和数据通路两条通道.一般来说,FTP标准命令TCP端口号是21,Port方式数据传输端口是20. FileZilla作为p ...

  10. log4j源码解析-文件解析

    承接前文log4j源码解析,前文主要介绍了log4j的文件加载方式以及Logger对象创建.本文将在此基础上具体看下log4j是如何解析文件并输出我们所常见的日志格式 附例 文件的加载方式,我们就选举 ...

随机推荐

  1. ESP32-Arduino物联网工控(一)串口转TCP转发机项目简介

    Arduino 在物联网上配合ESP32简直就是神器! 然后现在大老板说新设备用ESP32来帮助原本没有联网功能的STM32单片机来进行调试...... 实现需求: 1.指定命令拍照,协议HTTP,并 ...

  2. Eplan是什么软件?学习Eplan软件的几个关键要点

    EPLAN是一款电气计算机辅助设计软件.我是一名Eplan软件的学习者,最近在学习这个专业的电气设计软件时,总结了一些关键要点,希望能与大家分享. 1. 熟悉软件界面和功能:首先,我们需要熟悉Epla ...

  3. 十、buildroot系统 桌面配置

    4.4.桌面控制 4.4.1.weston 文件夹路径 /common/overlays/10-weston 1.核心设置 配置 Weston 的核心设置 文件 /etc/xdg/weston/wes ...

  4. C#中DataGridView动态添加行及添加列的方法 并赋值在第一行

    http://www.jb51.net/article/72259.htm Datagridview添加列: ? 1 2 3 4 5 DataGridViewTextBoxColumn acCode ...

  5. SpringBoot3 + LangChain4j + Redis 实现大模型多轮对话及工具调用

    引言 在人工智能快速发展的当下,大语言模型(LLM)已成为构建智能应用的核心技术之一.LangChain4j 作为 Java 生态中领先的 LLM 应用开发框架,为开发者提供了强大的工具,助力构建基于 ...

  6. Codeforces Round #613 (Div. 2) ABC 题解

    A. Mezo Playing Zoma 题意:给你一个向右向左的指令,每个指令可以朝那个方向走一个单位,问你可以随意选出子序列来走,那可能到达的点有多少个. 思路:从范围上考虑就秒了.看最左和最右能 ...

  7. leetcode 151 翻转字符串

    简介 推荐使用API code class Solution { public String reverseWords(String s) { s = s.trim(); // 正则撇皮连续的空白字符 ...

  8. leetcode 53 最大自序列和

    简介 暴力只要变量两遍, 挺好的, 不过更好的应该是动态规划. 应该是最简单的动态规划了吧 code class Solution { public: int maxSubArray(vector&l ...

  9. EASY CONNECT安装使用

    最近在项目运维中遇到要连远程服务器,刚开始客户提供的VPN有问题,老是不稳定,后建议客户使用Easy Connect,记录一下. 1.EASY CONNECT的下载与安装 一般百度EASY CONNE ...

  10. 产品更新 | 数据集成ETLCloud V3.9 社区版发布,新增及优化组件近20项

    亲爱的用户们,ETLCloud社区版的新年第一次版本更新来了!本次更新版本号为V3.9,我们为大家新增及优化通用组件17项,功能优化11项,让我们一起来看看吧! 新组件,拓展无限可能 本次更新新增了一 ...