OpenGL Utility Toolkit (GLUT)

OpenGL 是非常普遍的 2D3D 圖像函式庫,開發者可藉由 OpenGL 撰寫 2D 與 3D 的繪圖程式,且支援多種程式語言,C/C++、C#、Java、Python...等。基本上 Visual Studio 都已經把 gl.h 和 glu.h 給包在裡面了,所以只要到 OpenGL 官網再安裝 GLUT 就可以使用了。

OpenGL環境設定

  1. Download : http://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip
  2. 將 glut.dll 和 glut32.dll 放到 C:\WINDOWS\system32 目錄下。
  3. 將 glut32.lib 和 glut.lib 放到C:\Program Files\Microsoft Visual Studio 9.0\VC\lib目錄下。
  4. 將 glut.h 放到C:\Program Files\Microsoft Visual Studio 9.0\VC\ Include\GL\目錄下
  5. Download GLEW file : http://glew.sourceforge.net/,glew 是提供更多 library 使用,像是支援cube map texture 的 function 使用。
  6. 將 glew.h、glxew.h 和 wglew.h 放到C:\Program Files\Microsoft Visual Studio 9.0\VC\ Include\GL\目錄下。
  7. 在 project 的屬性/連結器/輸入 加入 glut.lib 和 glut32.lib。

gl.h :: 是主要的檔案,包含所有基本的函式,用到 gl 開頭的 function 都是來自於 gl.h。

glu.h :: 則是 gl.h 的輔助,提供更多 function 使用。

glut.h :: 是個獨立的視窗介面,像是 MFC 或 Qt 用來編寫視窗的 library。

glaux.h :: 與 glew.h 一樣,擴增新的函式使用,但只 for windows。

 

 


reference : http://www.cc.ntu.edu.tw/chinese/epaper/0024/20130320_2410.html

 

Create a SkyBox

上一篇我們將六張影像( TOP、DOWN、FRONT、BACK、LEFT、RIGHT)投影到一個立方體上,現在要將 Cube投射到一個球狀平面上,來得到一個無線視野的平面影像,利用下圖說明,可以看到 sphere與 cube影像之間的關係。

skymapdiagram

在skybox的概念下指的是我們人眼睛在球形中心向外看出去所得到一個無死角的360°的 perspective影像,然而在opengl裡本來就有內建 gluPerspective function可以使用,自動將3D的 cube轉換成2D的 perspective影像。

glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
WHratio = (width)/height;
gluPerspective(fov, WHratio, 25.0/16,16*25);
glMatrixMode(GL_MODELVIEW);

void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar); fovy指的是視野角度,aspect指視窗長寬比,zNear與zFar指的是可視範圍。

因此可以藉由調整不同的 fov與 cube的旋轉角度而得到不同方位與不同視野的影像。

cubeimagesview in the cube

Cubeview outside of the cube

 cubeview of six cubic images


這裡我將 FRONT這張影像全填為綠色,觀看實際投影效果,可以看到影像內容會根據我不同的視角位置而有不同的 perspective結果。

cube perspective

一開始在實做skybox時,原本以為是利用 Equirectangular影像裡球座標的角度來求得在 cube裡的影像資訊,但後來玩過別人的 cubemap後才發現我大錯特錯,他們是利用 opengl將六張影像投影在 cube上後再做投影,投影方程式 opengl都已經幫我們寫好了,如果要自己實作 Perspective Projection的話應該也是可以,但過程可能會很繁瑣就是了。

 

 


reference : http://www.braynzarsoft.net/index.php?p=D3D11CUBEMAP

 

Cube Texture Mapping

 

Cube Mapping是一種將周圍環境投影到無限大且無死角的360°平面上,就像天空一般。所謂Cube Map即是六張不同的2D影像,利用opengl將這六張影像投影到一個Cube上。Load Texture方式有兩種,一種是最簡單的2D Texture Mapping (GL_TEXTURE_2D);另一種是GL_TEXTURE_CUBE_MAP,使用GL_TEXTURE_CUBE_MAP必須include GL/glew.h這個head檔才能使用,GL_TEXTURE_CUBE_MAP已經將Cube面向給定義好了,只要依循下列位置擺放六張圖就可以了。

GL_TEXTURE_CUBE_MAP


OpenGL環境設定

  1. Download : http://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip
  2. 將glut32.dll放到C:\WINDOWS\system32目錄下
  3. 將glut32.lib和opengl32.lib放到C:\Program Files\Microsoft Visual Studio 9.0\VC\lib目錄下
  4. 將glut.h放到C:\Program Files\Microsoft Visual Studio 9.0\VC\ Include\GL\目錄下
  5. Download GLEW file : http://glew.sourceforge.net/
  6. 將glew.h、glxew.h和wglew.h放到C:\Program Files\Microsoft Visual Studio 9.0\VC\ Include\GL\目錄下

code::GL_TEXTURE_2D Texture Mapping

// FRONT
 if ( !buf.load( filename.c_str() ) ){
qWarning( "Could not read image file, using single-color instead." );
QImage dummy( 128, 128,QImage::Format_RGB32);
dummy.fill( Qt::green);
buf = dummy;}
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[0] ); 
glBindTexture( GL_TEXTURE_2D, texture[0] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
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_TO_EDGE); // for texture seamless from glew.h
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // for texture seamless from glew.h
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // for texture seamless from glew.h
// BACK
filename = folder + "BACK" + subfile;
buf.load(filename.c_str());
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[1] ); 
glBindTexture( GL_TEXTURE_2D, texture[1] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
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_TO_EDGE); // for texture seamless from glew.h
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // for texture seamless from glew.h
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // for texture seamless from glew.h
// TOP
filename = folder + "TOP" + subfile;
buf.load(filename.c_str());
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[2] ); 
glBindTexture( GL_TEXTURE_2D, texture[2] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
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_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
// DOWN
filename = folder + "DOWN" + subfile;
buf.load(filename.c_str());
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[3] ); 
glBindTexture( GL_TEXTURE_2D, texture[3] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
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_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
// RIGHT
filename = folder + "RIGHT" + subfile;
buf.load(filename.c_str());
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[4] );
glBindTexture( GL_TEXTURE_2D, texture[4] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
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_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
// LEFT
filename = folder + "LEFT" + subfile;
buf.load(filename.c_str());
tex = QGLWidget::convertToGLFormat( buf );
glGenTextures( 1, &texture[5] );
glBindTexture( GL_TEXTURE_2D, texture[5] );
glTexImage2D( GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
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_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

code::GL_TEXTURE_CUBE_MAP Texture Mapping

// FRONT
 if ( !buf.load( filename.c_str() ) ) 
 {
 qWarning( "Could not read image file, using single-color instead." );
 QImage dummy( 128, 128,QImage::Format_RGB32);
 dummy.fill( Qt::green);
 buf = dummy;
 }
 tex = QGLWidget::convertToGLFormat( buf );
 glGenTextures( 1, &texture[0] ); 
 glBindTexture( GL_TEXTURE_CUBE_MAP, texture[0] );
 glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 3, tex.width(), tex.height(), 0,
 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );
 // BACK
 filename = folder + "LEFT" + subfile;
 buf.load(filename.c_str());
 tex = QGLWidget::convertToGLFormat( buf );
 glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 3, tex.width(), tex.height(), 0,
 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );

// TOP
 filename = folder + "TOP" + subfile;
 buf.load(filename.c_str());
 tex = QGLWidget::convertToGLFormat( buf );
 glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 3, tex.width(), tex.height(), 0,
 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );

// DOWN
 filename = folder + "DOWN" + subfile;
 buf.load(filename.c_str());
 tex = QGLWidget::convertToGLFormat( buf );
 glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 3, tex.width(), tex.height(), 0,
 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );

// RIGHT
 filename = folder + "BACK" + subfile;
 buf.load(filename.c_str());
 tex = QGLWidget::convertToGLFormat( buf );
 glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 3, tex.width(), tex.height(), 0,
 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );

// LEFT
 filename = folder + "FRONT" + subfile;
 buf.load(filename.c_str());
 tex = QGLWidget::convertToGLFormat( buf );
 glTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 3, tex.width(), tex.height(), 0,
 GL_RGBA, GL_UNSIGNED_BYTE, tex.bits() );

// Texture Filter
 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

Textures :

cube

Cube Texture Mapping 結果:

Cube

 


 

reference :

http://learnopengl.com/#!Advanced-OpenGL/Cubemaps

http://www.braynzarsoft.net/index.php?p=D3D11CUBEMAP

 

Equirectangular Projection

一般要將3D球形表面投影到2D平面上,要找出經緯座標與平面直角坐標之間的關係式。在建立球面與平面之間的對應函數關係時並不能保持球面上的長度、面積與角度的原形,然而投影方式有很多種,最常見的方法就是Equirectangular Projection,利用極地角度(Polar Angles)表示水平座標與垂直座標。下圖為一個地球的Equirectangular Projection。

earth

Cubic Images to Equirectangular


HUMUS網頁上的Earth例子來說明,以六張cubic影像,但彼此之間seam已經是縫合好的情況下作為輸入,如下圖:

未命名

這六張影像分別在cube的相應位置為Top、Down、Front、Back、Left和Right。

未命名

Equirectangular Projection是將影像投影到水平座標(經度)為-\pi\pi之間,垂直座標(緯度)為-\frac { \pi }{ 2 } \frac { \pi }{ 2 } 之間。假設cube是在\left[ -1,1 \right]之間,2*2*2大小的立方體。w,h為Equirectangular影像的長和寬。

x=2.0*i/w-1.0

y=2.0*j/h-1.0

\theta=x*\pi

 \varphi=y*\frac { \pi }{ 2 }

 

藉由下面公式轉換,由經度與緯度計算單位向量。x向量朝向前方,y向量向下方,z向量指向右方

x=\cos { \varphi *\cos { \theta}}

y=\sin { \varphi}

z=\cos { \varphi *\sin { \theta}}

算出[x,y,z]大小後,向量裡元素數值最大的即為指向的面向,將另外兩個向量元素正規化後即可得到該面上的座標位置。例如算出來的[x,y,z]=[0.2099, -0.7289, 0.6516],數值最大為y且為負號,所以該面指向TOP Image,再將座標正規化後可以得到在TOP Image上相應的單位向量[x,z]=[0.2879, 0.8939 ]


所以只要利用上面的步驟就可以得到以經度(Longitude)和緯度(Latitude)為座標的Equirectangular影像。

out

 

 

 

 


reference :

http://www.wenyanan.com/cube2cyl/

http://stackoverflow.com/questions/11504584/

Image from : http://www.humus.name/index.php?page=Cubemap&item=Lycksele3