Custom SRP - Custom Render Pipeline

https://catlikecoding.com/unity/tutorials/custom-srp/custom-render-pipeline/

    1. 新建 Render Pipeline

    任何內容的渲染,最終都是要由 unity 決定在哪里,什么時候,以哪些參數進行渲染。根據目標效果的復雜程度,決定渲染的過程也很復雜。燈光,陰影,透明,圖像效果,體積效果等,必須以特定的順序渲染到最終的圖像。

    實際項目中,建議從URP定制管線。本教程依然是從頭定制管線。

    本篇教程展示基于前向渲染最簡單的 unlit 對象。之后會逐步加入光照,陰影等其它高級效果。

    1.1 項目設置

    創建3D項目。注意不要創建URP/HDRP項目。之后,可以到 Package Manager 中移除我們不需要的 package。我們只需要 Unity UI package 。

    我們的項目要使用 linear color space,在 Edit/Project Settings/Player,平臺設置區域,Other Settings中,找到 Rendering,檢查并確保切換到 linear color space。

    在場景中創建幾個對象,并為其指定材質:

    • 紅色立方體:Standard shader

    • 綠色,黃色立方體:Unlit/Color

    • 藍色球:Standard shader,并切換到透明模式,指定貼圖

    • 白色球:Unlit/Transparent

    1.2 Pipeline Asset

    我們按照URP的目錄組織方式,創建我們的目錄,并創建我們的 Pipeline Asset

    創建 CustomeRenderPipelineAsset.cs

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Rendering;[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
    public class CustomRenderPipelineAsset : RenderPipelineAsset 
    {protected override RenderPipeline CreatePipeline(){return null;}
    }
    • [CreateAssetMenu] 語義,向資產右鍵創建菜單中添加菜單項

    • RP Asset 必須派生自 RenderPipelineAsset

    • 必須實現 CreatePipeline 接口。Unity 通過調用該方法創建 RP 實例

    在Asset窗口,右鍵 Create/Rendering/Custom Render Pipeline,創建 CustomeRenderPipeline.asset

    在 Project Settings/Graphics 窗口,指定我們的管線:

    由于我們目前沒有創建管線實例,因此,整個 Unity 的渲染窗口,都不會執行任何渲染。

    1.3 Render Pipeline Instance

    創建 CustomRenderPipeline.cs

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Rendering;public class CustomRenderPipeline : RenderPipeline 
    {// RenderPipeline 定義的抽象接口,必須實現。但是由于 cameras 每幀分配內存,因此廢棄了。// 保持該方法為空即可protected override void Render(ScriptableRenderContext context, Camera[] cameras) { }// RenderPipeline 渲染入口protected override void Render(ScriptableRenderContext context, List<Camera> cameras){ }
    }

      2. 渲染

      Unity 每幀調用 RP 實例的 Render 來執行渲染:

      • ScriptableRenderContext 提供引擎渲染接口,連接 Native Engine,我們將用該對象完成渲染。

      • cameras 場景中可能會使用多個對象,Unity 根據順序,用該參數傳入。

      2.1 Camera Renderer

      每個 Camera 都需要獨立渲染,我們可以直接在 CustomRenderPipeline.Render 中實現渲染邏輯,但是渲染邏輯代碼量會很大,為了代碼結構更易維護,更清晰,我們專門建立一個類,來渲染每個攝像機。為了方便,緩存下渲染參數。

      using UnityEngine;
      using UnityEngine.Rendering;public class CameraRenderer
      {ScriptableRenderContext context;Camera camera;public void Render(ScriptableRenderContext context, Camera camera){this.context = context;this.camera = camera;}
      }

      基于 CameraRenderer,RP中的渲染代碼看起來是這樣的:

      public class CustomRenderPipeline : RenderPipeline 
      {CameraRenderer cameraRenderer = new CameraRenderer();// RenderPipeline 渲染入口protected override void Render(ScriptableRenderContext context, List<Camera> cameras){ for(int i = 0; i < cameras.Count; i++){// 用 CameraRenderer 渲染每個攝像機cameraRenderer.Render(context, cameras[i]);}}
      }

      URP 中也是定義了 CameraRenderer 來執行渲染。用這種方法,如果未來希望每個攝像機用不同的方式渲染,擴展起來會很方便,例如一個攝像機是 first-person 視口,而另一個用來渲染3D地圖,或者使用 forward / deferred 渲染。

      2.2 Draw the Skybox

      CameraRenderer 渲染指定攝像機可以“看到”的對象。為了代碼的清晰,把這些任務實現到獨立的方法 DrawVisibleGeometry 中,同時先把 Skybox 繪制出來。

      渲染時,通過方法 SetupCameraProperties 設置攝像機的VP矩陣,該矩陣可以在 shader 中,以 unity_MatrixVP 來訪問。

      public class CustomRenderPipeline : RenderPipeline 
      {CameraRenderer cameraRenderer = new CameraRenderer();// RenderPipeline 渲染入口protected override void Render(ScriptableRenderContext context, List<Camera> cameras){ for(int i = 0; i < cameras.Count; i++){// 用 CameraRenderer 渲染每個攝像機cameraRenderer.Render(context, cameras[i]);}}
      }

      現在,渲染視口將正常渲染 Skybox,并且可以旋轉攝像機看到天空盒的不同角度。

      2.3 Command Buffers

      只有我們 Submit 之后,Context 才會渲染。在這之前,我們可以進行配置,以及添加我們的渲染指令。像繪制天空這種,有專門的接口來提交渲染,但是其它的渲染,則需要通過另外的 CommandBuffer 來進行渲染。場景中其它幾何體的渲染,就是用 CommandBuffer 來渲染的。

      創建 CommandBuffer 我們可以直接創建一個 CommandBuffer,同時可以給它起名字,以在 Frame Debugger 中看到。

      分析 CommandBuffer CommandBuffer 可以注入分析,通過調用 BeginSampleEndSample 實現。分析數據可以顯示在 Profiler 和 Frame Debugger 中。

      執行 CommandBuffer CommandBuffer 執行通過調用 ExecuteCommandBuffer 。該方法將拷貝指令,不會清空它。我們后面要繼續復用該 CommandBuffer,因此我們要手動 Clear。我們把該流程定義成 ExecuteBuffer 方法。

      現在,代碼看起來是這樣

      public class CameraRenderer
      {ScriptableRenderContext context;Camera camera;const string bufferName = "Render Camera";CommandBuffer buffer = new CommandBuffer{name = bufferName};public void Render(ScriptableRenderContext context, Camera camera){this.context = context;this.camera = camera;Setup();DrawVisibleGeometry();Submit();}void Setup(){buffer.BeginSample(bufferName);ExecuteBuffer();context.SetupCameraProperties(camera);}void DrawVisibleGeometry(){context.DrawSkybox(camera);}void Submit(){buffer.EndSample(bufferName);ExecuteBuffer();context.Submit();}void ExecuteBuffer(){context.ExecuteCommandBuffer(buffer);buffer.Clear();}
      }

      2.4 Clearing the Render Target

      渲染結果最終體現在 Render Target 上,為了避免上一幀(也可能是上上幀)的圖像對當前幀產生影響,每次渲染,我們都要清理 Render Target,通過調用 CommandBuffer.ClearRenderTarget 完成。

      ClearRenderTarget 會自動封裝一個以 CommandBuffer 的名字的采樣,因此在 FrameDebugger 中會出現嵌套

      先執行 Clear,再啟用我們的 Sample ,可以避免。

      如果執行 Clear 時,還沒有執行 SetupCameraProperties,Unity 會用 Hidden/InternalClear Shader 來渲染一個矩形的方式來“清理”(Draw GL),這種方式相對很慢。我們可以先執行 SetupCameraProperties,再 Clear,這樣 Unity 會通過API層的 Clear 調用來完成清理,效率高得多。

      現在,代碼是這樣

          void Setup(){context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(bufferName);ExecuteBuffer();}

      2.5 Culling

      根據當前攝像機,裁剪出所有在視錐體內的 Renderer Component。

      • camera.TryGetCullingParameters(out ScriptableCullingParameters p)

      • CullingResults cullingResults = context.Cull(ref p);

      定義一個 Cull 方法實現裁剪,如果成功,則獲取裁剪結果:

          CullingResults cullingResults;bool Cull(){if(camera.TryGetCullingParameters(out ScriptableCullingParameters p)){cullingResults = context.Cull(ref p);return true;}return false;}

      在渲染中,執行裁剪,如果失敗,則中止渲染,直接返回。

          CullingResults cullingResults;bool Cull(){if(camera.TryGetCullingParameters(out ScriptableCullingParameters p)){cullingResults = context.Cull(ref p);return true;}return false;}

      2.6 Draw Geometry 分別繪制不透明和透明物體

      得到裁剪結果后,就可以通過 context.DrawRenderers 來渲染他們了。在調用該接口前,需要進行設置:

      • DrawingSettings

        • 通過 ShaderTagId 指定繪制 shader 的哪個 pass. 目前我們只繪制 Pass SRPDefaultUnlit

        • 通過 SortingSetings 指定如何排序????指定排序策略為 SortingCriteria.CommonOpaque,從前到后的順序

      • FilteringSettings 指示渲染哪些隊列?通過為 filteringSettings 傳入參數 RenderQueueRange,指示渲染哪些內容。

      代碼是這樣的:

          void DrawVisibleGeometry(){// 渲染不透明物體var sortingSettings = new SortingSettings(camera){ criteria = SortingCriteria.CommonOpaque };var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);context.DrawSkybox(camera);// 渲染透明物體sortingSettings.criteria = SortingCriteria.CommonTransparent;drawingSettings.sortingSettings = sortingSettings;filteringSettings.renderQueueRange = RenderQueueRange.transparent;context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);}

      現在渲染結果是這樣的:

        3. Editor Rendering 編輯器渲染

        現在我們的 RP 正確的渲染了 unlit 的材質,但是對于 standard 材質不能正確渲染。在編輯器中,我們要以特殊方式將無法渲染的材質渲染出來,并告訴用戶出錯了,這對用戶體驗很重要。

        3.1 Drawing Legacy Shaders

        如果項目過程中切到我們的 RP,場景中可能會使用一些我們不支持的 Shader。把不支持的 Shader 記錄下來,并在最后用特殊的材質將他們渲染出來,以向用戶提示這些材質需要更換。

            void DrawVisibleGeometry(){// 渲染不透明物體var sortingSettings = new SortingSettings(camera){ criteria = SortingCriteria.CommonOpaque };var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);context.DrawSkybox(camera);// 渲染透明物體sortingSettings.criteria = SortingCriteria.CommonTransparent;drawingSettings.sortingSettings = sortingSettings;filteringSettings.renderQueueRange = RenderQueueRange.transparent;context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);}

        錯誤材質

        通過調用 new Material(Shader.Find("Hidden/InternalErrorShader")); 來創建一個材質,用來渲染材質錯誤的情況。

        定義 DrawUnsupportedShaders 接口來渲染他們:

            void DrawUnsupportedShaders(){if(errorMaterial == null){errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));}var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))// 指示錯誤的材質{ overrideMaterial = errorMaterial };for (int i = 1; i < legacyShaderTagIds.Length; i++){drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);}var filteringSettings = FilteringSettings.defaultValue;context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);}

        不支持的 Standard Shader 將會以紫色顯示:

        3.2 Partial Class

        渲染錯誤材質,僅在編輯器下是有用的,在 Release 時是不需要被渲染的。得益于C# 的 partial 機制,可以讓我們將一個類的定義分散到多個文件中。因此我們把這部分代碼定義到 CameraRender.Editor.cs 中,同時用 UNITY_EDITOR 宏讓這部分代碼僅在編輯器時有效.

        3.3 Draw Gizmos

        可以通過 UnityEditor.Handles.ShouldRenderGizmos 判斷是否需要渲染 Gizmos,如果需要,則調用 context.DrawGizmos

        • 第一個參數是攝像機

        • 第二個參數指定要渲染的 Gizmos 的子集:

          • image effect 階段之前

          • image effect 階段之后

        目前我們還沒有 image effect,因此直接渲染兩者:

            partial void DrawGizmos();
        #if UNITY_EDITORpartial void DrawGizmos(){if (Handles.ShouldRenderGizmos()){context.DrawGizmos(camera, GizmoSubset.PreImageEffects);context.DrawGizmos(camera, GizmoSubset.PostImageEffects);}}
        #endif

        3.4 Draw Unity UI

        渲染 Scene 窗口 中的 UI

        如果當前攝像機是 CameraType.SceneViewn 類型,通過調用 ScriptableRenderContext.EmitWorldGeometryForSceneView(camera) 提交UI的渲染。

            partial void PrepareForSceneWindow();
        #if UNITY_EDITOR    partial void PrepareForSceneWindow(){if (camera.cameraType == CameraType.SceneView){ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);}}
        #endif

        在繪制中調用該接口:

        public void Render(ScriptableRenderContext context, Camera camera)
        {...PrepareForSceneWindow();if (!Cull())return;...
        }

          4. 多攝像機

          場景中可能有多個攝像機,需要我們正確的處理

          4.1 兩個攝像機

          每個攝像機都有Depth 屬性,默認著攝像機的 depth =-1,多個攝像機以 depth 升序進行渲染。

          我們之前為 CommandBuffer 設置的剖析時的名字,是用的固定的字符串。當有多個攝像機時,由于名字一致,導致無法將兩個攝像機的渲染區分開來。

          因此我們需要根據攝像機的名字來設置剖析的名字

          同時,在調用 BeginSample/EndSample 時,需要指定同樣的名字,否則編輯器會報 BeginSample and EndSample counts 不匹配的錯誤信息。

          由于獲取攝像機名字,會導致內存分配,因此將其包裹在 "EditorOnly" 中,以做區分

          #if UNITY_EDITOR    string SampleName { get; set; }partial void PrepareBuffer(){// 由于獲取攝像機名字,會導致內存分配,因此將其包裹在 "EditorOnly" 中,以做區分Profiler.BeginSample("Editor Only");buffer.name = SampleName = camera.name;Profiler.EndSample();}
          #elseconst string SampleName = bufferName;
          #endif
              void Setup(){context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(SampleName);ExecuteBuffer();}...void Submit(){buffer.EndSample(SampleName);ExecuteBuffer();context.Submit();}

          4.2 Layers

          可以在編輯器中設置對象的 Layer,并設置攝像機的 Culling Mask,使攝像機只能看到我們想讓它看到的東西。

          4.3 Clear Flags

          我們可以通過配置后續攝像機的 Clear Flags 來合并兩個攝像機的渲染結果。

          camera.clearFlags屬性返回枚舉類型 CameraClearFlags 。然后在 ClearRenderTarget 時,適當的使用這個屬性。

          如果使用攝像機顏色進行清空,也要正確使用攝像機顏色

              void Setup(){context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(SampleName);ExecuteBuffer();}...void Submit(){buffer.EndSample(SampleName);ExecuteBuffer();context.Submit();}

          如果修改了 Camera.ViewRect,則 Clear 將會利用 Hidden/InternalClear shader 進行清屏,效率低。

          本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
          如若轉載,請注明出處:http://www.pswp.cn/web/90064.shtml
          繁體地址,請注明出處:http://hk.pswp.cn/web/90064.shtml
          英文地址,請注明出處:http://en.pswp.cn/web/90064.shtml

          如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

          相關文章

          C語言面向對象編程

          1.內核通用鏈表一、什么是 list_head&#xff1f;list_head 是 Linux 內核中自己實現的一種 雙向循環鏈表 的結構&#xff0c;定義在 <linux/list.h> 中。它設計得非常輕巧、靈活&#xff0c;廣泛用于內核模塊、驅動、進程調度、網絡協議棧等。它的關鍵思想是&#xff1a…

          Spring Boot+Redis Zset:三步構建高可靠延遲隊列系統

          系統設計架構圖---------------- ----------------- ---------------- | | | | | | | 生產者 |------>| Redis ZSet |------>| 定時任務消費者 | | (添加延遲任務) | | (延…

          MCP vs 傳統集成方案:REST API、GraphQL、gRPC的終極對比

          MCP vs 傳統集成方案&#xff1a;REST API、GraphQL、gRPC的終極對比 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般絢爛的技術棧中&#xff0c;我是那個永不停歇的色彩收集者。 &#x1f98b; 每一個優化都是我培育的花朵&#xff0c;每一個特…

          SQL語句中鎖的使用與優化

          一、鎖機制簡介1.定義在數據庫中&#xff0c;除了傳統的計算資源&#xff08;如CPU、RAM、I/O等&#xff09;的爭用以外&#xff0c;數據也是一種供需要用戶共享的資源。如何保證數據并發訪問的一致性、有效性是所有數據庫必須解決的一個問題&#xff0c;鎖沖突也是影響數據庫并…

          Linux筆記1——簡介安裝

          操作系統給用戶一個操作界面&#xff0c;用戶通過操作界面使用系統資源Linux內核管理控制硬件&#xff0c;和硬件打交道SCSI&#xff08;盤&#xff09;sd**;第一個*表示磁盤順序&#xff0c;第二個*表示分區。例如&#xff1a;sda\sdb\sdc,sda1,sda2NVMe&#xff08;盤&#x…

          GoLand 部署第一個項目

          前言&#xff1a;Go環境部署分為兩種模式&#xff0c;一種是基于GOPATH部署&#xff08;老版本&#xff09;&#xff0c;另一種是基于Module部署&#xff08;新版本v1.11開始&#xff09;。GOPATH&#xff1a;需要配置GOPATH路徑&#xff0c;將GOPATH目錄視為工作目錄&#xff…

          Mosaic數據增強介紹

          1. 核心概念與目標Mosaic 是一種在計算機視覺&#xff08;尤其是目標檢測任務&#xff09;中非常流行且強大的數據增強技術。它最早由 Ultralytics 的 Alexey Bochkovskiy 在 YOLOv4 中提出并推廣&#xff0c;后來被廣泛應用于 YOLOv5, YOLOv7, YOLOv8 等模型以及其他目標檢測框…

          LINUX 722 邏輯卷快照

          邏輯卷快照 lvcreate -L 128M -s -n lv1-snap /dev/vg1/lv1 lvs lvscan mount -o ro /dev/vg1/lv1 /mmt/lv1-snap dmsetup ls --tree 測試 lvs /dev/vg1/lv1-snap dd if/dev/zero of/uc1/test bs1M count40 lvs /dev/vg1/lv1-snap 問題 [rootweb ~]# cd /mnt [rootweb mnt]# m…

          Springboot+vue個人健康管理系統的設計與實現

          文章目錄前言詳細視頻演示具體實現截圖后端框架SpringBoot前端框架Vue持久層框架MyBaits成功系統案例&#xff1a;代碼參考數據庫源碼獲取前言 博主介紹:CSDN特邀作者、985高校計算機專業畢業、現任某互聯網大廠高級全棧開發工程師、Gitee/掘金/華為云/阿里云/GitHub等平臺持續…

          數據結構 --棧和隊鏈

          一.棧的概念一種特殊的線性表&#xff0c;只能從固定的一端插入和刪除元素。棧中元素遵循先進后出的原則。二.模擬實現public class MyStack {public int size;public int[] array;public MyStack(){array new int[10];}private void grow(){array Arrays.copyOf(array,array…

          文檔處理控件TX Text Control系列教程:使用 C# .NET 將二維碼添加到 PDF 文檔

          PDF 文檔通常是合同、發票、證書和報告的最終格式。盡管它們在設計上是靜態的&#xff0c;但用戶現在希望能夠與它們交互、驗證信息并直接從這些文件訪問數字服務。這時&#xff0c;二維碼就變得至關重要。 PDF 文檔中的二維碼將印刷或數字內容與動態在線體驗連接起來。用戶只需…

          Google Chrome 谷歌瀏覽器全部版本集合

          Google Chrome 谷歌瀏覽器全部版本集合 Collection of all software versions of Google Chrome. 項目介紹 本項目為Google Chrome谷歌瀏覽器的全部版本集合&#xff0c;方便大家下載舊版本使用。 因為Gitee項目限制倉庫1G大小&#xff0c;所以許多谷歌瀏覽器版本無法上傳。…

          論文略讀:Towards Safer Large Language Models through Machine Unlearning

          ACL 2024大型語言模型&#xff08;LLMs&#xff09;的迅猛發展展現了其在多個領域的巨大潛力&#xff0c;這主要得益于其廣泛的預訓練知識和出色的泛化能力。然而&#xff0c;當面對問題性提示&#xff08;problematic prompts&#xff09;時&#xff0c;LLMs 仍然容易生成有害…

          深度學習 ---參數初始化以及損失函數

          深度學習 —參數初始化以及損失函數 文章目錄深度學習 ---參數初始化以及損失函數一&#xff0c;參數初始化1.1 固定值初始化1.1.1 全0初始化1.1.2 全1初始化1.3 任意常數初始化1.2 隨機初始化一&#xff0c;參數初始化 神經網絡的參數初始化是訓練深度學習模型的關鍵步驟之一…

          JS--M端事件

          移動端&#xff08;Mobile 端&#xff0c;簡稱 M 端&#xff09;開發中&#xff0c;由于設備特性&#xff08;觸摸屏、手勢操作等&#xff09;&#xff0c;需要處理一些與桌面端不同的事件。這些事件主要針對觸摸交互、手勢識別等場景 一、觸摸事件&#xff08;Touch Events&am…

          Linux網絡編程-tcp

          tcp、udp對比&#xff1a;UDP1. 特點無連接&#xff1a;無需建立連接即可發送數據。不可靠&#xff1a;不保證數據順序或完整性。低延遲&#xff1a;適合實時性要求高的場景。2. 應用場景視頻/音頻流傳輸&#xff08;如直播&#xff09;。DNS 查詢、在線游戲。TCP1. 特點面向連…

          記一次flink資源使用優化

          一.現狀分析 現有任務的資源配置如下&#xff0c;根據ui監控中Garbage Collection可以發現&#xff0c;此任務頻繁的發生GC&#xff0c;且老年代GC時間較久二.整體memory使用分析如下Framework Heap&#xff08;框架堆內存&#xff09;用于Flink框架自身的堆內存&#xff08;如…

          Vue底層換成啥了?如何更新DOM的?

          摘要&#xff1a;之前的vue是使用虛擬 DOM的&#xff0c;但是Vue 3.6 帶來了一個意義重大的更新&#xff1a; Vapor Mode 渲染模式。Vue 渲染策略的演進&#xff1a; Vue 1.x&#xff1a; 基于模板渲染策略&#xff0c;直接將模板轉換為DOM元素&#xff0c;并為每個DOM元素創建…

          0722 數據結構順序表

          Part 1.順序表的代碼一.順序表的內存申請head.h: typedef int datatype;typedef struct sqlist {//數據元素datatype data[MAXSIZE];//順序表長度int len;}*sqlist; //*sqlist的作用: //sqlist:struct Sqlist * sqlist create();head.c: sqlist create() {sqlist list (sqlist)…

          為何在 Vue 的 v-model 指令中不能使用可選鏈(Optional Chaining)?

          Vue 的 v-model 是實現組件與數據雙向綁定的核心指令之一&#xff0c;它本質上是一個語法糖&#xff0c;用于簡化對表單元素和組件 props 的同步更新。然而&#xff0c;在 Vue 3&#xff08;以及 Vue 2 的某些模式下&#xff09;&#xff0c;開發者嘗試在 v-model 中使用 JavaS…