一个月前,想开始看下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 pointreturn 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 valueint Index = GetPointIndexForInputValue(InVal);// If before the first point, return its tangent valueif (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 pointreturn 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 zeroreturn default(T);}}
}

InterpCurve

  实现就是拷的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 200CGPROGRAM// 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.0sampler2D _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 splinefloat 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 colorfixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;o.Albedo = c.rgb;// Metallic and smoothness come from slider variableso.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 initializationvoid 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 framevoid 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;}
}

VineShow

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

  附件:SplineMeshUE4.zip

移植UE4的Spline与SplineMesh组件到Unity5相关推荐

  1. Unreal4 使用spline , splinemesh组件构建赛道小例子

    本人最近几天一直想写一个赛道构建的例子,一下使用UnrealReal4中spline,splinemesh组件.具体怎么用大家去看官方的wiki就行了,这里直接贴代码`. // Fill out yo ...

  2. UE4用Spline Component蓝图生成连续SplineMesh路径模型

    Spline Component(不是SplineMesh)是UE4常用的样条曲线组件,是生成各种可视化路径.控制物体运行轨迹.生成排列组合模型等的基础. Spline可以不断增加控制点,以生成复杂的 ...

  3. UE4之Spline

    案例1 案例2 首先创建一个蓝图控件继承Actor 添加组件,一个骨骼和一个Spline 事件图标中 添加一个时间轴, 然后获取 Spline的长度乘以时间轴,得到Location和Rotation ...

  4. un4 unreal4 创建路径 曲线 管道 Spline组件 使用方法

    想创建自定义曲线,自定义的管道,路径,或者任何跟随路径的模型 找了半天,发现个 Spline 和 SplineMesh 这两个组件 找来找去没什么资料... 最后在油管找到个视频,抱歉无法搬运 在上面 ...

  5. [UE4]SplineMesh应用--鼠标轨迹动态生成Mesh

    1.蓝图新建Actor 添加组件如下:Spline,SplineMesh,Scene 添加函数-->CreateMesh(太大了,分两张图),变量MeshArr(数组) 2.设置参数 Splin ...

  6. UE4数字孪生 OD线开发浅析

    文章转自(山中传奇工作室): UE4数字孪生 OD线开发浅析 - 知乎啦啦啦,我又来啦~这次为大家带来UE4中OD线的开发流程讲解: 支持功能:支持基线(绿色那条线)的半径控制.颜色.亮度控制支持流动 ...

  7. carla创建地图(四)基于ue4创建地图

    对于carla这其实是一种没有完成的方法,因为没有办法产生xodr文件,但是其实可以通过采集的道路经纬度通过脚本生成osm文件(已经补充了相关脚本).这种方法的优势是自由度高,因为在ue4里其实什么都 ...

  8. 【UE4官方文档翻译】Unreal Engine 4 For Unity Developers (针对Unity开发者的UE4)

    ------------------------------------------------------------------ 说明:       本翻译是参考.修正.整理后的文档.如有错误,请 ...

  9. 【UE4】二十三、UE4笔试面试题

    在CSDN博客看到的,带着这些问题,多多留意,正所谓带着问题学习. 一. 1.Actor的EndPlay事件在哪些时候会调用? 2.BlueprintImplementableEvent和Bluepr ...

  10. cjson使用_LiteOS云端对接教程01-cJSON组件使用教程

    1. JSON与cJSON JSON -- 轻量级的数据格式 JSON 全称 JavaScript Object Notation,即 JS对象简谱,是一种轻量级的数据格式. 它采用完全独立于编程语言 ...

最新文章

  1. python httpstr find_Python爬虫 | BeautifulSoup使用
  2. 世界大学城空间代码_C++中命名空间的五大常见用法
  3. redis key失效的事件_Redis常见、常用的知识点
  4. windows转换U盘格式
  5. 理解 Flexbox:你需要知道的一切
  6. 卸载删除gitlab
  7. 基因重组-冲刺日志(第一天)
  8. 《Word中从正文开始设置页码》
  9. 使用Picasso加载图片的内存优化实践
  10. J-Link V9驱动
  11. 【Leetcode】644. Maximum Average Subarray II
  12. AWS OpenSearch 1.0 简单部署安装
  13. pytho自动发送微信消息
  14. 关于GPRS模块启动问题。
  15. 分享 70 个你可能会用到的正则表达式
  16. Mac终端查看CPU资源信息
  17. [ hadoop ] hadoop入门 : 组成架构.环境搭建.运行模式
  18. 程序员的奋斗史(二十七)——谈谈英语学习
  19. Linux运维02:top命令详解
  20. TimeCLR: A self-supervised contrastive learning framework for univariate time series representation

热门文章

  1. 【window操作系统下Github版本的回滚问题】
  2. 【VS2010学习笔记】【函数学习】一(MFC+OpenCV2.4.7读取摄像头之WM_TIMER消息处理函数的添加问题)
  3. Python单下划线与双下划线
  4. 基于麻雀优化的BP神经网络(分类应用) - 附代码
  5. python根据文件名列表筛选满足条件的文件
  6. 树分类、线性回归和树回归的感性认知
  7. ArcGIS 制作林地成分栅格数据
  8. 如何使用WindowsPerformanceToolKit对程序进行分析
  9. C++ error C3867 请使用 ““ 来创建指向成员的指针
  10. vue混入html,vue混入(mixins)的应用