素材中有四个.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 Bitmap
BMP;                      // Bitmap Structure
        GLuint *texid=new GLuint; //生成后直接使用,不必放在外面
glGenTextures(1, &texid); // Create The Texture
hBMP = (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 ID
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Min Filter
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Mag Filter
glTexImage2D(GL_TEXTURE_2D, 0, 3, BMP.bmWidth, BMP.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits); DeleteObject(hBMP); // Delete The Object return TRUE; // Loading Was Successful
}

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

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

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

但是这个库中的

unsigned int
SOIL_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

opengl绘制三维人物luweiqi的更多相关文章

  1. QT OpenGL绘制三维图形(立方体、圆柱体、圆锥、球体、圆环等等)

    本文使用QGLWidget来绘制各种三维基本图形,包括立方体.圆柱体.圆锥.球体.圆环等等,涉及包括基本绘制以及上色.纹理.旋转等操作. 使用的软件版本:QT5.12 + QT Creater4.8. ...

  2. 基于OpenGL的三维曲面动态显示实现

    在使用Visual C++的MFC AppWizard建立应用程序框架后,生成了多个类,与OpenGL编程相关的类是视图类,主要的显示任务都在其中完成. 1.基于OpenGL绘图的基本设置 1.1 设 ...

  3. Opengl绘制我们的小屋(二)第一人称漫游

    这章我们先讲第一人称漫游的实现.在openTK里,我们用函数Matrix4.LookAt(caram.Eye,caram.Target,Vector3.UnitY)来放置摄像机,其中三个参数分别与摄像 ...

  4. OpenGl 绘制一个立方体

    OpenGl 绘制一个立方体 为了绘制六个正方形,我们为每个正方形指定四个顶点,最终我们需要指定6*4=24个顶点.但是我们知道,一个立方体其实总共只有八个顶点,要指定24次,就意味着每个顶点其实重复 ...

  5. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

  6. matlab绘制三维图形

    原文地址:种三维曲面图. 程序如下: [x,y]=meshgrid(-8:0.5:8); z=sin(sqrt(x.^2+y.^2))./sqrt(x.^2+y.^2+eps); subplot(2, ...

  7. opengl绘制正弦曲线

    利用opengl绘制正弦曲线 ,见代码: #include <windows.h> //#include <GLUT/glut.h> #include <GL/glut. ...

  8. OpenGL绘制自由落体小球

    OpenGL绘制自由落体小球 一.    程序运行的软硬件环境 本次设计在window10系统下进行,运用C++进行编写,在CodeBlocks环境下使用OpenGL进行设计. 所需环境配置分为2部分 ...

  9. OpenGL绘制简单场景,实现旋转缩放平移和灯光效果

    本项目实现了用OpenGL绘制一个简单场景,包括正方体.球体和网格,实现了物体的旋转.缩放.平移和灯光效果.附有项目完整代码.有具体凝视.适合刚開始学习的人熟悉opengl使用. 开发情况 开发环境V ...

随机推荐

  1. Linux 进程间通信(IPC)

    Linux 进程间通信(IPC): Linux系统中除了进程和进程之间通信,我想大家也应该关注用户空间与内核空间是怎样通信的.例如说netlink等等. 除了传统进程间通信外像Socket通信也须要掌 ...

  2. springboot actuator shutdown正确的关闭操作

    今天整合ehcache时发现一个很重要的问题,就是程序关闭(硬关闭)之后,持久化到磁盘的缓存数据没能正确写入加载,问题还是硬关闭的问题,所以就使用actuator 进行监听 <dependenc ...

  3. 131.lambda表达式小结

    C++ 11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作.Lambda的语法形式如下:[函数对象参数](操作符重载函数参数) mutable或exception声明->返回 ...

  4. 关于iOS声音识别的框架

    你好,我现在的项目中需要用到"声纹识别"这方面的需求,以前没做过,请教了.有没有这方面的框架和工具? 关于iOS声音识别的框架 >> ios这个答案描述的挺清楚的:ht ...

  5. 日前加拿大平板厂商 Datawind和印度运营商Reliance Communications日前宣布合作

    全球最便宜智能手机只要15美元 随着手机进入智能时代,这些年智能手机的发展可谓迅猛,苹果三星这样的手机厂商成为最大的受益者同时,低门槛也让越来越多的人开始意识到,全民智能时代确实要来了. 为了能让第三 ...

  6. 数据库Tsql语句创建--约束--插入数据

    1.创建数据库 use master go if exists(select * from sysdatabases where name='数据库名字') drop database 数据库名字 g ...

  7. php八大设计模式之简介篇

    设计模式的在面向对象中的重要性?       更深入的理解面向对象的思想,有利于开发出扩展性强的程序.在 PHP 面向对象中有一个 "开闭原则" :"软件实体应当对扩展开 ...

  8. [NOIP2009提高组]最优贸易

    题目:洛谷P1073.Vijos P1754.codevs1173. 题目大意:有n点m边的图,边分有向和无向.每个点有一个价格,用这个价格可以买入或卖出一个东西.一个人从1出发,要到n,途中可以买入 ...

  9. windows下用winscp的root连接ubuntu“拒绝访问”的解决方法

    转载:https://www.cnblogs.com/weizhxa/p/10098640.html 解决: 1.修改ssh配置文件:sudo vim etc/ssh/sshd_config 在#Pe ...

  10. 解决zabbix容器中文无法选择的问题

    1.查看zabbiz-web运行的容器 2.进入容器 3.安装语言包(针对centos7) yum install -y kde-l10n-chinese 4.更新gitbc包(因为镜像阉割了该包的部 ...