1. 程式人生 > >3天學會MaxScript教程之(第二三天:編寫一個高階Max頂點動畫back到Texture的外掛)

3天學會MaxScript教程之(第二三天:編寫一個高階Max頂點動畫back到Texture的外掛)

3天學會MaxScrip的第一天在這裡:點選開啟連結

有了第一天的知識和初步認識,我們就來製作一個高階點的指令碼外掛吧。max指令碼其實非常簡單,主要知道語法就可以了,邏輯難度幾乎為零。首先來看這個外掛的效果

做動畫其實有很多種方式,除了骨骼,目標變形,物理解算外,還有BackToTextureAnimation。其實這個和GPUSkin原理類似。這個外掛的原理是將所有頂點的位置資料拍成一列,然後再把每幀的這個陣列再排到幀陣列中,這樣就能再Shader中讀取頂點的位置,然後讓頂點動畫在引擎裡還原了。最後存出來是一張這種樣子的圖,它記錄了模型位置的變化。

現在知道了原理,那麼它是如何在max中生成然後匯入引擎裡,然後引擎的shader去讀取識別然後還原的呢。下面就來一步一步製作。

首先建個文字檔案,命名為VertexAnimationTool.ms

然後用VSCode開啟。我們先建立一個max的工作視窗。

macroScript TextureAnimation category:"Texture_Vertex_Animaton" buttontext:"Vertex Animation Tools" tooltip:"Vertex Animation Tools"
(
    rollout TexMorphRollout "Vertex texture Animation Tool" 
    (
    )
    global Morph_Floater = newRolloutFloater "" 200 230 
	addRollout TexMorphRollout Morph_Floater
)
macros.run "Texture_Vertex_Animaton" "TextureAnimation" 

把指令碼拖進去就能看到如下的效果了。

如果一切正常,下面我們來繼續編寫我們的工具。我們的工具需要指明我們需要Bake的動畫幀範圍,比如0到30幀的動畫範圍我們需要把它bake到我們的貼圖裡。所以我們需要一個指認動畫範圍的UI。同時我們的工具還需要動畫的取樣密度,0~30幀這個範圍我們是把每幀都記錄下來還是每隔一幀記錄一次。然後我們還需要一個按鈕,當我們設定好後,點選這個按鈕後就開始bake工作流程並把烘焙的頂點動畫貼圖匯出。

所以我們的程式碼變成了這樣:

macroScript TextureAnimation category:"Texture_Vertex_Animaton"buttontext:"Vertex Animation Tools"
tooltip:"Vertex Animation Tools"(    rollout TexMorphRollout "Vertex texture Animation Tool"    (group"Morpher Meshes"        (spinner spinnerAnimationRangeStart "Anim Start"type:#integerrange:[0,1000000,animationRange.start] spinner spinnerAnimationRangeEnd "Anim End"type:#integerrange:[0,1000000,animationRange.end] spinner spinnerAnimationRate "Frame step Skip"type:#integerrange:[0,1000000,0] button CreateVertexAnimation "Create Vertex Animation"        )    )global Morph_Floater = newRolloutFloater ""200230    addRollout TexMorphRollout Morph_Floater)macros.run"Texture_Vertex_Animaton""TextureAnimation"

把指令碼拖進max你將會看到:

現在還沒完,我們的模型UV是用來給模型紋理對映用的,那我們的這張頂點動畫貼圖應該怎樣將儲存進貼圖裡的值取出來然後給對應的頂點呢。答案是分第二套UV,把頂點按照順序排列起來,然後在sample的時候直接就把值取出來給到頂點了,這是在為在引擎裡還原頂點動畫作考慮了。

所以我們的工具還需要給模型指認一套UV,然後把模型的UV按照一定順序排列成一排。再給工具加上Help按鈕,我們的工具UI宣告程式碼如下macroScript TextureAnimation category:"Texture_Vertex_Animaton"buttontext:"Vertex Animation Tools"tooltip:"Vertex Animation Tools"(    rollout TexMorphRollout "Vertex texture Animation Tool"    (group"Morpher Meshes"        (spinner spinnerAnimationRangeStart "Anim Start"type:#integerrange:[0,1000000,animationRange.start] spinner spinnerAnimationRangeEnd "Anim End"type:#integerrange:[0,1000000,animationRange.end] spinner spinnerAnimationRate "Frame step Skip"type:#integerrange:[0,1000000,0] dropdownlist ddlTextureCoordinate "Texture Coordinate:"items:#("2","3","4","5","6","7","8")tooltip:"用第二套UV來放頂點動畫的頂點位置"button CreateVertexAnimation "Create Vertex Animation"        )button help "help"    )global Morph_Floater = newRolloutFloater ""200230    addRollout TexMorphRollout Morph_Floater)macros.run"Texture_Vertex_Animaton""TextureAnimation"我們要完成我們的操作,首先需要有一個儲存原始模型的變數,一個儲存頂點數的變數來決定頂點陣列的長度,當然還要一個儲存模型頂點的陣列。我們還需要一個二維陣列用來儲存每幀模型所有頂點的位置。我們還需要一個數組用來儲存所有頂點對應的UV的位置。所以我們的程式碼變成了如下的樣子:macroScript TextureAnimation category:"Texture_Vertex_Animaton"buttontext:"Vertex Animation Tools"tooltip:"Vertex Animation Tools"(    rollout TexMorphRollout "Vertex texture Animation Tool"    (global originalMeshglobal copyBaseMesh global numberofVerts                                   --原始模型的頂點樹木global originalMeshVertPositions =#()global MorphTargetArrayglobal Morph_Floaterglobal internalArrayOfStaticBaseMeshes=#()--選中的模型們的一維陣列global vertexUVPosition=#()--儲存頂點模型的UV的位置global MorphNormalArray=#()global MorphVertOffsetArray=#()global MorphTargetProgressPercentage =0.0global masterMorphArray=#()--二維陣列,第一層為選中的模型,第二層為那個模型對應時間範圍內的所有snapshotglobal noMeshesArray=#(" No meshes processed"asstring)group"Morpher Meshes"        (spinner spinnerAnimationRangeStart "Anim Start"type:#integerrange:[0,1000000,animationRange.start] spinner spinnerAnimationRangeEnd "Anim End"type:#integerrange:[0,1000000,animationRange.end] spinner spinnerAnimationRate "Frame step Skip"type:#integerrange:[0,1000000,0] dropdownlist ddlTextureCoordinate "Texture Coordinate:"items:#("2","3","4","5","6","7","8")tooltip:"用第二套UV來放頂點動畫的頂點位置"button CreateVertexAnimation "Create Vertex Animation"        )button help "help"    )global Morph_Floater = newRolloutFloater ""200230    addRollout TexMorphRollout Morph_Floater)macros.run"Texture_Vertex_Animaton""TextureAnimation"

我們的工具程式碼主要分為兩部分,一部分為邏輯程式碼,一部分為UI互動程式碼。我們需要宣告兩個函式,來處理:

macroScript TextureAnimation category:"Texture_Vertex_Animaton"buttontext:"Vertex Animation Tools"tooltip:"Vertex Animation Tools"(    rollout TexMorphRollout "Vertex texture Animation Tool"    (global originalMeshglobal copyBaseMesh global numberofVerts                                   --原始模型的頂點樹木global originalMeshVertPositions =#()global MorphTargetArrayglobal Morph_Floaterglobal internalArrayOfStaticBaseMeshes=#()--選中的模型們的一維陣列global vertexUVPosition=#()--儲存頂點模型的UV的位置global MorphNormalArray=#()global MorphVertOffsetArray=#()global MorphTargetProgressPercentage =0.0global masterMorphArray=#()--二維陣列,第一層為選中的模型,第二層為那個模型對應時間範圍內的所有snapshotglobal noMeshesArray=#(" No meshes processed"asstring)group"Morpher Meshes"        (spinner spinnerAnimationRangeStart "Anim Start"type:#integerrange:[0,1000000,animationRange.start] spinner spinnerAnimationRangeEnd "Anim End"type:#integerrange:[0,1000000,animationRange.end] spinner spinnerAnimationRate "Frame step Skip"type:#integerrange:[0,1000000,0] dropdownlist ddlTextureCoordinate "Texture Coordinate:"items:#("2","3","4","5","6","7","8")tooltip:"用第二套UV來放頂點動畫的頂點位置"button CreateVertexAnimation "Create Vertex Animation"        )button help "help"
        on CreateVertexAnimation pressed do        (
        )        on help pressed do        (        )
    )global Morph_Floater = newRolloutFloater ""200230    addRollout TexMorphRollout Morph_Floater)macros.run"Texture_Vertex_Animaton""TextureAnimation"

首先我們來補全Help函式:

on help pressed do        (            S =#()            HelpString =""append S "第一步:輸入頂點動畫開始的位置。"append S "第二步:輸入頂點動畫結束的位置。"append S "第三步:輸入頂點動畫需要跳過的位置。"append S "第四步:選擇一個供頂點動畫貼圖sample的UV空間,預設使用第二套UV"append S "第五步:點選生成頂點動畫按鈕,選擇匯出路徑。"for i in S do HelpString += i +"\r\r"            messageBox HelpString        )

你將會看到如下效果:

下面我們來補全最為重要的CreateVertexAnimation函式。首先我們這個函式需要做以下幾件事情

(1)先要判斷模型資源,單位長度設定是否正確。不能有單獨的點,沒有用的點,或者說是破面啥的。

(2)把每一幀的模型SnapShot出來,然後把這一幀的頂點陣列壓入陣列。(3)建立一個原模型的克隆,然後給它分好UV和平滑組。(4)清空每一幀建立的模型。(5)把頂點陣列烘焙到紋理上然後匯出。

這個函式大概的結構是這樣的,下面我們一步一步完善它

       on CreateVertexAnimation pressed do        (/*判斷一下系統單位是否和引擎保持一致*/if (CheckUnits() ==true)do            (trywith redraw off                (                    ReInitVarriables()/*把選中的模型壓入陣列*/for i inselectiondoif CheckMesh i doappend internalArrayOfStaticBaseMeshes i                    geoConversionModelFailNamelist=#()--遍歷所有選中的需要處理的模型,把有問題的模型找出來for i in internalArrayOfStaticBaseMeshes do                    (                        CopyMesh =convertTo (snapshot i) Editable_Polyif ((getNumVerts i) != (getNumVerts CopyMesh))then                        (append geoConversionModelFailNamelist i.name                        )                        delete CopyMesh                    )--如果找到了模型,則不會進行頂點動畫的烘焙ifgeoConversionModelFailNamelist.count>0then                    (string S ="模型有問題"for i in geoConversionModelFailNamelist doappend S ("\r"+ i)                        messageBox S                    )else                    (ifinternalArrayOfStaticBaseMeshes.count>0then                        (--把選中的模型在指定範時間圍的狀態全部snapshot出來,並且把這些資料儲存在二維陣列masterMorphArray中                            MakeAndMergeSnapShots internalArrayOfStaticBaseMeshes                            SmoothCopyMesh masterMorphArray[1]                            PackVertexUVs originalMesh                            populateMorphTargetArrays()                            ClearMeshes()                            RenderTexture()                        )                    )
                ) catch                (                    messageBox "Catched Error !!!"                    ResumeEditing()                )            )            ResumeEditing()        )首先我們有個try with catch結構,為了不讓我們的程式出問題了直接就崩了,所以這裡需要有個這個。        function ReInitVarriables =        (            masterMorphArray=#()            MorphVertOffsetArray=#()            originalMesh=undefined            numberofVerts=0            internalArrayOfStaticBaseMeshes=#()            MorphTargetProgressPercentage=0.0            originalMeshVertPositions=#()            MorphNormalArray=#()            tempMorphArray=#()        )ReInitVarribles重新初始化我們的那個globle變數。        function CheckMesh selectmesh =        (            isvalidnode selectmesh andsuperclassof selectmesh ==GeometryClass        )CheckMesh是為了檢查一次模型是不是集合體。        function MakeAndMergeSnapShots ArrayOfMeshes =        (ifArrayOfMeshes.count>0do            (for i in ArrayOfMeshes do                (--把每一幀的模型全部snapshot出來,並且儲存在全域性變數masterMorphArray二維陣列中的第二維。if CheckMesh i doappend masterMorphArray (MakeSnapShotsReturnArray i)                )--如果有多個有關鍵幀的原始模型,則會把每幀的兩個模型的關鍵幀克隆attach到一起,如果沒有,下面的邏輯沒跑                masterMorphArray1Count = masterMorphArray[1].countifmasterMorphArray.count>1do                (for i =2tomasterMorphArray.countdo                    (for framecount =1to masterMorphArray1Count do                        (                            currentMasterObject = masterMorphArray[1][framecount]                            attachMeshes currentMasterObject masterMorphArray[i][framecount]                        )                    )                )                masterMorphArray = masterMorphArray[1]            )        )

這裡是給每一幀都建立一個snapshot。

然後把snapshot的頂點壓入陣列。        function SmoothCopyMesh Meshes =        (            OrgName =Meshes.name            originalMesh =attime0snapshot MeshesoriginalMesh.name= OrgName +"_MorphUV"+ (targetMorphUV asstring) +"_MorphExport"            s =smooth()s.smoothingBits=1addModifier originalMesh s
            numberofVerts =getNumVerts originalMesh            originalMeshVertPositions =#()--清空位置陣列,它是定義在全域性的ifClassOforiginalMesh.baseobject==Editable_Polythen            (for i =1 numberofVerts do                (append originalMeshVertPositions (incoordsys world polyop.getVert originalMesh i)                )            )else            (for i =1to numberofVerts do                (append originalMeshVertPositions (incoordsys world getVert originalMesh i)                )            )        )瞭解這些核心函式後,我將我整個指令碼的程式碼奉上:macroScript TextureAnimation category:"Texture_Vertex_Animaton"buttontext:"Vertex Animation Tools"tooltip:"Vertex Animation Tools"(    ResumeEditing()escapeEnable=trueglobal targetMorphUV =2
    rollout TexMorphRollout "Vertex texture Animation Tool"    (global originalMeshglobal copyBaseMesh global numberofVerts                                   --原始模型的頂點樹木global originalMeshVertPositions =#()global MorphTargetArrayglobal Morph_Floaterglobal internalArrayOfStaticBaseMeshes=#()--選中的模型們的一維陣列global vertexUVPosition=#()--儲存頂點模型的UV的位置global MorphNormalArray=#()global MorphVertOffsetArray=#()global MorphTargetProgressPercentage =0.0global masterMorphArray=#()--二維陣列,第一層為選中的模型,第二層為那個模型對應時間範圍內的所有snapshotglobal noMeshesArray=#(" No meshes processed"asstring)group"Morpher Meshes"        (spinner spinnerAnimationRangeStart "Anim Start"type:#integerrange:[0,1000000,animationRange.start] spinner spinnerAnimationRangeEnd "Anim End"type:#integerrange:[0,1000000,animationRange.end] spinner spinnerAnimationRate "Frame step Skip"type:#integerrange:[0,1000000,0] dropdownlist ddlTextureCoordinate "Texture Coordinate:"items:#("2","3","4","5","6","7","8")tooltip:"用第二套UV來放頂點動畫的頂點位置"button CreateVertexAnimation "Create Vertex Animation"        )button help "help"
/*******************************************************************************功能函式**************************************************************************************/
        function CheckUnits =        (if(units.SystemType!=#Centimeters)then            (                messageBox "請校準好Max的系統單位,保持與Unity中的一致"returnfalse            )else            (returntrue            )        )
        function CheckMesh selectmesh =        (            isvalidnode selectmesh andsuperclassof selectmesh ==GeometryClass        )
        function ClearMeshes =        (if isValidNode masterMorphArray[1] andmasterMorphArray.count>0do            (                delete masterMorphArray                masterMorphArray=#()            )        )
        function updateProgAmount i myArrayCount =        (            MorphTargetProgressPercentage=((i asfloat/myArrayCount asfloat)*100.0)            progressUpdate MorphTargetProgressPercentage   if MorphTargetProgressPercentage ==100.0do progressEnd()if getProgressCancel() ==truedo            (                progressEnd()            ) -- returns true if cancelled        )
        function ReInitVarriables =        (            masterMorphArray=#()            MorphVertOffsetArray=#()            originalMesh=undefined            numberofVerts=0            internalArrayOfStaticBaseMeshes=#()            MorphTargetProgressPercentage=0.0            originalMeshVertPositions=#()            MorphNormalArray=#()            tempMorphArray=#()        )
        function MakeSnapShotsReturnArray MeshToSnapShot =        (            progressStart "Create morph targets"            FrameArray =#()            NumOfFrames =floor (spinnerAnimationRangeEnd.value-spinnerAnimationRangeStart.value)for i =0to NumOfFrames by (spinnerAnimationRate.value+1)do            (                newtime =spinnerAnimationRangeStart.value