移植UE4的Spline与SplineMesh组件到Unity5
一个月前,想开始看下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
本来还准备完善下才发出来,但是时间太紧,没有时间来完善这个,特此记录下实现本文遇到的相关点供以后查找。
移植UE4的Spline与SplineMesh组件到Unity5的更多相关文章
- 移植UE4的模型操作到Unity中
最近在Unity上要写一个东东,功能差不多就是在Unity编辑器上的旋转,移动这些,在手机上也能比较容易操作最好,原来用Axiom3D写过一个类似的,有许多位置并不好用,刚好在研究UE4的源码,在模型 ...
- UDKtoUE4Tool-UDKUE3资源移植UE4工具
UDKtoUE4Tool UDKtoUE4Tool 是一个把UE3/UDK资源包(T3D格式)转换成UE4(T3D格式)的工具.作者Matt3D使用C#实现,未来考虑发布到Unreal Marketp ...
- STM32 移植 RT-Thread 标准版的 FinSH 组件
一.移植准备 开发版STM32F10xC8T6 准备好移植RT-Thread的移植工程 没动手移植过RT-Thread的小伙伴,可以看RT-Thread移植到stm32 我这里是将控制台信息打印到串口 ...
- [UE4]让Spline具象化
接上一个实例 一.在TestSpline蓝图,切换到蓝图构造函数Constrction Script事件中,添加如下代码: 二.别忘记个Add Spline Mesh Component设置Stati ...
- [UE4]移动相机,使用Arrow组件来标记移动位置
一.创建一个Arrow组件来标记要移动的位置(Arrow的用法之一就是用来标注坐标). 二.使用TimeLine时间轴结合插值Lerp来移动相机
- [UE4]C++代码操作SplineMesh
转自:http://aigo.iteye.com/blog/2279503 void ARaceSpline::OnConstruction(const FTransform& Transfo ...
- Unity 由Verlet数值积分产生的头发运动
先发下效果图. 参考项目unitychan-crs-master与miloyip大神的博客 爱丽丝的发丝,使用Verlet数值积分,根据旧的现在位置与上一桢位置来计算现在的位置,得到新的方向,上面的运 ...
- [UE4]虚幻4 spline组件、spline mesh组件的用法
最近公司项目需要,把这两个东东好好看了下.不得不说,这两个组件还是非常方便的,但是相关的介绍.教程却非常的少.它们概念模糊,用法奇特,我就总结下吧. 首先,先要明白spline component.s ...
- .NET程序猿 - 提升幸福感的组件一览
1.Newtonsoft.Json.net 操作JSON最简便的方式. .Net 3.5开始,Framework集成Json序列化器:JavaScriptSerializer,然而Json.net给 ...
随机推荐
- C++类库介绍
如果你有一定的C基础可能学起来比较容易些,但是学习C++的过程中又要尽量避免去使用一些C中的思想:平时还要多看一些高手写的代码,遇到问题多多思考,怎样才能把问题抽象化,以使自己头脑中有类的概念:最后别 ...
- Dynamic CRM 2013学习笔记(二十六)报表设计:Reporting Service报表 动态参数、参数多选全选、动态列、动态显示行字体颜色
上次介绍过CRM里开始报表的一些注意事项:Dynamic CRM 2013学习笔记(十五)报表入门.开发工具及注意事项,本文继续介绍报表里的一些动态效果:动态显示参数,参数是从数据库里查询出来的:参数 ...
- 拟物设计和Angular的实现 - Material Design(持续更新)
Material Design是Google最新发布的跨平台统一视觉设计语言.直接翻译是物质设计,但是我更倾向于使用"拟物设计"更为准确. 据谷歌介绍,Material Desig ...
- [ACM_搜索] ZOJ 1103 || POJ 2415 Hike on a Graph (带条件移动3盘子到同一位置的最少步数 广搜)
Description "Hike on a Graph" is a game that is played on a board on which an undirected g ...
- 51单片机-PC数据传输 温度 距离 监控系统设计
>_<:功能概述: 通过串口PC和单片机通信,可以询问单片机测得的温度,可以询问声呐测距的测量距离,同时把测量温度显示在数码管上. >_<:PC部分 这里com.cpp和com ...
- SQL Server 添加链接服务器
背景 在SQL SERVER中,如果我们查询数据库需要关联另外一台数据库中表,在这种情况下我们可以通过添加服务器链接来实现. 案列 方式1.sql server 提供了图形化界面,如下: 右键> ...
- 解决 "Windows 无法启动Mongo DB服务 错误:1067 进程意外终止"
在启动MongoDB服务时,有时会报上图所示的错误,解决方案为: 1. MongoDB安装目录\data\将此文件夹下的mongod.lock删除 2. 查看官方文档或按照上一篇安装文章检查是否设置d ...
- 测量行业(RTK)相关的小知识总结
RTK测量系统,一般需要先架设基准站(参考站),数据链模式外挂大电台(有天线大功率模式,理想范围方圆18公里以内).基准站通过收到卫星(多星)进行计算出当前的位置. 其中涉及到频率的设置,波特率,以及 ...
- eclipse 打开是报错"reload maven project has encountered a problem"
不需要删除整个 .metadata 如果删除这个代价是重新导入全部项目 D:\eclipse-workspace\.metadata\.plugins\org.eclipse.e4.workbench ...
- Leetcode 83 Remove Duplicates from Sorted List 链表
就是将链表中的重复元素去除 我的方法很简单就是如果链表的前后元素相同的话,将后一个元素删除 /** * Definition for singly-linked list. * struct List ...