Director 偶而上來逛逛的過客
註冊時間: 2013-11-04 文章: 13
381.66 果凍幣
|
發表於: 2013-11-4, PM 6:34 星期一 文章主題: GLSL&GLUT 從環境設定開始的基礎教學(01) |
|
|
大家好,初來乍到。
在拜讀了還是零分大大的OpenGL入門教學系列後,我大致花了點時間做了個3D遊戲的地圖編輯器,像這樣
雖然大概只有一萬行左右的規模,但也算是基礎成形的繪圖器。
再感謝有這個論壇之餘,要繼續寫下去做更精細的光影時,才發現好像沒有GLSL的教學文章,可能大家都用別的 ? 但因為我還是想做GLSL,所以在看了一些外國的教學後,我發現入門就有點麻煩,就想來說明一下環境的設置,希望能幫助到其他想要寫GLSL的朋友可以輕鬆開始學習。
也希望有更多人能學會這個東西來互相討論
我的基礎介面因為習慣問題,還是以GLUT來建構的,那接下來就開始介紹,若有錯誤也請指證
我是從這部教學影片開始學的https://www.youtube.com/watch?v=pE9ZDNcg3kw
所以下面的範例程式也會相當類似。
首先說明一下什麼是GLSL,他是OpenGL Shading Language的簡寫,Shader,著色器,簡言之就是去更直接的控制OpenGL對點(vertex)上色的語言,算是比較高難度的(至少我是這麼覺得 )圖學程式設計。
引言回覆: |
題外話,我學了兩天大概只能做到normal mapping,但也不算完全理解,所謂normal mapping是像這樣。
讓平面的圖案看起來像擁有立體細緻度的貼圖實作。
|
在這裡我只會介紹實作的部分,詳細的電腦圖學概念(vertex, fragment)我不會做太多詳述,想知道的可以去問問學校教授或著wiki上面兩個單字加上computer graphics。
GLSL的運作方法就像是在你的OpenGL程式裡面再compile一個程式負責去管理調整你的著色功能,那問題來了,要怎麼在程式裡面在編譯另外一個程式?
很簡單,把程式碼讀進一個字串堆裡,然後交給OpenGL就好了。
應該有不少人都有去查過GLSL實作教學,基本上應該大多都會在主程式的.cpp外再多加上兩個以上文件,這裡面裝的就是要拿來給OpenGL編譯的程式碼。
所以整個GLSL的基礎環境就像是:
Step1: 把著色器的程式碼寫在兩個檔案(要分vertex和fragment)
Step2: 讀取兩個文件把它存進memory(const char*)
Step3: 把他跟OpenGL的相關函式連在一起,然後compile他們
Step4: 然後就可以開始用你剛剛寫的著色器程式來渲染你的面
那麼我們就一步一步來。
引言回覆: | Step1
分兩個檔案顧名思義就是你要寫兩段不同的程式來處理vertex跟fragment的部分,在一開始你可以把vertex當作是處理空間矩陣的部分,而fragment是處理顏色的部分。
然而實際上這是相當複雜的,需要具備相當多的這方面知識才能理解,這邊我只打算說明實作部分就不再贅述(關於vertex, fragment可參考http://nehe.gamedev.net/article/glsl_an_introduction/25007/)
以下用vertex shader和fragment shader稱呼他們。
這裡先附上最基礎的vertex shader和fragment shader程式碼(副檔名隨意,跟你程式讀檔寫一樣的就好)。
代碼: | // vertex.vs
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
} |
代碼: | // fragment.frag
void main()
{
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
} |
gl_Position
-> 是一個4維的向量(vector),就是整個環境你設置的每一個vertex的最終型態,他只能在vertex shader中存在。
gl_FragColor
-> 是一個4維的向量,就是你設定的畫面中每一個pixel的最終型態,他只能出現在fragment shader。
gl_ModelViewProjectionMatrix
-> 是一個4*4的矩陣,代表把vertex轉換成model-view-projection position的矩陣。
gl_Vertex
-> 是一個4維的向量,代表每一個vertex。
※稱vector是向量可能不太好,畢竟不是全部的使用都會具有方向性,我也不能很好的說明,但我個人會認為把它看作1*4的矩陣可能會比較好理解。
這些在上面PO的那篇NeHe介紹GLSL的文章裡都有說到。
簡言之,無論OpenGL之後會對gl_Position和gl_FragColor做什麼,在最後算出來的gl_Position就會是你在畫面上面看到的每一個頂點。
而gl_FragColor就會是畫面上面每一個"pixel"的顏色,請注意不是每個頂點,是每個像素的顏色。
綜上所述,要理解那兩個子程式應該足夠簡單了。
vertex.vs裡面說的就是,我要把我的每個立體空間座標轉換成投影在螢幕上的點。
fragment.frag則是,把存在面的地方上的每個pixel都塗成紅色的。
|
那麼再來的問題就是怎麼compile這兩個shader程式碼了。
引言回覆: | Step2
首先我們需要一個能夠讀取文件的Function。
代碼: | void loadFile( const char* filename, std::string &string )
{
std::ifstream fp(filename);
if( !fp.is_open() ){
std::cout << "Open <" << filename << "> error." << std::endl;
return;
}
char temp[300];
while( !fp.eof() ){
fp.getline( temp, 300 );
string += temp;
string += '\n';
}
fp.close();
} |
很基礎的C++讀寫檔應該不用多介紹,就是把檔案存進引數的string裡。
|
然後就要把這段引進的程式碼跟OpenGL的關聯函式做連結了!
引言回覆: | Step3-1
代碼: | GLuint loadShader(std::string &source, GLenum type)
{
GLuint ShaderID;
ShaderID = glCreateShader( type ); // 告訴OpenGL我們要創的是哪種shader
const char* csource = source.c_str(); // 把std::string結構轉換成const char*
glShaderSource( ShaderID, 1, &csource, NULL ); // 把程式碼放進去剛剛創建的shader object中
glCompileShader( ShaderID ); // 編譯shader
char error[1000] = "";
glGetShaderInfoLog( ShaderID, 1000, NULL, error ); // 這是編譯過程的訊息, 錯誤什麼的把他丟到error裡面
std::cout << "Complie status: \n" << error << std::endl; // 然後輸出出來
return ShaderID;
} |
glCreateShader( type );
首先我們要告訴OpenGL我們現在要compile的是vertex shader還是fragment shader。
type通常是放GL_VERTEX_SHADER或是GL_FRAGMENT_SHADER。
也有更多其他功能的type,可參照http://www.opengl.org/sdk/docs/man/xhtml/glCreateShader.xml
glShaderSource( shader_id, how_many_string_array, string_array, string_array_length );
再來我們要把剛剛載入的程式碼送進去OpenGL的shader object裡面。
要注意,一份source code一般就放在一個string,string array是說有很多份source code。
第一個是你的shader的handle或是代號,就類似glut裡的window number那樣的東西。
第二個how_many_string_array,不是說有幾行程式碼,像我們一個shader才用一份程式碼就寫1。
第三個string_array結構是const char**,結構是字串陣列,所以只有一個字串的話就要記得加上&。
第四個也是要用string array才會用上的,一般寫NULL就好。
glCompileShader( shader_id );
用跟glShaderSource一樣的ID就可以compile那個shader。
glGetShaderInfoLog
如註解不贅述,可用可不用。
Step3-2
再來要做的是,把vertex shader和fragment shader連結,做成program。
代碼: | GLuint vs, fs, program; // 用來儲存shader還有program的id
void initShader(const char* vname, const char* fname)
{
std::string source;
loadFile( vname, source ); // 把程式碼讀進source
vs = loadShader( source, GL_VERTEX_SHADER ); // 編譯shader並且把id傳回vs
source = "";
loadFile( fname, source );
fs = loadShader( source, GL_FRAGMENT_SHADER );
program = glCreateProgram(); // 創建一個program
glAttachShader( program, vs ); // 把vertex shader跟program連結上
glAttachShader( program, fs ); // 把fragment shader跟program連結上
glLinkProgram( program ); // 根據被連結上的shader, link出各種processor
glUseProgram( program ); // 然後使用它
} |
glCreateProgram( );
創建一個program,算是乘載著各種shader的東西。
glAttachShader( program_id, shader_id );
把你compile好的shader連上program,可以連上複數個shader。
glLinkProgram( program_id );
各種shader對應的processor可參照http://www.opengl.org/sdk/docs/man/xhtml/glLinkProgram.xml
glUseProgram( program_id );
使用這個program。
|
那這樣子GLSL裡面shader的基礎再基礎的建置介紹大概就這樣完成了,依照Step1寫的fragment shader,在這個程式裡不管畫什麼都會是紅色的。
之後若想要設定不同點不同顏色,或是貼材質,製作光影等等各種更高階的運用,就要正式介紹shader的程式設計,我想這要等到我更加了解它之後才能來這說明了。
網路上很多範例的shader程式碼,雖然都是外文的,但不算太難,在文章頭貼的那個YouTube影片算是我看過的裡面算很好的教程(雖然他唸英文很快 ),他講得都很詳細而且會順便連數學都一起教你XD,這部http://youtu.be/pE9ZDNcg3kw
最後希望我寫教學02的那天可以趕快到來
附上本篇程式範例碼,沒意外的話會輸出Step1第三張圖的那個程式。
滑鼠的部分是我自己寫來控制3D視角的,可以自己改寫。
只要再創建兩個檔案命名為vertex.vs和fragment.frag,內容如Step1,然後放在跟主程式相同目錄就能運行了。
代碼: |
//---------------------------------------------------------
// 作者: DR
// 2013/11/4
//
// GLSL&GLUT 從環境設定開始的基礎教學(01)
//---------------------------------------------------------
//
#include <iostream>
#include <GL\GLee.h>
#include <GL\freeglut.h>
#include <vector>
#include <string>
#include <fstream>
#define WINDOW_SIZE_W 480 // 起始視窗寬度
#define WINDOW_SIZE_H 480 // 啟示視窗高度
#define WINDOW_VISION_ANGLE 50 // 攝影機視角
#define WINDOW_VISION_NEAR 1 // 攝影機最近視野
#define WINDOW_VISION_FAR 10001 // 攝影機最遠視野
struct Camera{
double Stie[3]; // 主相機本身座標, [0]->x, [1]->y, [2]->z
double Point[3]; // 主相機目標座標, [0]->x, [1]->y, [2]->z
double Nvector[3]; // 主相機上方向量, [0]->x, [1]->y, [2]->z
double Vision; // 主相機視野, 保持Site與Point距離用
};
Camera MainCamera;
void loadFile( const char* filename, std::string &string )
{
std::ifstream fp(filename);
if( !fp.is_open() ){
std::cout << "Open <" << filename << "> error." << std::endl;
return;
}
char temp[300];
while( !fp.eof() ){
fp.getline( temp, 300 );
string += temp;
string += '\n';
}
fp.close();
}
GLuint loadShader(std::string &source, GLenum type)
{
GLuint ShaderID;
ShaderID = glCreateShader( type ); // 告訴OpenGL我們要創的是哪種shader
const char* csource = source.c_str(); // 把std::string結構轉換成const char*
glShaderSource( ShaderID, 1, &csource, NULL ); // 把程式碼放進去剛剛創建的shader object中
glCompileShader( ShaderID ); // 編譯shader
char error[1000] = "";
glGetShaderInfoLog( ShaderID, 1000, NULL, error ); // 這是編譯過程的訊息, 錯誤什麼的把他丟到error裡面
std::cout << "Complie status: \n" << error << std::endl; // 然後輸出出來
return ShaderID;
}
GLuint vs, fs, program; // 用來儲存shader還有program的id
void initShader(const char* vname, const char* fname)
{
std::string source;
loadFile( vname, source ); // 把程式碼讀進source
vs = loadShader( source, GL_VERTEX_SHADER ); // 編譯shader並且把id傳回vs
source = "";
loadFile( fname, source );
fs = loadShader( source, GL_FRAGMENT_SHADER );
program = glCreateProgram(); // 創建一個program
glAttachShader( program, vs ); // 把vertex shader跟program連結上
glAttachShader( program, fs ); // 把fragment shader跟program連結上
glLinkProgram( program ); // 根據被連結上的shader, link出各種processor
glUseProgram( program ); // 然後使用它
}
void clean()
{
glDetachShader( program, vs );
glDetachShader( program, fs );
glDeleteShader( vs );
glDeleteShader( fs );
glDeleteProgram( program );
}
void init()
{
glEnable( GL_DEPTH_TEST );
initShader( "vertex.vs", "fragment.frag" );
// 主視窗的攝影機初始設定
MainCamera.Stie[0] = 20;
MainCamera.Stie[1] = -20;
MainCamera.Stie[2] = 20;
MainCamera.Point[0] = 0;
MainCamera.Point[1] = 0;
MainCamera.Point[2] = 0;
MainCamera.Nvector[0] = 0;
MainCamera.Nvector[1] = 0;
MainCamera.Nvector[2] = 1;
MainCamera.Vision = pow((MainCamera.Stie[0]-MainCamera.Point[0]), 2)+pow((MainCamera.Stie[1]-MainCamera.Point[1]), 2)+pow((MainCamera.Stie[2]-MainCamera.Point[2]), 2);
//
}
void display()
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt( MainCamera.Stie[0], MainCamera.Stie[1], MainCamera.Stie[2]
, MainCamera.Point[0], MainCamera.Point[1], MainCamera.Point[2]
, MainCamera.Nvector[0], MainCamera.Nvector[1], MainCamera.Nvector[2]);
glBegin(GL_QUADS);
glVertex3f(10, 0, -10);
glVertex3f(-10, 0, -10);
glVertex3f(-10, 0, 10);
glVertex3f(10, 0, 10);
glEnd();
glutSwapBuffers();
}
void Reshape(int w, int h)
{
glViewport( 0, 0, w, h );
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective( WINDOW_VISION_ANGLE, (float)w/(float)h, WINDOW_VISION_NEAR, WINDOW_VISION_FAR );
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
bool MOUSE_LIFT = false, MOUSE_RIGHT = false;
int mouse_x = 0, mouse_y = 0, old_mouse_x = 0, old_mouse_y = 0;
void Mouse(int button, int state, int x, int y)
{
switch(button){
case GLUT_LEFT_BUTTON:
if(state) {
MOUSE_LIFT = false;
mouse_x = 0; // 沒有歸零會有不理想的結果
mouse_y = 0;
}
else {
MOUSE_LIFT = true;
old_mouse_x = x;
old_mouse_y = y;
}
break;
case GLUT_RIGHT_BUTTON:
if(state){
MOUSE_RIGHT = false;
mouse_x = 0; // 沒有歸零會有不理想的結果
mouse_y = 0;
}
else{
MOUSE_RIGHT = true;
old_mouse_x = x;
}
break;
}
}
void MotionMouse(int x, int y)
{
double RATE_MOUSE_LIFT_X = 0.0025;
double RATE_MOUSE_LIFT_Y = 0.07;
double RATE_MOUSE_RIGHT_X = 0.003;
if( MOUSE_LIFT ){
mouse_x = x - old_mouse_x;
mouse_y = y - old_mouse_y;
double TEMP_RATE_SHIFT[2];
TEMP_RATE_SHIFT[0] = (MainCamera.Point[1]-MainCamera.Stie[1])*(float)mouse_x*RATE_MOUSE_LIFT_X;
TEMP_RATE_SHIFT[1] = (MainCamera.Stie[0]-MainCamera.Point[0])*(float)mouse_x*RATE_MOUSE_LIFT_X;
MainCamera.Point[0] -= TEMP_RATE_SHIFT[0];
MainCamera.Point[1] -= TEMP_RATE_SHIFT[1];
MainCamera.Point[2] += (float)mouse_y*RATE_MOUSE_LIFT_Y;
double TEMP_RATE_VISION = sqrt(pow((MainCamera.Stie[0]-MainCamera.Point[0]), 2)+pow((MainCamera.Stie[1]-MainCamera.Point[1]), 2)+pow((MainCamera.Stie[2]-MainCamera.Point[2]), 2));
MainCamera.Stie[0] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[0]-MainCamera.Point[0]);
MainCamera.Stie[1] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[1]-MainCamera.Point[1]);
MainCamera.Stie[2] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[2]-MainCamera.Point[2]);
old_mouse_x = x;
old_mouse_y = y;
glutPostRedisplay();
}
else if( MOUSE_RIGHT ){
mouse_x = x - old_mouse_x;
double TEMP_RATE_SHIFT[2];
TEMP_RATE_SHIFT[0] = (MainCamera.Point[1]-MainCamera.Stie[1])*(float)mouse_x*RATE_MOUSE_RIGHT_X;
TEMP_RATE_SHIFT[1] = (MainCamera.Stie[0]-MainCamera.Point[0])*(float)mouse_x*RATE_MOUSE_RIGHT_X;
MainCamera.Stie[0] -= TEMP_RATE_SHIFT[0];
MainCamera.Stie[1] -= TEMP_RATE_SHIFT[1];
MainCamera.Point[0] -= TEMP_RATE_SHIFT[0];
MainCamera.Point[1] -= TEMP_RATE_SHIFT[1];
double TEMP_RATE_VISION = sqrt(pow((MainCamera.Stie[0]-MainCamera.Point[0]), 2)+pow((MainCamera.Stie[1]-MainCamera.Point[1]), 2)+pow((MainCamera.Stie[2]-MainCamera.Point[2]), 2));
MainCamera.Stie[0] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[0]-MainCamera.Point[0]);
MainCamera.Stie[1] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[1]-MainCamera.Point[1]);
old_mouse_x = x;
glutPostRedisplay();
}
}
void WheelMouse(int wheel, int direction, int x, int y)
{
double RATE_WHEEL_X = (MainCamera.Stie[0]-MainCamera.Point[0])*0.1;
double RATE_WHEEL_Y = (MainCamera.Stie[1]-MainCamera.Point[1])*0.1;
double RATE_WHEEL_Z = (MainCamera.Stie[2]-MainCamera.Point[2])*0.1;
switch(direction){
case 1:
MainCamera.Stie[0] -= RATE_WHEEL_X;
MainCamera.Stie[1] -= RATE_WHEEL_Y;
MainCamera.Stie[2] -= RATE_WHEEL_Z;
MainCamera.Point[0] -= RATE_WHEEL_X;
MainCamera.Point[1] -= RATE_WHEEL_Y;
MainCamera.Point[2] -= RATE_WHEEL_Z;
break;
case -1:
MainCamera.Stie[0] += RATE_WHEEL_X;
MainCamera.Stie[1] += RATE_WHEEL_Y;
MainCamera.Stie[2] += RATE_WHEEL_Z;
MainCamera.Point[0] += RATE_WHEEL_X;
MainCamera.Point[1] += RATE_WHEEL_Y;
MainCamera.Point[2] += RATE_WHEEL_Z;
break;
}
double TEMP_RATE_VISION = sqrt(pow((MainCamera.Stie[0]-MainCamera.Point[0]), 2)+pow((MainCamera.Stie[1]-MainCamera.Point[1]), 2)+pow((MainCamera.Stie[2]-MainCamera.Point[2]), 2));
MainCamera.Stie[0] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[0]-MainCamera.Point[0]);
MainCamera.Stie[1] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[1]-MainCamera.Point[1]);
MainCamera.Stie[2] += ((sqrt(MainCamera.Vision)-TEMP_RATE_VISION)/TEMP_RATE_VISION)*(MainCamera.Stie[2]-MainCamera.Point[2]);
glutPostRedisplay();
}
GLint WinNumber = NULL;
int main(int argc, char **argv)
{
glutInit( &argc, argv );
glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( WINDOW_SIZE_W, WINDOW_SIZE_H );
glutInitWindowPosition( 0, 0 );
WinNumber = glutCreateWindow( "test window" );
init();
glutReshapeFunc ( Reshape );
glutDisplayFunc ( display );
glutMouseFunc ( Mouse );
glutMotionFunc ( MotionMouse );
glutMouseWheelFunc( WheelMouse );
glutMainLoop();
clean();
return 0;
} |
那麼到此為止,我想的到的應該都寫完了,祝各位順利建置GLSL環境 |
|