電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
GLSL&GLUT 從環境設定開始的基礎教學(01)

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫
上一篇主題 :: 下一篇主題  
發表人 內容
Director
偶而上來逛逛的過客


註冊時間: 2013-11-04
文章: 13

381.66 果凍幣

發表發表於: 2013-11-4, PM 6:34 星期一    文章主題: GLSL&GLUT 從環境設定開始的基礎教學(01) 引言回覆

引言回覆:
2015/11/12更新

最近再等當兵,想把這系列GLSL教學更新然後統整在我自己的blog,有興趣的可以來看看http://programerkk.blogspot.tw/ Very Happy


 大家好,初來乍到。

 在拜讀了還是零分大大的OpenGL入門教學系列後,我大致花了點時間做了個3D遊戲的地圖編輯器,像這樣
 
 雖然大概只有一萬行左右的規模,但也算是基礎成形的繪圖器。

 再感謝有這個論壇之餘,要繼續寫下去做更精細的光影時,才發現好像沒有GLSL的教學文章,可能大家都用別的 Embarassed ? 但因為我還是想做GLSL,所以在看了一些外國的教學後,我發現入門就有點麻煩,就想來說明一下環境的設置,希望能幫助到其他想要寫GLSL的朋友可以輕鬆開始學習。

 也希望有更多人能學會這個東西來互相討論 Very Happy

 我的基礎介面因為習慣問題,還是以GLUT來建構的,那接下來就開始介紹,若有錯誤也請指證 Smile

引言回覆:

 ※我的IDE用的是Visual Studio 2012,所以用Dev寫的話,環境設置出錯我可能也不太知道怎麼解QQ。

 ※用Visual Studio編譯出來的.exe要拿到別台電腦上跑,都需要安裝"全部版本"的可轉散發套件http://www.microsoft.com/zh-tw/download/details.aspx?id=30679

 ※記得下載並安裝GLee(GL Easy Extension library)函式庫,官網的好像壞掉了http://www.opengl.org/sdk/libs/GLee/
  ,所以我就自己上傳了https://www.dropbox.com/s/piw2oymi3lb4u9k/GLee5_4.zip

 ※2015/7/5補充:
  其實已經過很久了,GLee似乎已經沒再繼續更新,我在寫了這篇教學不久後也改用了GLEW取代Glee,它們的功能及函式名稱其實是幾乎一樣的,安裝方式同下的Step5, 6
  所以只需要將glee.h及glee.lib改為glew.h及glew.lib就好,這是下載的地方,抓Binaries那個http://glew.sourceforge.net/
  lib用 lib\Release\Win32 裡面的。


 ※這是我自己打的freeglut在Visual Studio 2012的環境建置方法https://www.dropbox.com/s/xhc06ibnhdnmosf/00_%E5%AE%89%E8%A3%9D.docx?dl=0

 ※GLee的安裝方式同上的Step5, 6
  但是記得要做環境設置,到專案(Project) -> 屬性(Properties) -> 連結器(Linker) -> 輸入(Input) -> Additional Dependencies(中文版我忘記了QQ 就是寫很多.lib的地方,在這加上GLee.lib)

 ※專案開起要選擇Visual C++ -> Win32 -> Win32 主控台應用程式(Win32 Console Application) -> Next -> 附加選項(Addition options) -> 勾選空專案(Empty project)



 我是從這部教學影片開始學的https://www.youtube.com/watch?v=pE9ZDNcg3kw
 所以下面的範例程式也會相當類似。

 首先說明一下什麼是GLSL,他是OpenGL Shading Language的簡寫,Shader,著色器,簡言之就是去更直接的控制OpenGL對點(vertex)上色的語言,算是比較高難度的(至少我是這麼覺得 Crying or Very sad )圖學程式設計。
引言回覆:

 題外話,我學了兩天大概只能做到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影片算是我看過的裡面算很好的教程(雖然他唸英文很快 Shocked ),他講得都很詳細而且會順便連數學都一起教你XD,這部http://youtu.be/pE9ZDNcg3kw

 最後希望我寫教學02的那天可以趕快到來 Very Happy

 附上本篇程式範例碼,沒意外的話會輸出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環境 Very Happy
回頂端
檢視會員個人資料 發送私人訊息 發送電子郵件 參觀發表人的個人網站
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作