【学习笔记】虚幻SkeletalMesh学习(一)基础介绍

news2024/12/27 13:49:39

文章目录

    • 零、前言
    • 一、资源介绍
      • 1.1 骨架资源
      • 1.2 骨架网格体资源
    • 二、UE4中的定义
      • 2.1 骨骼数据
      • 2.2 模型网格数据
    • 三、渲染
      • 3.1 RenderData的初始化
      • 3.2 渲染对象的创建
      • 3.3 渲染对象的更新
        • 3.3.1 游戏线程的更新(*FSkeletalMeshObjectGPUSkin::Update*)
        • 3.3.2 渲染线程的更新(*FSkeletalMeshObjectGPUSkin::UpdateDynamicData_RenderThread*)
      • 3.4 Shader浅析
    • 四、类图
    • 参考文章

零、前言

本文大量内容来自网络文章,整理作为笔记。

感谢各位原作者!

一、资源介绍

1.1 骨架资源

骨架资源,是整个动画系统的基础。

其主要作用是记录了骨架以下信息:

  • 骨骼层级信息
  • 参考姿势信息
  • 骨骼名称插槽(Socket)信息
  • 曲线(Curve)信息
  • 动画通知(Animation Notify)信息
  • 插槽数据(插槽名称、所属骨骼、Transform等)
  • 虚拟骨骼信息
  • 骨骼名称Index映射表
  • 其他骨骼设置信息:包括位移重定向(Translation Retarget)设置,LOD设置等信息

请添加图片描述

1.2 骨架网格体资源

骨骼模型是在骨骼基础之上的模型,通俗来说就是绑定骨骼后的网格体。

其主要包含以下信息:

  • 模型的顶、线、面信息
  • 顶点的骨骼蒙皮权重
  • 模型的材质信息
  • 模型LOD信息
  • Morph Target信息
  • Physics Asset(物理)设置信息
  • 布料系统相关设置

在这里插入图片描述

二、UE4中的定义

2.1 骨骼数据

骨骼相关的数据都封装在USkeleton类,其中包括了骨架数据、插槽、重定向等逻辑。

USkeleton并不会直接使用自身的数据,而是会生成一个FReferenceSkeleton,存储骨骼姿势数据,来提供给Mesh来使用。

在这里插入图片描述

FReferenceSkeleton中,将所有的原始Bone数据分成两份:

  • 一份存储Bone的名字和父节点的索引,一份是Bone的Transform。

请添加图片描述

其中,FMeshBoneInfo的数据结构保存BoneName父节点的索引

在这里插入图片描述

还包含了其他的数据信息,例如Name和Index的关系。

在这里插入图片描述

2.2 模型网格数据

FFbxImporter::ImportSkeletalMesh,负责将导入的模型生成FSkeletalMeshModel对象(USkeletalMesh的一个编辑器成员变量)。

该对象存储着模型网格的几何数据。

在这里插入图片描述

SkeletalMesh存在多个LOD,每个LOD等级下的三角形面数,顶点数是不同的。
FSkeletalMeshModel中存储了各个LOD等级的模型信息(FSkeletalMeshLODModel)。

在这里插入图片描述

FSkeletalMeshLODModel中,主要包含了如下的数据:
在这里插入图片描述

FSkelMeshSection,表示一组使用相同材质的三角形网格数据。

  • 存储了FSoftSkinVertex的数组。
  • FSoftSkinVertex,表示一个顶点,包含了Position、UV、Normal等,以及每个顶点受影响的骨骼索引和权重。

在这里插入图片描述

经Debug可以得知:

  • 骨骼权重用uint8整数表示,最大骨骼权重值为255, 所有骨骼的权重加起来等于255,真正的骨骼权重只需要除以255。

在这里插入图片描述

Section中的BoneMap,存储的是这个Section使用到的Bone在骨骼树上的真正索引。

在这里插入图片描述

FSoftSkinVertex存储的骨骼索引InfluenceBones是“虚拟骨骼索引”,而非真正的骨骼索引。想要获得真正的骨骼索引,需要进行一次转换。即经历Section的BoneMap的映射才得到真正的骨骼索引。

代码如下:

在这里插入图片描述

如图所示,LOD0中有两个Section,Section0使用了60个骨骼,而Section1仅有4个骨骼。

在这里插入图片描述

可以通过**骨骼树信息(FReferenceSkeleton)**看出Section1使用的是哪些骨骼:
在这里插入图片描述
在这里插入图片描述

三、渲染

3.1 RenderData的初始化

FSkeletalMeshModel数据不会在运行时使用,运行时使用的是由该几何数据初始化的FSkeletalMeshRenderData(渲染数据)对象。

在RunTime中,Mesh相关的几何数据实际存储FSkeletalMeshRenderData渲染数据中。

在这里插入图片描述

导入模型的过程中,会调用FSkeletalMeshRenderData::Cache对RenderData进行初始化。

逐级遍历ImportedModel的的LOD,调用FSkeletalMeshLODRenderData::BuildFromLODModel,来实现数据的初始化。

代码分析如下:

1)用ImportedModel中的Sections数据,来初始化LODRenderData的Section信息。

在这里插入图片描述

2)获取顶点数据等,来初始化Mesh的各种VertexBuffer。

在这里插入图片描述

Buffer的Layout:

  • Position [Section0] [Section1]…

  • Tangent和UV同上

3)初始化蒙皮权重缓冲等

在这里插入图片描述

如有颜色,初始化颜色缓冲
在这里插入图片描述
如ClothData的初始化
在这里插入图片描述

4)拷贝ActiveBoneIndices和RequiredBones数据

在这里插入图片描述

上述介绍了编辑器下导入模型创建RenderData的过程。

而运行时,RenderData是创建是来自于USkeletalMesh::Serialize序列化。

在这里插入图片描述

3.2 渲染对象的创建

USkinnedMeshComponent::CreateRenderState_Concurrent,在该函数中会进行一些渲染相关数据的创建。

其中,会由SkeletalMesh的SkelMeshRenderData(FSkeletalMeshRenderData)创建出MeshObject(FSkeletalMeshObject)。

  • 创建之后,MeshObject将常驻内存,轻易不会销毁。

FSkeletalMeshObject:渲染对象的基类,通过该对象从Game线程往渲染线程传递数据。

会根据选项创建其子类。子类如有:

  • FSkeletalMeshObjectCPUSkin
  • FSkeletalMeshObjectGPUSkin
  • FSkeletalMeshObjectStatic

在这里插入图片描述

UE4默认是GPU蒙皮,以FSkeletalMeshObjectGPUSkin为例:

在其构造函数里,调用了FSkeletalMeshObjectGPUSkin::InitResources

在该方法主要是遍历LODs(FSkeletalMeshObjectLOD的数组) ,调用FSkeletalMeshObjectGPUSkin::FSkeletalMeshObjectLOD::InitResources

  1. 初始化顶点蒙皮权重缓存:MeshObjectWeightBuffer
  2. 初始化颜色缓存,MeshObjectColorBuffer
  3. 获取RenderLODData中的顶点数据,存到中间变量FVertexFactoryBuffers
  4. 用Buffers初始化MeshObjectLOD成员变量GPUSkinVertexFactories(FVertexFactoryData对象),初始化GPUSkin顶点工厂
  5. 如果有布料数据,则拿FVertexFactoryBuffers也初始化ClothVertexFactories

3.3 渲染对象的更新

FSkeletalMeshObject::Update,该函数实现了将GameThread更新后的DynamicData发送至渲染线程。

调用堆栈如下:

在这里插入图片描述
UActorComponent::DoDeferredRenderUpdates_Concurrent中会检查组件当前帧bRenderTransformDirtybRenderDynamicDataDirty是否为真。

  • bRenderTransformDirty为真表示Transform发生了变化,调用SendRenderTransform_Concurrent用于更新渲染线程的Transform数据。
  • bRenderDynamicDataDirty为真表示DynamicData的数据发生变化,调用SendRenderDynamicData_Concurrent更新DynamicData。

调用MarkRenderDynamicDataDirty会将bRenderDynamicDataDirty标记为真。

其调用堆栈如下:

  • 在动画更新完成后,会进行调用,从而触发渲染数据的更新。

在这里插入图片描述

FSkeletalMeshObject::Update,不同的MeshObject,更新的数据不同。

下面以FSkeletalMeshObjectGPUSkin为例,进行介绍。

3.3.1 游戏线程的更新(FSkeletalMeshObjectGPUSkin::Update

其更新的数据是:FDynamicSkelMeshObjectDataGPUSkin

存储用来更新蒙皮顶点的矩阵,在GameThread被创建,当更新时会发送到渲染线程。

其部分数据结构如下:

// 蒙皮矩阵相关
TArray<FMatrix> ReferenceToLocal;
TArray<FMatrix> PreviousReferenceToLocal;
TArray<FTransform> MeshComponentSpaceTransforms;

流程如下:

1、InitMorphResources

更新激活的MorphTarget和对应的权重,并会筛选出影响Mesh的曲线,剔除不需要的。

2、InitDynamicSkelMeshObjectDataGPUSkin

初始化GPU蒙皮数据,使用新的动态数据更新 ReferenceToLocal 矩阵。

该函数的主要逻辑:

(1)调用UpdateRefToLocalMatrices,更新ReferenceToLocal

(2)调用UpdatePreviousRefToLocalMatrices,更新PreviousReferenceToLocal

(3)调用UpdateClothSimulationData,更新布料模拟Mesh的顶点位置和法线

UpdateRefToLocalMatrices和UpdatePreviousRefToLocalMatrices,二者的差别在于ComponentTransform。

前者是GetComponentSpaceTransforms,后者是GetPreviousComponentTransformsArray。

二者核心都是调用UpdateRefToLocalMatricesInner计算蒙皮矩阵

蒙皮矩阵的作用:将顶点从模型空间下的绑定姿势,变换到模型空间下的当前动画姿势

  • 蒙皮矩阵计算前后都是Local Space

所以需要先将顶点变换到骨骼空间,再应用骨骼全局姿势矩阵

绑定姿势矩阵:每骨骼的全局绑定姿势矩阵,用于将顶点从某骨骼空间变换到模型空间。

绑定姿势逆矩阵:用于将顶点从模型空间变换到骨骼空间。

在该函数中,先遍历所有会对蒙皮产生影响的骨骼,获取其 Component Space Bone Transform 对应的矩阵(即从Bone Space 变到当前的 Local Space),对于不对蒙皮产生影响的骨骼,这里会保持为 Identity 矩阵。

然后,遍历所有骨骼,乘上 Ref-Pose ,Bone Space 到 Local space 的变化矩阵的逆矩阵(即从 Local Space 变回 Bone Space)。

在这里插入图片描述

3、将动态数据更新命令发送到渲染线程

在这里插入图片描述

3.3.2 渲染线程的更新(FSkeletalMeshObjectGPUSkin::UpdateDynamicData_RenderThread

1、调用FreeDynamicSkelMeshObjectDataGPUSkin,释放上一帧的DynamicData。

2、调用ProcessUpdatedDynamicData,具体处理传输的数据。

ProcessUpdatedDynamicData的主要逻辑如下:

  • 遍历当前LOD的Section,获得每个Section的顶点工厂VertexFactory。

  • 更新顶点工厂的ShaderData,FGPUBaseSkinVertexFactory::FShaderDataType::UpdateBoneData

UpdateBoneData 中,UE 并不会将蒙皮矩阵全部传到 GPU。对于任意一个 section,UE 只会传递这个 section 用到的骨骼

在这里插入图片描述

将蒙皮矩阵写入VertexBuffer(BoneBuffer)中。

3.4 Shader浅析

SKeletalMesh使用的顶点工厂是:FGPUBaseSkinVertexFactory

通过IMPLEMENT_GPUSKINNING_VERTEX_FACTORY_TYPE宏,将GPU蒙皮的VertexFactory与对应的Shader(GpuSkinVertexFactory.ush)进行了绑定。

在这里插入图片描述

打开GpuSkinVertexFactory.ush,可以看到包含一大堆宏定义代码。

最好的方式是用RenderDoc截帧一下,获取一份不带宏的示例代码。

XXVertexFactory.ush 使用了模板函数,需要定义接口的函数和结构。后续会整理一个文档。

输入布局FVertexFactoryInput的定义:

  • Position,模型顶点位置
  • BlendIndices,影响顶点的骨骼Index列表
  • BlendWeights,影响顶点的骨骼权重列表
struct FVertexFactoryInput
{
	float4 Position : ATTRIBUTE0;
	float3  TangentX : ATTRIBUTE1;
	float4  TangentZ : ATTRIBUTE2;
	uint4 BlendIndices : ATTRIBUTE3;
	uint4 BlendIndicesExtra : ATTRIBUTE14;
	float4 BlendWeights : ATTRIBUTE4;
	float4 BlendWeightsExtra : ATTRIBUTE15;
	float2 TexCoords[ 1 ] : ATTRIBUTE5;
	float3 PreSkinOffset : ATTRIBUTE11;
	float3 PostSkinOffset : ATTRIBUTE12;
	float4 Color : ATTRIBUTE13;
};

FVertexFactoryIntermediates的定义

struct FVertexFactoryIntermediates
{
	float3x4  BlendMatrix;
	float3 UnpackedPosition;
	float3x3 TangentToLocal;
	float4 Color;
};

这是一个Caching机制(只计算一次)的结构。

  • 主要是获取了位置、BlendMatrix、TangentToLocal矩阵、颜色。
FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput Input)
{
	FVertexFactoryIntermediates Intermediates;
	Intermediates.UnpackedPosition = UnpackedPosition(Input);
	Intermediates.BlendMatrix = CalcBoneMatrix( Input );
	Intermediates.TangentToLocal = SkinTangents(Input, Intermediates);
	Intermediates.Color = Input.Color.rgba ;
	return Intermediates;
}

其中,CalcBoneMatrix为计算加权后的蒙皮矩阵,具体的逻辑如下:

  • 线性蒙皮,将所有对该顶点有影响的骨骼变换矩阵,乘上权重再累加起来,得到一个完整的蒙皮矩阵。
  • 支持4骨骼或8骨骼蒙皮
float3x4  GetBoneMatrix(int Index)
{
	float4 A = BoneMatrices[Index * 3];
	float4 B = BoneMatrices[Index * 3 + 1];
	float4 C = BoneMatrices[Index * 3 + 2];
	return  float3x4 (A,B,C);
}

float3x4  CalcBoneMatrix( FVertexFactoryInput Input )
{
	float3x4  BoneMatrix = Input.BlendWeights.x * GetBoneMatrix(Input.BlendIndices.x);
	BoneMatrix += Input.BlendWeights.y * GetBoneMatrix(Input.BlendIndices.y);
	BoneMatrix += Input.BlendWeights.z * GetBoneMatrix(Input.BlendIndices.z);
	BoneMatrix += Input.BlendWeights.w * GetBoneMatrix(Input.BlendIndices.w);
	if (NumBoneInfluencesParam >  4 )
	{
		BoneMatrix += Input.BlendWeightsExtra.x * GetBoneMatrix(Input.BlendIndicesExtra.x);
		BoneMatrix += Input.BlendWeightsExtra.y * GetBoneMatrix(Input.BlendIndicesExtra.y);
		BoneMatrix += Input.BlendWeightsExtra.z * GetBoneMatrix(Input.BlendIndicesExtra.z);
		BoneMatrix += Input.BlendWeightsExtra.w * GetBoneMatrix(Input.BlendIndicesExtra.w);
	}

	return BoneMatrix;
}

对于VS而言,是计算顶点的世界位置,具体的计算如下:

  • 顶点乘上混合后的蒙皮矩阵,再乘以Local到世界的变换矩阵。
float3 SkinPosition( FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates )
{
	float3 Position = Intermediates.UnpackedPosition;
	Position += Input.PreSkinOffset;
	Position = mul(Intermediates.BlendMatrix, float4(Position, 1));
	Position += Input.PostSkinOffset;
	return Position;
}

float4 CalcWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	return TransformLocalToTranslatedWorld(SkinPosition(Input, Intermediates));
}
float4 VertexFactoryGetWorldPosition(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermediates)
{
	return CalcWorldPosition(Input, Intermediates);
}

四、类图

在这里插入图片描述

参考文章

  • UE4动画系统的那些事(一):UE4动画系统基础

  • UE4源码阅读_骨骼模型与动画系统_Mesh

  • UE4之StaticMesh和SkeletalMesh类图

  • UE4获取SkeletalMesh顶点的骨骼权重

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1931419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型“重构”教育:解构学习奥秘,推动教育普惠

大模型“重构”千行百业系列选题 生成式人工智能的热潮&#xff0c;为AI领域的发展注入新的活力&#xff0c;而“赋能千行百业”已经成为人们普遍对于人工智能和大模型的全新理解。 人工智能和大模型技术的迅猛发展正在以前所未有的速度深刻改变着各个行业。正如专家所预测&a…

《昇思25天学习打卡营第23天|onereal》

第23天学习内容简介&#xff1a; ----------------------------------------------------------------------------- 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。 1 环境配置 配置网络线路 2 代码开发 下载权重大约需要10分钟 ------------------------------- 运…

汇总国内镜像提供了Redis的下载地址

文章目录 1. 清华大学开源软件镜像站&#xff1a;2. 中国科技大学开源软件镜像&#xff1a;3. 阿里云镜像&#xff1a;4. 华为云镜像&#xff1a;5. 腾讯云镜像&#xff1a;5. 官方GitHub仓库&#xff08;虽然不是镜像&#xff0c;但也是一个可靠的下载源&#xff09;&#xff…

XX2104 培训【C++解决】

描述 某培训机构的学员有如下信息&#xff1a; 姓名&#xff08;字符串&#xff09; 年龄&#xff08;周岁&#xff0c;整数&#xff09; 去年 NOIP 成绩&#xff08;整数&#xff0c;且保证是 5 的倍数&#xff09; 经过为期一年的培训&#xff0c;所有同学的成绩都有所提高&…

【数据结构与算法】数据结构(Data Structure)的基本概念及其研究对象

什么是程序 算法数据结构程序 —— Nicklaus Wirth(尼古拉斯沃斯) Niklaus Wirth是一位著名的计算机科学家&#xff0c;他提出了"程序算法数据结构"的观点。他认为&#xff0c;程序不仅仅是执行特定任务的一段代码&#xff0c;而是由算法和数据结构两部分组成的。算法…

Linux--线程同步

目录 0.上篇 1. 线程同步概念 2.认识条件变量 2.1条件变量的概念 2.2认识接口 2.3写一个测试代码 3.生产者消费者模型 3.1概念部分 1.基本概念 2.主要问题 3.优点 4.思考切入点&#xff08;321原则&#xff09; 3.2编写基于BlockingQueue的生产者消费者模型&…

js执行机制----事件循环

前言 问题 一般情况下,我们都认为js是顺序执行的 但是遇到下列情况 setTimeout(function(){console.log(定时器开始啦) });new Promise(function(resolve){console.log(马上执行for循环啦);for(var i 0; i < 10000; i){i 99 && resolve();} }).then(function(…

MySQL数据库查询索引失效场景

在连表情况下,如果排序字段涉及到了两个表,排序字段将无法走索引. 加上第二个排序字段之后,走全表扫描了. 或者尽量让两次排序都用同一个表的字段,这样可以建联合索引让排序也能走索引.&#xff08;不想建联合索引的话&#xff0c;可以第二次排序用表id&#xff0c;这样单个的…

天池AI大模型技术提升营火热上线,四重好礼等你来拿!

目标锁定&#xff0c;加速成长&#xff0c;四大活动玩法助您提升技术&#xff0c;赢取四重好礼&#xff1a; 【活动一】完成3步学习任务&#xff0c;赢取定制加湿器 【活动二】邀请好友报名指定学习赛&#xff0c;累计助力赢苹果iPad、大疆无人机、韶音蓝牙耳机等好礼 【活动三…

华为1000人校园实验记录

在这里插入代码片1000人校园区网设计 1、配置Eth-trunk实现链路冗余 vlan 900 管理WLAN #接入SW8 操作&#xff1a;sys undo in en sysname JR-SW8 int Eth-Trunk 1 mode lacp-static trunkport g0/0/1 0/0/2 port link-type trunk port trunk allow-pass vlan 200 900 qu vla…

NSSCTF-Web题目26(PHP弱比较)

目录 [SWPUCTF 2022 新生赛]funny_php 1、题目 2、知识点 3、思路 [ASIS 2019]Unicorn shop 4、题目 5、知识点 6、思路 [SWPUCTF 2022 新生赛]funny_php 1、题目 2、知识点 弱比较、双写绕过 3、思路 出现源代码&#xff0c;我们进行审计 第一个if 这里要我们GET方…

OPC UA S7-1500客户端学习

OPC UA S7-1500 OPC UA服务器功能 浏览PLC&#xff0c;服务器中的数据是是一个个节点&#xff0c;上下有联系&#xff0c;浏览请求是请求一个节点&#xff0c;展开上一级或者下一节数据。 符号方式读访问PLC数据。客户端发一条读请求&#xff0c;服务器回应。 写也是一样的 注…

永久删除的文件如何恢复?文件恢复,3种方法任君选择!

“我不小心把回收站清空了&#xff0c;把里面的所有文件都永久删除了&#xff0c;里面一些重要的文件还能恢复吗&#xff1f;” 当我们在清理电脑的时候&#xff0c;一些重要的文件有时会夹杂着垃圾文件一起被我们清理干净&#xff0c;我们只能回想着刚刚窗口弹出来“永久删除…

Spring Boot集成Activity7实现简单的审批流

由于客户对于系统里的一些新增数据&#xff0c;例如照片墙、照片等&#xff0c;想实现上级逐级审批通过才可见的效果&#xff0c;于是引入了Acitivity7工作流技术来实现&#xff0c;本文是对实现过程的介绍讲解&#xff0c;由于我是中途交接前同事的这块需求&#xff0c;所以具…

ospf的MGRE实验

第一步&#xff1a;配IP [R1-GigabitEthernet0/0/0]ip address 12.0.0.1 24 [R1-GigabitEthernet0/0/1]ip address 21.0.0.1 24 [R1-LoopBack0]ip address 192.168.1.1 24 [ISP-GigabitEthernet0/0/0]ip address 12.0.0.2 24 [ISP-GigabitEthernet0/0/1]ip address 21.0.0.2 24…

Python | Leetcode Python题解之第238题除自身以外数组的乘积

题目&#xff1a; 题解&#xff1a; class Solution:def productExceptSelf(self, nums: List[int]) -> List[int]:length len(nums)# L 和 R 分别表示左右两侧的乘积列表L, R, answer [0]*length, [0]*length, [0]*length# L[i] 为索引 i 左侧所有元素的乘积# 对于索引为…

C4D各版本软件下载+自学C4D 从入门到精通【学习视频教程全集】+【素材笔记】

下载链接&#xff1a; 迅雷网盘https://pan.xunlei.com/s/VO1tydOxEo-Az_QCM-Jz2R4RA1?pwdvxg4# 夸克网盘https://pan.quark.cn/s/fe7450b02d80 百度网盘https://pan.baidu.com/s/1Omj4WL93F1DNdA2iP4SiMQ?pwdwmb8

vue仿甘特图开发工程施工进度表

前言 本文是根据项目实际开发中一个需求开发的demo&#xff0c;仅用了elementUI&#xff0c;可当作独立组件使用&#xff0c;C V即用。 当然没考虑其他的扩展性和一些数据的校验&#xff0c;主要是提供一个处理思路&#xff0c;有需要的小伙伴可以直接复制&#xff1b;本demo的…

python-区间内的真素数(赛氪OJ)

[题目描述] 找出正整数 M 和 N 之间&#xff08;N 不小于 M&#xff09;的所有真素数。真素数的定义&#xff1a;如果一个正整数 P 为素数&#xff0c;且其反序也为素数&#xff0c;那么 P 就为真素数。 例如&#xff0c;11&#xff0c;13 均为真素数&#xff0c;因为 11 的反序…

open3d:随机采样一致性分割平面

1、背景介绍 随机采样一致性算法&#xff08;RANSAC Random Sample Consensus&#xff09;是一种迭代的参数估计算法&#xff0c;主要用于从包含大量噪声数据的样本中估计模型参数。其核心思想是通过随机采样和模型验证来找到数据中最符合模型假设的点。因此&#xff0c;只要事…