接着上一篇讲,前面已经展示了纹理把图片贴到立方体上,立方体表面是平面的,纹理坐标不需要设计,下面的两个例子都是基于球面贴图的,把鱼眼和全景图片贴到球面上,即实现曲面贴图。实际上,曲面贴图和平面贴图使用的纹理方面的知识没变化,主要难点在于纹理坐标设计,纹理坐标设计涉及到变换模型设计,需要一定的数学基础,下面例子中的两种贴图方法是比较简单的,网上的资料也非常多,学习本章例子的话,最好能推导例子中用的公式(都是简单的三角函数应用)。
1. 鱼眼球面贴图
一般的鱼眼图是一张正方形图(长方形也无所谓),有效的鱼眼图像都是中心的圆部分,四角部分都是无效的像素区域(一般是黑的),这种鱼眼图片映射到半球面上,即把圆面映射到半球面上,使用这种模型,可以做鱼眼矫正(效果并非最好,实现方便),鱼眼图片一般分前向(对应摄像机壁装)及俯视、仰视(对应摄像机顶装与底装)等几种拍摄方式,下面的例子采用的是仰视方式贴图,另外两种场景算法原理是一致的,会推导一种就能掌握这种贴图方法。
圆映射到半球面,推导方法如下:
1. 假定半球面圆心在原点,半球面在z轴正方形,圆面在x,y平面上(z=0),圆半径R=0.5(直径为1.0,方便对应纹理坐标范围)
2. 半球面上每个点(x,y,z)映射到圆面的坐标为(x,y,0),也就是把z坐标直接丢掉,即圆面是半球面在x,y平面(z=0)上的投影。
3. 所以z坐标可以由x,y坐标表示出来,圆面的(x,y )确定了,半球面的(x,y,z)的坐标也就确定了,反之亦然。
4. 圆面坐标(x,y)与纹理坐标的映射就很简单了,分别加上0.5即可,即把范围有-0.5~0.5映射到0~1.0之间。
绘制的时候,半球面的纹理坐标直接根据顶点坐标计算即可,不需要单独传纹理坐标。
下面的例子使用一张鱼眼图片上下对称映射成一个球体(沿用上篇球的画法),如想只画半球,顶点坐标设计成半球即可,这是从网上找的一张全景图,如下(使用以下源码,需把图片转成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(2)
#define SPLIT_NUM 10
#define POINT_CNT (2*SPLIT_NUM + 1) /* 每条边对应的点数*/
#define QUAD_CNT (2*SPLIT_NUM) /* 每条边对应的正方形个数*/
#define FISHEYE_IMG "fisheye.bmp"
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;
typedef struct
{
GLfloat x;
GLfloat y;
GLfloat z;
} POINT_S;
static const GLchar * vertex_source =
"#version 330 core\n"
"uniform mat4 pos_matrix;\n"
"uniform mat4 face_matrix;\n"
"layout (location = 0) in vec3 in_position;\n"
"out vec2 tex_coord;\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"
" vec4 vpos = vec4(in_position.x/dist,in_position.y/dist,in_position.z/dist,1.0);\n"
" vec4 tpos = face_matrix * vpos;\n"
" tex_coord = vec2(tpos.x+0.5,tpos.y+0.5);\n"
" gl_Position = pos_matrix * face_matrix * vpos;\n"
"}\n";
static const GLchar * frag_source =
"#version 330 core\n"
"in vec2 tex_coord;\n"
"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);
bmpInfo.biHeader.biSizeImage = bmpInfo.biHeader.biWidth * bmpInfo.biHeader.biHeight * 3;
}
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, ebo,texture;
mat4 pos_matrix,face_matrix;
GLuint pos_matrix_idx,face_matrix_idx;
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, vao);
glUniformMatrix4fv(pos_matrix_idx, 1, GL_FALSE, pos_matrix);
face_matrix = vmath::translate(0.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(90.0f, 1.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(-90.0f, 1.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(90.0f, 0.0f, 1.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(-90.0f, 0.0f, 1.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(180.0f, 1.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glutSwapBuffers();
}
/* 只分配一个面的顶点数据,其他面通过旋转绘制*/
POINT_S vertex_list[POINT_CNT][POINT_CNT];
/* 绘制索引,这里使用点的形式划线,注意空间分配*/
GLuint index_list[QUAD_CNT][QUAD_CNT][4];
void init(void)
{
int i = 0, j = 0, n = 0, cnt = 0;
POINT_S * p = NULL;
BitmapLoad textureImage = {0};
memset(vertex_list, 0, sizeof(vertex_list));
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.5f / SPLIT_NUM)*128;
p->y = (0.5f - i * 0.5f / SPLIT_NUM)*128;
p->z = 0.5f * 128;
}
}
/* 添加索引信息,这里按四边形绘制*/
for (i = 0; i < QUAD_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;
index_list[i][j][2] = POINT_CNT * (i + 1) + j + 1;
index_list[i][j][3] = POINT_CNT * (i + 1) + j;
}
}
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);
glGenTextures(1, &texture);
if (LoadBmp(FISHEYE_IMG, &textureImage) != NULL)
{
glBindTexture(GL_TEXTURE_2D, texture);
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_NEAREST);//GL_LINEAR GL_NEAREST
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
free(textureImage.data);
}
/* 定义旋转数组,主要存放当前朝向和旋转信息*/
face_matrix_idx = glGetUniformLocation(program, "face_matrix");
pos_matrix_idx = glGetUniformLocation(program, "pos_matrix");
pos_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 '-':
pos_matrix *= vmath::scale(0.95f);
break;
case '=':
case '+':
pos_matrix *= vmath::scale(1.05f);
break;
case 'i':
pos_matrix = vmath::frustum(-0.5, 0.5, -0.5, 0.5, 0.4, 10.0);
break;
case 'o':
pos_matrix = vmath::translate(0.0f, 0.0f, 0.0f);
break;
default:
break;
}
glutPostRedisplay();
}
void specialKey(GLint key, GLint x, GLint y)
{
float step = 2.0f;
switch (key)
{
case GLUT_KEY_UP:
pos_matrix *= vmath::rotate(step, 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_DOWN:
pos_matrix *= vmath::rotate(-1.0f * step, 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_LEFT:
pos_matrix *= vmath::rotate(step, 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_RIGHT:
pos_matrix *= vmath::rotate(-1.0f * step, 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_PAGE_UP :
pos_matrix *= vmath::rotate(step, 0.0f, 0.0f, 1.0f);
break;
case GLUT_KEY_PAGE_DOWN:
pos_matrix *= vmath::rotate(-1.0f * step, 0.0f, 0.0f, 1.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("FishEye");
glewInit();
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKey);
glutMainLoop();
return 0;
}
效果如下:
2. 全景球面贴图
全景图和鱼眼图不一样,可以看作是球面映射的两个相反的方面,鱼眼是半球面投影,而全景图是曲面展开,所以鱼眼图像是可以转成全景图的。这里从网上找了一张全景图(世界地图),宽高比例是2:1,为什么是2:1呢?原因是:
1. 全景图是按球面展开,球的圆周(赤道)展开为宽,经线展开为高。
2. 圆周长度为2πR,经线长为πR,所以是2:1。
注意:这种是等弧长映射,球面经线方向的点和全景图高度方向的点是一一对应的,而不是简单的认为全景图高度等于球体的高度。
纹理坐标映射推导过程:
1. 与上例一样,球面半径为0.5,圆心在原点。
2. 画一个外接圆柱体,半径0.5,总高度为πR,即π/2,把全景图映射到圆柱面上。
3. 横坐标的关系是,宽度=水平弧长,最终总宽度=圆周。
4. 纵坐标的关系是,高度=垂直弧长,总高度=半圆周。(所以叫等弧长映射)
5. 有了上面两个关系量,可以得到球面坐标和纹理坐标的关系(利用好等弧长这个条件即可)。
上面的过程比较难说清楚,如果理解了映射过程(平面->柱面->球面),自己推导比看人家讲解的快(只使用了最基本的三角和反三角函数),先把网上找的图贴一下(使用以下源码,需把图片转成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(2)
#define SPLIT_NUM 10
#define POINT_CNT (2*SPLIT_NUM + 1) /* 每条边对应的点数*/
#define QUAD_CNT (2*SPLIT_NUM) /* 每条边对应的正方形个数*/
#define FISHEYE_IMG "world2.bmp"
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;
typedef struct
{
GLfloat x;
GLfloat y;
GLfloat z;
} POINT_S;
static const GLchar * vertex_source =
"#version 330 core\n"
"uniform mat4 pos_matrix;\n"
"uniform mat4 face_matrix;\n"
"layout (location = 0) in vec3 in_position;\n"
"out vec2 tex_coord;\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"
" vec4 vpos = vec4(in_position.x/dist,in_position.y/dist,in_position.z/dist,1.0);\n"
" vec4 tpos = face_matrix * vpos;\n"
" float theta = atan(tpos.y/tpos.x);\n"
" if( tpos.x < 0.0) theta+=3.14; \n"
" else if(tpos.y<0.0) theta+=6.28; \n"
" float ty = asin(2*tpos.z)/3.14;\n"
" tex_coord = vec2(theta/3.14/2,ty * (-1.0)+0.5);\n"
" gl_Position = pos_matrix * face_matrix * vpos;\n"
"}\n";
static const GLchar * frag_source =
"#version 330 core\n"
"in vec2 tex_coord;\n"
"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);
bmpInfo.biHeader.biSizeImage = bmpInfo.biHeader.biWidth * bmpInfo.biHeader.biHeight * 3;
}
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, ebo,texture;
mat4 pos_matrix,face_matrix;
GLuint pos_matrix_idx,face_matrix_idx;
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, vao);
glUniformMatrix4fv(pos_matrix_idx, 1, GL_FALSE, pos_matrix);
face_matrix = vmath::translate(0.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(90.0f, 1.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(-90.0f, 1.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(90.0f, 0.0f, 1.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(-90.0f, 0.0f, 1.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
face_matrix = vmath::rotate(180.0f, 1.0f, 0.0f, 0.0f);
glUniformMatrix4fv(face_matrix_idx, 1, GL_FALSE, face_matrix);
glDrawElements(GL_QUADS, QUAD_CNT * QUAD_CNT * 4, GL_UNSIGNED_INT, (GLvoid *)(0));
glutSwapBuffers();
}
/* 只分配一个面的顶点数据,其他面通过旋转绘制*/
POINT_S vertex_list[POINT_CNT][POINT_CNT];
/* 绘制索引,这里使用点的形式划线,注意空间分配*/
GLuint index_list[QUAD_CNT][QUAD_CNT][4];
void init(void)
{
int i = 0, j = 0, n = 0, cnt = 0;
POINT_S * p = NULL;
BitmapLoad textureImage = {0};
memset(vertex_list, 0, sizeof(vertex_list));
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.5f / SPLIT_NUM)*128;
p->y = (0.5f - i * 0.5f / SPLIT_NUM)*128;
p->z = 0.5f * 128;
}
}
/* 添加索引信息,这里按四边形绘制*/
for (i = 0; i < QUAD_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;
index_list[i][j][2] = POINT_CNT * (i + 1) + j + 1;
index_list[i][j][3] = POINT_CNT * (i + 1) + j;
}
}
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);
glGenTextures(1, &texture);
if (LoadBmp(FISHEYE_IMG, &textureImage) != NULL)
{
glBindTexture(GL_TEXTURE_2D, texture);
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_NEAREST);//GL_LINEAR GL_NEAREST
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
free(textureImage.data);
}
/* 定义旋转数组,主要存放当前朝向和旋转信息*/
face_matrix_idx = glGetUniformLocation(program, "face_matrix");
pos_matrix_idx = glGetUniformLocation(program, "pos_matrix");
pos_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 '-':
pos_matrix *= vmath::scale(0.95f);
break;
case '=':
case '+':
pos_matrix *= vmath::scale(1.05f);
break;
case 'i':
pos_matrix = vmath::frustum(-0.5, 0.5, -0.5, 0.5, 0.4, 10.0);
break;
case 'o':
pos_matrix = vmath::translate(0.0f, 0.0f, 0.0f);
break;
default:
break;
}
glutPostRedisplay();
}
void specialKey(GLint key, GLint x, GLint y)
{
float step = 2.0f;
switch (key)
{
case GLUT_KEY_UP:
pos_matrix *= vmath::rotate(step, 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_DOWN:
pos_matrix *= vmath::rotate(-1.0f * step, 1.0f, 0.0f, 0.0f);
break;
case GLUT_KEY_LEFT:
pos_matrix *= vmath::rotate(step, 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_RIGHT:
pos_matrix *= vmath::rotate(-1.0f * step, 0.0f, 1.0f, 0.0f);
break;
case GLUT_KEY_PAGE_UP :
pos_matrix *= vmath::rotate(step, 0.0f, 0.0f, 1.0f);
break;
case GLUT_KEY_PAGE_DOWN:
pos_matrix *= vmath::rotate(-1.0f * step, 0.0f, 0.0f, 1.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("WorldMap");
glewInit();
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKey);
glutMainLoop();
return 0;
}
效果如下:
说明:上面的贴图方式并不完善,大家可以看到接缝处有一条黑线,原因是顶点模型选择的问题,立方体逼近的方法,在上例鱼眼贴图中效果会好一些,但本例的全景贴图,球面顶点模型用经纬法的更好,否则没办法处理这种黑线问题,黑线产生的原因是,通过顶点坐标计算出的纹理坐标,在接缝线左(靠近1.0)右(靠近0.0)差不多相差1.0,假如有一个四边形跨了接缝,实际跨度很小,只跨了一条缝,但顶点坐标跨度范围会非常大,如0.1~0.9,这个小四边形差不多把整个纹理都填充在里面,所以表现为一条黑线,实际上我们要的效果是0.0~0.1,0.9~1.0这样的填充,这就必须要求接缝线是直线(意思是经度相同的线,球面上的直线),立方体逼近方法没办法保证接缝线是直线,所以很难去除黑线问题,而经纬法画的球体,刚好有这种特性。大家可以试着把顶点模型改成经纬法的球面,来看效果。
3. 鱼眼矫正
鱼眼矫正的算法有很多种(网上有很多论文),上面例子中的映射方法是鱼眼矫正方法的一种,矫正的效果一般,但胜在原理简单,速度快,有产品使用该模型进行实时视频矫正。
再看一下上面两个例子,是有一些联系的,全景图可以和鱼眼图相互转化,即全景图片<-->柱面映射<-->球面映射<-->鱼眼图片,理解这中间的映射方式,就可以做鱼眼矫正了,鱼眼矫正有转全景、云台、柱面、分割、缩放等多种操作方式,在这种方法中,最关键的就是球面模型建立与处理。鱼眼矫正产品化有一定难度,但原理不复杂,可以用来练手,如把视点定在原点,对球面做投影视图变换,调整角度,就可以实现类似云台的功能。
4. 关于调试
shader程序的调试很麻烦,涉及到模型变换时,稍有不对,得出的结果跟预想的完全不一样。shader调试没有很直接的方法,连打印调试都做不到,这正是并行运算的特点,即使可以打印,打印的数据也是乱序,且数据会太多,网上提到的颜色调试、调试器调试不直观也比较麻烦,个人主要调试方法为:
1. 分析与尝试,屏蔽无关顶点,只保留关注的顶点,方便分析,只需要在顶点着色器中把正常的顶点过滤掉即可(判断顶点坐标为不关注顶点时,main函数空函数)。
2. 逻辑部分代码移到CPU上跑,调试OK后,再移到shader里,由于shader语法与C语法类似,移植还是比较方便的,虽然麻烦但有效。
3. 调纹理的时候使用测试图,调好过后再替换成效果图,测试图带定位信息,更容易看清楚顶点和纹理坐标的对应关系。
下面是调试时使用的测试图(最好看图就能知道对应纹理的位置,格子,环状图都可以),本图做的比较烂,大家可以参考。