一個月前,想開始看下UE4的源碼,剛開始以為有Ogre1.9與Ogre2.1源碼的基礎 ,應該還容易理解,把源碼下起后,發現我還是想的太簡單了,UE4的代碼量對比Ogre應該多了一個量級,畢竟Ogre只是一個渲染引擎,而UE4包含渲染,AI,網絡,編輯器等等,所以要理解UE4的源碼,應該帶着目地去看,這樣容易理解。
在看UE4提供的ContentExamples例子中,一個樹生長的例子感覺不錯,與之有關的Spline與SplineMesh組件代碼比較獨立也很容易理解,剛好拿來移植到Unity5中,熟悉UE4與Unity5這二種引擎,哈哈,如下是現在Unity5中的效果圖,其中樹苗與管子模型默認都是直的,UE4的效果就不截了,因為移植的還是差了好多,有興趣的大家可以完善,因為時間和水平,暫時只做到這里了。
如下是改寫UE4中的FInterpCurve的C#版InterpCurve,都是泛形版的,在這要說下由於C#泛型對比C++泛形缺少很多功能,如T+T這種,C++在編譯時能正確指出是否實現+。而C#就算使用泛形約束,也不能指定實現重載+的類型,然后如局部泛形實例化的功能也沒有。可以使用泛形加繼承來實現,父類泛形T,子類繼承泛形的實例化(A : T[Vector3])來完成類似功能。在這我們不使用這種,使用另外一種把相應具體類型有關的操作用委托包裝起來,這樣也可以一是用來擺脫具體操作不能使用的局限,二是用來實現C++中的局部泛形實例化。照說C#是運行時生成的泛形實例化代碼,應該比C++限制更少,可能是因為C#要求安全型等啥原因吧,只能使用功能有限的泛形約束。

public class InterpCurve<T> { public List<InterpCurveNode<T>> Points = new List<InterpCurveNode<T>>(); public bool IsLooped; public float LoopKeyOffset; public InterpCurve(int capity = 0) { for (int i = 0; i < capity; ++i) { this.Points.Add(new InterpCurveNode<T>()); } } public InterpCurveNode<T> this[int index] { get { return this.Points[index]; } set { this.Points[index] = value; } } public void SetLoopKey(float loopKey) { float lastInKey = Points[Points.Count - 1].InVal; if (loopKey < lastInKey) { IsLooped = true; LoopKeyOffset = loopKey - lastInKey; } else { IsLooped = false; } } public void ClearLoopKey() { IsLooped = false; } /// <summary> /// 計算當線曲線的切線 /// </summary> /// <param name="tension"></param> /// <param name="bStationaryEndpoints"></param> /// <param name="computeFunc"></param> /// <param name="subtract"></param> public void AutoSetTangents(float tension, bool bStationaryEndpoints, ComputeCurveTangent<T> computeFunc, Func<T, T, T> subtract) { int numPoints = Points.Count; int lastPoint = numPoints - 1; for (int index = 0; index < numPoints; ++index) { int preIndex = (index == 0) ? (IsLooped ? lastPoint : 0) : (index - 1); int nextIndex = (index == lastPoint) ? (IsLooped ? 0 : lastPoint) : (index + 1); var current = Points[index]; var pre = Points[preIndex]; var next = Points[nextIndex]; if (current.InterpMode == InterpCurveMode.CurveAuto || current.InterpMode == InterpCurveMode.CurevAutoClamped) { if (bStationaryEndpoints && (index == 0 || (index == lastPoint && !IsLooped))) { current.ArriveTangent = default(T); current.LeaveTangent = default(T); } else if (pre.IsCurveKey()) { bool bWantClamping = (current.InterpMode == InterpCurveMode.CurevAutoClamped); float prevTime = (IsLooped && index == 0) ? (current.InVal - LoopKeyOffset) : pre.InVal; float nextTime = (IsLooped && index == lastPoint) ? (current.InVal + LoopKeyOffset) : next.InVal; T Tangent = computeFunc(prevTime, pre.OutVal, current.InVal, current.OutVal, nextTime, next.OutVal, tension, bWantClamping); current.ArriveTangent = Tangent; current.LeaveTangent = Tangent; } else { current.ArriveTangent = pre.ArriveTangent; current.LeaveTangent = pre.LeaveTangent; } } else if (current.InterpMode == InterpCurveMode.Linear) { T Tangent = subtract(next.OutVal, current.OutVal); current.ArriveTangent = Tangent; current.LeaveTangent = Tangent; } else if (current.InterpMode == InterpCurveMode.Constant) { current.ArriveTangent = default(T); current.LeaveTangent = default(T); } } } /// <summary> /// 根據當前inVale對應的Node與InterpCurveMode來得到在對應Node上的值 /// </summary> /// <param name="inVal"></param> /// <param name="defalutValue"></param> /// <param name="lerp"></param> /// <param name="cubicInterp"></param> /// <returns></returns> public T Eval(float inVal, T defalutValue, Func<T, T, float, T> lerp, CubicInterp<T> cubicInterp) { int numPoints = Points.Count; int lastPoint = numPoints - 1; if (numPoints == 0) return defalutValue; int index = GetPointIndexForInputValue(inVal); if (index < 0) return this[0].OutVal; // 如果當前索引是最后索引 if (index == lastPoint) { if (!IsLooped) { return Points[lastPoint].OutVal; } else if (inVal >= Points[lastPoint].InVal + LoopKeyOffset) { // Looped spline: last point is the same as the first point return Points[0].OutVal; } } //check(Index >= 0 && ((bIsLooped && Index < NumPoints) || (!bIsLooped && Index < LastPoint))); bool bLoopSegment = (IsLooped && index == lastPoint); int nextIndex = bLoopSegment ? 0 : (index + 1); var prevPoint = Points[index]; var nextPoint = Points[nextIndex]; //當前段的總長度 float diff = bLoopSegment ? LoopKeyOffset : (nextPoint.InVal - prevPoint.InVal); if (diff > 0.0f && prevPoint.InterpMode != InterpCurveMode.Constant) { float Alpha = (inVal - prevPoint.InVal) / diff; //check(Alpha >= 0.0f && Alpha <= 1.0f); if (prevPoint.InterpMode == InterpCurveMode.Linear) { return lerp(prevPoint.OutVal, nextPoint.OutVal, Alpha); } else { return cubicInterp(prevPoint.OutVal, prevPoint.LeaveTangent, nextPoint.OutVal, nextPoint.ArriveTangent, diff, Alpha); } } else { return Points[index].OutVal; } } /// <summary> /// 因為Points可以保證所有點讓InVal從小到大排列,故使用二分查找 /// </summary> /// <param name="InValue"></param> /// <returns></returns> private int GetPointIndexForInputValue(float InValue) { int NumPoints = Points.Count; int LastPoint = NumPoints - 1; //check(NumPoints > 0); if (InValue < Points[0].InVal) { return -1; } if (InValue >= Points[LastPoint].InVal) { return LastPoint; } int MinIndex = 0; int MaxIndex = NumPoints; while (MaxIndex - MinIndex > 1) { int MidIndex = (MinIndex + MaxIndex) / 2; if (Points[MidIndex].InVal <= InValue) { MinIndex = MidIndex; } else { MaxIndex = MidIndex; } } return MinIndex; } public T EvalDerivative(float InVal, T Default, Func<T, T, float, T> subtract, CubicInterp<T> cubicInterp) { int NumPoints = Points.Count; int LastPoint = NumPoints - 1; // If no point in curve, return the Default value we passed in. if (NumPoints == 0) { return Default; } // Binary search to find index of lower bound of input value int Index = GetPointIndexForInputValue(InVal); // If before the first point, return its tangent value if (Index == -1) { return Points[0].LeaveTangent; } // If on or beyond the last point, return its tangent value. if (Index == LastPoint) { if (!IsLooped) { return Points[LastPoint].ArriveTangent; } else if (InVal >= Points[LastPoint].InVal + LoopKeyOffset) { // Looped spline: last point is the same as the first point return Points[0].ArriveTangent; } } // Somewhere within curve range - interpolate. //check(Index >= 0 && ((bIsLooped && Index < NumPoints) || (!bIsLooped && Index < LastPoint))); bool bLoopSegment = (IsLooped && Index == LastPoint); int NextIndex = bLoopSegment ? 0 : (Index + 1); var PrevPoint = Points[Index]; var NextPoint = Points[NextIndex]; float Diff = bLoopSegment ? LoopKeyOffset : (NextPoint.InVal - PrevPoint.InVal); if (Diff > 0.0f && PrevPoint.InterpMode != InterpCurveMode.Constant) { if (PrevPoint.InterpMode == InterpCurveMode.Linear) { //return (NextPoint.OutVal - PrevPoint.OutVal) / Diff; return subtract(NextPoint.OutVal, PrevPoint.OutVal, Diff); } else { float Alpha = (InVal - PrevPoint.InVal) / Diff; //check(Alpha >= 0.0f && Alpha <= 1.0f); //turn FMath::CubicInterpDerivative(PrevPoint.OutVal, PrevPoint.LeaveTangent * Diff, NextPoint.OutVal, NextPoint.ArriveTangent * Diff, Alpha) / Diff; return cubicInterp(PrevPoint.OutVal, PrevPoint.LeaveTangent, NextPoint.OutVal, NextPoint.ArriveTangent, Diff, Alpha); } } else { // Derivative of a constant is zero return default(T); } } }
實現就是拷的UE4里的邏輯,泛形主要是提供公共的一些實現,下面會放出相應附件,其中文件InterpHelp根據不同的T實現不同的邏輯,UESpline結合這二個文件來完成這個功能。
然后就是UE4里的SplineMesh這個組件,上面的Spline主要是CPU解析頂點和相應數據,而SplineMesh組件是改變模型,如果模型頂點很多,CPU不適合處理這種,故相應實現都在LocalVertexFactory.usf這個着色器代碼文件中,開始以為這個很容易,后面花的時間比我預料的多了不少,我也發現我本身的一些問題,相應矩陣算法沒搞清楚,列主序與行主序搞混等,先看如下一段代碼。

//如下頂點位置偏移右上前1 float4x4 mx = float4x4(float4(1, 0, 0, 0), float4(0, 1, 0, 0), float4(0, 0, 1, 0), float4(1, 1, 1, 1)); //矩陣左,向量右,向量與矩陣為列向量。 v.vertex = mul(transpose(mx), v.vertex); //向量左,矩陣右,則向量與矩陣為行向量。 v.vertex = mul(v.vertex, mx); //向量左,矩陣右,([1*N])*([N*X]),向量與矩陣為行向量。 float4x3 mx4x3 = float4x3(float3(1, 0, 0), float3(0, 1, 0), float3(0, 0, 1),float3(1,1,1)); v.vertex = float4(mul(v.vertex,mx4x3),v.vertex.w); //矩陣左與向量右,([X*N])*([N*1]) mx3x4 = transpose(mx4x3),表面看矩陣無意義,實際是mx4x3的列向量 float3x4 mx3x4 = float3x4(float4(1, 0, 0, 1), float4(0, 1, 0, 1), float4(0, 0, 1, 1)); v.vertex = float4(mx3x4, v.vertex), v.vertex.w); //這種錯誤,mx4x3是由行向量組成,必需放左邊才有意義 v.vertex = mul(mx4x3, v.vertex.xyz);
其中,Unity本身用的是列矩陣形式,我們定義一個矩陣向x軸移動一個單位,然后打印出來看下結果就知道了,然后把相應着色器的代碼轉換到Unity5,這段着色器代碼我並不需要改變很多,只需要在模型空間中頂點本身需要做點改變就行,那么我就直接使用Unity5中的SurfShader,提供一個vert函數改變模型空間的頂點位置,后面如MVP到屏幕,繼續PBS渲染,陰影我都接着用,如下是針對LocalVertexFactory.usf的簡單改版。

Shader "Custom/SplineMeshSurfShader" { Properties{ _Color("Color", Color) = (1,1,1,1) _MainTex("Albedo (RGB)", 2D) = "white" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 //_StartPos("StartPos",Vector) = (0, 0, 0, 1) //_StartTangent("StartTangent",Vector) = (0, 1, 0, 0) //_StartRoll("StartRoll",float) = 0.0 //_EndPos("EndPos",Vector) = (0, 0, 0, 1) //_EndTangent("EndTangent",Vector) = (0, 1, 0, 0) //_EndRoll("EndRoll",float) = 0.0 //_SplineUpDir("SplineUpDir",Vector) = (0, 1, 0, 0) //_SplineMeshMinZ("SplineMeshMinZ",float) = 0.0 //_SplineMeshScaleZ("SplineMeshScaleZ",float) = 0.0 //_SplineMeshDir("SplineMeshDir",Vector) = (0,0,1,0) //_SplineMeshX("SplineMeshX",Vector) = (1,0,0,0) //_SplineMeshY("SplineMeshY",Vector) = (0,1,0,0) } SubShader{ Tags { "RenderType" = "Opaque" } LOD 200 CGPROGRAM // Upgrade NOTE: excluded shader from OpenGL ES 2.0 because it uses non-square matrices #pragma exclude_renderers gles // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows vertex:vert // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; float3 _StartPos; float3 _StartTangent; float _StartRoll; float3 _EndPos; float3 _EndTangent; float _EndRoll; float3 _SplineUpDir; float _SplineMeshMinZ; float _SplineMeshScaleZ; float3 _SplineMeshDir; float3 _SplineMeshX; float3 _SplineMeshY; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; float3 SplineEvalPos(float3 StartPos, float3 StartTangent, float3 EndPos, float3 EndTangent, float A) { float A2 = A * A; float A3 = A2 * A; return (((2 * A3) - (3 * A2) + 1) * StartPos) + ((A3 - (2 * A2) + A) * StartTangent) + ((A3 - A2) * EndTangent) + (((-2 * A3) + (3 * A2)) * EndPos); } float3 SplineEvalDir(float3 StartPos, float3 StartTangent, float3 EndPos, float3 EndTangent, float A) { float3 C = (6 * StartPos) + (3 * StartTangent) + (3 * EndTangent) - (6 * EndPos); float3 D = (-6 * StartPos) - (4 * StartTangent) - (2 * EndTangent) + (6 * EndPos); float3 E = StartTangent; float A2 = A * A; return normalize((C * A2) + (D * A) + E); } float4x3 calcSliceTransform(float YPos) { float t = YPos * _SplineMeshScaleZ - _SplineMeshMinZ; float smoothT = smoothstep(0, 1, t); //實現基於frenet理論 //當前位置的頂點與方向根據起點與終點的設置插值 float3 SplinePos = SplineEvalPos(_StartPos, _StartTangent, _EndPos, _EndTangent, t); float3 SplineDir = SplineEvalDir(_StartPos, _StartTangent, _EndPos, _EndTangent, t); //根據SplineDir與當前_SplineUpDir 計算當前坐標系(過程類似視圖坐標系的建立) float3 BaseXVec = normalize(cross(_SplineUpDir, SplineDir)); float3 BaseYVec = normalize(cross(SplineDir, BaseXVec)); // Apply roll to frame around spline float UseRoll = lerp(_StartRoll, _EndRoll, smoothT); float SinAng, CosAng; sincos(UseRoll, SinAng, CosAng); float3 XVec = (CosAng * BaseXVec) - (SinAng * BaseYVec); float3 YVec = (CosAng * BaseYVec) + (SinAng * BaseXVec); //mul(transpose(A),B), A為正交矩陣,A由三軸組成的行向量矩陣. //簡單來看,_SplineMeshDir為x軸{1,0,0},則下面的不轉換,x軸={0,0,0},y軸=XYec,z軸=YVec //_SplineMeshDir為y軸{0,1,0},則x軸=YVec,y軸={0,0,0},z軸=XYec //_SplineMeshDir為z軸{0,0,1},則x軸=XYec,y軸=YVec,z軸={0,0,0} float3x3 SliceTransform3 = mul(transpose(float3x3(_SplineMeshDir, _SplineMeshX, _SplineMeshY)), float3x3(float3(0, 0, 0), XVec, YVec)); //SliceTransform是一個行向量組成的矩陣 float4x3 SliceTransform = float4x3(SliceTransform3[0], SliceTransform3[1], SliceTransform3[2], SplinePos); return SliceTransform; } void vert(inout appdata_full v) { float t = dot(v.vertex.xyz, _SplineMeshDir); float4x3 SliceTransform = calcSliceTransform(t); v.vertex = float4(mul(v.vertex,SliceTransform),v.vertex.w); } void surf(Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
樹的動畫就簡單了,對應UE4相應的藍圖實現,自己改寫下。

public class VineShow : MonoBehaviour { public AnimationCurve curve = null; private UESpline spline = null; private UESplineMesh splineMesh = null; // Use this for initialization void Start() { spline = GetComponentInChildren<UESpline>(); splineMesh = GetComponentInChildren<UESplineMesh>(); spline.SceneUpdate(); if (curve == null || curve.length == 0) { curve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(6, 1)); } } // Update is called once per frame void Update() { float t = Time.time % curve.keys[curve.length - 1].time; var growth = curve.Evaluate(t); float length = spline.GetSplineLenght(); var start = 0.18f * growth; float scale = Mathf.Lerp(0.5f, 3.0f, growth); UpdateMeshParam(start * length, scale, ref splineMesh.param.StartPos, ref splineMesh.param.StartTangent); UpdateMeshParam(growth * length, scale, ref splineMesh.param.EndPos, ref splineMesh.param.EndTangent); splineMesh.SetShaderParam(); } public void UpdateMeshParam(float key, float scale, ref Vector3 position, ref Vector3 direction) { var pos = this.spline.GetPosition(key); var dir = this.spline.GetDirection(key); position = splineMesh.transform.worldToLocalMatrix * InterpHelp.Vector3To4(pos); direction = (splineMesh.transform.worldToLocalMatrix * dir) * scale; } }
本來還准備完善下才發出來,但是時間太緊,沒有時間來完善這個,特此記錄下實現本文遇到的相關點供以后查找。