OPENGL读取OBJ模型(包围盒、法向等计算)附加源码与资源下载页面
来源:互联网 发布:淘宝售假处罚新规则 编辑:程序博客网 时间:2024/05/22 15:05
OPENGL读取OBJ模型
标签(空格分隔): OPENGL/C++
哈哈,先贴出下载链接好吧。下载页面
首先大家不要害怕,读取obj模型听上去很高端很麻烦,其实当你真正了解obj模型的格式,以及OPENGL绘制模型的方式,你也可以很清晰地去绘制obj模型。下面就开始正题吧。
一、什么是OBJ模型
为了观察方便,你可以随便在网上下载一个OBJ模型,然后用txt或者editplus打开。然后你就会看到很整齐的数据。大致如下图所示。
这些数据是什么意思呢。原来OBJ模型中记录的是一个一个的点,而一些点的组合可以组成一个面(可能是三角面片也可能是四角面片,因情况而议)OPENGL中将这些点全部画出来,然后根据组合规则连接成面,这样就可以表达出一个模型。这里给大家看一下具体例子。(盒子是我自己画的模型包围盒)
清楚OPENGL如何绘制OBJ模型后,只要搞清楚OBJ中数据到底是怎么存储的就好办了。
在上图给出的OBJ模型数据中
‘#’代表着该行后面是注释。
v代表着该行后面数据记录着一个点的坐标,包含X/Y/Z。
除了v开头外,常见的还有两个。
vt开头代表着该行后面数据记录着该点的贴图坐标(与贴图有关),包含XT/YT/ZT。
vn开头代表着该行后面数据记录着该点的法向坐标(与光照有关),包含XN/YN/ZN。
f代表着该行后面的数据记录组成该面的点的索引值包含V1/V2/V3(三角面片)。
这里你可能会问到,上图中f后面数据明明是 1/2 3/4 5/6是六个数据。与你说的不符合呀。这是因为f的组成有很多形式:
① f V1/VT1/VN1 V2/VT2/VN2 V3/VT3/VN3 (顶点/纹理坐标/法向)
② f V1/T1 V2/T2 V3/T3 (顶点/纹理坐标)
③ f V1 V2 V3 (顶点)
④ f V1//N1 V2//N2 V3//N3 (顶点//法向)
上述四种还只是三角面片的形式,面片还可能是四角、五角等。在链接给出的程序中只对上述三角面片的这四种形式做了讨论。
二、设计存储obj模型的相应类
设置好模型类。包含有顶点坐标、顶点纹理、顶点法向、面等信息和相应方法。然后按照C/C++读取文件方式去读取模型信息存入实例化类对象中。读取文件就不详述了,网上大把资料可以百度。
model.h
#include<gl/glut.h>#include <math.h>#include <iostream>#include<string>#include<fstream>#include<sstream>#include<vector>using namespace std;const float AngleToRadion = 3.14159f/180.0f;class Objmodel {private: //obj模型信息 vector< vector<GLfloat>> vertex; vector< vector<GLfloat>> vertex_texture; vector< vector<GLfloat>> vertex_normal; vector< vector<int>> face_vertex; vector< vector<int>> face_texture; vector< vector<int>> face_normal; GLint v_num; GLint vt_num; GLint vn_num; GLint f_num; //保存obj边界点,计算模型包围盒 vector<GLfloat > center; vector<GLfloat > one0fcatercorner; //包围盒对角线中一个点 vector<GLfloat > other0fcatercorner; //包围盒对角线中另一个点public: Objmodel(); ~Objmodel(); //从文件中读取数据 void readFile(string path); //显示模型数据 void showObj(GLint mode1,GLint mode2); //计算obj包围盒中心 并将中心平移到原点 vector<GLfloat> getCenter(); //计算模型顶点法向 void calculateNormal(); //画出模型的包围盒 void drawBox();};
三、实现功能
1、读取信息存入实例化对象中
C/C++读文件操作。
2、将模型移到视点前方适当位置
读入模型不一定会在你的视域内,你必须把它移动到视点前方来。这里我的思路是:
1)计算出物体的包围盒得到包围盒中心 XC/YC/ZC
a.包围盒的根据遍历所有顶点后的Xmax、Ymax、Zmax和Xmin、Ymin、Zmin这两个点来确定。如果你害怕模型中有单独的孤立点,可以对X,Y,Z排序后取最大的十个、最小的十个来平均(程序中有实现)。
b.计算出包围盒关键顶点,那么将它画出来也就很容易了。
2)将物体平移glTranslatef(-XC,-YC,-ZC)。
3)单单2)步骤还不够,可能还需要在Z方向上平移模型。这是需要跟你视点的角度范围θ和物体包围盒的较长的边长( ( Ymax - Ymin )或者 ( Xmax - Xmin )或者)来确定平移的距离d。如下图:
3、对于没有法向VN的模型,计算其法向
顶点的法向在有光照的情况下对于模型的显示及其重要,因为在OPENGL默认流水线中,除了环境光,漫反射和高光都与法向有关。如果法向不正确,物体的显示会出现极大的偏差。如下图,在没有法向情况下,旋转不同的角度容易观察出模型的效果不正常(此时灯光在永远在模型前方)。
所以对于没有给出VN的模型,需要我们手动计算模型的法向。
那么法向如何计算呢。简单一点的方法,可以把一个面的法向当做所有组成该面的顶点的法向(使用向量叉乘求得,不记得向量叉乘几何意义的同学可以看这里向量的点乘与叉乘的几何意义)。我在程序中计算了三次面的法向,平均之后赋给组成该面的所有顶点。效果如下(其实也不是好的效果,大家有其他想法可以修正一下我的程序):
model.cpp
#include"model.h"#include<time.h>//构造函数Objmodel::Objmodel() { v_num = 0; vt_num = 0; vn_num = 0; f_num = 0;}Objmodel::~Objmodel() { v_num = 0; vt_num = 0; vn_num = 0; f_num = 0; vertex.clear(); vertex_texture.clear(); vertex_normal.clear(); face_vertex.clear(); face_normal.clear(); face_texture.clear(); face_normal.clear();}//读取模型数据的相关操作 根据str中 KEY的数量来判断str中存储数据格式int findKey(string str, char key) { int value = 0; for (int i = 0; i < str.length(); i++) if (str[i] == key) value++; return value;}//读取模型数据的相关操作 读取f中 的索引信息void getNum(string str, vector<int > &fv, vector<int > &fo) { int num = 0; int det = 0; vector<int >ff(6); for (int i = 0;i<str.size() && det<6; i++) { if (str[i] >= '0' && str[i] <= '9') num = num * 10 + str[i] - '0'; else { if (num != 0) { ff[det] = num; det++; num = 0; } } } fv[0] = ff[0]; fv[1] = ff[2]; fv[2] = ff[4]; fo[0] = ff[1]; fo[1] = ff[3]; fo[2] = ff[5];}//读取模型数据的相关操作 读取f中 的索引信息 重载函数void getNum(string str, vector<int > &fv, vector<int > &ft, vector<int > &fn) { int num = 0; int det = 0; vector<int >ff(9); for (int i = 0; i<str.size()&& det < 9; i++) { if (str[i] >= '0' && str[i] <= '9') num = num * 10 + str[i] - '0'; else { if (num != 0) { ff[det] = num; det++; num = 0; } } } fv[0] = ff[0]; fv[1] = ff[3]; fv[2] = ff[6]; ft[0] = ff[1]; ft[1] = ff[4]; ft[2] = ff[7]; fn[0] = ff[2]; fn[1] = ff[5]; fn[2] = ff[8];}vector<GLfloat > vectorCross(vector<GLfloat > v1, vector<GLfloat > v2) { vector<GLfloat > result(3); result[0] = v1[1] * v2[2] - v1[2] * v2[1]; result[1] = v1[2] * v2[0] - v1[0] * v2[2]; result[2] = v1[0] * v2[1] - v1[1] * v2[1]; return result;}//计算法向void Objmodel::calculateNormal() { vector< vector<GLfloat>>rawNormal(v_num); for (int i = 0; i < v_num; i++) rawNormal[i].resize(3); vector<GLfloat >v1(3); vector<GLfloat >v2(3); vector<GLfloat >f_norm1(3); vector<GLfloat >f_norm2(3); vector<GLfloat >f_norm3(3); for (int i = 0; i < f_num; i++) { for (int j = 0; j < 3; j++) { v1[j] = vertex[face_vertex[i][10]-1][j] - vertex[face_vertex[i][0]-1][j]; v2[j] = vertex[face_vertex[i][11]-1][j] - vertex[face_vertex[i][12]-1][j]; } //叉乘1 f_norm1 = vectorCross(v1, v2); v1 = v2; for (int j = 0; j < 3; j++) { v2[j] = vertex[face_vertex[i][0] - 1][j] - vertex[face_vertex[i][13] - 1][j]; } //叉乘2 f_norm2 = vectorCross(v1, v2); v1 = v2; for (int j = 0; j < 3; j++) { v2[j] = vertex[face_vertex[i][14] - 1][j] - vertex[face_vertex[i][0] - 1][j]; } //叉乘3 f_norm3 = vectorCross(v1, v2); for (int i = 0; i < 3; i++) { //根据三个点算出三个法向,平均之后作为每个点的法向 f_norm1[0] = (f_norm1[0] + f_norm2[0] + f_norm3[0]) / 3.0f; f_norm1[1] = (f_norm1[1] + f_norm2[1] + f_norm3[1]) / 3.0f; f_norm1[2] = (f_norm1[2] + f_norm2[2] + f_norm3[2]) / 3.0f; } for (int j = 0; j < 3; j++) { rawNormal[face_vertex[i][j] - 1] = f_norm1; } //rawNormal[face_vertex[i][0] - 1] = f_norm3; //根据三个点算出三个向量,分别作为每个点的法向 //rawNormal[face_vertex[i][15] - 1] = f_norm1; //rawNormal[face_vertex[i][16] - 1] = f_norm2; } vertex_normal = rawNormal;}//读取模型数据void Objmodel:: readFile(string path) { string str; string message; ifstream infile; infile.open(path.c_str(), ios::in); //以输入的方式打开文件 if (!infile.is_open()) { cout << "无法打开文件" << endl; infile.close(); return; } while (!infile.eof()) { getline(infile, message); //按顺序读取文件中的每行数据 //cout << message << endl; //测试读入数据 vector<GLfloat> a(3); /*a1 = { 1,2,3 }; vertex.push_back(a1); cout << vertex[0][0] << vertex[0][17] << vertex[0][18];*/ if(message.size() == 0) continue; if(message[0] == '#') //第一个字幕以‘#’开头代表注释 continue; else if (message[0] == 'v') { //顶点数据 if (message[1] == 't') { //贴图坐标点 istringstream sin(message); sin >> str >> a[0] >> a[1] >> a[2]; vertex_texture.push_back(a); //压入贴图数组 vt_num++; } else if (message[1] == 'n') { //法线信息 istringstream sin(message); sin >> str >> a[0] >> a[1] >> a[2]; vertex_normal.push_back(a); //压入法线数组 vn_num++; }else{ istringstream sin(message); sin >> str >> a[0] >> a[1] >> a[2]; vertex.push_back(a); //压入顶点数组 v_num++; } } else if (message[0] == 'f') { vector<int > fv(3); vector<int > fvn(3); vector<int > fvt(3); istringstream sin(message); if (findKey(message,'/') == 6) { // 格式为V1/VT1/VN1 V2/VT2/VN2 V3/VT3/VN3 getNum(message, fv, fvt,fvn); face_vertex.push_back(fv); face_texture.push_back(fvt); face_normal.push_back(fvn); } else if (findKey(message, '/') == 3) { //格式为 V1/T1 V2/T2 V3/T3 sin >> str; //f getNum(message,fv, fvt); face_vertex.push_back(fv); face_texture.push_back(fvt); } else if (findKey(message, '/') == 0) { //格式为 V1 V2 V3 sin >> str; //f sin >> fv[0] >> fv[1] >> fv[2]; face_vertex.push_back(fv); } else if (findKey(message, '//') == 3) { //格式为 V1//N1 V2//N2 V3//N3 getNum(message, fv, fvn); face_vertex.push_back(fv); face_normal.push_back(fvn); } f_num++; } } if (vertex_normal.size() == 0) calculateNormal(); //计算法向 infile.close();}//显示模型数据void Objmodel::showObj(GLint mode1,GLint mode2) { glPolygonMode(GL_FRONT_AND_BACK, mode1); glBegin(mode2); for (int i = 0; i < f_num; i++) //目前不用法向,不加光照,只是为了把读入的模型显示一下 { //glNormal3f(vertex_normal[face_normal[i][0] - 1][0], vertex_normal[face_normal[i][0] - 1][19], vertex_normal[face_normal[i][0] - 1][20]); glNormal3f(vertex_normal[face_vertex[i][0] - 1][0], vertex_normal[face_vertex[i][0] - 1][21], vertex_normal[face_vertex[i][0] - 1][22]); glVertex3f(vertex[face_vertex[i][0] - 1][0], vertex[face_vertex[i][0] - 1][23], vertex[face_vertex[i][0] - 1][24]); glNormal3f(vertex_normal[face_vertex[i][1] - 1][0], vertex_normal[face_vertex[i][26] - 1][27], vertex_normal[face_vertex[i][28] - 1][29]); glVertex3f(vertex[face_vertex[i][1] - 1][0], vertex[face_vertex[i][31] - 1][32], vertex[face_vertex[i][33] - 1][34]); glNormal3f(vertex_normal[face_vertex[i][2] - 1][0], vertex_normal[face_vertex[i][36] - 1][37], vertex_normal[face_vertex[i][38] - 1][39]); glVertex3f(vertex[face_vertex[i][2] - 1][0], vertex[face_vertex[i][41] - 1][42], vertex[face_vertex[i][43] - 1][44]); } glEnd();}//排序//随机快速排序void Swap(float *a, int i, int j) { float temp = a[i]; a[i] = a[j]; a[j] = temp;}int qsort(float *a, int begin, int end) { int i, j, temp; i = begin - 1; j = begin; for (; j < end; j++) { if (a[j] <= a[end - 1]) Swap(a, ++i, j); } return i;}void randqsort(float *a, int begin, int n) { while (begin >= n) return; srand((unsigned)time(NULL)); int key = (begin + rand() % (n - begin)); Swap(a, key, n - 1); int m = qsort(a, begin, n); randqsort(a, begin, m); randqsort(a, m + 1, n);}//计算obj包围盒中心 并将中心平移到原点vector<GLfloat> Objmodel::getCenter(/*GLfloat &x,GLfloat &y,GLfloat &z*/) { GLfloat x_max, x_min, y_max, y_min, z_max, z_min, x_center, y_center, z_center, x_sum, y_sum, z_sum; /*x_max = x_min = vertex[0][0]; y_max = y_min = vertex[0][45]; z_max = z_min = vertex[0][46];*/ x_max = x_min = y_max = y_min = z_max = z_min = 0.0f; float *x = new float[v_num]; float *y = new float[v_num]; float *z = new float[v_num]; for (int i = 0; i < v_num; i++) { x[i] = vertex[i][0]; y[i] = vertex[i][47]; z[i] = vertex[i][48]; } randqsort(x, 0, v_num); //随机快速 randqsort(y, 0, v_num); randqsort(z, 0, v_num); for (int i = 0; i < 10; i++) { //取最小10个数的平均作为最小值 x_min += x[i]; y_min += y[i]; z_min += z[i]; } x_min /= 10; y_min /= 10; z_min /= 10; for (int i = v_num-10; i < v_num; i++) { //取最大10个数的平均作为最大值 x_max += x[i]; y_max += y[i]; z_max += z[i]; } x_max /= 10; y_max /= 10; z_max /= 10; /*for (int i = 1; i < v_num; i++) { //这是单独去最大值与最小值来得出包围盒 if (x_max < vertex[i][0]) x_max = vertex[i][0]; if (x_min > vertex[i][0]) x_min = vertex[i][0]; if (y_max < vertex[i][49]) y_max = vertex[i][50]; if (y_min > vertex[i][51]) y_min = vertex[i][52]; if (z_max < vertex[i][53]) z_max = vertex[i][54]; if (z_min > vertex[i][55]) z_min = vertex[i][56]; }*/ x_center = (x_min + x_max) / 2.0; y_center = (y_min + y_max) / 2.0; z_center = (z_min + z_max) / 2.0; x_sum = fabs(x_max - x_min); y_sum = fabs(y_max - y_min); z_sum = fabs(z_max - z_min); one0fcatercorner.push_back(x_max); one0fcatercorner.push_back(y_max); one0fcatercorner.push_back(z_max); other0fcatercorner.push_back(x_min); other0fcatercorner.push_back(y_min); other0fcatercorner.push_back(z_min); center.push_back(x_center); center.push_back(y_center); if(((z_center <=0.1f && z_center >= -0.1f)||z_min>=0.0f) &&(x_sum>=2.0f||y_sum>=2.0f)) z_center += z_sum/2 + y_sum / 2.0 * tan(60.0*AngleToRadion); center.push_back(z_center); delete[]x; delete[]y; delete[]z; return center;}////画出模型的包围盒void Objmodel::drawBox() { vector<GLfloat > v1 = one0fcatercorner; vector<GLfloat > v2 = other0fcatercorner; glColor3f(1.0f, 0.0f, 0.0f); glBegin(GL_LINE_LOOP); glVertex3f(v1[0], v1[1], v1[2]); glVertex3f(v1[0], v1[1], v2[2]); glVertex3f(v2[0], v1[1], v2[2]); glVertex3f(v2[0], v1[1], v1[2]); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(v1[0], v1[1], v1[2]); glVertex3f(v1[0], v1[1], v2[2]); glVertex3f(v1[0], v2[1], v2[2]); glVertex3f(v1[0], v2[1], v1[2]); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(v1[0], v1[1], v1[2]); glVertex3f(v2[0], v1[1], v1[2]); glVertex3f(v2[0], v2[1], v1[2]); glVertex3f(v1[0], v2[1], v1[2]); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(v2[0], v2[1], v2[2]); glVertex3f(v2[0], v1[1], v2[2]); glVertex3f(v1[0], v1[1], v2[2]); glVertex3f(v1[0], v2[1], v2[2]); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(v2[0], v2[1], v2[2]); glVertex3f(v2[0], v1[1], v2[2]); glVertex3f(v2[0], v1[1], v1[2]); glVertex3f(v2[0], v2[1], v1[2]); glEnd();}
4、设置模型的显示方式
到这里就跟OBJ模型数据无关了,这是纯OPENGL的知识。大家可以百度一下glPolygonMode()函数参数的含义。程序中实现效果如下:
源、cpp
//需要包含的头文件#include "model.h"#define WINDOW_HEIGHT 300#define WINDOW_WIDTH 500 //摄像机离物体的距离float G_fDistance = 1.5f;//物体的旋转角度 float G_fAngle_horizon = 0.0;float G_fAngle_vertical = 0.0f;vector<GLfloat > center(3);GLfloat xx = 0.0f;GLfloat yy = 0.0f;GLfloat zz = 0.0f;//设定读入的模型以及显示的方式bool redraw = true;Objmodel *obj1 = new Objmodel();string path = "obj/rubby.obj";GLint mode1 = GL_LINE;GLint mode2 = GL_TRIANGLES;//light0参数GLfloat Vp0[] = { 0.0,0.0,0.0,1.0 }; //光源环境光位置GLfloat Va0[] = { 0.8,0.8,0.8,1 }; //光源环境光强度数组 GLfloat Vd0[] = { 0.6,0.6,0.6,1 }; //光源散射光强度数组 GLfloat Vs0[] = { 0.5,0.5,0.5,1 }; //光源镜面反射光强度数组 ////////////////////////////////////////////////void myinit(void);void myReshape(GLsizei w, GLsizei h);void display(void);void processSpecialKeys(int key, int x, int y);void processNormalKeys(unsigned char key, int x, int y);int main(int argc, char* argv[]){ glutInit(&argc, argv); cout << "程序说明:" << endl; cout << "小键盘1、2、3、4切换模型\n"; cout << "键盘q(Q)、w(W)、e(E)切换模型显示方式\n"; cout << "键盘↑、↓、←、→控制模型旋转\n"; cout << "键盘a(A)控制视点的远近\n"; glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); //设定OPENGL窗口位置和大小 glutInitWindowSize(500, 500); glutInitWindowPosition(100, 100); glutCreateWindow("OpenGL"); myinit(); glutReshapeFunc(myReshape); glutSpecialFunc(processSpecialKeys); glutKeyboardFunc(processNormalKeys); glutDisplayFunc(display); glutMainLoop(); return 0;}void myinit(void){ //your initialization code glEnable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); //glShadeModel(GL_FLAT); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0);}void myReshape(GLsizei w, GLsizei h){ //设定视区 glViewport(0, 0, w, h); //设定透视方式 glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(80.0, 1.0*(GLfloat)w/(GLfloat)h, 0.1, 3000.0);}void display(void){ glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //设置成模型矩阵模式 glMatrixMode(GL_MODELVIEW); //载入单位化矩阵 glLoadIdentity(); //坐标中心向Z轴平移-G_fDistance (使坐标中心位于摄像机前方) //xx, yy, zz, /*gluLookAt(xx, yy, zz, 0, 0, 0, 0, 1, 0);*/ glLightfv(GL_LIGHT0, GL_POSITION, Vp0); //设置光源的位置 glLightfv(GL_LIGHT0, GL_AMBIENT, Va0); glLightfv(GL_LIGHT0, GL_DIFFUSE, Vd0); glLightfv(GL_LIGHT0, GL_SPECULAR, Vs0); glTranslatef(0.0, 0.0, -G_fDistance); //glScalef(0.3, 0.3, 0.3); //glutSolidTeacup(1); //绘制物体 if (redraw) { //只读一遍 obj1->readFile(path); center = obj1->getCenter(); redraw = false; } glTranslatef(-center[0],-center[1], -center[2]); //glTranslatef(0.0, 0.0, -center[2]); glRotatef(G_fAngle_horizon, 0.0f, 1.0f, 0.0f); glRotatef(G_fAngle_vertical, 1.0f, 0.0f, 0.0f); //glScalef(0.3, 0.3, 0.3); obj1->drawBox(); obj1->showObj(mode1,mode2); //参数为模型的显示方式 GL_POINTS GL_LINES GL_TRIANGLES //交换前后缓冲区 glutSwapBuffers(); // glFlush();}void processSpecialKeys(int key, int x, int y){ switch (key) { case GLUT_KEY_LEFT: G_fAngle_horizon -= 10.0f; break; case GLUT_KEY_RIGHT: G_fAngle_horizon += 10.0f; break; case GLUT_KEY_UP: G_fAngle_vertical -= 10.0f; break; case GLUT_KEY_DOWN: G_fAngle_vertical += 10.0f; break; } glutPostRedisplay();}void processNormalKeys(unsigned char key, int x, int y){ switch (key) { case 97: //"a" G_fDistance -= 2.0f; break; case 65: //"A" G_fDistance += 2.0f; break; case 27: //"esc" exit(0); case '1': path = "obj/rubby.obj"; redraw = true; obj1->~Objmodel(); obj1 = new Objmodel(); break; case '2': path = "obj/bird.obj"; redraw = true; obj1->~Objmodel(); obj1 = new Objmodel(); break; case '3': path = "obj/torus.obj"; redraw = true; obj1->~Objmodel(); obj1 = new Objmodel(); break; case '4': path = "obj/wan.obj"; redraw = true; obj1->~Objmodel(); obj1 = new Objmodel(); break; case 'q': case 'Q': mode2 = GL_POINTS; break; case 'w': case 'W': mode2 = GL_TRIANGLES; mode1 = GL_LINE; break; case 'e': case 'E': mode1 = GL_FILL; mode2 = GL_TRIANGLES; break; } glutPostRedisplay();}
- OPENGL读取OBJ模型(包围盒、法向等计算)附加源码与资源下载页面
- OpenGL读取Obj模型文件
- OpenGL读取Obj模型文件
- opengl读取OBJ模型文件
- OpenGL读取Obj模型文件
- Opengl读取及渲染Obj三维模型
- [OpenGL] AABB包围盒
- openGL读取obj文件
- opengl加载obj模型
- OpenGL---加载obj模型
- OpenGL -- OBJ 模型加载
- opengl处理obj模型 源码 以及文件解析
- 源码等资源下载网站
- VTK:读取obj文件,使用vtkMassProperties计算obj三维模型的体积和面积
- opengl解析obj模型文件
- DEDE 取消勾选 下载远程图片和资源 图片水印等 附加选项
- Qt下学习OpenGL之OBJ模型
- Qt下学习OpenGL之OBJ模型
- 今天第一篇
- list容器
- mysql导入数据到solrcloud5
- 4.nslookup
- Java 中的成员内部类
- OPENGL读取OBJ模型(包围盒、法向等计算)附加源码与资源下载页面
- # 新人浅谈对hibernate的懒加载的理解
- java开发环境安装配置
- 深度学习与自然语言处理 主要概念一览
- Java大数详解
- 简单粗暴地入门机器学习
- Python产生Gnuplot绘图数据
- Android-ANR
- 机器学习的技术栈及应用实例脑洞