在看Cg教程中,看到关键桢插值来表示一个动画的物体,例如一个动物拥有站着,奔跑,下跪等动画序列,美工将这些特定的姿态称为一个关键桢。为什么要用关键桢这种来表示了,这个比较容易理解,我们知道我们看的一些卡通动画,都不是每桢来画的,都是准备一些关键的过渡动画,然后,美工人员在根据每二幅之间来补充一些中间的动画,以增加精细的效果。

  MD2模型文件就是存储一些关键桢的动画模型,格式还是很简单的,对比OBJ模型来说,更容易读取,分为几个主要部分,一部分是头文件,里面对相应的数据描述在那,如多个面,多少桢,从那读顶点,读桢都有说明,头文件后就是数据存放位置了。

  我们先来看下头文件的定义,有用的部分我做了注释。

 type Md2Header =
struct
val magic: int //MD2文件标示
val version: int //MD2版本
val skinWidth: int //纹理宽度
val skinHeight: int //纹理长度
val frameSize: int //桢的大小
val numSkins: int //
val numVertices: int //多少个顶点(每桢数量相同,数据不同)
val numTexCoords: int //多少个纹理顶点(所有桢共用)
val numTriangles: int //每桢由多少个三角形组成,所有桢是一样的
val numGlCommands: int //用VBO直接放弃
val numFrames: int //多少桢
val offsetSkins: int //
val offsetTexCoords: int //从那开始读纹理数据
val offsetTriangles: int //从那开始读三角形
val offsetFrames: int //从那开始读桢数据
val offsetGlCommands:int //无用
val offsetEnd: int //可以用来检查
end

MD2 头部格式

  然后就是对MD2模型文件的读取了,对MD2整个解析,不包含着色器代码只有200行,可以说读取与绘制比较容易,需要注意的是,一个MD2模型文件中三角形也就我们要画的面是所有桢共有的,在三角形中包含当前顶点的偏移量。这样在所有桢中,三角形的顶点不一样,但是他的纹理索引与纹理是一样的,每桢要画的三角形的个数也是一样的。所以在模型中,他们可以共有纹理缓冲区与顶点索引缓冲区,而每桢要自己建立顶点缓冲区,因顶点的不同,造成法线也会变,故每桢还需要自己建立法线缓冲区,下面是主要代码。

 type Md2Frame(md2Model: Md2Model,count:int) =
let mutable points = Array2D.create .f
let mutable vbo =
member val Vectexs = Array.create count Vector3.Zero
member val Name = ""
member this.VBO with get() = vbo
member this.Faces with get() : ArrayList<int[]*int[]> = md2Model.Faces
member this.TexCoords with get():ArrayList<float32*float32> = md2Model.TexCoords
member this.ElementCount with get() = md2Model.ElementCount
member this.DataArray
with get() =
if points.Length = then this.CreateData()
points
//MD2中不变的是面的面数.面里的顶点根据桢里保存的不同而不同,而面用的纹理是用的同一数据
member this.CreateData() =
let normals = Array.create this.Vectexs.Length (.f,Vector3.Zero)
//遍历第一次,生成面法线,记录对应点的共面,共法线信息
this.Faces.ForEach(fun p ->
let vi = fst p
let p1 = this.Vectexs.[vi.[]] - this.Vectexs.[vi.[]]
let p2 = this.Vectexs.[vi.[]] - this.Vectexs.[vi.[]]
let normal = -Vector3.Cross(p1,p2)
vi |> Array.iter(fun v ->
let mutable ind,n = normals.[v]
n <- n + normal
normals.[v] <- (ind+.f,n)
)
)
//平均点的法线信息并且组装N3fV3f
points <- Array2D.init this.ElementCount (fun i j ->
//当前面,当前面的第几个点
let m,n = i/,i%
let vi = fst this.Faces.[m]
match j with
| || ->
let vn = snd normals.[vi.[n]]/fst normals.[vi.[n]]
if j = then vn.X elif j = then vn.Y else vn.Z
| || ->
let p = this.Vectexs.[vi.[n]]
if j = then p.X elif j = then p.Y else p.Z
)
member this.CreateVBO() =
if vbo = then
vbo <- GL.GenBuffers()
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
GL.BufferData(BufferTarget.ArrayBuffer,IntPtr ( * this.ElementCount * ),this.DataArray,BufferUsageHint.StaticDraw) and Md2Model(fileName:string,?texureName:string) =
inherit ModelCommon()
let mutable vbo,ebo = ,
member val Header = Md2Header() with get,set
member val Faces = new ArrayList<int[]*int[]>()
member val TexCoords = new ArrayList<float32*float32>()
member val Frames = new ArrayList<Md2Frame>()
member val texID = with get,set
member val CurrentFrame = .f with get,set
member this.ElementCount with get() = this.Faces.Count *
member this.TotalFrames with get() = this.Frames.Count
member this.LoadModel() =
//加载纹理
if texureName.IsSome then
let dict = Path.GetDirectoryName(fileName)
this.texID <- TexTure.Load(Path.Combine(dict,texureName.Value))
//加载MD2程序
let file = new FileStream(fileName,FileMode.Open, FileAccess.Read)
let binary = new BinaryReader(file)
let size = Marshal.SizeOf(this.Header)
let mutable bytes = Array.create size 0uy
file.Read(bytes, , bytes.Length) |> ignore
let allocIntPtr = Marshal.AllocHGlobal(size)
Marshal.Copy(bytes,,allocIntPtr,size)
this.Header <- Marshal.PtrToStructure(allocIntPtr,typeof<Md2Header>) :?> Md2Header
//读取纹理数据
file.Seek(int64 this.Header.offsetTexCoords,SeekOrigin.Begin)|> ignore
let mTexCoords = Array.init this.Header.numTexCoords (fun p ->
float32 (binary.ReadInt16())/float32 this.Header.skinWidth,
float32 (binary.ReadInt16())/float32 this.Header.skinWidth
)
this.TexCoords.AddRange(mTexCoords)
//读取面数(顶点索引与纹理索引)
file.Seek(int64 this.Header.offsetTriangles,SeekOrigin.Begin)|> ignore
let mtriangles = Array.init this.Header.numTriangles (fun p ->
[|int (binary.ReadInt16());int (binary.ReadInt16());int (binary.ReadInt16())|],
[|int (binary.ReadInt16());int (binary.ReadInt16());int (binary.ReadInt16())|]
)
this.Faces.AddRange(mtriangles)
//读取所有桢
file.Seek(int64 this.Header.offsetFrames,SeekOrigin.Begin)|> ignore
let frames = Array.init this.Header.numFrames (fun p ->
let frame = Md2Frame(this,this.Header.numVertices)
let scale = Vector3(binary.ReadSingle(),binary.ReadSingle(),binary.ReadSingle())
let translate = Vector3(binary.ReadSingle(),binary.ReadSingle(),binary.ReadSingle())
let name = binary.ReadChars()
//这桢的所有点
let vectexs = Array.init this.Header.numVertices (fun t ->
let mvertex = [|binary.ReadByte();binary.ReadByte();binary.ReadByte()|]
let mlightNormalIndex = binary.ReadByte()
mvertex,mlightNormalIndex
)
//桢上的点精确化
vectexs |> Array.iteri(fun i v ->
frame.Vectexs.[i].X <- float32 (fst v).[] * scale.X + translate.X
frame.Vectexs.[i].Y <- float32 (fst v).[] * scale.Z + translate.Z
frame.Vectexs.[i].Z <- float32 (fst v).[] * -scale.Y - translate.Y
)
frame
)
this.Frames.AddRange(frames)
//生成正确的数据
binary.Close()
file.Close()
member this.FrameStep
with get() =
let currentFrame = int (Math.Floor(float this.CurrentFrame))
let step = this.CurrentFrame - float32 currentFrame
currentFrame,step
member this.CreateEBO() =
let len = this.ElementCount -
let eboData = [|..len|]
ebo <- GL.GenBuffers()
GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
GL.BufferData(BufferTarget.ElementArrayBuffer,IntPtr ( * this.ElementCount),eboData,BufferUsageHint.StaticDraw)
member this.CreateVBO() =
let texCoords = Array2D.init this.ElementCount (fun i j ->
//当前面,当前面的第几个点
let m,n = i/,i%
let ti = snd this.Faces.[m]
let u,v = this.TexCoords.[ti.[n]]
if j = then u else v
)
vbo <- GL.GenBuffers()
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
GL.BufferData(BufferTarget.ArrayBuffer,IntPtr ( * * this.ElementCount),texCoords,BufferUsageHint.StaticDraw)
member this.Render() =
if vbo = then this.CreateVBO()
if ebo = then this.CreateEBO()
if this.CurrentFrame >= float32 this.TotalFrames - .f then this.CurrentFrame <- .f
let currentFrame = this.Frames.[fst this.FrameStep]
let nextFrame = this.Frames.[fst this.FrameStep + ]
currentFrame.CreateVBO()
nextFrame.CreateVBO()
//当前桢的法线与顶点
GL.BindBuffer(BufferTarget.ArrayBuffer,currentFrame.VBO)
GL.InterleavedArrays(InterleavedArrayFormat.N3fV3f,,IntPtr.Zero)
//如果有纹理
if this.texID > && vbo > then
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
GL.ClientActiveTexture(TextureUnit.Texture0)
GL.EnableClientState(ArrayCap.TextureCoordArray)
GL.TexCoordPointer(,TexCoordPointerType.Float,,IntPtr.Zero)
//下一桢的法线与顶点存放在Texture1与Texture2
GL.BindBuffer(BufferTarget.ArrayBuffer,nextFrame.VBO)
//下一桢顶点
GL.ClientActiveTexture(TextureUnit.Texture1)
GL.EnableClientState(ArrayCap.TextureCoordArray)
GL.TexCoordPointer(,TexCoordPointerType.Float,,IntPtr )
//下一桢法线
GL.ClientActiveTexture(TextureUnit.Texture2)
GL.EnableClientState(ArrayCap.TextureCoordArray)
GL.TexCoordPointer(,TexCoordPointerType.Float,,IntPtr.Zero)
//绘画
GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
GL.DrawElements(BeginMode.Triangles,this.ElementCount,DrawElementsType.UnsignedInt,IntPtr.Zero)
//一定要按顺序执行这几行,不行,会影响后面的代码
GL.ClientActiveTexture(TextureUnit.Texture0)
GL.DisableClientState(ArrayCap.TextureCoordArray)
GL.ClientActiveTexture(TextureUnit.Texture1)
GL.DisableClientState(ArrayCap.TextureCoordArray)
GL.ClientActiveTexture(TextureUnit.Texture2)
GL.DisableClientState(ArrayCap.TextureCoordArray)

MD2 读取模型。

  一些部分我做了注释,相信看懂不难。这段代码有些长,因为读取与存取缓冲区,绘画全在这里了,介绍一下主要方法实现,为了免去桢与模型中的数据交换,故让他们互相引用,其中F#需要二个类用and来连接,Md2Model的方法LoadModel主要加载纹理,然后根据头文件里的各部分偏移量加载纹理坐标信息,加载三角形面数,加载桢数据,需要注意的量,纹理读取出的是当前像素位置,意思给opengl需要除以对应的长宽,而桢里的数据因为MD2模型生成工具的Z是向上的,Y是从人向屏幕的方向,而Opengl中Z是屏幕向人的方向,Y才是向上的,帮我们需要仔细对应。

  如前面所面,模型自己建立了纹理数组的缓冲区以及顶点索引缓冲区,在Md2Model中用vbo,ebo表示,而在桢里,需要自己建立桢自己的顶点与法线缓冲区,法线生成方法和上遍中OBJ模型中法线生成是一样的,定义一个和顶点一样长的数据,以顶点的下标来表示顶点的法线。

  建立了各个缓冲区,我们需要来画了,根据前面对关键桢的介绍,我想我们需要当前桢与下一桢的数据,在这里面,我们定义一个不断向前走的CurrentFrame,他在等于2.3时,我们知道,他在第二桢与第三桢之间,靠近第二桢多点。在Md2Model里的Renader有具体实现,对当前桢,我们以正常的方式传入,顶点,法线以OpenGL的方式来,但是下一桢的数据如何传了,在这里和上遍中OBJ传入切线的方法比较相似,我们用当前第几份纹理来存取,不用着色器可不容易取来当正确数据用了,分别设点当前纹理,然后存入对应下一桢的顶点与法线到对应的纹理坐标中,这里首先要注意,顶点与法线放在一个数组里,所以设定的时候要注意正确的偏移量,最后注意要执行下面的关闭纹理代码,不然会影响当前与后面执行过程。

  数据传入OpenGL后,我们需要在顶点着色器中执行插值过程,使之看起来连续,一般我们采用线性插值方式,使用的是Cg着色器语言,后面如果没特别指定,默认都是Cg着色器语言,相关如果启用Cg环境,请看上篇文章。  

 void v_main(float3 positionA : POSITION,
float3 normalA : NORMAL,
float2 texCoord : TEXCOORD0,
float3 positionB : TEXCOORD1,
float3 normalB : TEXCOORD2,
out float4 oPosition : POSITION,
out float3 objectPos : TEXCOORD0,
out float3 oNormal : TEXCOORD1,
out float2 oTexCoord : TEXCOORD2,
uniform float framstep,
uniform float4x4 mvp)
{
float3 position = lerp(positionA, positionB,framstep);//positionA;
oPosition = mul(mvp,float4(position,1.0));
oNormal = lerp(normalA, normalB,framstep);//normalA;
oTexCoord = texCoord;
objectPos = position.xyz;
}

顶点着色器

  整个过程很简单,对当前桢与下一桢做线性插值,传入的不带前缀的参数中,对应的后缀指向当前Opengl传入的数据,如POSITION是当前桢的顶点,Normal是当前桢的法线,而TEXCOORD1与TEXCOORD2分别指定下一桢的顶点与法线。带Out前缀的,除了POSITION后缀有意义,别的后缀都只是用来与片断着色器对应的,没有具体的意义。

  片断着色器和上篇中的一样,就不贴出来了,下面看下效果图。

  主要代码 引用DLL Md2模型文件 和前面一样,其中EDSF前后左右移动,鼠标右键加移动鼠标控制方向,空格上升,空格在SHIFT下降。

  大家组织好对应目录应该就可以看到效果了。

PS 2013/12/20 16:20.

  在上面的把数据从OpenGL传入着色器中时,模访的是Cg基础教程16课,但是总感觉别扭,把法线顶点分别放入纹理这种方式,就和前面把切线放入本来颜色位置一样,感觉不爽,虽然功能是实现了,但是代码总感觉阅读时容易出乱子,后查找得这个API(glvertexattribpointer),在GLSL里,着色器根据传入的attribut来对每个顶点附加数据,glsl里有的,没道理cg里没有,查找在http://3dgep.com/?p=2665中如下:

Cg defines the following default semantics and the default generic attribute ID’s that are bound to the semantic.

BINDING SEMANTICS NAME CORRESPONDING DATA
POSITION, ATTR0 Input Vertex, Generic Attribute 0
BLENDWEIGHT, ATTR1 Input vertex weight, Generic Attribute 1
NORMAL, ATTR2 Input normal, Generic Attribute 2
DIFFUSE, COLOR0, ATTR3 Input primary color, Generic Attribute 3
SPECULAR, COLOR1, ATTR4 Input secondary color, Generic Attribute 4
TESSFACTOR, FOGCOORD, ATTR5 Input fog coordinate, Generic Attribute 5
PSIZE, ATTR6 Input point size, Generic Attribute 6
BLENDINDICES, ATTR7 Generic Attribute 7
TEXCOORD0-TEXCOORD7, ATTR8-ATTR15 Input texture coordinates (texcoord0-texcoord7), Generic Attributes 8-15
TANGENT, ATTR14 Generic Attribute 14
BINORMAL, ATTR15 Generic Attribute 15

Don’t worry if this concept of semantics doesn’t make sense yet. I will go into more detail about semantics when I show how we send the vertex data to the shader program. I will just define a few macros that are used to refer to these predefined generic attributes.

  根据上面描述,把原来里面绘制二桢数据传值部分改为:

     member this.Render() =
if vbo = then this.CreateVBO()
if ebo = then this.CreateEBO()
if this.CurrentFrame >= float32 this.TotalFrames - .f then this.CurrentFrame <- .f
let currentFrame = this.Frames.[fst this.FrameStep]
let nextFrame = this.Frames.[fst this.FrameStep + ]
currentFrame.CreateVBO()
nextFrame.CreateVBO()
//当前桢的法线与顶点
GL.BindBuffer(BufferTarget.ArrayBuffer,currentFrame.VBO)
GL.VertexAttribPointer(,,VertexAttribPointerType.Float,false,,IntPtr )
GL.EnableVertexAttribArray()
GL.VertexAttribPointer(,,VertexAttribPointerType.Float,false,,IntPtr.Zero)
GL.EnableVertexAttribArray()
//如果有纹理
if this.texID > && vbo > then
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
GL.VertexAttribPointer(,,VertexAttribPointerType.Float,false,,IntPtr.Zero)
GL.EnableVertexAttribArray()
//下一桢的法线与顶点存放在Texture1与Texture2
GL.BindBuffer(BufferTarget.ArrayBuffer,nextFrame.VBO)
GL.VertexAttribPointer(,,VertexAttribPointerType.Float,false,,IntPtr )
GL.EnableVertexAttribArray()
GL.VertexAttribPointer(,,VertexAttribPointerType.Float,false,,IntPtr.Zero)
GL.EnableVertexAttribArray()
//绘画
GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
GL.DrawElements(BeginMode.Triangles,this.ElementCount,DrawElementsType.UnsignedInt,IntPtr.Zero)

新版 绘画动画

  着色器部分改为:

 void v_main(float3 positionA : ATTR0,
float3 normalA : ATTR3,
float2 texCoord : ATTR8,
float3 positionB : ATTR9,
float3 normalB : ATTR10,
out float4 oPosition : POSITION,
out float3 objectPos : TEXCOORD0,
out float3 oNormal : TEXCOORD1,
out float2 oTexCoord : TEXCOORD2,
uniform float framstep,
uniform float4x4 mvp)
{
float3 position = lerp(positionA, positionB,framstep);//positionA;
oPosition = mul(mvp,float4(position,1.0));
oNormal = lerp(normalA, normalB,framstep);//normalA;
oTexCoord = texCoord;
objectPos = position.xyz;
}

新版 着色器

  可以看到,完美运行,这部分附件就不放了,大家直接复制到原来的代码上就好了,其中,代码里的glvertexattribpointer给的序号与Opengl脱离顶点,法线等对应关系上,上面写的好像0对应顶点一样,实际我的代码开始也是根据对应关系来写的,但是根据实际测试,1放顶点,只要着色器ATTR1对应放顶点也是可以的,这样想想才是对的,都已经脱离固定管线了,本来传上来的数据各式各样,系统根据定义名称来对应本就死板,给我们自己联系就好.改好后,看这代码再也没别扭的地方了.

  

 

  

MD2关键桢动画3D模型加载.的更多相关文章

  1. WPF 3D动态加载模型文件

    原文:WPF 3D动态加载模型文件 这篇文章需要读者对WPF 3D有一个基本了解,至少看过官方的MSDN例子. 一般来说关于WPF使用3D的例子,都是下面的流程: 1.美工用3DMAX做好模型,生成一 ...

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

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

  3. OpenGL OBJ模型加载.

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

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

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

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

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

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

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

  7. 使用lua实现Spine动画的预加载

    创建spine动画有两种方法,分别是createwithfile和createwithdata. createWithFile是通过加载动作数据马上进行创建,如果spine动画中的json文件大小超过 ...

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

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

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

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

随机推荐

  1. CentOS7静态IP设置

    [root@localhost network-scripts]# pwd /etc/sysconfig/network-scripts [root@localhost network-scripts ...

  2. Android4.42-Settings源代码分析之蓝牙模块Bluetooth总体实现(总)

    本文为博主原创,转载请注明出处:http://blog.csdn.net/zrf1335348191/article/details/50995466 蓝牙相关代码已在另两篇文章中介绍,有须要的能够查 ...

  3. python geoip2使用

    使用geoip可以查询ip的详细地址信息,简单记录下使用方法(centos python2.7): 1.安装 yum -y install geoip geoip-devel pip install ...

  4. 菜鸟调错(五)——jetty运行时无法保存文件

    背景交代: 上一篇博客写的是用jetty和Maven做开发.测试.在使用的过程中遇到一个小问题,就是在jetty启动以后,修改了jsp.xml等文件无法保存. 错误信息: 解决方案: 到Maven库( ...

  5. ASP.NET Core2.0 环境下MVC模式的支付宝PC网站支付接口-沙箱环境开发测试

    1.新建.NET Core web项目 2.Controllers-Models-Views 分三个大部分 3.下载安装最新sdk 官方的SDK以及Demo都还是.NET Framework的,根据官 ...

  6. Shell脚本判断内容为None的方式

    1.判断变量 read -p "input a word :" word if [ ! -n "$word" ] ;then echo "you ha ...

  7. 每日英语:A Buying Guide to Air-Pollution Masks

    Blue skies were finally visible in the capital on Thursday after the region suffered fromseven strai ...

  8. IIS6 301重定向和IIS7 301重定向

    IIS6 301重定向 1.先在IIS里把网站正常发布,例如域名为(www.114390.com) 2.再硬盘上建一个空文件夹 3.再到IIS里建一个网站,例如域名为(114390.com),指向这个 ...

  9. Django-管理站点重写admin模板

    参考链接:https://blog.csdn.net/u013378306/article/details/79023242 使用Django的admin管理工具,可以快速的构建自己的管理平台,使用D ...

  10. 【爬虫】通用抽取网页URL

    package model; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; i ...