前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现。

  混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同的顶点,如果模型再细分一些,则比较恐怖了。在这基础上,则发展出了骨骼动画模型,原理说起来很简单,比如我们人类,做的各种动作具体都是由几个关节点来控制,比如你抬腿,你只把你大腿的骨骼调动起来,而大腿的肌肉跟着骨骼向上。由些我们只需要保存每桢的骨骼变动,然后再上面蒙上表皮。因此大量简单了顶点存储,并且,我们能方便的对骨骼实时改动就能添加不同的动画,但是因为骨骼的改变都是针对父骨骼来的,而蒙皮操作又是针对骷髅节点来做的,这些操作需要大量的运算。

  下面我们来解析MD5骨骼模型中,一些基本的概念与实现,在MD5,除去纹理图片,有二个比较主要的文件,一个是后缀为md5mesh的文件,一个是后缀为md5anim的文件,二个文件如他们的后缀名所表达的意思一样,前者和OBJ模型里的描述比较类似,主要包含每部分的顶点,面,纹理组成,不同于OBJ模型的的,这些元素是变化的,因此在OBJ模型有一些新的元素,如顶点不是单独的顶点,而是由一个或多个权重点构成,每个权重点关联着对应着骨骼节点,这样骨骼节点的改变能引起权重点的改变,而权重点的改变又引起了顶点的改变,至于为什么要用到权重点来连接骨骼和顶点,而不是直接用骷髅和顶点关联,首先拿我们来说,我们身上有些位置并不是只和一个骨骼节点有关,更多是和多个节点有关,这样能让动画更真实,也避免在关节点产生重合和断裂的现象。

  首先我们来解析md5mesh文件里的信息,在这个文件里,主要有二大元素,一个是骨骼节点信息,一个是多个部位蒙皮信息,下面我简化了一个md5mesh,实际肯定不可能这样,主要是用来说明各节点用的。

  

  在文件中我都加了注释,简单来说,第一行是版本信息,下面写的解析也是针对这个10的版本,然后是命令行,骨骼节点数,蒙皮组件数,然后是骨骼节点的具体信息,在这里包含每个骨骼的父索引,顶点位置,四元数(包含旋转信息).在这里特别说下,这个骨骼节点的顺序暗含他们自己的索引,还有特别一点,在md5mesh文件中,骨骼的顶点位置与旋转信息是针对模型空间的,后面我们会看到在md5anim也有骨骼节点下的顶点位置与旋转信息,但是那是针对父骨骼节点坐标来的。然后就是蒙皮各个部分的详细信息,包含纹理坐标位置,顶点数,面的信息,权重信息,如前面所说,一个面包含3个顶点,每个顶点包含多个权重,每个权重关联一个或多个骨骼信息。下面根据上面各个部位来定义我们代码里各个类:

 type ArrayList<'T> = System.Collections.Generic.List<'T>
let filtLine (line:string) = not (String.IsNullOrEmpty(line)) && line <> "(" && line <> ")"
let getLineData (line:string) = line.Split(' ','\t','\"') |> Array.filter filtLine
let getFloat str = snd (System.Single.TryParse(str))
let getInt str = snd (System.Int32.TryParse(str))
let getw x y z = 1.0f - x*x - y*y - z*z |> fun p -> if p < .f then .f else float32 (-Math.Sqrt(float p)) //--------mesh里用的结构
type Md5Joint() =
member val Name = "" with get,set
member val Index = - with get,set
//位移
member val Position = Vector3.Zero with get,set
//旋转
member val Quat = Quaternion.Identity with get,set
member val ParentIndex = - with get,set
member this.SetValue(line:string,ind:int) =
let ls = getLineData line// line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))//&& p <> @"\t" && p.Length > 0)
let pos,quat =
let gf str = snd (System.Single.TryParse(str))
let x,y,z,a,b,c = (gf ls.[]),(gf ls.[]),(gf ls.[]),(gf ls.[]),(gf ls.[]),(gf ls.[])
let d = getw a b c
Vector3(x,y,z),Quaternion(a,b,c,d)
this.Name <- ls.[]
this.ParentIndex <- snd (System.Int32.TryParse(ls.[]))
this.Position <- pos
this.Quat <- quat
this //Vert包含纹理坐标,以及关联的权重,根据权重求顶点
type Md5Vert() =
member val Index = - with get,set
member val Texcoord = Vector2.Zero with get,set
member val WeightStart = with get,set
member val WeightCount = with get,set
//经过权重求顶点实际位置
member val Position = Vector3.Zero with get,set
member val Normal = Vector3.Zero with get,set
member this.SetValue(line:string) =
let ls =getLineData line// line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))
let gf str = snd (System.Single.TryParse(str))
this.Index <- snd (System.Int32.TryParse(ls.[]))
this.Texcoord <- Vector2(gf ls.[],gf ls.[])
this.WeightStart <- snd (System.Int32.TryParse(ls.[]))
this.WeightCount <- snd (System.Int32.TryParse(ls.[]))
this
member this.DataArray with get() =[| this.Texcoord.X;this.Texcoord.Y;this.Position.X;this.Position.Y;this.Position.Z|] //三角形(包含Vert的索引)
type Md5Tri() =
member val Index = - with get,set
member val VertorIndexs = Array.create with get,set
member this.SetValue(line:string) =
let ls = getLineData line // line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))
let gi str = snd (System.Int32.TryParse(str))
this.Index <- gi ls.[]
this.VertorIndexs <- [|gi ls.[];gi ls.[];gi ls.[]|]
this //权重,(权重顶点用于计算Md5Vert,权重的JointIndex用于得到对应joint的四元数)
type Md5Weight() =
member val Index = - with get,set
member val JointIndex = - with get,set
member val Bias = .f with get,set
member val Position = Vector3.Zero with get,set
member this.SetValue(line:string) =
let ls = line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))
let gf str = snd (System.Single.TryParse(str))
this.Index <- snd (System.Int32.TryParse(ls.[]))
this.JointIndex <- snd (System.Int32.TryParse(ls.[]))
this.Bias <- gf ls.[]
this.Position <- Vector3(gf ls.[],gf ls.[],gf ls.[])
this type Md5Mesh() =
let mutable vbo,ebo = ,
member val TexID = with get,set
member val ShaderPath = "" with get,set
member val Verts = ArrayList<Md5Vert>() with get,set
member val Faces = ArrayList<Md5Tri>() with get,set
member val Weights = ArrayList<Md5Weight>() with get,set
member this.ElementCount with get() = this.Faces.Count *

蒙皮节点描述类

  这里的类与文件里各个描述部分差不多都是一一对应,很好理解,因元组在F#编译器级别的默认支持,使我们不用想尽办法组织结构,让结构和原始文件保持一致就行,然后要用到的时候因函数式操作相关便利性,很少的代码就能拿到需要组合的数据。

  在下面,我们具体处理如何加载md5mesh文件。

         let file = new StreamReader(fileName)
while not file.EndOfStream do
let str = file.ReadLine()
match str with
| StartsWith "joints" true ->
let mutable isJoint = true
while isJoint do
let joint = file.ReadLine()
isJoint <- not (joint.Contains("}"))
if isJoint then this.Joints.Add(Md5Joint().SetValue(joint,this.Joints.Count))
| StartsWith "mesh" true ->
let mutable isMesh = true
let md5mesh = Md5Mesh()
this.Meshs.Add(md5mesh)
while isMesh do
let mesh = file.ReadLine()
match mesh with
| StartsWith "shader" true ->
let dict = Path.GetDirectoryName(fileName)
let fileName = (getLineData mesh).[]// mesh.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p))) |> fun p -> p.[1].Trim('\"')
md5mesh.ShaderPath <- Path.Combine(dict,fileName)
| StartsWith "vert" true ->
md5mesh.Verts.Add(Md5Vert().SetValue(mesh))
| StartsWith "tri" true ->
md5mesh.Faces.Add(Md5Tri().SetValue(mesh))
| StartsWith "weight" true ->
md5mesh.Weights.Add(Md5Weight().SetValue(mesh))
| StartsWith "}" true ->
isMesh <- false
| _ -> printfn "%s" ("---------"+str)
| _ -> printfn "%s" str
file.Close()

读取蒙皮文件信息

  在这里,差不多就把蒙皮文件里的所有信息处理完毕。其实如果只是md5mesh,他就相当于一个复杂了些,包含了权重的OBJ模型,组织方式都大同小异,不信请看下面。我们记的在md5mesh前面骨骼也包含了顶点位置与四元数信息,根据这个,可以求得默认的权重点具体位置,然后就能得到顶点的具体位置,然后得到面,然后绘制,下面这段代码可以在没有md5anim文件里,绘制一个静态的,相当于OBJ模型一样功能的模型。  

 //先求得顶点的实际数据
this.Meshs.ForEach(fun mesh ->
mesh.Verts.ForEach(fun vert ->
for i in [vert.WeightStart .. vert.WeightStart + vert.WeightCount - ] do
let weigth = mesh.Weights.[i]
let joint = this.Joints.[weigth.JointIndex]
vert.Position <- vert.Position + (joint.Position + Vector3.Transform(weigth.Position,joint.Quat)) * weigth.Bias
)
)
this.Meshs.ForEach(fun mesh -> mesh.CreateVBO())

根据蒙皮文件绘制模型

  这段代码比较简单,就是上面所说,求面中的顶点,顶点根据权重求,权重根据骨骼当前状态来得到,还是和上面一样说明下,md5mesh里的骨骼节点是模型坐标系下的,所以骨骼节点不需要做转化。

  这里说下四元数,在3D中,我们表示旋转一般有矩阵,欧拉角,四元数,平常我们所用都是矩阵与欧拉角,四元数用到复数,理解起来比较麻烦,我现在也只是记着一些四元数的特性,能实现平滑插值,点p用四元数旋转后得到点p1=ap(a的逆).四元数和矩阵一样,满足结合律,但是不满足交换律。四元数的有向量部分v(x,y,z)和一个分量w,几何意义可以描述为对于一个向量n,旋转@角,四元数就是[w=cos(@/2),sin(@/2)*n]=[w=cos(@/2),sin(@/2)*nx,sin(@/2)*ny,sin(@/2)*nz],根据这个定义,可以推导出一些四元数的特性,如四元数的共轭和四元数代表相反的角位移,上面的p1=ap(a的逆).

  如果没有md5anim文件,MD5文件也就和OBJ文件一样,只是一个静态的模型,下面让我们来分析md5anim的相关格式。下面一样给出一个简化了的样式。

  各信息我给出了基本标注,比较重要的每秒多少桢,桢的具体信息,这个顺序与前面md5mesh是对应的,父索引也是一样的,不同的是,后面二个整数,一个表示应该读frame的那些数据,一个表示读的位置的起点。给出对应的代码格式。

 type Md5JointInfo() =
member val Name = "" with get,set
member val Index = - with get,set
member val ParentIndex = - with get,set
member val Flags = with get,set
member val StartIndex = with get,set
member this.SetValue(line:string,ind:int) =
let ls = getLineData line
this.Name <- ls.[]
this.ParentIndex <- getInt ls.[]
this.Index <- ind
this.Flags <- getInt ls.[]
this.StartIndex <- getInt ls.[]
this type Md5BaseFrame() =
member val Index = - with get,set
//位移
member val Positions = ArrayList<Vector3>() with get,set
//旋转
member val Quats = ArrayList<Quaternion>() with get,set
member this.SetValue(line:string) =
let ls = getLineData line
let pos,quat =
let gf str = snd (System.Single.TryParse(str))
let x,y,z,a,b,c = (gf ls.[]),(gf ls.[]),(gf ls.[]),(gf ls.[]),(gf ls.[]),(gf ls.[])
let d = getw a b c
Vector3(x,y,z),Quaternion(a,b,c,d)
this.Positions.Add(pos)
this.Quats.Add(quat) type Md5Frame() =
member val Index = - with get,set
member val Points = ArrayList<float32>() with get,set
member this.SetValue(line:string) =
let ls = getLineData line
let ds = ls |> Array.map (fun p -> getFloat p)
this.Points.AddRange(ds) //桢动画计算得出如下内容
type Md5SkeletonJoin() =
member val ParentIndex = - with get,set
//位移
member val Position = Vector3.Zero with get,set
//旋转
member val Quat = Quaternion.Identity with get,set
type Md5FrameSkeleton = ArrayList<Md5SkeletonJoin>

MD5动画格式

  分别定义了,Md5JointInfo,Md5BaseFrame,Md5Frame,大家可以看出多了Md5SkeletonJoin与Md5FrameSkeleton,没有与文件里的信息对应,这里就是要大家前面老注意的一个地方,在md5mesh文件,给的骨骼节点坐标已经是模型坐标系下的,而md5anim给出的骨骼节点坐标只是针对父骨骼节点里的,Md5SkeletonJoin与Md5FrameSkeleton就是Md5Frame根据父骨骼节点求出的在模型坐标系下的坐标。

  下面首先是加载md5anim信息的代码:

             let path = Path.Combine(Path.GetDirectoryName(fileName),animName.Value)
let animFile = new StreamReader(path)
while not animFile.EndOfStream do
let str = animFile.ReadLine()
match str with
| StartsWith "frameRate" true ->
this.Animation.FrameRate <- getFloat (getLineData str).[]
| StartsWith "hierarchy" true ->
let mutable isJoinHierarchy = true
while isJoinHierarchy do
let joint = animFile.ReadLine()
isJoinHierarchy <- not (joint.Contains("}"))
if isJoinHierarchy then this.Animation.JointInfos.Add(Md5JointInfo().SetValue(joint,this.Animation.JointInfos.Count))
| StartsWith "bounds" true ->
let mutable isbound = true
while isbound do
let bound = animFile.ReadLine()
isbound <- not (bound.Contains("}"))
if isbound then
let data = getLineData bound
let a,b,c,x,y,z = getFloat data.[],getFloat data.[],getFloat data.[],getFloat data.[],getFloat data.[],getFloat data.[]
this.Animation.Bounds.Add(Vector3(a,b,c),Vector3(x,y,z))
| StartsWith "baseframe" true ->
let mutable isFrame = true
let mf = this.Animation.BaseFrame
while isFrame do
let frameLine = animFile.ReadLine()
isFrame <- not (frameLine.Contains("}"))
if isFrame then mf.SetValue(frameLine)
| StartsWith "frame" true ->
let mutable isFrame = true
let mf = Md5Frame()
mf.Index <- getInt (getLineData str).[]
this.Animation.Frames.Add(mf)
while isFrame do
let frameLine = animFile.ReadLine()
isFrame <- not (frameLine.Contains("}"))
if isFrame then mf.SetValue(frameLine)
| _ -> printfn "%s" str
animFile.Close()
//把骨骼动画中,各节点由父骨骼节点坐标转化成模型坐标
this.Animation.CreateFrameSkeleton()
//生成纹理
this.Meshs.ForEach(fun mesh -> if mesh.TexID = && File.Exists mesh.ShaderPath then mesh.TexID <- TexTure.Load(mesh.ShaderPath))

加载MD5动画模型

  这部分代码也是一些IO操作,把读到的信息都放入Md5Animation里去,这个类主要做二件事,一是得到正确的Md5SkeletonJoin与Md5FrameSkeleton,就是得到Md5Frame根据父骨骼节点求出的在模型坐标系下的坐标。然后一些,就是根据当前时间,当前桢率得到正确的插值,这部分和MD2插值差不多。请看主要代码:

 type Md5Animation() =
let mutable currentTime = .f
member val FrameRate = .f with get,set
//JointInfos集合对象的索引就是本身在文件中的位置,他们本身的顺序就是正序加1
member val JointInfos = ArrayList<Md5JointInfo>() with get,set
member val Bounds = ArrayList<Vector3*Vector3>() with get,set
member val Frames = ArrayList<Md5Frame>() with get,set
member val BaseFrame = Md5BaseFrame() with get,set
member val FrameSkeletonList = ArrayList<Md5FrameSkeleton>() with get,set
//二个目标,一是转化Frames里的数据成对应一桢的所有骨骼节点信息
//二是把所有骨骼节点在父骨骼坐标系中的位置转化成模型坐标系
member this.CreateFrameSkeleton() =
for frame in this.Frames do
let md5FrameSkeleton = Md5FrameSkeleton()
for jointInfo in this.JointInfos do
//skeleton的顺序因为JointInfos的特殊性,也是正序加1
let skeleton = Md5SkeletonJoin()
md5FrameSkeleton.Add(skeleton)
skeleton.ParentIndex <- jointInfo.ParentIndex
let position = this.BaseFrame.Positions.[jointInfo.Index]
let quat = this.BaseFrame.Quats.[jointInfo.Index]
let setFlags index =
let flag = int (Math.Pow(float ,float index))
if jointInfo.Flags &&& flag = flag then frame.Points.[jointInfo.StartIndex + index]
else
match index with
| -> position.X
| -> position.Y
| -> position.Z
| -> quat.X
| -> quat.Y
| -> quat.Z
| _ -> quat.W
let x,y,z,a,b,c = setFlags ,setFlags ,setFlags ,setFlags ,setFlags ,setFlags
let w = getw a b c
//currentPos,currentQuat都是针对父骨骼来的坐标,要转化得到模型坐标
let currentPos,currentQuat=Vector3(x,y,z),Quaternion(a,b,c,w)
skeleton.Position <- currentPos
skeleton.Quat <- currentQuat
if skeleton.ParentIndex >= then
let parentSkeleton = md5FrameSkeleton.[skeleton.ParentIndex]
//先得到currentPos经过父骨骼四元数旋转后的值
let pos = Vector3.Transform(currentPos,parentSkeleton.Quat)
//模型坐标示下的点
skeleton.Position <- parentSkeleton.Position + pos
//模型坐标系下的四元数
skeleton.Quat <- Quaternion.Normalize(parentSkeleton.Quat * skeleton.Quat)
this.FrameSkeletonList.Add(md5FrameSkeleton)
member this.CurrentTime
with get() =
if currentTime > float32 this.Frames.Count/this.FrameRate then currentTime <- .f
currentTime
and set value = currentTime <- value
member this.GetCurrentFrameSkeleton() =
//得到当前的时间所在动画循环的位置
let current = this.CurrentTime * this.FrameRate
//得到所在位置的当前桢索引,与运动到下一桢的位置
let currentFrame,currentStep= int (Math.Floor(float current)),current - float32 (Math.Floor(float current))
//得到下一桢索引,到桢尾就从头开始
let nextFrame = if currentFrame < this.Frames.Count - then currentFrame + else
//得到当前桢,下一桢具体信息
let currentSkeleton,nexSkeleton = this.FrameSkeletonList.[currentFrame],this.FrameSkeletonList.[nextFrame]
let joints = ArrayList<Md5Joint>()
for i in [| .. this.JointInfos.Count - |] do
//根据当前桢位置求得对应四元数与顶点的插值
let lerpPosition = Vector3.Lerp(currentSkeleton.[i].Position,nexSkeleton.[i].Position,currentStep)
let slerpQuat = Quaternion.Slerp(currentSkeleton.[i].Quat,nexSkeleton.[i].Quat,currentStep)
joints.Add(Md5Joint(Index = i,Position = lerpPosition,Quat = slerpQuat))
joints

MD5动画计算模型坐标下骨骼节点与线性插值

  关键部分我都写了注释,应该容易看明白,如上面所说,二件事,一是CreateFrameSkeleton,这个首先根据flag与startIndex读取文件,然后把在父骨骼坐标系中的点转化成模型坐标系下的点。二是GetCurrentFrameSkeleton,分别得到所在时间的当前桢与下一桢,然后根据在这桢之间的位置插值得到各骨骼节点正确的坐标。渲染部分在这里,考虑到因为一个MD5模型本来包含几部分Mesh,然后每部分Mesh又包含各桢的情况,再想用MD2中关键桢顶点信息做VBO不现实,故直接用VA来输出渲染。  

 type Md5Model(fileName:string,?animName:string) =
member this.Render()=
//生成骨骼节点的信息
let joints = this.Animation.GetCurrentFrameSkeleton()
//根据骨骼节点生成顶点.也就是蒙皮
this.Meshs.ForEach(fun mesh ->
mesh.Verts.ForEach(fun vert ->
vert.Position <- Vector3.Zero
for i in [vert.WeightStart .. vert.WeightStart + vert.WeightCount - ] do
let weigth = mesh.Weights.[i]
let joint = joints.[weigth.JointIndex]
vert.Position <- vert.Position + (joint.Position + Vector3.Transform(weigth.Position,joint.Quat)) * weigth.Bias
)
)
//顶点绘制
this.Meshs.ForEach(fun mesh -> mesh.Render()) type Md5Mesh() =
member this.Render() =
let vboData = Array2D.init this.ElementCount (fun i j ->
let a,b = i/,i%
this.Verts.[this.Faces.[a].VertorIndexs.[b]].DataArray.[j]
)
GL.InterleavedArrays(InterleavedArrayFormat.T2fV3f,,vboData)
if this.TexID > then
GL.Enable(EnableCap.Texture2D)
GL.BindTexture(TextureTarget.Texture2D,this.TexID)
GL.DrawElements(BeginMode.Triangles,this.ElementCount,DrawElementsType.UnsignedInt,[|..this.ElementCount - |])

输出MD5动画

  到此,整个过程就差不多了,下面给出效果图:

  

  代码:源码与执行文件 http://files.cnblogs.com/zhouxin/MD5Load.zip 其中\bin\Release\CgTest.exe为可执行文件

  其中EDSF前后左右移动,鼠标右键加移动鼠标控制方向,空格上升,空格在SHIFT下降。再发现,整个工程中,去掉OBJ,MD2模型后,加上DLL一共27M,压缩下才5M,能上传上来,前面每次都分开上传给大家造成不便了其中为了突出MD5的重点,相应的法线没有自动生成,相关方法可以看前面OBJ,MD2里的,计算过程一样。

  CPU和GPU各应该执行的操作让我的理解应该是,一次计算很久变一次应该交给CPU,而在渲染过程快速,大量执行的代码应该交给GPU来算,下一步目标,改进里面关于骨骼位置的计算,以及相应蒙皮的操作应该交给GPU,也就是放到着色器中去处理。

MD5骨骼动画模型加载(一)的更多相关文章

  1. MD5骨骼动画模型加载

    前面我们分析了静态模型OBJ格式,桢动画模型MD2,这篇主要分析骨骼动画MD5的一些概念并且实现. 混合桢动画有计算简单,容易实现等优点,但是在需要比较细致的效果时,则需要更多的关键桢,每桢都添加相同 ...

  2. DirectX11 With Windows SDK--19 模型加载:obj格式的读取及使用二进制文件提升读取效率

    前言 一个模型通常是由三个部分组成:网格.纹理.材质.在一开始的时候,我们是通过Geometry类来生成简单几何体的网格.但现在我们需要寻找合适的方式去表述一个复杂的网格,而且包含网格的文件类型多种多 ...

  3. OpenGL OBJ模型加载.

    在我们前面绘制一个屋,我们可以看到,需要每个立方体一个一个的自己来推并且还要处理位置信息.代码量大并且要时间.现在我们通过加载模型文件的方法来生成模型文件,比较流行的3D模型文件有OBJ,FBX,da ...

  4. 6_1 持久化模型与再次加载_探讨(1)_三种持久化模型加载方式以及import_meta_graph方式加载持久化模型会存在的变量管理命名混淆的问题

    笔者提交到gitHub上的问题描述地址是:https://github.com/tensorflow/tensorflow/issues/20140 三种持久化模型加载方式的一个小结论 加载持久化模型 ...

  5. cesium模型加载-加载fbx格式模型

    整体思路: fbx格式→dae格式→gltf格式→cesium加载gltf格式模型 具体方法: 1. fbx格式→dae格式 工具:3dsMax, 3dsMax插件:OpenCOLLADA, 下载地址 ...

  6. Wish3D用户必看!模型加载失败原因汇总

    上传到Wish3D的模型加载不出来,作品显示页面漆黑一片,是什么原因? 很有可能是操作过程中的小失误,不妨从以下几点检查.还是不行的请加QQ群(Wish3D交流群3):635725654,@Wish3 ...

  7. 从零开始openGL——三、模型加载及鼠标交互实现

    前言 在上篇文章中,介绍了基本图形的绘制.这篇博客中将介绍模型的加载.绘制以及鼠标交互的实现. 模型加载 模型存储 要实现模型的读取.绘制,我们首先需要知道模型是如何存储在文件中的. 通常模型是由网格 ...

  8. PyTorch模型加载与保存的最佳实践

    一般来说PyTorch有两种保存和读取模型参数的方法.但这篇文章我记录了一种最佳实践,可以在加载模型时避免掉一些问题. 第一种方案是保存整个模型: 1 torch.save(model_object, ...

  9. CC模型加载太慢?一招破解!

    伴随无人机性能的提升,单个项目涉及到的倾斜摄影数据范围不断扩大,模型的数据量越来越大,在同配置机器上的显示速度也相应的越来越慢,那么如何在不升级配置的情况下提升模型的加载速度呢? 01 百GB倾斜摄影 ...

随机推荐

  1. [nginx]盗链和防盗链场景模拟实现

    盗链环境模拟 http://www.daolian.com/index.html 这个页面盗用http://www.maotai.com/qq.jpg这个站点页面的图. <!doctype ht ...

  2. IIS配置,权限

      2. cd  C:\Windows\Microsoft.NET\Framework64\v4.0.30319 3. aspnet_regiis.exe -i Chen 19:04:42 %wind ...

  3. PostgreSQL判断是否为空coalesce

    coalesce(expr1,expr2,expr3...) 直到找到一个非null值返回,右边的表达式则不参与运算:若所有为null,返回null. eg:判断json是否包含某属性,若无,则取默认 ...

  4. ZOOKEEPER解惑[转]

    今年年初的时候,写了一篇ZooKeeper的入门文章<初识ZooKeeper>,一直到这一周,才有时间将ZooKeeper整个源码通读了一遍.不能说完全理解了ZooKeeper的工作原理与 ...

  5. error: insufficient permissions for device(解决adb shell问题)

    今天在linux下连接平板usb,试用adb shell时出现error: insufficient permissions for device, 而且我们输入adb devices显示: xxna ...

  6. SQL2008R2 安装图解

    安装SQL Server 2008 R2需要.NET Framework 3.5 SP1支持 这里我们的操作系统是Windows Server 2008 R2,已经默认自带了.NET Framewor ...

  7. replace()的使用方法

    replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串. 数字类型的必须转换成字符串才能使用replace,否则会报错

  8. sql server 与oracle 中字段类型的对应

    SqlServer 2k转换为Oracle 10g 列名 SqlServer数据类型 SqlServer长度 Oracle数据类型 column1 ) column2 ) column3 ) colu ...

  9. LeetCode: Binary Tree Maximum Path Sum 解题报告

    Binary Tree Maximum Path SumGiven a binary tree, find the maximum path sum. The path may start and e ...

  10. Python3求m以内的素数、求m个数中最小的n个数

    [本文出自天外归云的博客园] 题1:求m以内的素数(m>2) def find_all_primes_in(m): def prime(num): for i in range(2, num): ...