FIRE

Nothing is impossible

用OpenGL做个史蒂夫?

随语

这两天倒腾计算机图形学,简直打开了新世界的大门,功能都能媲美建模软件了,这么好玩的东西怎么能不安排一下呢,史蒂夫搞起!(所谓史蒂夫,肯定就是”我的世界”中的人物啦~)

史蒂夫就在这里啦~欢迎大家一起玩👉OpenGL计算机图形学构建我的世界人物(含贴图)

关键技术

✔简介

任务目标很简单,即用OpenGL制作人物。”我的世界”人物的特点在于头、身体、四肢均为6个不同的棱柱,只需使用不同的函数进行构造即可。人物的图片纹理同样均是基于“我的世界”人物,通过Photoshop软件进行绘画,以纹理贴图的形式加载至项目中,场景也亦是如此。

相对而言,动作方面就会比较单一:人物的位置,头部、臂部、腿部只可使用键盘简单地绕轴运动

场景方面营造的是一种沙漠环境,因此相应地采取了金字塔这种辅景。金字塔的绘制通过函数建立不同大小的四棱锥,用纹理贴图进行描摹,再使用不同的坐标位置进行放置。此外,项目还包括光照、雾化、半透明的操作,采用不同位置的光源,黄色的多层雾化效果,使得整个画面更加趋于真实。

PS:由于自学的缘故,很多高深的东西还等待琢磨啦~( ̄▽ ̄~)~


✔人物建模(以头部为例)

人物建模其实就和搭积木一样简单,说白了就是6个柱体的位置摆放的确定,完成后直接在上面贴上贴图就好😎

原理都是一样的啦~这里就展示一个头部的完整建模好了

✨头部的位置确定
1
2
3
4
5
//头
glPushMatrix();
glTranslatef(-1.0f + xmove2, ymove2 + 1.5f, zmove2);
DrawSquare(width);
glPopMatrix();

确定头部以后,需要根据头的位置,更改glTranslatef中的参数,确定躯干和四肢的位置。更改参数的根据同样是看预览效果。

在定义人物的其他部分位置时,依旧采用xmove2ymove2zmove2,确保人物的每个组成部分始终在一起不会分开。eg.身体👇

1
2
3
4
5
//身体
glPushMatrix();
glTranslatef(-1.0f + xmove2, ymove2, zmove2);
DrawCubeST(width);
glPopMatrix();

PS:后续的人物移动需要的就是xmove2/ymove2/zmove2~


✨头部皮(tiē)肤(tú)添加

一个立方体具有6个面,因此皮肤贴图需要添加6次,且需要逐个绑定相对应的面

加载贴图纹理👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if (TextureImage[0] = LoadBMP("Data/zm.bmp"))
{
Status = TRUE;

glGenTextures(3, &texture[0]);

//创建NEAREST滤波纹理贴图
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

//创建线性纹理贴图
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

//创建MipMapped滤波纹理贴图
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
}

头部构建与贴图绑定👇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//头rot
void DrawSquare(float width)//一个棱长为width的正方体
//定义定点、拓扑结构(哪几个点构成正方形)
{
glRotatef(yrot, 0.0f, 1.0f, 0.0f);//指明绕哪个轴旋转

glBindTexture(GL_TEXTURE_2D, texture[filter]);
glBegin(GL_QUADS);

//正面
glNormal3f(0.0f, 0.0f, 1.0f);//指定法向量
//glTexCoord2f纹理映射
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);//正面左下A(-1,-1,1)//1均为比例
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);//正面右下B(1,-1,1)
glTexCoord2f(1.0f, 1.0f); glVertex3f(width / 2, width / 2, width / 2);//正面右上C(1,1,1)
glTexCoord2f(0.0f, 1.0f); glVertex3f(-width / 2, width / 2, width / 2);//正面左上D(-1,1,1)
glEnd();

glBindTexture(GL_TEXTURE_2D, texture[filter+9]);//绑定贴图文件
glBegin(GL_QUADS);
//背面
glNormal3f(0.0f, 0.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-width / 2, -width / 2, -width / 2);//背面左下E(-1,-1,-1)
glTexCoord2f(1.0f, 1.0f); glVertex3f(-width / 2, width / 2, -width / 2);//背面左上F(-1,1,-1)
glTexCoord2f(0.0f, 1.0f); glVertex3f(width / 2, width / 2, -width / 2);//背面右上G(1,1,-1)
glTexCoord2f(0.0f, 0.0f); glVertex3f(width / 2, -width / 2, -width / 2);//背面右下H(1,-1,-1)
glEnd();

glBindTexture(GL_TEXTURE_2D, texture[filter + 12]);
glBegin(GL_QUADS);
//顶面
glNormal3f(0.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-width / 2, width / 2, -width / 2);//F
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, width / 2, width / 2);//D
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, width / 2, width / 2);//C
glTexCoord2f(1.0f, 1.0f); glVertex3f(width / 2, width / 2, -width / 2);//G
glEnd();

glBindTexture(GL_TEXTURE_2D, texture[filter + 15]);
glBegin(GL_QUADS);
//底面
glNormal3f(0.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-width / 2, -width / 2, -width / 2);//E
glTexCoord2f(0.0f, 1.0f); glVertex3f(width / 2, -width / 2, -width / 2);//H
glTexCoord2f(0.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);//B
glTexCoord2f(1.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);//A
glEnd();

glBindTexture(GL_TEXTURE_2D, texture[filter + 3]);
glBegin(GL_QUADS);
//右侧面
glNormal3f(1.0f, 0.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, -width / 2, -width / 2);//H
glTexCoord2f(1.0f, 1.0f); glVertex3f(width / 2, width / 2, -width / 2);//G
glTexCoord2f(0.0f, 1.0f); glVertex3f(width / 2, width / 2, width / 2);//C
glTexCoord2f(0.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);//B
glEnd();

glBindTexture(GL_TEXTURE_2D, texture[filter + 6]);
glBegin(GL_QUADS);
//左侧面
glNormal3f(-1.0f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, -width / 2, -width / 2);//E
glTexCoord2f(1.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);//A
glTexCoord2f(1.0f, 1.0f); glVertex3f(-width / 2, width / 2, width / 2);//D
glTexCoord2f(0.0f, 1.0f); glVertex3f(-width / 2, width / 2, -width / 2);//F
glEnd();
}

✨头部的动作绑定
🔴躯干动作

因为所建人物处于三维空间中,动作即为绕轴运动,而在建模过程中就已经确定了每个部分的可转动轴,比如头部转动轴为y轴,手臂转动轴为x轴等等,根据按键修改glRotatef中的xrot/yrot/zrot值,即可实现动作。

1
2
3
4
5
6
7
8
if (keys['D'])//头向右转
{
yrot += 0.005f;
}
if (keys['A'])//头向左转
{
yrot -= 0.005f;
}
🔴人物移动

在定义头部的位置时,提及到了xmove2ymove2zmove2,在三维空间中,人物的移动改变即为这三个坐标值的改变绑定按键完成实现。

1
2
3
4
5
6
7
8
9
10
11
12
if (keys['1'])//人向上挪动
{
ymove2 += 0.0001f;
}
if (keys['4'])//人向右挪动
{
xmove2 += 0.0001f;
}
if (keys['6'])//人向近处挪动
{
zmove2 -= 0.0001f;
}

✔场景搭建

✨环境光

本项目采取了4个光源,具体的参数设置如下。调参很简单,运行时根据效果更改即可(´-ω-`)

记得在InitGL中调用~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GLfloat	z = -6.0f;	//控制观察点离物体的远近

GLfloat LightAmbient[] = { 1.0f, 1.0f, 1.0f, 1.0f }; //环境光1参数
GLfloat LightDiffuse[] = { 1.0f, 0.0f, 0.0f, 0.0f }; //漫反射光1参数
GLfloat LightPosition[] = { -5.0f, -3.0f, 1.0f, 0.0f }; //光源1位置(三维坐标轴x,y,z为前3个)

GLfloat LightAmbient2[] = { 1.0f, 1.0f, 1.0f, 1.0f }; //环境光2参数
GLfloat LightDiffuse2[] = { 0.0f, 1.0f, 0.0f, 0.0f }; //漫反射光2参数
GLfloat LightPosition2[] = { 5.0f, -4.0f, 1.5f, 0.0f }; //光源2位置

GLfloat LightAmbient3[] = { 1.0f, 1.0f, 1.0f, 1.0f }; //环境光3参数
GLfloat LightDiffuse3[] = { 0.0f, 0.0f, 1.0f, 0.0f }; //漫反射光3参数
GLfloat LightPosition3[] = { 0.5f, 4.0f, 0.5f, 0.0f }; //光源3位置

GLfloat LightAmbient4[] = { 1.0f, 1.0f, 1.0f, 1.0f }; //环境光4参数
GLfloat LightDiffuse4[] = { 0.0f, 1.0f, 1.0f, 0.0f }; //漫反射光4参数
GLfloat LightPosition4[] = { -0.7f, -5.0f, 1.5f, 0.0f }; //光源4位置

✨天气效果

沙漠里的浓雾效果可以使用OpenGL库中的雾化效果实现,选择模式、颜色、雾气种类,搞定✔

1
2
3
4
5
6
GLuint filter;			//滤波类型
GLuint texture[9]; //9种纹理的存储空间 3种不同的纹理3张图片
GLuint fogMode[] = { GL_EXP, GL_EXP2, GL_LINEAR }; // 雾气的模式
GLuint fogfilter = 0; // 使用哪一种雾气
GLfloat fogColor[4] = { 1.0f, 1.0f, 0.5f, 1.0f };
// 雾的颜色设为黄色

✨辅景-金字塔

与人物建模原理相同,不过这次需要构建的是三棱柱,且只有5面,导入贴图纹理,在构建完成三棱柱后,绑定面即可。

需要注意的是三棱柱有一个面是正方形~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//金字塔
void DrawPyramid(float width)
{
glBindTexture(GL_TEXTURE_2D, texture[filter + 45]);
glBegin(GL_TRIANGLES);
//正面
glNormal3f(0.0f, 0.5f, 1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);
glTexCoord2f(0.5f, 1.0f); glVertex3f(0, width / 2, 0);

glNormal3f(-1.0f, 0.5f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, -width / 2, -width / 2);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);
glTexCoord2f(0.5f, 1.0f); glVertex3f(0, width / 2, 0);

glNormal3f(0.0f, 0.5f, -1.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, -width / 2, -width / 2);
glTexCoord2f(0.5f, 1.0f); glVertex3f(0, width / 2, 0);

glNormal3f(1.0f, 0.5f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(width / 2, -width / 2, -width / 2);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-width / 2, -width / 2, -width / 2);
glTexCoord2f(0.5f, 1.0f); glVertex3f(0, width / 2, 0);
glEnd();

//底面正方形
glBegin(GL_QUADS);
glNormal3f(0.0f, -1.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);
glTexCoord2f(1.0f, 1.0f); glVertex3f(width / 2, -width / 2, -width / 2);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-width / 2, -width / 2, -width / 2);
glEnd();
}

✨背景

背景就相对而言更简单了,一个平面+一幅背景位图纹理放在后面就好啦~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//背景
void BackGround(float width)
{
glBindTexture(GL_TEXTURE_2D, texture[filter + 48]);
glBegin(GL_QUADS);
//正面
glNormal3f(0.0f, 0.0f, 1.0f);//指定法向量
//glTexCoord2f纹理映射
glTexCoord2f(0.0f, 0.0f); glVertex3f(-width / 2, -width / 2, width / 2);//正面左下A(-1,-1,1)//1均为比例
glTexCoord2f(1.0f, 0.0f); glVertex3f(width / 2, -width / 2, width / 2);//正面右下B(1,-1,1)
glTexCoord2f(1.0f, 1.0f); glVertex3f(width / 2, width / 2, width / 2);//正面右上C(1,1,1)
glTexCoord2f(0.0f, 1.0f); glVertex3f(-width / 2, width / 2, width / 2);//正面左上D(-1,1,1)
glEnd();
}

成品展示

-------- 🎈本文结束 感谢阅读🎈 --------
感謝老闆支持!