C# winform用sharpGL(OpenGl)解析读取3D模型obj

原文作者:aircraft

原文链接:https://www.cnblogs.com/DOMLX/p/11783026.html

    自己写了个简单的类读取解析obj模型,使用导入类,然后new个对象,在读取obj模型,然后调用显示列表显示就可以了。至于其他什么旋转移动的你们自己加起来应该很容易的,因为我有看过c#下别人写的obj模型解析的代码项目,加了很多东西,我都找不到自己要用的代码在哪里,而我只需要读取解析obj模型这块代码而已,气的我自己写了个类自己解析,所以我怕我代码写多了,

你们反而看起来不好理解hhhhhh

在c++下用OpenGL解析的话可以看我其他博客

运行环境:vs2017,需要配置的库为:sharpGL

一.读取3D模型

  在3d图形处理中,一个模型(model)通常由一个或者多个Mesh(网格)组成,一个Mesh是可绘制的独立实体。例如复杂的人物模型,可以分别划分为头部,四肢,服饰,武器等各个部分来建模,这些Mesh组合在一起最终形成人物模型。

Mesh由顶点、边、面Faces组成的,它包含绘制所需的数据,例如顶点位置、纹理坐标、法向量,材质属性等内容,它是OpenGL用来绘制的最小实体。Mesh的概念示意如下图所示(来自:What is a mesh in OpenGL?):

Mesh

Mesh可以包含多个Face,一个Face是Mesh中一个可绘制的基本图元,例如三角形,多边形,点。要想模型更加逼真,一般需要增加更多图元使Mesh更加精细,当然这也会受到硬件处理能力的限制,例如PC游戏的处理能力要强于移动设备。由于多边形都可以划分为三角形,而三角形是图形处理器中都支持的基本图元,因此使用得较多的就是三角形网格来建模。例如下面的图(来自:What
is a mesh in OpenGL?)表达了使用越来越复杂的Mesh建模一只兔子的过程:

Mesh2

随着增加三角形个数,兔子模型变得越来越真实。

  读取3d模型有很多种方法,但是最常用的无非就是调用别人写好的库,比如(openmesh),其次呢就是自己读取解析3d模型文件里面的一个个坐标数据,什么v  vf  vn之类的。

那么现在就是讲一下第二种方法,就是直接解析读取3d模型文件,提取里面我们所需的数据。下面是一个obj的模型文件,我们可以使用记事本打开看看里面是什么:

# Blender3D v249 OBJ File: untitled.blend
# www.blender3d.org
mtllib cube.mtl
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vt 0.748573 0.750412
vt 0.749279 0.501284
vt 0.999110 0.501077
vt 0.999455 0.750380
vt 0.250471 0.500702
vt 0.249682 0.749677
vt 0.001085 0.750380
vt 0.001517 0.499994
vt 0.499422 0.500239
vt 0.500149 0.750166
vt 0.748355 0.998230
vt 0.500193 0.998728
vt 0.498993 0.250415
vt 0.748953 0.250920
vn 0.000000 0.000000 -1.000000
vn -1.000000 -0.000000 -0.000000
vn -0.000000 -0.000000 1.000000
vn -0.000001 0.000000 1.000000
vn 1.000000 -0.000000 0.000000
vn 1.000000 0.000000 0.000001
vn 0.000000 1.000000 -0.000000
vn -0.000000 -1.000000 0.000000
usemtl Material_ray.png
s off
f 5/1/1 1/2/1 4/3/1
f 5/1/1 4/3/1 8/4/1
f 3/5/2 7/6/2 8/7/2
f 3/5/2 8/7/2 4/8/2
f 2/9/3 6/10/3 3/5/3
f 6/10/4 7/6/4 3/5/4
f 1/2/5 5/1/5 2/9/5
f 5/1/6 6/10/6 2/9/6
f 5/1/7 8/11/7 6/10/7
f 8/11/7 7/12/7 6/10/7
f 1/2/8 2/9/8 3/13/8
f 1/2/8 3/13/8 4/14/8

对这个文本格式做一个简要说明:

  1. 以#开始的行为注释行
  2. usemtl和mtllib表示的材质相关数据,解析材质数据稍微繁琐,本节我们只是为了说明加载模型的原理,不做讨论。
  3. o 引入一个新的object
  4. v 表示顶点位置
  5. vt 表示顶点纹理坐标
  6. vn 表示顶点法向量
  7. f 表示一个面,面使用1/2/8这样格式,表示顶点位置/纹理坐标/法向量的索引,这里索引的是前面用v,vt,vn定义的数据 注意这里Obj的索引是从1开始的,而不是0

那么我们只要拿到这些数据,按照opengl的绘制的规则,不就可以把他们都绘制出来了吗?

读取数据obj的类myReadobj.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections;

using SharpGL;
namespace WindowsFormsApp5
{
    class myReadObj
    {
        public myReadObj()
        {

        }
        public class POINT3
        {
            public double X;
            public double Y;
            public double Z;
        };
        public class WenLi
        {
            public double TU;
            public double TV;
        };
        public class FaXiangLiang
        {
            public double NX;
            public double NY;
            public double NZ;
        };
        public class Mian
        {
            public int[] V = new int[3];
            public int[] T = new int[3];
            public int[] N = new int[3];
        };
        public class Model
        {

            public List<POINT3> V = new List<POINT3>();//V:代表顶点。格式为V X Y Z,V后面的X Y Z表示三个顶点坐标。浮点型
            public List<WenLi> VT = new List<WenLi>();//表示纹理坐标。格式为VT TU TV。浮点型
            public List<FaXiangLiang> VN = new List<FaXiangLiang>();//VN:法向量。每个三角形的三个顶点都要指定一个法向量。格式为VN NX NY NZ。浮点型
            public List<Mian> F = new List<Mian>();//F:面。面后面跟着的整型值分别是属于这个面的顶点、纹理坐标、法向量的索引。
                                                   //面的格式为:f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3
        }

        public Model mesh = new Model();
        public float movX;
        public float movY;
        public float movZ;
        public float xRotate;
        public float yRotate;
        public float x;
        public float y;

        //放缩参数
        public static float scale;
        //显示列表
        public uint showFaceList;

        public  int YU = 1;

        public void loadFile(String fileName)
        {
            // Mian[] f;
            //POINT3[] v;
            //FaXiangLiang[] vn;
            //WenLi[] vt;

            StreamReader objReader = new StreamReader(fileName);
            ArrayList al = new ArrayList();
            string texLineTem = "";
            while (objReader.Peek() != -1)
            {
                texLineTem = objReader.ReadLine();
                if (texLineTem.Length < 2) continue;
                if (texLineTem.IndexOf("v") == 0)
                {
                    if (texLineTem.IndexOf("t") == 1)//vt 0.581151 0.979929 纹理
                    {
                        string[] tempArray = texLineTem.Split(‘ ‘);
                        WenLi vt = new WenLi();
                        vt.TU = double.Parse(tempArray[1]);
                        vt.TV = double.Parse(tempArray[2]);
                        mesh.VT.Add(vt);
                    }
                    else if (texLineTem.IndexOf("n") == 1)//vn 0.637005 -0.0421857 0.769705 法向量
                    {
                        string[] tempArray = texLineTem.Split(new char[] { ‘/‘, ‘ ‘ }, System.StringSplitOptions.RemoveEmptyEntries);
                        FaXiangLiang vn = new FaXiangLiang();
                        vn.NX = double.Parse(tempArray[1]);
                        vn.NY = double.Parse(tempArray[2]);
                        if (tempArray[3] == "\\")
                        {
                            texLineTem = objReader.ReadLine();
                            vn.NZ = double.Parse(texLineTem);
                        }
                        else vn.NZ = double.Parse(tempArray[3]);

                        mesh.VN.Add(vn);
                    }
                    else
                    {//v -53.0413 158.84 -135.806 点
                        string[] tempArray = texLineTem.Split(‘ ‘);
                        POINT3 v = new POINT3();
                        v.X = double.Parse(tempArray[1]);
                        v.Y = double.Parse(tempArray[2]);
                        v.Z = double.Parse(tempArray[3]);
                        mesh.V.Add(v);
                    }
                }
                else if (texLineTem.IndexOf("f") == 0)
                {
                    //f 2443//2656 2442//2656 2444//2656 面
                    string[] tempArray = texLineTem.Split(new char[] { ‘/‘, ‘ ‘ }, System.StringSplitOptions.RemoveEmptyEntries);
                    Mian f = new Mian();
                    int i = 0;
                    int k = 1;
                    while (i < 3)
                    {
                        if (mesh.V.Count() != 0)
                        {
                            f.V[i] = int.Parse(tempArray[k]) - 1;
                            k++;
                        }
                        if (mesh.VT.Count() != 0)
                        {
                            f.T[i] = int.Parse(tempArray[k]) - 1;
                            k++;
                        }
                        if (mesh.VN.Count() != 0)
                        {
                            f.N[i] = int.Parse(tempArray[k]) - 1;
                            k++;
                        }
                        i++;
                    }
                    mesh.F.Add(f);

                }
            }

        }

        public uint createListFace(ref SharpGL.OpenGL gl)
        {
            gl.NewList(showFaceList, OpenGL.GL_COMPILE);
            if(mesh.V.Count() == 0) return 119;
            for (int i = 0; i < mesh.F.Count(); i++)
            {
                gl.Begin(OpenGL.GL_TRIANGLES);                          // 绘制三角形
                if (mesh.VT.Count() != 0) gl.TexCoord(mesh.VT[mesh.F[i].T[0]].TU, mesh.VT[mesh.F[i].T[0]].TV);  //纹理
                if (mesh.VN.Count() != 0) gl.Normal(mesh.VN[mesh.F[i].N[0]].NX, mesh.VN[mesh.F[i].N[0]].NY, mesh.VN[mesh.F[i].N[0]].NZ);//法向量
                gl.Vertex(mesh.V[mesh.F[i].V[0]].X / YU, mesh.V[mesh.F[i].V[0]].Y / YU, mesh.V[mesh.F[i].V[0]].Z / YU);        // 上顶点

                if (mesh.VT.Count() != 0) gl.TexCoord(mesh.VT[mesh.F[i].T[1]].TU, mesh.VT[mesh.F[i].T[1]].TV);  //纹理
                if (mesh.VN.Count() != 0) gl.Normal(mesh.VN[mesh.F[i].N[1]].NX, mesh.VN[mesh.F[i].N[1]].NY, mesh.VN[mesh.F[i].N[1]].NZ);//法向量
                gl.Vertex(mesh.V[mesh.F[i].V[1]].X / YU, mesh.V[mesh.F[i].V[1]].Y / YU, mesh.V[mesh.F[i].V[1]].Z / YU);        // 左下

                if (mesh.VT.Count() != 0) gl.TexCoord(mesh.VT[mesh.F[i].T[2]].TU, mesh.VT[mesh.F[i].T[2]].TV);  //纹理
                if (mesh.VN.Count() != 0) gl.Normal(mesh.VN[mesh.F[i].N[2]].NX, mesh.VN[mesh.F[i].N[2]].NY, mesh.VN[mesh.F[i].N[2]].NZ);//法向量
                gl.Vertex(mesh.V[mesh.F[i].V[2]].X / YU, mesh.V[mesh.F[i].V[2]].Y / YU, mesh.V[mesh.F[i].V[2]].Z / YU);        // 右下
                gl.End();                                        // 三角形绘制结束
            }
            gl.EndList();
            return  showFaceList;
        }
    }
}
自己拿来用的话改一下这句 为你们的命名空间名字就行了 namespace WindowsFormsApp5

因为前天才开始学c#,所以还是不太懂得c#的一些语法,写法风格也偏向我经常写的c++,大家将就着看吧。。。。。等我多学几天,学点c#的代码规范再重新改吧。。。

调用方法也很简单,在winform下的话,在openglControl控件的draw事件中加下面的代码:

第一步:New一个对象

第二步:读取自己路径下的obj模型文件

第三步:调用显示列表绘制图案

 private void openGLControl1_OpenGLDraw(object sender, SharpGL.RenderEventArgs args)
        {
            // 创建一个GL对象
            SharpGL.OpenGL gl = this.openGLControl1.OpenGL;

            gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);    // 清空屏幕
            gl.LoadIdentity();                    // 重置
            gl.Translate(0.0f, 0.0f, -6.0f);    // 设置坐标,距离屏幕距离为6

            gl.Rotate(0, 1.0f, 0.0f, 0.0f);    // 绕X轴旋转
            gl.Rotate(y, 0.0f, 1.0f, 0.0f);    // 绕Y轴旋转
            gl.Rotate(0, 0.0f, 0.0f, 1.0f);    // 绕Z轴旋转
            gl.Scale(0.003, 0.003, 0.003);
            myReadObj obj = new myReadObj();
            obj.loadFile("bunny.obj");
            obj.createListFace(ref gl);
            gl.CallList(obj.showFaceList);

        }

对了如果用opengl读取模型解析3d模型后看起来像个2d的样子,无非就是你的光照问题,或者模型文件里面没有顶点法线vt存在。。这时候可以借助一些3d模型操作软件 导入重新生成保存。

运行结果:

项目代码百度云链接关注下面公众号,添加小编微信,发送文章标题加源码获取。。。。。

若有兴趣交流分享技术,可关注本人公众号,里面会不定期的分享各种编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识

原文地址:https://www.cnblogs.com/DOMLX/p/11783026.html

时间: 2024-10-10 12:35:19

C# winform用sharpGL(OpenGl)解析读取3D模型obj的相关文章

关于c#winform用sharpGL(OpenGL)绘制不出图形,绘制窗口是个黑框的坑

原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11790309.html 在c++的opengl中可能是因为是最基本的库,很多东西都把你做好了 转到c#下用大牛们对opengl的支持库,比如sharpGL,使用起来需要非常的严谨 就比如: gl.Color(0.0f, 0.0f, 1.0f);这个设置颜色的代码 在c++下不管是0.0f也好还是0也好都可以,而在c#下sharpGL没有写0.0f的这种格式 图像就完全绘制不出来.让你完全找不

OpenGl 实现鼠标分别移动多个物体 ----------移动一个物体另外一个物体不动--读取多个3d模型操作的前期踏脚石

原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11620088.html 前言: 因为接下来的项目需求是要读取多个3D模型,并且移动拼接,那么我就先实现鼠标控制两个物体移动互不干扰来当踏脚石. 一.鼠标控制函数准备 我们需要对鼠标信息的获取,那么必然需要一个鼠标事件的响应函数来控制,很好opengl已经有内部的鼠标控制函数了,我们直接拿来使用就行了. glutMouseFunc( (void*)Func(int button, int st

openGL三维网格坐标,旋转,缩放,灯光设置,纹理读取,模型读取(MFC单文档)

最近学习计算机图形学写的基于opengGL的作业 源码下载链接:链接:http://pan.baidu.com/s/1slANShZ 密码:hbwj 1.三维网格坐标建立 2.基本3维图形创建 3.鼠标相应旋转缩放 4.键盘相应旋转缩放 5.灯光设置 6.纹理载入映射 7.读取模型 关于MFC配置编写openGL网上有很多教程 需要的函数创建一般是: OnCreat() OnDestroy() Onsize() PreCreateWindow() OnDraw() 在我的MFC单文档项目中ena

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图 +BIT祝威+悄悄在此留下版了个权的信息说: 最近需要用OpenGL绘制文字,这是个很费时费力的事.一般的思路就是解析TTF文件从而得到字形的贴图,然后通过OpenGL绘制贴图的方式显示文字. 本篇记录了解析TTF文件并把所有字形安排到一张大贴图上的过程. 使用FreeType 想从零开始解析TTF文件是一个比较大的工程,所以目前就借助FreeType.FreeType是一个开源的跨平台的TTF文件解析器.当然

Android中解析读取复杂word,excel,ppt等的方法

前段时间在尝试做一个Android里的万能播放器,能播放各种格式的软件,其中就涉及到了最常用的office软件.查阅了下资料,发现Android中最传统的直接解析读取word,excel的方法主要用了java里第三方包,比如利用tm-extractors-0.4.jar和jxl.jar等,下面附上代码和效果图. 读取word用了tm-extractors-0.4.jar包,代码如下: package com.example.readword; import java.io.File; impor

C#+OpenGL+FreeType显示3D文字(3) - 用PointSprite绘制文字

C#+OpenGL+FreeType显示3D文字(3) - 用PointSprite绘制文字 上一篇实现了把文字绘制到OpenGL窗口,但实质上只是把含有文字的贴图贴到矩形模型上.本篇我们介绍用PointSprite绘制文字,这可以只用1个点绘制文字,并确保文字始终面相窗口.用PointSprite绘制的文字,其大小范围有限,本篇提供的Demo中,Max Row Width最大只有256.现在能够绘制少量的文字,为其指定的位置的过程与为一个点指定位置的过程是相同的,所以此方式的应用范围还是比较广

C#游戏之路-winform,wpf,directx,opengl的了解

终于审核通过了,开通了我的博客,虽然这么多年,已经长期在从事java相关的架构工作,但是,其实我内心,喜欢的语言一直是c#,可惜,很多时候光靠一人的力量,改变不了什么,现在的我,打算抛开自己的工作,做自己真正喜欢的事情,我开这个博客的目的主要就是为了用c#来开发大型游戏,我的第一个目标,打算使用c#实现<传奇>这样的大型网络游戏,这样的游戏会分成服务端和客户端分别开发,我的前期目标是先实现客户端,如果想了解服务端的可以订阅我,我后续会一并写完的. 从现在开始重拾8年前的c#确实有点生疏,但其实

分享非常有用的Java程序 (关键代码)(六)---解析/读取XML 文件(重要)

原文:分享非常有用的Java程序 (关键代码)(六)---解析/读取XML 文件(重要) XML文件 <?xml version="1.0"?> <students> <student> <name>John</name> <grade>B</grade> <age>12</age> </student> <student> <name>Mar

Java解析(读取)Json数据

以前看过书上说,XML是web service间传输信息的标准格式吧,就看了看XML.最近在做个网站,又说是有了JSON,第一回听说就看了看,总结总结一下. 1.JSON介绍 JSON比XML简单,主要体现在传输相同信息的情况下,文件的大小不同. JSON只用于传输信息,XML还可以用于配置文件的使用. JSON中的符号主要有: " , [ {: 2.JSON中的数组和对象 2.1数组(JSONArray) 数组用一对[],表示存放的是一般的数组数据. 如:["11",&qu