一个月前,想开始看下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 = )
{
for (int i = ; 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 - ].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 - ;
for (int index = ; index < numPoints; ++index)
{
int preIndex = (index == ) ? (IsLooped ? lastPoint : ) : (index - );
int nextIndex = (index == lastPoint) ? (IsLooped ? : lastPoint) : (index + ); var current = Points[index];
var pre = Points[preIndex];
var next = Points[nextIndex]; if (current.InterpMode == InterpCurveMode.CurveAuto
|| current.InterpMode == InterpCurveMode.CurevAutoClamped)
{
if (bStationaryEndpoints && (index == ||
(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 == ) ? (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 - ; if (numPoints == )
return defalutValue;
int index = GetPointIndexForInputValue(inVal);
if (index < )
return this[].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[].OutVal;
}
} //check(Index >= 0 && ((bIsLooped && Index < NumPoints) || (!bIsLooped && Index < LastPoint)));
bool bLoopSegment = (IsLooped && index == lastPoint);
int nextIndex = bLoopSegment ? : (index + ); 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 - ;
//check(NumPoints > 0);
if (InValue < Points[].InVal)
{
return -;
} if (InValue >= Points[LastPoint].InVal)
{
return LastPoint;
} int MinIndex = ;
int MaxIndex = NumPoints; while (MaxIndex - MinIndex > )
{
int MidIndex = (MinIndex + MaxIndex) / ; 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 - ; // If no point in curve, return the Default value we passed in.
if (NumPoints == )
{
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 == -)
{
return Points[].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[].ArriveTangent;
}
} // Somewhere within curve range - interpolate.
//check(Index >= 0 && ((bIsLooped && Index < NumPoints) || (!bIsLooped && Index < LastPoint)));
bool bLoopSegment = (IsLooped && Index == LastPoint);
int NextIndex = bLoopSegment ? : (Index + ); 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);
}
}
}

InterpCurve

  实现就是拷的UE4里的逻辑,泛形主要是提供公共的一些实现,下面会放出相应附件,其中文件InterpHelp根据不同的T实现不同的逻辑,UESpline结合这二个文件来完成这个功能。

  然后就是UE4里的SplineMesh这个组件,上面的Spline主要是CPU解析顶点和相应数据,而SplineMesh组件是改变模型,如果模型顶点很多,CPU不适合处理这种,故相应实现都在LocalVertexFactory.usf这个着色器代码文件中,开始以为这个很容易,后面花的时间比我预料的多了不少,我也发现我本身的一些问题,相应矩阵算法没搞清楚,列主序与行主序搞混等,先看如下一段代码。

//如下顶点位置偏移右上前1
float4x4 mx = float4x4(float4(, , , ), float4(, , , ), float4(, , , ), float4(, , , ));
//矩阵左,向量右,向量与矩阵为列向量。
v.vertex = mul(transpose(mx), v.vertex);
//向量左,矩阵右,则向量与矩阵为行向量。
v.vertex = mul(v.vertex, mx); //向量左,矩阵右,([1*N])*([N*X]),向量与矩阵为行向量。
float4x3 mx4x3 = float4x3(float3(, , ), float3(, , ), float3(, , ),float3(,,));
v.vertex = float4(mul(v.vertex,mx4x3),v.vertex.w);
//矩阵左与向量右,([X*N])*([N*1]) mx3x4 = transpose(mx4x3),表面看矩阵无意义,实际是mx4x3的列向量
float3x4 mx3x4 = float3x4(float4(, , , ), float4(, , , ), float4(, , , ));
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) = (,,,)
_MainTex("Albedo (RGB)", 2D) = "white" {}
_Glossiness("Smoothness", Range(,)) = 0.5
_Metallic("Metallic", Range(,)) = 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 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 ((( * A3) - ( * A2) + ) * StartPos) + ((A3 - ( * A2) + A) * StartTangent) + ((A3 - A2) * EndTangent) + (((- * A3) + ( * A2)) * EndPos);
} float3 SplineEvalDir(float3 StartPos, float3 StartTangent, float3 EndPos, float3 EndTangent, float A)
{
float3 C = ( * StartPos) + ( * StartTangent) + ( * EndTangent) - ( * EndPos);
float3 D = (- * StartPos) - ( * StartTangent) - ( * EndTangent) + ( * 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(, , 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(, , ), XVec, YVec));
//SliceTransform是一个行向量组成的矩阵
float4x3 SliceTransform = float4x3(SliceTransform3[], SliceTransform3[], SliceTransform3[], 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"
}

SplineMesh

  树的动画就简单了,对应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 == )
{
curve = new AnimationCurve(new Keyframe(, ), new Keyframe(, ));
}
} // Update is called once per frame
void Update()
{
float t = Time.time % curve.keys[curve.length - ].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;
}
}

VineShow

  本来还准备完善下才发出来,但是时间太紧,没有时间来完善这个,特此记录下实现本文遇到的相关点供以后查找。

  附件:SplineMeshUE4.zip

移植UE4的Spline与SplineMesh组件到Unity5的更多相关文章

  1. 移植UE4的模型操作到Unity中

    最近在Unity上要写一个东东,功能差不多就是在Unity编辑器上的旋转,移动这些,在手机上也能比较容易操作最好,原来用Axiom3D写过一个类似的,有许多位置并不好用,刚好在研究UE4的源码,在模型 ...

  2. UDKtoUE4Tool-UDKUE3资源移植UE4工具

    UDKtoUE4Tool UDKtoUE4Tool 是一个把UE3/UDK资源包(T3D格式)转换成UE4(T3D格式)的工具.作者Matt3D使用C#实现,未来考虑发布到Unreal Marketp ...

  3. STM32 移植 RT-Thread 标准版的 FinSH 组件

    一.移植准备 开发版STM32F10xC8T6 准备好移植RT-Thread的移植工程 没动手移植过RT-Thread的小伙伴,可以看RT-Thread移植到stm32 我这里是将控制台信息打印到串口 ...

  4. [UE4]让Spline具象化

    接上一个实例 一.在TestSpline蓝图,切换到蓝图构造函数Constrction Script事件中,添加如下代码: 二.别忘记个Add Spline Mesh Component设置Stati ...

  5. [UE4]移动相机,使用Arrow组件来标记移动位置

    一.创建一个Arrow组件来标记要移动的位置(Arrow的用法之一就是用来标注坐标). 二.使用TimeLine时间轴结合插值Lerp来移动相机

  6. [UE4]C++代码操作SplineMesh

    转自:http://aigo.iteye.com/blog/2279503 void ARaceSpline::OnConstruction(const FTransform& Transfo ...

  7. Unity 由Verlet数值积分产生的头发运动

    先发下效果图. 参考项目unitychan-crs-master与miloyip大神的博客 爱丽丝的发丝,使用Verlet数值积分,根据旧的现在位置与上一桢位置来计算现在的位置,得到新的方向,上面的运 ...

  8. [UE4]虚幻4 spline组件、spline mesh组件的用法

    最近公司项目需要,把这两个东东好好看了下.不得不说,这两个组件还是非常方便的,但是相关的介绍.教程却非常的少.它们概念模糊,用法奇特,我就总结下吧. 首先,先要明白spline component.s ...

  9. .NET程序猿 - 提升幸福感的组件一览

    1.Newtonsoft.Json.net 操作JSON最简便的方式.  .Net 3.5开始,Framework集成Json序列化器:JavaScriptSerializer,然而Json.net给 ...

随机推荐

  1. Android的构造器

    当Java代码创建一个View实例,或根据XML布局文件加载并构建界面时将需要调用该构造器1.onFinishInflate():这是一个回调方法,当应用从XML布局文件加载该组件并利用它来构建界面之 ...

  2. 一个通用的makefile

    # ESDK the makefile setting file - chenwg@20131014 # you can modify "PC = 1" such as " ...

  3. C++混合编程之idlcpp教程Lua篇(3)

    上一篇 C++混合编程之idlcpp教程Lua篇(2) 是一个 hello world 的例子,仅仅涉及了静态函数的调用.这一篇会有新的内容. 与LuaTutorial0相似,工程LuaTutoria ...

  4. We have detected that MySQL products under the Commercial license are installed. In order to proceed with this GPL installation these Commercial

    下载了MySQL 5.6.15,在安装时,出现了下面的提示信息: 按提示信息的要求单击“是”,结果安装就无法进行下去. 从提示信息上看,意思是指电脑中原来安装有商业版的许可,现在要转换成为GPL许可. ...

  5. MySQL5.6 报错1067

    http://www.cnblogs.com/lixiaolun/p/5303687.html

  6. 分分钟用上C#中的委托和事件之窗体篇

    上次以鸿门宴的例子写了一篇名为<分分钟用上C#中的委托和事件>的博文,旨在帮助C#初学者迈过委托和事件这道坎,能够用最快的速度掌握如何使用它们.如果觉得意犹未尽,或者仍然不知如何在实际应用 ...

  7. Windows上一步一步CoreRun

    起步只有3个文件:CoreRun.exe, coreclr.dll, mscorlib.dll, HelloWorld.exe 运行命令:CoreRun HelloWorld.exe 出错: Asse ...

  8. 深入理解java虚拟机【垃圾回收算法】

    Java虚拟机的内存区域中,程序计数器.虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭:栈中的栈帧随着方法的进入和退出而进行入栈和出栈操作,每个栈帧中分配多少内存基本上是在类结构 ...

  9. [WinAPI] API 12 [获取程序所在的目录、程序模块路径,获取和设置当前目录]

    Windows系统提供一组API实现对程序运行时相关目录的获取和设置.用户可以使用GetCurrentDirectory和SetCurrentDirectory获取程序的当前目录,获取模块的路径使用G ...

  10. Gradle里配置jetty实现静态资源的热部署

    通过Gradle我们可以很方便的使用内置jetty启动我们的web程序,在本地进行调试.但是在使用的过程中,我发现了几个问题,导致本地调试的效率大受影响. 如果使用gradle jettyRun启动j ...