UE4 动画系统
动画基础概念
3D模型动画的基本原理是让模型中各顶点的位置随时间变化。主要种类有Morph(变形)动画,关节动画和骨骼蒙皮动画(SkinnedMesh)。
(自己搭的blog被黑了,只有本地备份,太伤了,所以文章会有一些格式问题)
从动画数据的角度来说,三者一般都采用关键帧技术,即只给出关键帧的数据,其他帧的数据使用插值得到。但由于这三种技术的不同,关键帧的数据是不一样的。
- Morph(渐变,变形)动画是直接指定动画每一帧的顶点位置,其动画关键中存储的是Mesh所有顶点在关键帧对应时刻的位置。
- 关节动画的模型不是一个整体的Mesh,而是分成很多部分(Mesh),通过一个父子层次结构将这些分散的Mesh组织在一起,父Mesh带动其下子Mesh的运动,各Mesh中的顶点坐标定义在自己的坐标系中,这样各个Mesh是作为一个整体参与运动的。动画帧中设置各子Mesh相对于其父Mesh的变换(主要是旋转,当然也可包括移动和缩放),通过子到父,一级级的变换累加(当然从技术上,如果是矩阵操作是累乘)得到该Mesh在整个动画模型所在的坐标空间中的变换(从本文的视角来说就是世界坐标系了,下同),从而确定每个Mesh在世界坐标系中的位置和方向,然后以Mesh为单位渲染即可。关节动画的问题是,各部分Mesh中的顶点是固定在其Mesh坐标系中的,这样在两个Mesh结合处就可能产生裂缝。
- 第三类就是骨骼蒙皮动画即SkinnedMesh了,骨骼蒙皮动画的出现解决了关节动画的裂缝问题,而且效果非常酷,发明这个算法的人一定是个天才,因为SkinnedMesh的原理简单的难以置信,而效果却那么好。骨骼动画的基本原理可概括为:在骨骼控制下,通过顶点混合动态计算蒙皮网格的顶点,而骨骼的运动相对于其父骨骼,并由动画关键帧数据驱动。一个骨骼动画通常包括骨骼层次结构数据,网格(Mesh)数据,网格蒙皮数据(skin info)和骨骼的动画(关键帧)数据。下面将具体分析。
SkinnedMesh中文一般称作骨骼蒙皮动画,正如其名,这种动画中包含骨骼(Bone)和蒙皮(Skinned Mesh)两个部分,Bone的层次结构和关节动画类似,Mesh则和关节动画不同:关节动画中是使用多个分散的Mesh,而Skinned Mesh中Mesh是一个整体,也就是说只有一个Mesh,实际上如果没有骨骼让Mesh运动变形,Mesh就和静态模型一样了。
Skinned Mesh技术的精华在于蒙皮,所谓的皮并不是模型的贴图(也许会有人这么想过吧),而是Mesh本身,蒙皮是指将Mesh中的顶点附着(绑定)在骨骼之上,而且每个顶点可以被多个骨骼所控制,这样在关节处的顶点由于同时受到父子骨骼的拉扯而改变位置就消除了裂缝。
Skinned Mesh这个词从字面上理解似乎是有皮的模型,哦,如果贴图是皮,那么普通静态模型不也都有吗?所以我觉得应该理解为具有蒙皮信息的Mesh或可当做皮肤用的Mesh,这个皮肤就是Mesh。
而为了有皮肤功能,Mesh还需要蒙皮信息,即Skin数据,没有Skin数据就是一个普通的静态Mesh了。
Skin数据决定顶点如何绑定到骨骼上。顶点的Skin数据包括顶点受哪些骨骼影响以及这些骨骼影响该顶点时的权重(weight),另外对于每块骨骼还需要骨骼偏移矩阵(BoneOffsetMatrix)用来将顶点从Mesh空间变换到骨骼空间。在本文中,提到骨骼动画中的Mesh特指这个皮肤Mesh,提到模型是指骨骼动画模型整体。骨骼控制蒙皮运动,而骨骼本身的运动呢?当然是动画数据了。
每个关键帧中包含时间和骨骼运动信息,运动信息可以用一个矩阵直接表示骨骼新的变换,也可用四元数表示骨骼的旋转,也可以随便自己定义什么只要能让骨骼动就行。除了使用编辑设定好的动画帧数据,也可以使用物理计算对骨骼进行实时控制。
-
线性混合蒙皮算法
在骨骼动画的蒙皮算法中,出现最早、最经典,也是应用最为广泛的算法是线性混合蒙皮算法。
根据骨骼动画的基本原理,动画模型之所以能够运动,是由于其骨骼带动了蒙在骨骼之上的皮肤一同动作,实现了动画效果。因此,因首先设置好模型骨架以及各骨骼之间的关联性,当运动数据到来时,计算皮肤顶点的新位置,就可以完成模型的运动。
黑色与白色的皮肤顶点分别与其相同颜色的骨骼相绑定。
方框里的皮肤顶点离两个骨骼关节最近,它们同时受到两个骨骼关节的影响。当骨架运动的时候,对于这些受多个骨骼共同影响的皮肤顶点,我们要计算它们变换后的位置信息,即找到皮肤网格自动变形后的方法,传统一般采用线性混合蒙皮算法。线性混合蒙皮算法是由 Lander 最早提出并实现的一种柔性绑定算法。Lander 利用线性混合蒙皮算法实现了人体上臂的动画,解决了之前的刚性绑定算法在关节处的失真问题。该算法的基本原理可以用下列公式表示
V表示顶点变换前的世界坐标系中的位置,V'表示顶点变换后的位置,i 表示同时影响该顶点的骨骼数量,一般取 2-4 之间的值。W_i表示第 i 个骨骼对该顶点的施加的影响权重,取 0-1 之间的值,M_i表示在模型初始参考姿势下,与顶点相关的第 i 个骨骼由本地坐标转换为世界坐标的转换矩阵(即骨骼变换的绝对矩阵),通过矩阵M_i能将骨骼 i 从初始位置转换到动画数据来到时的新位置上.
综上所述,线性混合蒙皮算法即是求得一个顶点在每个骨骼影响下的一系列新的位置,然后对这些位置数据进行加权平均计算得到最后的结果。
在线性混合蒙皮算法中,顶点的新位置 V′是通过其初始位置V 乘以一个矩阵 C 得到,这个矩阵被称为变换矩阵。
我们可以使用 OFFSET(偏移)的 3 个量来表示子关节相对父关节的偏移量;用 CHANNELS 来表示关节旋转通道数量和旋转顺序,其中根关节有6个通道,其他关节有3个通道,与根关节相比少了XYZ的位置(position)信息,这是因为其他关节都可以根据相对其于父关节的偏移量计算坐标位置。
运动数据对应的是骨架信息中各关节点的层次数据,即CHANNELS 中 Zrotation Xrotation Yrotation 顺序的数据。对于子关节来说,平移信息存储在骨架信息的 OFFSET 中,旋转信息则来自于运动数据部分;对于根关节来说,平移量是 OFFSET 和运动数据部分中定义的平移量之和。要得到蒙皮所需的绝对变换矩阵,首先需要根据 BVH 文件中的旋转数据分别创建三个方向轴(Y 轴,X 轴,Z 轴)对应的旋转矩阵,然后将它们按顺序相乘得到矩阵R (也称相对矩阵):
绝对变换矩阵是由关节的相对矩阵乘上它的父关节的绝对矩阵得到的,其中,根关节的绝对变换矩阵就是它的相对矩阵。因此,根据骨架各关节之间的关系,可以计算出每一个关节的绝对变换矩阵,用来将关节的本地坐标变换为世界坐标。
在骨骼动画中,一般使用正向运动学和逆向运动学将运动数据作用到动画模型上。正向运动学是从模型的根节点开始(对人体模型来说,髋关节就是根节点),根据骨骼的拓展顺序,逐个计算各关节在动画数据下的偏移和旋转量,直至到达末端节点为止。
蒙皮算法中变换矩阵的计算实际上就是插值的计算。对于动画中发生动作的骨骼,应根据该骨骼的数据找出其前后两个关键帧,根据时间差进行插值计算。对于使用四元数表示旋转的情况,可以使用四元数线性插值或四元数球面插值。将插值得到的四元数转换成变换矩阵(旋转矩阵部分),最后更新骨骼之间的层次关系,计算出各个骨骼的绝对变换矩阵,完成顶点的新位置计算。
2.线性混合蒙皮算法的缺陷
线性混合蒙皮算法需要手工设置骨骼对皮肤顶点影响的权重值,这项工作繁琐耗时,并且要求设计者对模型的构成要比较熟悉。不过随着建模软件的日趋完善,现在已经有很多建模软件简化了权重设置这项工作,比如常用的 3DMax、Maya 等大型 3D 建模软件,为骨骼与皮肤的绑定提供了很多便捷的操作功能,能大大节省该工作的时间,提高工作效率。
另外,线性混合蒙皮算法因其原理为线性计算,有一个无法克服的缺陷:对于比较灵活的关节(如肩膀),当关节处旋转角度很大时,会产生皮肤失真的结果,比如皮肤的塌陷、扭曲打结(裹糖纸)等现象。
v1和v2是皮肤顶点v分别受两端骨骼单独作用时变换的位置,v点变换后的坐标是v1和v2的线性加权平均。因为v1和v2都是在世界坐标系下变换得到的顶点坐标,直接的线性加权平均导致混合后的新顶点损失了v在关节局部坐标系下的向量长度信息,所以导致了皮肤塌陷的现象。除了产生皮肤塌陷的失真问题,发生更大角度的旋转的关节区域的皮肤还会出现扭曲现象(即“裹糖纸”现象)。我们假设人体模型的肩部关节绕 x 轴旋转180 度,那么在上述公式中骨骼变换的绝对矩阵可以写为:
也就是说,我们可以将骨骼变换的绝对矩阵进行混合操作,再与顶点位置V相乘得到新顶点的位置。可以看到,即使所有的变换矩阵Mi是刚性的,括号内也是一个线性的变换过程,得到的结果不一定是刚性的转换矩阵(比如几个正交矩阵的线性组合不一定还是正交矩阵),这便可以解释线性混合蒙皮在关节旋转角度过大时出现皮肤塌陷或扭曲的现象了:在骨骼的旋转变换过程中出现了我们不需要的缩放和平移信息,而骨架只提供运动信息,没有对皮肤的体积进行很好的控制和支撑,因此皮肤可以任意内陷。在关节旋转超过 60 度时,这种内陷尤其明显,这就是线性混合蒙皮中皮肤失真的主要原因。
- World Space:世界坐标系
- local-space :局部空间假定 骨骼变换相对于其父骨骼
- Component Space:组件空间假定骨骼变换相对于 SkeletalMeshComponent
- Bone Space:以EffectorSpaceBoneName配置的骨骼为原点的坐标系。
Poses 的变换可以在localSpace和Component Space都可以,一般来讲,在动画蓝图中使用姿势时,它们都位于局部空间中。但是,特定混合 节点和所有SkeletalControl都在组件空间中运算。这意味着,在将输入姿势传入这些类型的节点前, 需要变换这些姿势。如果输入姿势来自输出局部空间姿势的某个节点, 必须先将该姿势转换到正确的空间,然后SkeletalControl才能对它执行运算。 在执行运算后,必须将转换后的姿势重新转换回 局部空间,以便为其他混合或“结果(Result)”引脚提供输入。
一.UE4动画基本数据结构解析
骨架网格体由两部分构成:构成骨架网格体表面的一组多边形,用于是使多边形顶点产生动画的一组层次化的关联骨骼。USkeletalMeshComponent继承自USkinnedMeshComponent,也支持骨骼蒙皮的基本组件。
1.1USkeleton
对骨骼的理解
我们想要理解骨骼,首先先看看静态模型吧,静态模型没有骨骼,我们在世界坐标系中放置静态模型时,只要指定模型自身坐标系在世界坐标系中的位置和朝向。
在骨骼动画中,不是把Mesh直接放到世界坐标系中,Mesh只是作为Skin使用的,是依附于骨骼的,真正决定模型在世界坐标系中的位置和朝向的是骨骼。
在渲染静态模型时,由于模型的顶点都是定义在模型坐标系中的,所以各顶点只要经过模型坐标系到世界坐标系的变换后就可进行渲染。
而对于骨骼动画,我们设置模型的位置和朝向,实际是在设置根骨骼的位置和朝向,然后根据骨骼层次结构中父子骨骼之间的变换关系计算出各个骨骼的位置和朝向,然后根据骨骼对Mesh中顶点的绑定计算出顶点在世界坐标系中的坐标,从而对顶点进行渲染。
要记住,在骨骼动画中,骨骼才是模型主体,Mesh不过是一层皮,一件衣服。
如何理解骨骼?请看第二个观念:骨骼可理解为一个坐标空间。
在一些文章中往往会提到关节和骨骼,那么关节是什么?骨骼又是什么?下图是一个手臂的骨骼层次的示例。
骨骼只是一个形象的说法,实际上骨骼可理解为一个坐标空间,关节可理解为骨骼坐标空间的原点。关节的位置由它在父骨骼坐标空间中的位置描述。上图中有三块骨骼,分别是上臂,前臂和两个手指。Clavicle(锁骨)是一个关节,它是上臂的原点,同样肘关节(elbow joint)是前臂的原点,腕关节(wrist)是手指骨骼的原点。关节既决定了骨骼空间的位置,又是骨骼空间的旋转和缩放中心。为什么用一个4X4矩阵就可以表达一个骨骼,因为4X4矩阵中含有的平移分量决定了关节的位置,旋转和缩放分量决定了骨骼空间的旋转和缩放。我们来看前臂这个骨骼,其原点位置是位于上臂上某处的,对于上臂来说,它知道自己的坐标空间某处(即肘关节所在的位置)有一个子空间,那就是前臂,至于前臂里面是啥就不考虑了。当前臂绕肘关节旋转时,实际是前臂坐标空间在旋转,从而其中包含的子空间也在绕肘关节旋转,在这个例子中是finger骨骼。和实际生物骨骼不同的是,我们这里的骨骼并没有实质的骨头,所以前臂旋转时,他自己没啥可转的,改变的只是坐标空间的朝向。你可以说上图的蓝线在转,但实际蓝线并不存在,蓝线只是画上去表示骨骼之间关系的,真正转的是骨骼空间,我们能看到在转的是wrist joint,也就是两个finger骨骼的坐标空间,因为他们是子空间,会跟随父空间运动,就好比人跟着地球转一样。
骨骼就是坐标空间,骨骼层次就是嵌套的坐标空间。关节只是描述骨骼的位置即骨骼自己的坐标空间原点在其父空间中的位置,绕关节旋转是指骨骼坐标空间(包括所有子空间)自身的旋转,如此理解足矣。但还有两个可能的疑问,一是骨骼的长度问题,由于骨骼是坐标空间,没有所谓的长度和宽度的限制,我们看到的长度一方面是蒙皮后的结果,另一方面子骨骼的原点(也就是关节)的位置往往决定了视觉上父骨骼的长度,比如这里upper arm线段的长度实际是由elbow joint的位置决定的。第二个问题,手指的那个端点是啥啊?实际上在我们的例子中手指没有子骨骼,所以那个端点并不存在:)那是为了方便演示画上去的。实际问题中总有最下层的骨骼,他们不能决定其他骨骼了,他们的作用只剩下控制Mesh顶点。对了,那么手指的长度如何确定?我们看到的长度应该是由手指部分的顶点和蒙皮决定的,也就是由Mesh中属于手指的那些点离腕关节的距离决定。
经过一段长篇大论,我们终于清楚骨骼和骨骼层次是啥了,但是为什么要将骨骼组织成层次结构呢?答案是为了做动画方便,设想如果只有一块骨骼,那么让他动起来就太简单了,动画每一帧直接指定他的位置即可。如果是n块呢?通过组成一个层次结构,就可以通过父骨骼控制子骨骼的运动,牵一发而动全身,改变某骨骼时并不需要设置其下子骨骼的位置,子骨骼的位置会通过计算自动得到。上文已经说过,父子骨骼之间的关系可以理解为,子骨骼位于父骨骼的坐标系中。
我们知道物体在坐标系中可以做平移变换,以及自身的旋转和缩放变换。子骨骼在父骨骼的坐标系中也可以做这些变换来改变自己在其父骨骼坐标系中的位置和朝向等。那么如何表示呢?
由于4X4矩阵可以同时表示上述三种变换,所以一般描述骨骼在其父骨骼坐标系中的变换时使用一个矩阵,也就是DirectX SkinnedMesh中的FrameTransformMatrix。实际上这不是唯一的方法,但应该是公认的方法,因为矩阵不光可以同时表示多种变换还可以方便的通过连乘进行变换的组合,这在层次结构中非常方便。
UE4中的定义
/** New Reference skeleton type **/
FReferenceSkeleton RefSkeleton;
在UE4中,USkeleton并不会直接使用自身的数据而是会生成一个FReferenceSkeleton来提供给mesh来使用。
其将所有的原始Bone数据分成两份,一份存储Bone的名字和父节点名称,一份是之前我们说的Transform来表示一个bone。当然还会有其他的数据信息,例如Name和Index的关系,等等,
在SkinnedMeshComponent中,将会把数据放在
1.2USkeletalMesh
作为链接Mesh和动画的桥梁,其实其中所拥有的数据几乎没有什么东西。
- BlendProfiles:混合描述文件
- AnimRetargetSources:对于这个骨骼来说可以序列化的重定向资源
- Sockets:每一根骨骼的点
- VirtualBones:在引擎中不增加蒙皮的虚拟骨骼,可以在动画蓝图中使用,
- RefLocalPoses_DEPRECATED:和在local space 下的skeleton poses对应
- BoneTree:由boneNode组成,这里记录着的是所有的骨架结构
- SmartNames
渲染数据SkeletalMeshRenderData
对于mesh来说,不用于渲染的数据,其实并没有什么用处。
//标记是scetion的阶段TArray<FSkelMeshRenderSection> RenderSections;// Index Buffer (MultiSize: 16bit or 32bit)FMultiSizeIndexContainer MultiSizeIndexContainer;
FMultiSizeIndexContainer AdjacencyMultiSizeIndexContainer;//static vertices from chunks for skinning on GPU
FStaticMeshVertexBuffers StaticVertexBuffers;//Skin weights for skinning
FSkinWeightVertexBuffer SkinWeightVertexBuffer;// cloth mesh-mesh mapping
FSkeletalMeshVertexClothBuffer ClothVertexBuffer;//MorphTargets buffer
FMorphTargetVertexInfoBuffers MorphTargetVertexInfoBuffers;//权重数据,可以存储不同的权重FSkinWeightProfilesData SkinWeightProfilesData;//激活的骨骼TArray<FBoneIndexType> ActiveBoneIndices;//需要的骨骼TArray<FBoneIndexType> RequiredBones;
- ImportedModel:原始的mesh
- SkeletalMeshRenderData:用于渲染的资源,存储lod与每个lod中section的信息
- Materials:重新定义的FSkeletalMaterial
1.3.UAnimInstance
这里是我们最需要关心的地方之一,这是所有逻辑的控制中心。但是一般而言对他的更新我们为了高效率的进行,通常会在他的代理中进行,参考第二章的动画更新。
1.3.1节点
所有节点的父类是FAnimNode_Base。FAnimNode_Base是Anim graph中实时动画节点的父类,由于动画节点实在是太多了,我们将管中窥豹。
- Initialize_AnyThread:当节点第一次运行时,会运行Initialize_AnyThread。
- CacheBones_AnyThread:CacheBones用于刷新该节点所引用的骨骼索引。
- Update_AnyThread:更新
- Update_AnyThread:调用以根据Update()中设置的权重计算局部空间骨骼变换。
- GatherDebugData:GatherDebugData用于使用"ShowDebug Animation"数据进行调试。为了保持到子项的连接,使用这些是很重要的。
- FPoseLink SourcePose;这是在节点上的pose连结点,会串联出来基本的运行逻辑。基本上每个animNode都会。
1.3.1.1AssetPlayerBase节点
所有播放动画的节点父类是FAnimNode_AssetPlayerBase。
- FAnimNode_BlendSpacePlayer
- FAnimNode_AimOffsetLookAt
- FAnimNode_BlendSpaceEvaluator
- FAnimNode_RotationOffsetBlendSpace
- FAnimNode_PoseHandler
- FAnimNode_PoseBlendNode
- FAnimNode_PoseByName
- FAnimNode_PoseDriver
- Sqeuence.FAnimNode_SeequencePlayer
其大部分工作都是对于UAnimSequence 资源类型的更新。在执行时,执行:
Sequence->GetAnimationPose(Output.Pose, Output.Curve, FAnimExtractContext(InternalTimeAccumulator, Output.AnimInstanceProxy->ShouldExtractRootMotion()));
1.3.1.1FAnimNode_Base节点
1.3.1.1StateMachine节点
FAnimNode_StateMachineGetRelevantAssetPlayerFromState
1.3.1.1LiveLinkPose节点
这是进行LiveLink链接的重要节点,其中最重要的就是ILiveLinkClient,其通过流式传输从额外的数据源传递数据给Unreal。其更新并不需要我们来管理,我们只需要取其数据就可以了。
Evaluate_AnyThread
1.3.2连接点
1.3.2.1FPoseLinkBase ,FPoseLink
其表示一个 local空间的pose链接 用于pose 的传递 。
int32 LinkID;//所链接的ID
int32 SourceLinkID;
struct FAnimNode_Base* LinkedNode;//
从初始化的函数可以看出,其初始化流程是通过LinkedNode去驱动的,也就是说,在PoseLink初始化时会将链接的FAnimNode_Base初始化。
FPoseLink调用CacheBones_AnyThread时会驱动链接的CacheBones调用CacheBones_AnyThread
1.3.2.2链接点的数据传输
1.4 UAnimationAsset
从这里我们逐步将进入动画阶段,以为之气的资源类型要不是使用于控制,要不就是使用于存储数据。UAnimationAsset是所有动画资源的父类类型。
几乎跟所有的骨骼相关内容一样,所有的动画类型都必须进行骨骼的绑定,也就是说所有的动画都不是无根之木,都必须依赖于骨骼。当然除了标记动画继承关系外,这里并没有什么东西。所有的动画资源都是要在动画蓝图中使用。
其子类主要有三中,UAnimSequenceBase是所有动画的子类
1.4.1UAnimSequenceBase动画序列基类
UAnimSequenceBase是帧动画序列的类型。其主要是根据时间来进行的变化。其主要功能是动画通知和曲线控制。其子类为UAnimCompositeBase,UAnimSequence,UAnimStreamable
1.4.1.1UAnimCompositeBase动画合成基类
这里依然是一个虚的资源类型,这里是动画合成的基类。没什么东西
1.4.1.1.1UAnimComposite动画合成
- 在某些情况下,您可能会遇到这样的情况:您需要将多个动画序列拼接在一起,这样就可以将它们当作一个序列而不是多个序列来使用。这正是 动画合成 的目的。动画合成是一种动画资源,专门设计用于允许您将多个动画组合在一起以作为单个单元进行处理。但是,请注意合成只是追加动画;它不提供任何混合能力。
1.4.1.1.2UAnimMontage动画蒙太奇
动画蒙太奇(Animation Montage)(简称 蒙太奇)提供了一种直接通过蓝图或C++代码控制动画资源的途径。 你可以使用动画蒙太奇将多个不同动画序列 组合成一个资源。你可以将该资源分成若干 片段(Sections),选择播放其中的个别片段,或者选择播放所有片段。 你可以触发蒙太奇中的 事件(Events) 以执行各种本地或复制任务,例如播放Sound Cue或粒子效果,更改玩家数值(如弹药数量)等,甚至在动画启用“根运动”时复制联网游戏中的根运动 。
- FAnimMontageInstance
1.4.1.2UAnimSequence
动画序列 是可在骨架网格体上播放的单个动画资源。这些序列包含各个关键帧,而关键帧又规定了骨骼在特定时间点的位置、旋转和比例。依次回放这些关键帧(相互合成)可以顺利实现骨架网格体中的骨骼动画。
- AnimSequence几乎是所有动画的主要的组成部分,其主要的运行机理我们将在后面讨论。
1.4.2UBlendSpaceBase混合空间基类
其子类分别是UBlendSpace,UBlendSpace1D。混合空间(Blend Space) 允许根据两个输入的值混合动画。要根据一个输入在两个动画之间实现简单混合, 可以使用动画蓝图 中提供的一个标准 混合节点 。混合空间提供的方法是根据多个值(目前仅限于两个) 在多个动画之间进行更复杂的混合。这里没什么好说的。
1.4.3UPoseAsset
传统的动画都是关键帧,在时间轴上进行混合,形程所需要的姿势,但是对于面部表情来所,这种方式并不适用,而是使用曲线进行驱动,使用诸多加权值驱动动画表情。但是这里并不是只用于blendshape,其他的骨骼曲线也是可以使用的。
在资源里面存储着每一个pose 的基础数据。
由于如何使用AnimationAsset的数据是在动画蓝图中使用,所以在动画蓝图中将介绍其主要的功能。
1.5UPhysicsAsset
二.动画逻辑
init - 初始化
Update – 动画蓝图从游戏逻辑中收集状态变量并更新骨骼位置
Evaluate – 根据骨骼位置对动画进行解压和混合
Complete – 将运算后的顶点数据推送到渲染现场,更新物体位置和动画通知
2.1初始化
- USkeletalMeshComponent::InitAnim()
- ClearAnimScriptInstance();将animInstance和SubInstances赋空
- RecalcRequiredBones():计算特定LodIndex的需要的骨骼,来自于USkeletalMesh的Renerdata。会将虚拟骨骼加入其中。当然如果资源拥有PhysicsAsset,会将每个骨骼的phyBone一一对应的找到,并在之后会进行更新。找到Mesh 并对应所有的骨骼,mesh和骨骼的点不一定是一一对应的。然后把依赖所有的socket找到。
- InitializeAnimScriptInstance():这里主要是创建需要的动画蓝图,当然这里是有不同分支,一种是如果你指定了动画蓝图,并且其链接的骨骼是一致的,那么会进行创建和放置。还会判断是否会创建PostPhysicsInstance。并且会创建MorphTargetCurves这里由于非常的重要,将在下个一节详细介绍
- TickAnimation(0.f, false); 这里主要做的就是更新curve
- RefreshBoneTransforms();主要是更新bone
创建MorphTargets并更新
在初始化动画蓝图时,我们会进行MorphTargets的创建。这里需要注意的是,这里的curve并不是动画的curve,而是mesh 的。
首先,所有的MorphTargets是在Mesh中,所以我们会从我们现有的mesh中找到所有激活的MorphTarget分配空间。这里的存储结构会有一个浮点数序列存储其权重值。
之后,遍历所有的AnimScriptInstance和SubInstances,看是否这些动画蓝图有更改的的曲线,然后去更新MorphTargets。
2.2动画更新
执行更新的地方是在组件的TickComponent中,每帧进行调用。
:USkinnedMeshComponent:TickComponent
USkeletalMeshComponent::TickComponent
在USkinnedMeshComponent中主要是更新pose,更新Bone的操作,然后是TickAnimation,调用动画蓝图的UpdateAnimation。也就是说,在更新中是先更新动画蓝图,动画,然后根据曲线数据去更新骨骼和mesh。
TickPose(DeltaTime, false);
RefreshMorphTargets();
RefreshBoneTransforms(ThisTickFunction);
UpdateSlaveComponent();
其中,更新的内容主要是两部分,第一部分是animInstance的更新(TickPose),第二是自身数据的更新。
2.2.1更新动画实例
https://mp.weixin.qq.com/s/GSe_NIJZ-dGNjhfXAFKhqQ
在更新事件中,更新动画是我们数据驱动的主要方式。
bool UAnimInstance::UpdateAnimation();
{UpdateMontage(DeltaSeconds);PreUpdateAnimation(DeltaSeconds);UpdateMontageSyncGroup();BlueprintUpdateAnimation(DeltaSeconds);ParallelUpdateAnimation();PostUpdateAnimation();
}
这里是更新动画的主层逻辑,在这里之前,我们需要另一个重要的概念FAnimInstanceProxy。什么是AnimInstanceProxy?
2.2.1.1多线程动画更新
该选项控制默认情况下,是否允许在非游戏线程上执行动画蓝图图形更新。 还允许在动画蓝图编译器中进行一些额外检查,并在尝试执行不安全的操作时发出警告。 在 动画蓝图(Animation Blueprints) 中,也需要确保设置为 使用多线程动画更新(Use Multi Threaded Animation Update)。
在动画蓝图(Animation Blueprints)中的 类设置(Class Settings) 下面,确保启用 使用多线程动画更新(Use Multi Threaded Animation Update)。
其主要原因是为了更严密地控制各个线程中的数据访问。为此,大部分动画图形访问的数据已经从UAnimInstance 移至一个新的结构,名为FAnimInstanceProxy 。 该代理结构存放有关`UAnimInstance`的大量数据。
2.2.1.2动画实例代理AnimInstanceProxy
AnimInstanceProxy是,动画实例代理,属于多线程优化动画系统的核心对象。他可以分担动画蓝图的更新工作,将部分动画蓝图的任务分配到其他线程去做。
一般而言,不能从动画图形节点(Update/Evaluate calls)访问或修改`UAnimInstance,因为它们可以在其他线程上运行。 有一些锁定封装器(GetProxyOnAnyThread 和GetProxyOnGameThread )可以在任务运行期间阻止访问`FAnimInstanceProxy。
主要想法是在最差的情况下,任务等待完成,然后才允许从代理读取或写入数据。
从动画图形的角度而言,从动画节点只能访问`FAnimInstanceProxy,而不能访问`UAnimInstance。 对于FAnimInstanceProxy::PreUpdate 或FAnimInstaceProxy::PreEvaluateAnimation 中的每次更新,必须与代理交换数据(通过缓冲、复制或其他策略)。 接下来需要被外部对象访问的任何数据应该从FAnimInstanceProxy::PostUpdate 中的代理进行交换/复制。
这与`UAnimInstance`的一般用法冲突,在一般用法中,可以在任务运行期间从其他类访问成员变量。 建议最好不要从其他类直接访问动画实例。动画实例应从其他位置拉取数据。
总之,将游戏逻辑得更新从UAnimInstance转移到AnimInstanceProxy,并且动画图表中只能访问AnimInstanceProxy中得数据,从而做并行优化。
2.2.1.3具体细节
当我们知道动画的更新策略后,我们再仔细看一下其中的内容,
1. 更新蒙太奇UpdateMontage
更新蒙太奇,蒙太奇的数据主要是存在于动画实例中MontageInstances。首先更新他的权重,并且计算其对应的骨骼和curve的比重。
2. 准备工作
这里主要做的是在更新前的准备工作,诸如时间的计算,lod的切换,整体的Transform 的移动,通知事件的重置,所有的权重重置等等
在AnimInstanceProxy中,存储了这个AnimInstance所有的必要信息。所以对于其中的所有动画节点,也会在这个阶段进行准备工作
3. 组同步更新MontageSyncGroup
同步组 使相关的动画相互保持同步,即使它们长度不一也不例外。
4. BlueprintUpdateAnimation
更新动画蓝图,实现全在蓝图当中。
5. ParallelUpdateAnimation
这里是更新中最重要的函数,在这里会调用FAnimInstanceProxy的更新函数,使用GetProxyOnAnyThread<FAnimInstanceProxy>()调用以下两个函数
FAnimInstanceProxy::UpdateAnimation
FAnimInstanceProxy::TickAssetPlayerInstances
6. FAnimInstanceProxy ::UpdateAnimation
在AnimInstanceProxy,会从他的RootNode 开始。RootNode并不是最开始的点, 而是终点,其跟新是递归的找到最开始的点。
- 对于root点,在最开始CacheBones_AnyThread,这个点会根据他的sourcePose一直向前找,直至最前面没有deflaut的。
- 然后调用Root节点默认的Update。在root节点的update中调用Update_AnyThread。同样的我们的Root依旧会一次向前找,最终把所有的点进行更新
- UpdateAnimationNode()之后直接将该动画实例中存储的所有FAnimNode_SaveCachedPose进行更新。FAnimNode_SaveCachedPose是啥
7. FAnimInstanceProxy :: TickAssetPlayerInstances
我看这里全是同步组的东西,没兴趣
8. PostUpdateAnimation
2.2.2更新数据
之前所有的数据都是在animation中的数据,而我们的组件并没有获得。
2.2.2.1更新MorphTarget;
我们之前已经进行了整个组件中所有的动画实例的更新,我们把动画中所有的使用Curves进行更新。着里是非常简单的,毕竟MorphTarget只是曲线而已。
之后我们把使用到的MorphTarget添加。等待使用
2.2.2.2更新bonesRefreshBoneTransforms(ThisTickFunction);
计算所有需要的bones,之后我们将填充AnimEvaluationContext,之后我们会大概到了Evaluation。这里也会有多线程的优化,不过我们为了简单起见,看在GameThread的支线。
SwapEvaluationContextBuffers();
ParallelAnimationEvaluation();
SwapEvaluationContextBuffers();
首先是交换EvaluationContext,在组件上是有非常多的缓存,存储着主要的数据。我们把AnimEvaluationContext上的缓存进行交换。注意这里是进行了两次交换的。
在ParallelAnimationEvaluation,就复杂的多。
这里主要是执行AnimInstance的EvaluateAnimation,依旧是转为FAnimInstanceProxy。对于FAnimInstanceProxy来说,执行后返回的数据是FPoseContext,这里就是通过变换后的结果。
注意,直到这里,我们才执行了动画蓝图,而之前的更新proxy,我们只是更新他的状态,而没有执行他们的数据并且获得结果。
这里当然还是老套路,从root开始一直执行Evaluate_AnyThread,最后传出来。最后在固化就可以了
2.2.2.3UpdateSlaveComponent();
???
三.骨骼的渲染
3.1数据和流程
创建并生成渲染段的接口是USkinnedMeshComponent::CreateRenderState_Concurrent。基本的数据传输基本是Unreal Mesh Drawing 中的顺序和数据。所以我们最关系的就是FPrimitiveSceneProxy里面的数据和staticMesh的不同。
MeshRenderData 的数据存储在USkeletalMesh中,并且是其唯一占有。在创建渲染数据时,将把它拿出来用于创建MeshObject,其类型是FSkeletalMeshObject,创建会根据选项创建其子类(参见3.1.1)。当建立之后MeshObject将常驻内存,轻易不会销毁。
当我们创建出MeshObject后,就会根据它创建渲染代理。由于不是静态状态,所以每帧会根据initview里面的动态物体可见性检测来判断是否需要从proxy中创建 MeshBatch。可参见解析initview 。
至此会渲染数据进行提交和渲染。
3.1.1MeshObject分类
FSkeletalMeshObject是SkeletalMesh 蒙皮的父类。子类为:FSkeletalMeshObjectCPUSkin;FSkeletalMeshObjectGPUSkin;FSkeletalMeshObjectStatic。
在创建时,会根据是否静态渲染,是否支持GPU蒙皮和是否需要CPU蒙皮来创建。
对于FSkeletalMeshObjectStatic上的配置是GPU skin vertex buffer + LocalVertexFactory
- todo:三种的区分蒙皮研究
3.2渲染数据的更新
之前由于一直在渲染线程,并且大多都是对staticMesh进行相对应的研究和操作,由于本文是对skeletalMesh的研究,就避不开对动态数据的研究。MeshObject是我们需要重点关心的数据存储的地点,由于是指针传递,所以其实虽然是使用的指针,但是我们还是为了线程安全的问题,必须把需要更新的阶段交给渲染线程。
SendRenderDynamicData_Concurrent();
之后就会调用MeshObject->Update,并且调用UpdateMorphMaterialUsageOnProxy来更新MorphMaterial。当然这这些函数下由于还是在主线程,所以都会推到渲染线程去更新数据例如:
在父类中所做的更新有:
3.2.1更新的是什么数据
针对不同的MeshObject信息,我们的更新数据是不同的,对于CPUSkin我们更新FDynamicSkelMeshObjectDataCPUSkin;GPUSkin我们更新FDynamicSkelMeshObjectDataGPUSkin等等。
核心数据是在FSkeletalMeshObjectLOD中的
FDynamicSkelMeshObjectDataGPUSkin* DynamicData;
这个数据是我们更新和使用他渲染的数据结构。
InitMorphResources:对于MorphTarget非常的简单。更新激活的MorphTarget和对应的权重,并会筛选出影响mesh 的曲线,剔除不需要的,数据来自自己的SkeletalMeshCompent中的MorphTarget。
NewDynamicData->InitDynamicSkelMeshObjectDataGPUSkin:初始化GPU蒙皮Data
- UpdateRefToLocalMatrices
- UpdateRefToLocalMatricesInner
- UpdatePreviousRefToLocalMatrices
- UpdateRefToLocalMatricesInner
- UpdateClothSimulationData(InMeshComponent);
- MeshObject->UpdateDynamicData_RenderThread
- WaitForRHIThreadFenceForDynamicData、
- FreeDynamicSkelMeshObjectDataGPUSkin(DynamicData);
- ProcessUpdatedDynamicData
- UpdateMorphVertexBufferGPU
- ShaderData.UpdateBoneData
- ClothShaderData.UpdateClothSimulData
对于骨骼数据,我们
- UpdateRefToLocalMatrices
- ComponentSpaceTransformsArray
- UpdateClothSimulationData
- 更新矩阵,例如pose对应的local space transforms,遍历需要的骨骼,更新每一个骨骼的矩阵。
- 更新ClothSimulationData
当然更新的时候会设置fence。最终会在渲染线程去更新buffer例如UpdateMorphVertexBufferGPU
UpdateMorphVertexBufferCPU。关于其buffer中的东西我们将在shader和buffer中进行观察。
3.2.2数据的使用
3.3渲染shader和buffer
MorphVertexBuffer和MorphVertexBuffer都存在UAV当中。
UpdateMorphVertexBufferGPU
MorphVertexFactories
3.4渲染分析
3.4.1顶点工厂
我们几乎在之前是UE4渲染的主要结构框架都已经分析,完全,但是并没有进行经验性的总结和归纳,不过这并不影响我们来理解所有的Mesh和他的factory在其中的作用。其负责将顶点数据从C++端带到Shader端,继承自FRenderResource,是渲染资源的一种。
之前我们总是看的是staticMesh,对应的Factory也是FLocalVertexFactory。而在SkeletalMesh中,我们需要知道的是,这GPU和CPU skin的factory是不同的。
对于CPUSkin使用的依旧是FLocalVertexFactory,而对于GPUSkin,我们使用更多是其他的Factory。
- 如果GPU蒙皮Cache开启(啥时候开启,Cache了啥),我们提供FGPUSkinPassthroughVertexFactory,FGPUSkinPassthroughVertexFactory继承自FLocalVertexFactory。
- 如果布料模拟更新,提供FGPUBaseSkinAPEXClothVertexFactory
- 如果有任何的Morph Target被启动,我们使用FGPUBaseSkinVertexFactory
FGPUSkinPassthroughVertexFactory开启了GPUSKIN_PASS_THROUGH,在LocalVertexFactory中可以看到其应用。但是应该是不会用到的把,好像rayTrace要用到,那我就懂了。
所以其中最重要的就应该是FGPUBaseSkinVertexFactory
FGPUBaseSkinVertexFactory//(纯虚)TGPUSkinVertexFactoryTGPUSkinAPEXClothVertexFactoryTGPUSkinMorphVertexFactory
关于顶点工厂的内容,参考
顶点工厂FVertexFactory
这里已经讲的很详细了,所以我们直接进shader里看他到底传入什么东西。其会绑定一个shader文件,我们能找到。
struct FVertexFactoryInput
{float4 Position : ATTRIBUTE0;//切线half3 TangentX : ATTRIBUTE1;
//副法线half4 TangentZ : ATTRIBUTE2;
#if FEATURE_LEVEL >= FEATURE_LEVEL_ES3_1 || COMPILER_METAL || COMPILER_VULKANuint4 BlendIndices : ATTRIBUTE3;#if GPUSKIN_USE_EXTRA_INFLUENCESuint4 BlendIndicesExtra : ATTRIBUTE14;#endif
#else// Continue using int for SM3, compatibility of uint is unknown across SM3 platformsint4 BlendIndices : ATTRIBUTE3;#if GPUSKIN_USE_EXTRA_INFLUENCESint4 BlendIndicesExtra : ATTRIBUTE14;#endif
#endiffloat4 BlendWeights : ATTRIBUTE4;
#if GPUSKIN_USE_EXTRA_INFLUENCESfloat4 BlendWeightsExtra : ATTRIBUTE15;
#endif#if NUM_MATERIAL_TEXCOORDS_VERTEX// If this changes make sure to update LocalVertexFactory.usffloat2 TexCoords[NUM_MATERIAL_TEXCOORDS_VERTEX] : ATTRIBUTE5;#if NUM_MATERIAL_TEXCOORDS_VERTEX > 4#error Too many texture coordinate sets defined on GPUSkin vertex input. Max: 4.#endif
#endif#if GPUSKIN_MORPH_BLEND// NOTE: TEXCOORD6,TEXCOORD7 used instead of POSITION1,NORMAL1 since those semantics are not supported by Cg/** added to the Position */float3 DeltaPosition : ATTRIBUTE9; //POSITION1;/** added to the TangentZ and then used to derive new TangentX,TangentY, .w contains the weight of the tangent blend */float3 DeltaTangentZ : ATTRIBUTE10; //NORMAL1;
#endif
//顶点ClothID
#if GPUSKIN_APEX_CLOTHuint ClothVertexID : SV_VertexID;
#endif
//顶点色float4 Color : ATTRIBUTE13;
};
3.4.2蒙皮位置
接下来我们来看一下在GPU中是如何改变位置的。不管使用的是什么Factory,我们的shader是不变了的。所以我们看一下BasePassVertexShdader中的位置计算。
当然在此之前我们要看一下SkinVertexFactory使用到的uniform。
这里在FSkeletalMeshObjectGPUSkin::Update中进行填充和更新
在FGPUBaseSkinVertexFactory::FShaderDataType::UpdateBoneData进行更新数据
VertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
float4 WorldPosition = WorldPositionExcludingWPO;
float4 ClipSpacePosition;
float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPosition.xyz, TangentToLocal);
ClipSpacePosition = mul(float4(RasterizedWorldPosition.xyz + ODS, 1.0), ResolvedView.TranslatedWorldToClip);Output.Position = INVARIANT(ClipSpacePosition);
首先看GetVertexFactoryIntermediates得到的坐标。我们忽略GPUSKIN_APEX_CLOTH
- position就是本身传入的position。
- BlendMatrix,通过CalcBoneMatrix。BoneMatrices是每个骨骼的
- TangentToLocal
- Color
- VertexFactoryGetTangentToLocal直接返回
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 实战篇--常见的Shell脚本编写(二)
常见的Shell脚本编写(二)1.11 屏蔽网站访问频繁的IP1.12 判断输入是否为IP1.13 判断输入是否为数字1.15 监控目录,将新创建的文件名追加到日志中1.17 查看网卡实时流量1.18 MySQL数据库备份1.19 Nginx启动脚本1.20 选择SSH连接主机1.11 屏蔽网…...
2024/4/6 8:16:32 - Redis面试题(哨兵、复制、事务、集群、持久化)
Redis主要有哪些功能? 1、哨兵(Sentinel)和复制(Replication) Redis服务器毫无征兆的罢工是个麻烦事,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制。 Sentinel可以管…...
2024/4/27 21:52:32 - 如何设置在word中打开MathType的快捷键
如何设置在word中打开MathType的快捷键 我们将鼠标放在MathType插入公式处可以看到系统默认的快捷方式,如下: 不过可能会有很多人感觉这样的快捷方式太复杂,想设置成自己喜欢的方式,那么可按照如下方式进行修改: 依次…...
2024/4/6 8:16:30 - getea上代码提交后通过webhook触发jenkins进行构建
1. 环境 azure centos 8 、gitea, jenkins 2.创建gitea代码项目 3.创建jenkins任务 a. 配置gitea代码仓库,就是刚才的gitea项目的地址,然后创建访问的账号,用户名和密码方式。 b.勾选触发器的第一项“”触发远程构建“”…...
2024/4/6 8:16:29 - spring - shiro - session交给redis托管
shiro自己也有session,但是我希望将这个session交给redis托管,因为如果系统采用分布式,登录信息只保存在一个节点是肯定不行的。仔细弄了几天,遇到几个坑,查了很多资料,终于走通了。现在做个笔记。有关shir…...
2024/4/27 18:35:42 - 最棒 Spring Boot 干货总结(超详细,建议收藏)
前言:本文非常长,建议先mark后看,也许是最后一次写这么长的文章 说明:前面有4个小节关于Spring的基础知识 分别是:IOC容器、JavaConfig、事件监听、SpringFactoriesLoader详解 它们占据了本文的大部分内容ÿ…...
2024/4/6 7:00:42 - #SpringBoot入门程序 @FDDLC
一、创建Maven工程(无需选择模板) 二、导入依赖 1、添加父依赖: <parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.0.5.RELEASE</version> </p…...
2024/4/17 0:55:40 - SMM(spring +springmvc+mybatis)依赖注解等环境配置
pom文件 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 …...
2024/4/8 13:45:29 - React table 遍历根据状态显示不同样式
用的是 ant.design 里的table组件 <div style{{paddingTop:12px}}> // columns 表格列的配置描述 //dataSource 数据数组 //pagination 分页器,参考配置项或 pagination 文档,设为 false 时不展示和进行分页<Table columns{columns} dataSourc…...
2024/4/6 7:00:39 - Java实现基数排序(桶排序)
一、基数排序(桶排序)排序的基本介绍和排序思路: 1.基数排序(桶排序)基本介绍: (1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”&#x…...
2024/4/25 19:23:28 - Effective C++ 5535 随笔
Effective C 55&35 随笔Effective C 55More Effective C 35大概很有以前看过这两本书,不过那时候知道的也不太多,笔记在OneNote上也是乱七八糟的,后来因为某些原因在OneNote上的笔记怎么也找不到了,重新看一遍好好整理整理。其…...
2024/4/28 5:24:35 - 哈夫曼树(Huffman)编码 压缩数据编程
网上有不少人介绍哈夫曼树的生成方法,但是对于实现方式却都只字不提,只是放了代码,这里简单介绍一下一个我自己编写的哈夫曼树的实现方式。 如果你完全不知道哈夫曼树的生成方式,那么本篇文章并不适合你阅读,如果你已经…...
2024/4/28 4:06:45 - 这个世界上没有谁离不开谁,只是有你有你会更好
今天在抖音刷到一个视频,大概意思是: 这个世界上就没有谁离不开谁的,有些人认为离开了你没有了我会活不了;他们要相互依赖,但是不需要天天如泥似漆的在一起,一定要是两个相互独立的个体。不是没有你不行,而…...
2024/4/24 23:42:11 - 嘘!我在偷听阿里P8夜间密探:耗时三年才肝出来的微服务文档还有没有优化的地方?
微服务的概念提出已经有几年了,相对于传统技术架构来说微服务不仅仅是新一代架构,更是具有划时代意义的架构。目前国外一些大型互联网公司如亚马逊、Netflix、Spotify和一些传统 公司如沃尔玛都已经采用微服务架构并且实际效果非常好。 与此同时&#x…...
2024/4/25 11:10:25 - 携带cookie登录
scrapy模拟登录 为什么需要模拟登录 获取登录后的页面的信息 获取cookie 回顾: requests模块是如何模拟登录的? 1.直接携带cookie请求页面 headers{"User-Agent": "",Cookie: }responserequests.get(url,headersheaders)2.找接…...
2024/4/16 0:19:55 - 【优雅的避坑】避免HashMap扩容的正确姿势
设置HashMap的初始容量 设置HashMap的初始容量只是优化的开始。 HashMap在Java的使用中占据着很重要的地位,平时使用的时候,相信很多Java程序员都知道在定义HashMap的时候,给它设置一个初始容量,以便减少hashMap扩容(r…...
2024/4/22 4:56:27 - sso登陆劫持漏洞(单点登录劫持,低危)
正文 参考资料: 腾讯单点登录系统跨域劫持漏洞 https://lvwei.me/passport.html#toc_8 sso单点登陆,用户一次登陆,所有系统都可访问。 sso设计目的是简化登陆方式,可能有很多系统,但是多个系统都是一个机构的&…...
2024/4/25 6:45:47 - 四天Mysql进阶教程之Day01
1. Linux 系统安装MySQL 1.1 下载Linux 安装包 https://dev.mysql.com/downloads/mysql/5.7.html#downloads 备注:ubuntu或centos可在如下链接下载(速度很快): http://mirrors.aliyun.com/ubuntu-releases/ http://mirrors.aliyun.com/centos/ 1.2 安装MySQL …...
2024/4/25 13:57:27 - 2020-10-15 原生JS实现简易学生管理系统`
原生JS实现简易学生管理系统 html <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><!-- <link …...
2024/4/26 6:38:15 - 力扣刷题-----1295.统计位数为偶数的数字
文章目录统计位数为偶数的数字1.题目描述2.题目示例3.思路及代码统计位数为偶数的数字 1.题目描述 给你一个整数数组 nums,请你返回其中位数为 偶数 的数字的个数。 2.题目示例 示例 1:输入:nums [12,345,2,6,7896] 输出:2 解释…...
2024/4/24 12:34:17
最新文章
- 编辑器,编译器,IDE的区别
1.编辑器,编译器 简单说编辑器是用来写代码的,而编译器是对现场高级语言代码翻译成相对低级语言的一段小程序/指令。 2.IDE 集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序ÿ…...
2024/4/28 6:14:46 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - 论文阅读AI工具链
文献检索 可以利用智谱清言来生成合适的文献检索式,并根据需要不断调整。 谷歌学术 在Google Scholar中进行检索时,您可以使用类似的逻辑来构建您的搜索式,但是语法会有所不同。Google Scholar的搜索框接受普通的文本搜索,但是…...
2024/4/28 2:14:55 - 用html实现在页面底部养鱼的效果
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>在网页底部养鱼</title><link rel"stylesheet" href"./style.css"> </head> <body> <div id"fi…...
2024/4/24 17:02:47 - 416. 分割等和子集问题(动态规划)
题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义:dp[i][j]表示当背包容量为j,用前i个物品是否正好可以将背包填满ÿ…...
2024/4/28 4:04:40 - 【Java】ExcelWriter自适应宽度工具类(支持中文)
工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...
2024/4/27 3:39:11 - Spring cloud负载均衡@LoadBalanced LoadBalancerClient
LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon…...
2024/4/27 12:24:35 - TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案
一、背景需求分析 在工业产业园、化工园或生产制造园区中,周界防范意义重大,对园区的安全起到重要的作用。常规的安防方式是采用人员巡查,人力投入成本大而且效率低。周界一旦被破坏或入侵,会影响园区人员和资产安全,…...
2024/4/27 12:24:46 - VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...
2024/4/27 3:39:08 - 【Objective-C】Objective-C汇总
方法定义 参考:https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...
2024/4/27 3:39:07 - 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】
👨💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】🌏题目描述🌏输入格…...
2024/4/27 3:39:07 - 【ES6.0】- 扩展运算符(...)
【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数࿰…...
2024/4/27 12:44:49 - 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?
文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕,各大品牌纷纷晒出优异的成绩单,摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称,在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁,多个平台数据都表现出极度异常…...
2024/4/27 21:08:20 - Go语言常用命令详解(二)
文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令,这些命令可以帮助您在Go开发中进行编译、测试、运行和…...
2024/4/26 22:35:59 - 用欧拉路径判断图同构推出reverse合法性:1116T4
http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b,我们在 a i a_i ai 和 a i 1 a_{i1} ai1 之间连边, b b b 同理,则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然࿰…...
2024/4/27 18:40:35 - 【NGINX--1】基础知识
1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息,并安装一些有助于配置官方 NGINX 软件包仓库的软件包: apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...
2024/4/28 4:14:21 - Hive默认分割符、存储格式与数据压缩
目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限(ROW FORMAT)配置标准HQL为: ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...
2024/4/27 13:52:15 - 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法
文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中,传感器和控制器产生大量周…...
2024/4/27 13:38:13 - --max-old-space-size=8192报错
vue项目运行时,如果经常运行慢,崩溃停止服务,报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中,通过JavaScript使用内存时只能使用部分内存(64位系统&…...
2024/4/27 1:03:20 - 基于深度学习的恶意软件检测
恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞,例如可以被劫持的合法软件(例如浏览器或 Web 应用程序插件)中的错误。 恶意软件渗透可能会造成灾难性的后果,包括数据被盗、勒索或网…...
2024/4/27 3:22:12 - JS原型对象prototype
让我简单的为大家介绍一下原型对象prototype吧! 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象…...
2024/4/27 22:51:49 - C++中只能有一个实例的单例类
C中只能有一个实例的单例类 前面讨论的 President 类很不错,但存在一个缺陷:无法禁止通过实例化多个对象来创建多名总统: President One, Two, Three; 由于复制构造函数是私有的,其中每个对象都是不可复制的,但您的目…...
2024/4/27 3:39:00 - python django 小程序图书借阅源码
开发工具: PyCharm,mysql5.7,微信开发者工具 技术说明: python django html 小程序 功能介绍: 用户端: 登录注册(含授权登录) 首页显示搜索图书,轮播图࿰…...
2024/4/26 23:53:24 - 电子学会C/C++编程等级考试2022年03月(一级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...
2024/4/27 20:28:35 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57