素材中有四个.bmp格式的纹理文件和一个.txt的模型参数文件

文件格式说明:

纹理文件数量纹理文件1(字符串)//.bmp纹理文件2(字符串)纹理文件3(字符串)...
材质数量ambient(float[4])diffuse(float[4])specular(float[4]])emission(float[4])shininess(float[1])纹理文件索引(int[1])//0表示无ambient(float[4])diffuse(float[4])specular(float[4]])emission(float[4])shininess(float[1])纹理文件索引(int[1])//0表示无ambient(float[4])diffuse(float[4])specular(float[4]])emission(float[4])shininess(float[1])纹理文件索引(int[1])//0表示无..
顶点数量v1(float[3])v2(float[3])v3(float[3])..
贴图坐标数量t1(float[2])t2(float[2])t3(float[2])..
法线数量n1(float[3])n2(float[3])n3(float[3])..
模型分组数量
缩放系数(float[3])submodel1 三角形数量材质索引(int)//0表示无vi1 ti1 ni1 vi2 ti2 ni2 vi3 ti3 ni3 (unsigned int[9])vi1 ti1 ni1 vi2 ti2 ni2 vi3 ti3 ni3 (unsigned int[9])vi1 ti1 ni1 vi2 ti2 ni2 vi3 ti3 ni3 (unsigned int[9])..submodel2 三角形数量材质索引(int)//0表示无vi1 ti1 ni1 vi2 ti2 ni2 vi3 ti3 ni3 (unsigned int[9])vi1 ti1 ni1 vi2 ti2 ni2 vi3 ti3 ni3 (unsigned int[9])vi1 ti1 ni1 vi2 ti2 ni2 vi3 ti3 ni3 (unsigned int[9])...

人物分为四个子模型,分别有不同的顶点坐标,纹理文件,材质。

纹理文件名用来打开纹理文件

材质列表是用来设置每个子模型的材质,opengl中设置材质的函数将会用到这些参数,下面会详细说明

顶点列表给出了需要使用的所有顶点,这里不分子模型,不直接绘出(当然直接画出来也没关系,只是没有必要),而是在连线时引用这些参数

贴图坐标给出了二维的纹理文件到三维的人物模型表面的映射,每个三维顶点将会对应一个二维坐标,具体的对应关系会在文件的其他部分给出

法线给出了光照的参数,光照在生成时会依据三维物体表面的法线属性,一个三位顶点对应一个法线,具体的对应关系 会在文件的其他部分给出

每个子模型由一个三角形阵列表示,每个三角形由九个参数给出,三个vi表示三个顶点的索引,三个ti表示三个贴图坐标的索引,三个ni表示三个法线向量的索引。(索引为上面读入的数组,注意这里的索引是从1开始的)

人物绘制

模型是由多个三角形面片组合在表面形成的,每个三角形由三个顶点坐标指定,并且每个顶点有对应的法线参数,对应的纹理坐标,在OpenGL中指定后程序会自动的使用这些参数绘制。

将人物的绘制分为三步:画出线框图-->添加界面交互-->添加纹理

这里先画出线框图是为了先看到效果,实际在一步绘制时用的是三角形绘制而不是线,但是三角形绘制的结果是一片白,线框可以看到细节

界面交互是用来指定三维变换,三维观察,投影等参数

绘制线框图

这里只是用到了txt中的顶点坐标和三角形索引中的ti值

main函数中进行初始化:

int main(int argc, char *argv[])                                //主函数: 参数数量&参数值
{glutInit(&argc, argv);                                  //初始化glut: 接收主函数的参数glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);            //显示模式:颜色&缓冲glutInitWindowPosition(0, 0);                           //窗口相对屏幕位置glutInitWindowSize(720, 720);                           //窗口大小glutCreateWindow("luweiqi");               //创建窗口: 标题glutDisplayFunc(&display);                //指定显示函数,这个函数由自己实现gluLookAt(0.1, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); //这个是三维观察函数,这里不详细说明,如果不指定这些参数人物的角度会很奇怪glutMainLoop();                        //主循环,程序执行到这里将循环调用指定的display函数return 0;
}

display函数进行绘制:

void display(void)
{glClear(GL_COLOR_BUFFER_BIT);         //当前背景色填充窗口//画线glBegin(GL_LINE_LOOP);                //参数指定绘制类型,将会根据类型确定每次取几个点绘制for (int i = 0; i < allTriangleNum; i++){glVertex3f(vertex[triangle[i][0]][0], vertex[triangle[i][0]][1], vertex[triangle[i][0]][2]);                               //三个顶点坐标:glVertex3f(vertex[triangle[i][3]][0], vertex[triangle[i][3]][1], vertex[triangle[i][3]][2]);glVertex3f(vertex[triangle[i][6]][0], vertex[triangle[i][6]][1], vertex[triangle[i][6]][2]);}glEnd();                              //结束绘制,一旦缺失就可能会导致人物的某部分缺失glFlush();                            //输出缓冲区
}

效果:

有些线比较奇怪是因为本来这些索引是用于三角形(GL_TRIANGLES)绘制的,这里为了显示细节用了(GL_LINE_LOOP)

添加界面交互

三维变换的实现:

OpenGL中给了三维变换函数:

glTranslatef (GLfloat x, GLfloat y, GLfloat z);            //平移
glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z);//旋转
glScalef (GLfloat x, GLfloat y, GLfloat z);                //缩放

但是这里我们手动实现:

为了使平移旋转缩放都统一为矩阵乘法运算,使用齐次坐标

即将三维坐标扩展第四维,但是第四维恒为1,这样变换矩阵就变为:

| 1 0 0 dx | //平移
| 0 1 0 dy |
| 0 0 1 dz |
| 0 0 0 1  |
| sx 0 0 0 | //缩放
| 0 sy 0 0 |
| 0 0 sz 0 |
| 0 0  0 1 |

旋转矩阵需推导,不详述

所以就是将上一步的内容增加一步将所有的顶点坐标乘以指定的变换矩阵,在根据变换后的顶点绘制

void matrixMul(GLfloat matrix[4][4]){for (int i = 0; i < NumVertexs; i++)for (int j = 0; j < 4; j++)for (int k = 0; k < 4; k++)vertexChanged[i][j] += matrix[j][k] * vertex[i][k];
}

我将新旧图形画在一起了,这样方便看出效果:(旋转变换)

三维观察:

gluLookAt (GLdouble eyex, GLdouble eyey, GLdouble eyez,          //视点GLdouble centerx, GLdouble centery, GLdouble centerz, //观察中心 GLdouble upx, GLdouble upy, GLdouble upz);            //观察正向

透视投影:将构建出一个观察椎体

gluPerspective (//对称观察椎体GLdouble fovy, //竖直张角GLdouble aspect, //水平张角GLdouble zNear, //近观察平面GLdouble zFar);//远观察平面

glFrustum (//非对称观察椎体
GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble zNear,
GLdouble zFar
);

平行投影:(观察立方体)

gluOrtho2D (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);

交互:

在main函数中添加键盘事件:

glutKeyboardFunc(void (*func)(unsigned char key, int x, int y));

指定一个当发生键盘输入事件时要调用的函数func

func的第一个参数为按键类型,用于说明按得是哪个键,xy说明了按下按键时鼠标相对于屏幕左上角的坐标

在main函数中添加鼠标事件:

glutMouseFunc(void (*func)(int button, int state, int x, int y));

指定一个当发生鼠标点击/移动事件时要调用的函数func

func的第一个参数说明按的是哪个键,第二个说明了是按下还是松开等状态,xy说明了鼠标相对于屏幕左上角的坐标

添加菜单:

我实在发生鼠标点击事件时调用下面的函数,用于实现点击弹出菜单

glutCreateMenu(void (*)(int));

参数指定一个函数,函数的参数是选择的条目索引,用于根据索引采取不同的操作

glutAddMenuEntry(const char *label, int value);

用于给菜单添加条目,参数分别为菜单条目名,条目索引值

我实现交互的方式就是通过鼠标点击弹出菜单,根据菜单的选择指定当前修改的参数是哪些,通过键盘修改这些参数,实现对图像的改变。键盘修改的是恒定的指针中的内容,选择菜单就是指定这些指针指向了那些参数,参数设置为全局变量。

全局变量:

GLdouble translateX, translateY, translateZ;//平移
GLdouble rotateAngle, rotateX, rotateY, rotateZ;//旋转
GLdouble scaleX = 1.0, scaleY = 1.0, scaleZ = 1.0;//缩放
GLdouble fovy = 0, aspect = 0, zFar = 0, zNear = 0;//投影
GLdouble eyex = 0.1, eyey = 0.2, eyez = 0, centerx = 0, centery = 0, centerz = 0, upx = 0, upy = 0, upz = 1.0;//三维观察
GLdouble* indexA = NULL, *indexB = NULL, *indexC = NULL, *indexD = NULL;

鼠标点击函数:

void mouseFunc(GLint button, GLint action, GLint xMouse, GLint yMouse)
{glutCreateMenu(chooseMode);glutAddMenuEntry("translate", 0);glutAddMenuEntry("scale", 1);glutAddMenuEntry("rotate", 2);glutAddMenuEntry("perspective", 3);glutAddMenuEntry("glLookAt eye", 4);glutAddMenuEntry("glLookAt center", 5);glutAddMenuEntry("glLookAt up", 6);glutAttachMenu(GLUT_RIGHT_BUTTON);
}

菜单选择函数:

void chooseMode(GLint menuIteemNum)
{switch (menuIteemNum){case 0:indexA = &translateX; indexB = &translateY; indexC = &translateZ; indexD = NULL; break;case 1:indexA = &scaleX; indexB = &scaleY; indexC = &scaleZ; indexD = NULL; break;case 2:indexA = &rotateAngle; indexB = &rotateX; indexC = &rotateY; indexD = &rotateZ; break;case 3:indexA = &fovy; indexB = &aspect; indexC = &zFar; indexD = &zNear; break;case 4:indexA = &eyex; indexB = &eyey; indexC = &eyez; indexD = NULL; break;case 5:indexA = ¢erx; indexB = ¢ery; indexC = ¢erz; indexD = NULL; break;case 6:indexA = &upx; indexB = &upy; indexC = &upz; indexD = NULL; break;default:indexA = NULL; indexB = NULL; indexC = NULL; indexD = NULL; break;}
}

键盘输入函数:

void keyBoardFunc(unsigned char key, int x, int y)
{if (key == 'q')if (indexA)*indexA += 0.05;if (key == 'a')if (indexA)*indexA -= 0.05;if (key == 'w')if (indexB)*indexB += 0.05;if (key == 's')if (indexB)*indexB -= 0.05;if (key == 'e')if (indexC)*indexC += 0.05;if (key == 'd')if (indexC)*indexC -= 0.05;if (key == 'r')if (indexD)*indexD += 0.05;if (key == 'f')if (indexD)*indexD -= 0.05;//输出参数方便调整cout << "glScale:\t" << scaleX << '\t' << scaleY << '\t' << scaleZ << '\t' << endl;cout << "glRotate:\t" << rotateAngle * 20 << '\t' << rotateX << '\t' << rotateY << '\t' << rotateZ << endl;cout << "glTranslate:\t" << translateX << '\t' << translateY << '\t' << translateZ << endl;cout << "glLookAt eye:\t" << eyex << '\t' << eyey << '\t' << eyez << endl;cout << "glLookAtCenter:\t" << centerx << '\t' << centery << '\t' << centerz << endl;cout << "glLookAt up:\t" << upx << '\t' << upy << '\t' << upz << endl;cout << "glperspective:\t" << fovy * 20 << '\t' << aspect << '\t' << zFar << '\t' << zNear << '\t' << endl << endl;display();
}

效果:

添加纹理

大致流程:先根据.bmp文件读出纹理,再绑定纹理,在绘制的时候就可以用txt中的贴图坐标指定纹理了。

读纹理有多种方法

方法一:glaux库(库文件添加是另一个问题,假设你的库文件添加好了)

AUX_RGBImageRec * APIENTRY auxDIBImageLoadA(LPCSTR);

参数是指定格式的纹理文件名,返回一个指定格式的文件,该格式的文件在下面的函数中使用,之后就可以直接在画点是使用贴图了

glTexImage2D (GLenum target, GLint level, GLint internalformat,
GLsizei width, GLsizei height,
GLint border, GLenum format, GLenum type,
const GLvoid *pixels);

方法二:这个方法不用使用glaux库

bool LoadTexture(LPCSTR szFileName) // Creates Texture From A Bitmap File
{HBITMAP hBMP;             // Handle Of The BitmapBMP;                      // Bitmap StructureGLuint *texid=new GLuint; //生成后直接使用,不必放在外面glGenTextures(1, &texid); // Create The TexturehBMP = (HBITMAP)LoadImageA(GetModuleHandle(NULL), szFileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);if (!hBMP) {cout<<"load bit map error"<<endl;return FALSE; // If Not Return False}GetObject(hBMP, sizeof(BMP), &BMP); glPixelStorei(GL_UNPACK_ALIGNMENT, 4);glBindTexture(GL_TEXTURE_2D, texid); // Bind To The Texture IDglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Min FilterglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Mag FilterglTexImage2D(GL_TEXTURE_2D, 0, 3, BMP.bmWidth, BMP.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);DeleteObject(hBMP); // Delete The Objectreturn TRUE; // Loading Was Successful
}

在画图之前调用这个函数就可以了

方法三:使用SOIL库,简易OpenGL图像库(SimpleOpenGL Image Library)

前两种方法对bmp文件格式有要求,一旦格式不符合就会出错,如果想用的话就要将bmp文件转化为24位深的,但是这样会让最终的效果有偏差

但是这个库中的

unsigned intSOIL_load_OGL_texture(const char *filename,int force_channels,unsigned int reuse_texture_ID,unsigned int flags);

函数会自动根据文件的格式做不同的反应,返回值是一个纹理数据,好像并不需要再次调用glBindTexture函数绑定

texture[i] = SOIL_load_OGL_texture
(texture_file_name[index].c_str(),SOIL_LOAD_AUTO,SOIL_CREATE_NEW_ID,SOIL_FLAG_INVERT_Y
);

画图部分:

for (size_t j = 0; j < riangle_num; j++)//每次循环,三角形的三个顶点
{glNormal3fv("法线向量");//顶点一glTexCoord2f("贴图的二维坐标");glVertex3fv("顶点的三维坐标");glNormal3fv("法线向量");//顶点二glTexCoord2f("贴图的二维坐标");glVertex3fv("顶点的三维坐标");glNormal3fv("法线向量");//顶点三glTexCoord2f("贴图的二维坐标");glVertex3fv("顶点的三维坐标");
}

这里有很重要的一点:实际上的纹理贴图是要反转y轴的,即glTexCoord2f的第二个参数是(1-贴图y坐标)

材质添加:

使用函数:

glMaterialfv (GLenum face, GLenum pname, const GLfloat *params);

其中的pname是用来指定设置的是那种属性:如GL_AMBIENT/GL_DIFFUSE/GL_SPECULAR/GL_EMISSION/GL_SHININESS,设置镜面反射/漫反射等属性,具体的属性参数在parmsa中指定

在每次画图之前指定材质属性,画图是根据最后一次指定的材质属性绘制的

添加光照:

只有光照才能反映材质

在display之前,glEnable(GL_LIGHT0)来开启光源0,一共有八个光源

通过函数设置光源属性:

glLightfv (GLenum light, GLenum pname, const GLfloat *params);

pname的含义和上面设置材质时一致,而且还有一个GL_POSITION来设置光源位置

最后要开启光照:glEnable(GL_LIGHTING)

但是开启了光照好像没什么变化,但是其实是有变化的,关掉纹理,只画出白色的三角形面片就可以看出:

素材以及源代码:

原CSDN下载链接:luweiqi的素材文件链接:(并不知道怎么设置需要的积分为0)

https://download.csdn.net/download/qq_40711741/10435994

GitHub:https://github.com/biaoJM/luweiqi.git

转载于:https://www.cnblogs.com/biaoJM/p/10186713.html

opengl绘制三维人物luweiqi相关推荐

  1. Android开发笔记(一百五十三)OpenGL绘制三维图形的流程

    从这篇文章开始,接下来会连载一系列的OpenGL相关博文,好好探讨如何在Android中进行OpenGL开发. OpenGL的全称是"Open Graphics Library", ...

  2. android绘制过程3d图形,Android开发之OpenGL绘制三维图形的流程

    从这篇文章开始,接下来会连载一系列的OpenGL相关博文,好好探讨如何在Android中进行OpenGL开发. OpenGL的全称是"Open Graphics Library", ...

  3. 使用OpenGL绘制三维场景

    计算机图形学(OpenGL版) (第3版) COMPUTER GRAPHICS USING OpenGL 清华大学出版社 三维变换:在三维场景中如何把物体变换到所需的位置和朝向.(OpenGL 提供所 ...

  4. opengl绘制卡通人物哆啦A梦

    前段时间老师布置一作业,我画了一个哆啦A梦,代码如下 #include <Windows.h> #include <gl\glut.h> #include <stdio. ...

  5. D50.1.0 如何使用OpenGL绘制三维坐标系

    如何在屏幕左下角绘制直角坐标系? 第一步,指定屏幕绘制区域 第二步,设定投影效果.观察坐标及旋转缩放等 第三步,绘制坐标轴,绘制箭头 第四步,添加"xyz"字符 ​ ​ 第一,图中 ...

  6. 基于OpenGL的三维曲面数据场动态显示 (转)

    基于OpenGL的三维曲面数据场动态显示 2007-08-20 08:53 作者: 白婷 赵军 朱双华等 出处: 计算机与信息技术 责任编辑:方舟 摘 要 在大数据量条件下,实时动态显示三维曲面较困难 ...

  7. C++ VS OpenGL绘制教室三维立体旋转图像

    C++ VS OpenGL绘制教室三维立体旋转图像 如需安装运行环境或远程调试,可加QQ905733049, 或QQ2945218359由专业技术人员远程协助! 运行结果如下: 步骤: 第一步:安装V ...

  8. opengl三维图形绘制_Python matplotlib绘图示例 - 绘制三维图形

    Python matplotlib模块是扩展的MATLAB的一个绘图工具库.它可以绘制各种图形,下面就学习了下Python中的matplotlib模块,如何绘制三维图形. 示例代码一: # codin ...

  9. 【OpenGL】九、OpenGL 绘制基础 ( OpenGL 状态机概念 | OpenGL 矩阵概念 )

    文章目录 一.OpenGL 状态机概念 二.OpenGL 矩阵概念 上一篇博客 [OpenGL]八.初始化 OpenGL 渲染环境 ( 导入 OpenGL 头文件 | 链接 OpenGL 库 | 将窗 ...

最新文章

  1. 使用Minify合并css和js减少http请求
  2. tcpip运输层不同的两个协议_TCP/IP-运输层-你需要知道的运输层概念
  3. eclipse中配置Tomcat,并进行简单测试
  4. ORACLE会话数、连接数配置
  5. 域控制器的强制卸载,Active Directory系列之十四
  6. php嘲讽,PHP为何能够受到大家追捧,又为什么饱受嘲讽?
  7. metadata文件_用Kubernetes部署Springboot或Nginx,也就一个文件的事
  8. OpenCV学习——摄像头人脸识别
  9. 常见的二十种软件测试方法详解(史上最全)
  10. python---字典详解
  11. 微信小程序支持分享到朋友圈了
  12. galaxy s6 android8,这些三星机型确认可以升级Android 8.0
  13. 4103 yxc 的日常
  14. robots.txt
  15. WeChat------微信小程序相关动态页面代码总结
  16. 乱拔插U盘,导致U盘变成RAW未初始化格式,容量大小为0。
  17. KubeSphere DevOps流水线部署
  18. Python 生成个性二维码
  19. BAYC疯狂的一周:名人、巨鲸加持,无聊猿彻底与“穷人”说再见
  20. 38.6歌曲计算机,目前最火的6首歌曲, 38度6垫底, 沙漠骆驼第二, 第一百听不厌

热门文章

  1. IDE中使用package打包出现java.lang.TypeNotPresentException: Type org.springframework.boot.maven.RepackageMoj
  2. 如何压缩图片大小到200k以下?照片太大怎么压缩到200k?
  3. 魔方(9)四轴枫叶魔方、四轴斜转魔方
  4. CoreData数据库探索
  5. 最长公共子序列(LCS)算法
  6. LattePanda入手笔记
  7. Nginx $request_uri和$uri详解
  8. 进程平均周转时间的计算
  9. mac安装homebrew + git + nodejs
  10. 如何确定当前的iPhone /设备型号?