理解纹理可以先看一下第四章的例子,第四章例子使用片元着色器直接显示图片(1080p图片),做法是先确定矩形的顶点坐标,矩形包含1920*1080个顶点,每个顶点与一个像素对应,这种方法虽然能显示出图片,但缺点非常明显,效率低、绘制麻烦、缩放效果差等。
理论上来说所有的模型都可以使用顶点+顶点颜色的方式绘制出来,这样的话,制作模型就太麻烦,纹理正是为了解决该问题而出现的,使用纹理,较少的工作量就能做出较逼真的效果。这一章介绍一下纹理的基本用法,对应红宝书第6章,先通过红宝书的例子说明基本用法,再介绍球面鱼眼贴图和球面全景贴图以加深对纹理的理解。由于贴图和源码较多,分两章上传。
1. 纹理基本流程
虽然第四章的例子不实用,但这种方法对理解纹理贴图有一定帮助,两种方式有共同之处,之前在理解纹理的时候,看半天红宝书也没有想明白二维的纹理图片怎么映射到三维物体的表面,这里通过一个表来对比来说明,以帮助理解。
不使用纹理,直接点对点显示图片 (第四章例子) | 使用二维纹理贴图 | |
在矩形中显示一张图片 | 1.顶点坐标设计,设计1920列1080行个顶点 2.每个顶点对应一种颜色,着色器里与顶点一一对应 3.用点的方式绘制顶点数据 | 1.设计一个矩形的4个顶点就可以了 2.加载纹理图片,每个顶点对应一个纹理坐标 3.使用矩形方式绘制顶点数据 |
圆柱侧面上显示图片 | 1.顶点设计,把圆周划分1920等分,圆柱高分1080份。对应1920*1080个顶点 2.每个顶点对应一种颜色 3.使用点的方式绘制顶点数据 | 1.顶点设计,把圆分成N等份(如360等分),上下表面共720个顶点坐标 2.每个顶点坐标对应一个纹理坐标,纹理坐标把图片分成360个矩形。 3.使用矩形的方式绘制顶点数据,共绘制360个矩 |
由上表可以看出,纹理贴图与点对点绘制最大的区别是顶点坐标坐标的设计,点对点绘制,顶点个数和图片像素点一一对应,而纹理贴图则没有这种要求。矩形中显示图片,纹理贴图的四个顶点可以为(-1,1)、(1,1)、(1,-1),(-1,-1),对应的纹理坐标为(0,0)、(1,0)、(1,1)、(0,1),4个顶点对应4个纹理坐标,即每个角都一一对应。
为何只设置了4个坐标,就能显示出完整的图像呢?这就是问题的关键,纹理贴图可以对未设置的地方进行均匀填充,如对角线,它会把(0,0)与(1,1)之间的像素均匀的映射到对角线上,其他地方的点都是如此处理,这样一来,通过4个顶点及其对应的纹理坐标就把整个图显示出来了。
再看一下,映射到圆柱侧面,圆柱侧面被分成了360个矩形对应720个顶点坐标,图片也被分成了360个矩形对应720个纹理坐标,贴图时,对于每个最小填充单位即本例子中的局限(四边形填充)来说,他们都是平面的,但最终的结果是对应了圆柱体的曲面。3个纹理坐标(三角形画法)对应的图片区域被映射到对应的顶点,微观上每一次映射都是平面,但整体来看可以做曲面映射,这就是二维纹理映射到三维图像的过程。
纹理编程的流程与api:
void glGenTextures(GLsizei n, GLuint *textures);
void glBindTexture(GLenum target, GLuint texture );
纹理使用之前需创建与绑定,这两个API用法与缓存对象的创建绑定类似。
/**
* @param target: target参数指定设置的纹理目标,必须是GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D等参数。
* @param level: 指定纹理等级,0代表原始纹理,其余等级对应Mipmap纹理等级。
* @param internalFormat: 指定OpenGL存储纹理的格式
* @param width/height: 指定存储的纹理大小
* @param border: 为历史遗留参数,只能设置为0
* @param format: 指定原始图片数据的格式(format)
* @param type: 数据类型(GL_UNSIGNED_BYTE, GL_BYTE等值)
* @param type: 数据的内存地址(data指针)。
*/
void glTexImage2D( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height,
GLint border, GLenum format, GLenum type, const GLvoid * data);
加载纹理图像,一般是从图片中直接加载,也可以像如下例子直接从数据中加载,该函数的用法网上介绍的非常多,重点关注internalFormat与format参数的用法。
纹理编程最重要的就是纹理坐标的确定,坐标的确定需一定的算法支持,本章中后面两个例子就需要一些简单的三角函数知识作支撑,纹理在片元着色器中的用法比较固定,可以照着用,深入学习的话,可以深挖一下片元着色器中的texture函数,理解纹理坐标和颜色的填充关系,实现更复杂的效果。
2. 红宝书纹理例子
下面的例子来自红宝书第6.5章上,做了简单修改,可以看出使用流程。源码如下:
#include <iostream>
using namespace std;
#include <GL/glew.h>
#include <GL/glut.h>
/* 该头文件请到第章下载*/
#include "vmath.h"
using namespace vmath;
#define BUFFER_OFFSET(offset) ((void *)(offset))
static const char quad_shader_vs[] =
"#version 330 core\n"
"layout (location = 0) in vec2 in_position;\n"
"layout (location = 1) in vec2 in_tex_coord;\n"
"out vec2 tex_coord;\n"
"void main(void)\n"
"{\n"
" gl_Position = vec4(in_position, 0.5, 1.0);\n"
" tex_coord = in_tex_coord;\n"
"}\n";
static const char quad_shader_fs[] =
"#version 330 core\n"
"in vec2 tex_coord;\n"
"layout (location = 0) out vec4 color;\n"
"uniform sampler2D tex1;\n"
"void main(void)\n"
"{\n"
" color = texture(tex1, tex_coord);\n"
"}\n";
void loadShader(GLuint program,GLuint type,const GLchar * source)
{
GLint status = 0;
const GLchar * shaderSource[] = {source};
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, shaderSource, 0);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
glAttachShader(program, shader);
}
GLuint vao;
GLuint quad_vbo;
GLuint tex;
void init(void)
{
static const GLfloat quad_data[] =
{
0.75f, -0.75f,
-0.75f, -0.75f,
-0.75f, 0.75f,
0.75f, 0.75f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
GLuint program = glCreateProgram();
loadShader(program,GL_VERTEX_SHADER,quad_shader_vs);
loadShader(program,GL_FRAGMENT_SHADER,quad_shader_fs);
glLinkProgram(program);
glUseProgram(program);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &quad_vbo);
glBindBuffer(GL_ARRAY_BUFFER, quad_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_data), quad_data, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(8 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
static const unsigned char texture_data[] =
{
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF
};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, texture_data);
#if 1
static const GLint swizzles[] = { GL_RED,GL_RED,GL_RED , GL_ONE };
#else
static const GLint swizzles[] = { GL_ZERO,GL_RED,GL_ZERO , GL_ONE };
#endif
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzles);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//GL_NEAREST
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void display(void)
{
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClearDepth(1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glutSwapBuffers();
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition (140, 140);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow(argv[0]);
glewInit();
init();
glutDisplayFunc(display);
glutMainLoop();
}
例子中通过创建的数据来设置纹理,一般的纹理都可以直接从图片中加载,大家可以把格子替换成图片达到同样的效果。例子中用到GL_TEXTURE_SWIZZLE_RGBA这个东西,用来重新组织纹理通道,红宝书6.7.2节有讲,大家可以自己调整参数来加深理解,如下右图, 修改如下:
static const GLint swizzles[] = { GL_ZERO,GL_RED,GL_ZERO , GL_ONE };
效果如下:
3. 正方体表面分割逼近法绘制球面
后面几个例子是基于球面的,球面的绘制方法有多种,比较常见的有经纬法,画出的球面会存在两极线条密集,中间稀疏的情况,使用这种球体贴图并不适合所有情况,球体本身是完美对称的,为达到这个目标,这里介绍一种立方体表面分割逼近的方法来绘制球体。画法原理:
1. 画一个立方体,对立方体6个面进行分割。
2. 每个面分割成4个正方形,并把对应的顶点映射到球面上。
3. 进一步分割正方形,一步步逼近球体。
4. 最终把球体分割成6*2的N次方个正方形格子。
注意:最初的正方体,应与目标球体内接,最开始6个面的8个顶点应落在球面上,后续的分割以此为参考点。立方体外接球体应该也可以分割,方法会有所不同。
说明:
1. 球体均匀分割的算法有很多,但基本是近似分割(每个分割块大小相差尽量小)。本文的立方体分割会出现靠近立方体边的地方四边形会小一些,靠近中心的地方,四边形会大一些,贴图效果比经纬法画球面效果要好一些。
2. 类似的方法,可以应用在正四面体、正六面体(立方体)、正八面体等上面,面越多,分割出的球体越均匀,各块之间的差异越小,复杂度也就越高。
这种画法的关键点在于分割与坐标映射,把球体内的一个点映射到球面上,先看一下二维情况,即正方形到球面的映射:
正方体上的(x,y)映射到圆周上的(x',y')上,暗含条件是坐标点与x轴的夹角相同,即x' = R*cosα ,y' = R*sinα;而sinα与 cosα可以通过x,y的值得出,再考虑3维情况,可以得到如下公式:
不同的分割数,绘制球体的效果如下:
可以看出,这种方法分割的球体的各个四边形大小都差不多大,分割细腻度随着分割次数的2的N次方递增,可以看到20次分割是非常细腻的。
这 种分割逼近的方法,比较适合四边形渲染,如果想使用三角形渲染,可以使用正四面体通过三角分割(一个三角面分割成4个三角形的方式)逼近。
源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>
/* 该头文件请到第6章下载*/
#include "vmath.h"
using namespace vmath;
#define SPLIT_NUM 20
#define POINT_CNT (2*SPLIT_NUM + 1) /* 每条边对应的点数*/
#define QUAD_CNT (2*SPLIT_NUM) /* 每条边对应的正方形个数*/
typedef struct
{
GLfloat x;
GLfloat y;
GLfloat z;
} POINT_S;
static const GLchar * vertex_source =
"#version 330 core\n"
"uniform mat4 in_matrix;\n"
"layout (location = 0) in vec3 in_position;\n"
"void main(void)\n"
"{\n"
" float dist = 2 * sqrt(pow(in_position.x,2)+pow(in_position.y,2)+pow(in_position.z,2));\n"
" gl_Position = in_matrix * vec4(in_position.x/dist,in_position.y/dist,in_position.z/dist,1.0);\n"
"}\n";
static const GLchar * frag_source =
"#version 330 core\n"
"out vec4 color;\n"
"void main(void)\n"
"{\n"
" color = vec4(0.5,0.1,0.7,1.0);\n"
"}\n";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
GLint status = 0;
const GLchar * shaderSource[] = {source};
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, shaderSource, 0);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
glAttachShader(program, shader);
}
GLuint vao, vbo, ebo;
mat4 matrix;
GLuint matrix_idx;
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, vao);
/* 只计算了一个面的顶点坐标,其他面都是通过旋转绘制的,循环展开方便调试*/
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix);
glDrawElements(GL_LINES, QUAD_CNT * POINT_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix * vmath::rotate(90.0f, 1.0f, 0.0f, 0.0f));
glDrawElements(GL_LINES, QUAD_CNT * POINT_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix * vmath::rotate(-90.0f, 1.0f, 0.0f, 0.0f));
glDrawElements(GL_LINES, QUAD_CNT * POINT_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix * vmath::rotate(90.0f, 0.0f, 1.0f, 0.0f));
glDrawElements(GL_LINES, QUAD_CNT * POINT_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix * vmath::rotate(-90.0f, 0.0f, 1.0f, 0.0f));
glDrawElements(GL_LINES, QUAD_CNT * POINT_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix * vmath::rotate(180.0f, 1.0f, 0.0f, 0.0f));
glDrawElements(GL_LINES, QUAD_CNT * POINT_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glutSwapBuffers();
}
void init(void)
{
int i = 0, j = 0, n = 0, cnt = 0;
POINT_S * p = NULL;
/* 只分配一个面的顶点数据,其他面通过旋转绘制*/
POINT_S vertex_list[POINT_CNT][POINT_CNT];
memset(vertex_list, 0, sizeof(vertex_list));
/* 绘制索引,这里使用2点的形式划线,注意空间分配*/
GLuint index_list[POINT_CNT][QUAD_CNT][4];
memset(index_list, 0, sizeof(index_list));
for (i = 0; i < POINT_CNT ; i++)
{
for (j = 0; j < POINT_CNT; j++)
{
p = &vertex_list[i][j];
p->x = -0.5f + j * 0.5 / SPLIT_NUM;
p->y = 0.5f - i * 0.5 / SPLIT_NUM;
p->z = 0.5f;
}
}
/* 横向划线索引 */
for (i = 0; i < POINT_CNT; i++)
{
for (j = 0; j < QUAD_CNT; j++)
{
index_list[i][j][0] = POINT_CNT * i + j;
index_list[i][j][1] = POINT_CNT * i + j + 1;
}
}
/* 纵向划线索引 */
for (i = 0; i < POINT_CNT; i++)
{
for (j = 0; j < QUAD_CNT; j++)
{
index_list[i][j][2] = POINT_CNT * j + i;
index_list[i][j][3] = POINT_CNT * (j + 1) + i;
}
}
GLuint program = glCreateProgram();
loadShader(program, GL_VERTEX_SHADER, vertex_source);
loadShader(program, GL_FRAGMENT_SHADER, frag_source);
glLinkProgram(program);
glUseProgram(program);
glGenBuffers(1, &vao);
glBindBuffer(GL_ARRAY_BUFFER, vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_list), vertex_list, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
glEnableVertexAttribArray(0);
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_list), index_list, GL_STATIC_DRAW);
/* 定义旋转数组,主要存放当前旋转信息*/
matrix_idx = glGetUniformLocation(program, "in_matrix");
matrix = vmath::translate(0.0f, 0.0f, 0.0f);
glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
glLineWidth(1.0);
}
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
case '-':
matrix *= vmath::scale(0.95f);
break;
case '=':
case '+':
matrix *= vmath::scale(1.05f);
break;
default:
break;
}
glutPostRedisplay();
}
void specialKey(GLint key, GLint x, GLint y)
{
float step = 2.0f;
switch (key)
{
case GLUT_KEY_UP:
matrix *= vmath::rotate(step, 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_DOWN:
matrix *= vmath::rotate(-1.0f * step, 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_LEFT:
matrix *= vmath::rotate(step, 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_RIGHT:
matrix *= vmath::rotate(-1.0f * step, 0.0f, 1.0f, 0.0f);
break;
default:
break;
}
glutPostRedisplay();
}
int main(int argc, char * argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowPosition(200, 200);
glutInitWindowSize(400, 400);
glutCreateWindow("Sphere");
glewInit();
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKey);
glutMainLoop();
return 0;
}
客户端在vbo中存放的是立方体表面坐标,立方体表面坐标映射为球面坐标在shader中完成。vbo中只存放了一个面坐标,其他面采用旋转的方式完成。
4. 正方体多纹理贴图
把图片映射到正方体的6个面上,有两种方法:
1. 加载一个纹理图片,这个纹理图片包含6个面的信息,如图片2*3分割,每个块对应一个面。
2. 加载六个纹理图片,图片独立,每张图片对应一个面。
下面的例子采用第二种方法,使用多个纹理图片(不是多重纹理,多重纹理指纹理叠加,同一个面叠加多个纹理),绘制的时候,通过切换纹理图片的绑定来绘制不同的面,顶点坐标容易设计,正立方体的各个面都是规则的形状,纹理图片的4个顶点分别对应4个立方体表面顶点坐标即可。
注意:这种情况下,同一个顶点坐标,绘制不同的面时,对应的顶点坐标是不一样的,如绘制顶面和前侧面,有两个顶点坐标是一致的,但绘制这两个面对应的纹理坐标却不一致,大家可以看例子中的点{-0.5f, -0.5f, 0.5f },绘制两个相邻的侧面,一个纹理坐标是{1.0f, 0.0f}, 一个是{0.0f, 0.0f},这种情况下使用glDrawElements的方式就要注意了,需处理好同一个顶点对应不同纹理坐标的情况。
本例子需要6张bmp图片,名称分别为texture1.bmp~texture6.bmp,图片请大家自己找,这里就不贴了,完整源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GL/glut.h>
/* 该头文件请到第6章下载*/
#include "vmath.h"
using namespace vmath;
#pragma pack(1)
typedef unsigned char U8;
typedef unsigned short int U16;
typedef unsigned long int U32;
typedef struct
{
U16 bfType; /* windows下该值必需是x4D42,即字符'BM'*/
U32 bfSize; /* bmp文件大小,包含bmp文件头,信息头,调色板大小,数据大小*/
U16 bfReserved1; /* 保留,必须设置为*/
U16 bfReserved2; /* 保留,必须设置为*/
U32 bfOffBits; /* rgb数据相对文件头偏移量*/
} BitmapFileHeader;
typedef struct
{
U32 biSize; /* 信息头大小sizeof(BitmapInfoHeader) */
U32 biWidth; /* 图象的宽度,以象素为单位*/
U32 biHeight; /* 图象的高度,以象素为单位,正值倒向位图,负值正向位图*/
U16 biPlanes; /* 位面数,设为*/
U16 biBitCount; /* 位图位数,可以为,24,16,8,4,1 */
U32 biCompression; /* 说明图象数据压缩的类型,BI_RGB(0) BI_BITFIELDS(3)等*/
U32 biSizeImage; /* 图像数据大小,包括位图信息大小和数据大小*/
U32 biXPelsPerMeter;/* 水平分辨率,一般可填*/
U32 biYPelsPerMeter;/* 垂直分辨率,一般可填*/
U32 biClrUsed; /* 颜色索引使用数,一般填,表示都使用*/
U32 biClrImportant; /* 重要颜色索引数,一般填,表示都重要*/
} BitmapInfoHeader;
typedef struct
{
BitmapFileHeader bfHeader;
BitmapInfoHeader biHeader;
} BitmapInfo;
typedef struct
{
int width;
int height;
char * data;
} BitmapLoad;
static const GLchar * vertex_source =
"#version 330 core\n"
"uniform mat4 in_matrix;\n"
"layout (location = 0) in vec3 in_position;\n"
"layout (location = 1) in vec2 in_tex_coord;\n"
"out vec2 tex_coord;\n"
"void main(void)\n"
"{\n"
" gl_Position = in_matrix * vec4(in_position,1.0);\n"
" tex_coord = in_tex_coord;\n"
"}\n";
static const GLchar * frag_source =
"#version 330 core\n"
"in vec2 tex_coord;\n"
"layout (location = 0) out vec4 color;\n"
"uniform sampler2D tex;\n"
"void main(void)\n"
"{\n"
" color = texture(tex, tex_coord);\n"
"}\n";
void loadShader(GLuint program, GLuint type, const GLchar * source)
{
GLint status = 0;
const GLchar * shaderSource[] = {source};
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, shaderSource, 0);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
glAttachShader(program, shader);
}
BitmapLoad * LoadBmp(const char * filename, BitmapLoad * texture_image)
{
int pos = 0, dataSize = 0;
FILE * fp = NULL;
BitmapInfo bmpInfo;
fp = fopen(filename, "rb");
if (fp == NULL)
{
perror("fopen");
return NULL;
}
memset(&bmpInfo, 0, sizeof(BitmapInfo));
fread(&bmpInfo, 1, sizeof(BitmapInfo), fp);
printf("width:%lu height:%lu dataSize:%lu\n", bmpInfo.biHeader.biWidth, bmpInfo.biHeader.biHeight, bmpInfo.biHeader.biSizeImage);
if (bmpInfo.biHeader.biSizeImage != bmpInfo.biHeader.biWidth * bmpInfo.biHeader.biHeight * 3)
{
printf("biSizeImage size error! header:%d bmp size:%d\n", sizeof(BitmapInfo), bmpInfo.bfHeader.bfSize);
}
dataSize = bmpInfo.biHeader.biSizeImage;
texture_image->data = (char *)malloc(dataSize);
if (texture_image->data == NULL)
{
fclose(fp);
return NULL;
}
fread(texture_image->data, 1, dataSize, fp);
texture_image->width = bmpInfo.biHeader.biWidth;
texture_image->height = bmpInfo.biHeader.biHeight;
pos = ftell(fp);
printf("bmp pos:%d\n", pos);
fclose(fp);
return texture_image;
}
GLuint vao, vbo;
GLuint matrix_idx;
mat4 matrix;
GLuint texture[6];
int loadGLTextures()
{
int i;
char strPath[32];
BitmapLoad textureImage = {0};
glGenTextures(6, &texture[0]);
for (i = 0; i < 6; i++)
{
sprintf(strPath, "texture%d.bmp", i + 1);
if (LoadBmp(strPath, &textureImage) != NULL)
{
glBindTexture(GL_TEXTURE_2D, texture[i]);
glTexImage2D(GL_TEXTURE_2D, 0, 3, textureImage.width, textureImage.height, 0, GL_BGR, GL_UNSIGNED_BYTE, textureImage.data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
}
if (textureImage.data)
{
free(textureImage.data);
textureImage.data = NULL;
}
}
return 0;
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUniformMatrix4fv(matrix_idx, 1, GL_FALSE, matrix);
glBindBuffer(GL_ARRAY_BUFFER, vao);
for (int i = 0; i < 6; i++)
{
glBindTexture(GL_TEXTURE_2D, texture[i]);
glDrawArrays(GL_QUADS,i*4 ,4);
}
glutSwapBuffers();
}
void init(void)
{
int i = 0;
static GLfloat vertex_list[][3] =
{
/* 定义正方体个面*/
{ -0.5f, -0.5f, -0.5f},{ -0.5f, 0.5f, -0.5f},{ 0.5f, 0.5f, -0.5f},{ 0.5f, -0.5f, -0.5f},
{ -0.5f, -0.5f, -0.5f},{ -0.5f, -0.5f, 0.5f},{ -0.5f, 0.5f, 0.5f},{ -0.5f, 0.5f, -0.5f},
{ -0.5f, -0.5f, -0.5f},{ 0.5f, -0.5f, -0.5f},{ 0.5f, -0.5f, 0.5f},{ -0.5f, -0.5f, 0.5f},
{ -0.5f, -0.5f, 0.5f},{ 0.5f, -0.5f, 0.5f},{ 0.5f, 0.5f, 0.5f},{ -0.5f, 0.5f, 0.5f},
{ 0.5f, -0.5f, -0.5f},{ 0.5f, 0.5f, -0.5f},{ 0.5f, 0.5f, 0.5f},{ 0.5f, -0.5f, 0.5f},
{ -0.5f, 0.5f, -0.5f},{ -0.5f, 0.5f, 0.5f},{ 0.5f, 0.5f, 0.5f},{ 0.5f, 0.5f, -0.5f},
};
static GLfloat textCoord_list[][2] =
{
/* 对应指定每个面的纹理坐标*/
{ 0.0f, 0.0f},{ 0.0f, 1.0f}, {1.0f, 1.0f},{1.0f, 0.0f},
{ 0.0f, 0.0f},{ 0.0f, 1.0f}, {1.0f, 1.0f},{1.0f, 0.0f},
{ 0.0f, 0.0f},{ 0.0f, 1.0f}, {1.0f, 1.0f},{1.0f, 0.0f},
{ 0.0f, 0.0f},{ 0.0f, 1.0f}, {1.0f, 1.0f},{1.0f, 0.0f},
{ 0.0f, 0.0f},{ 0.0f, 1.0f}, {1.0f, 1.0f},{1.0f, 0.0f},
{ 0.0f, 0.0f},{ 0.0f, 1.0f}, {1.0f, 1.0f},{1.0f, 0.0f},
};
GLuint program = glCreateProgram();
loadShader(program, GL_VERTEX_SHADER, vertex_source);
loadShader(program, GL_FRAGMENT_SHADER, frag_source);
glLinkProgram(program);
glUseProgram(program);
glGenBuffers(1, &vao);
glBindBuffer(GL_ARRAY_BUFFER, vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_list) + sizeof(textCoord_list), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertex_list), vertex_list);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertex_list), sizeof(textCoord_list), textCoord_list);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid *)(sizeof(vertex_list)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
loadGLTextures();
matrix_idx = glGetUniformLocation(program, "in_matrix");
matrix = vmath::translate(0.0f, 0.0f, 0.0f);
glClearColor(0.5f, 0.5f, 1.0f, 1.0f);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
}
void keyboard(unsigned char key, int x, int y)
{
switch (key)
{
case '-':
matrix *= vmath::scale(0.9f);
break;
case '=':
case '+':
matrix *= vmath::scale(1.1f);
break;
default:
break;
}
glutPostRedisplay();
}
void specialKey(GLint key, GLint x, GLint y)
{
float step = 2.0f;
switch (key)
{
case GLUT_KEY_UP:
matrix *= vmath::rotate(1.0f, step, 0.0f, 0.0f);
break;
case GLUT_KEY_DOWN:
matrix *= vmath::rotate(1.0f, -1*step, 0.0f, 0.0f);
break;
case GLUT_KEY_LEFT:
matrix *= vmath::rotate(1.0f, 0.0f, step, 0.0f);
break;
case GLUT_KEY_RIGHT:
matrix *= vmath::rotate(1.0f, 0.0f, -1 * step, 0.0f);
break;
default:
break;
}
glutPostRedisplay();
}
int main(int argc, char * argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE |GLUT_DEPTH);
glutInitWindowPosition(200, 200);
glutInitWindowSize(500, 500);
glutCreateWindow("Cube");
glewInit();
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKey);
glutMainLoop();
return 0;
}
效果如下: