[Work/Class/OpenGL]

OpenGLを使った3Dグラフィックプログラミング授業資料

概要

OpenGLはマルチプラットフォームの3D描画ライブラリであり,GPUによるハードウェアアクセラレーションが可能で,複雑な図形の高速な描画に対応している.

OpenGLは基本的にはC言語から使うが,C++,Java(やProcessing),Python,Rubyなど主要な言語から使える他,特筆すべきはOpenGL ver.2系以降をベースとしたWebGLによりJavaScript,つまりWebブラウザから実行可能となる.各言語では関数名の頭にクラスを表す接頭辞が付くぐらいで基本的に全く同じ手順でプログラムを書くことができるため,C言語でOpenGLを学べば,ほぼすべてのプラットフォームで利用可能となる.

また本授業では,OpenGL ver.1.x系ではGLUT,ver.2系以降(正確には本授業ではWindows, OSX, macOS共に対応している3.2を扱う)ではGLFWというマルチプラットフォームのウィンドウ&イベントマネージャライブラリを用いる.これによりWindows,OSX,Linux等のOSで本授業のコードは利用可能である.

それに加えver.2系以降では,JavaScriptによるOpenGL実装WebGLを扱う.これによりWebブラウザでOpenGLを扱えるようになるため,Webアプリ制作なども行うことができる.

また,3以降の機能を使うためにGLEWライブラリ(ハードウェア・ドライバ的には対応しているがAPIがOS側のOpenGLライブラリで提供されていないAPI群をまとめたライブラリ)を,OpenGL ver.2系以降の変換用行列を生成・演算するためのglmというC++言語(特にC++11で効果が高い)で記述された行列演算ライブラリを用いる.(WebGLにもglm類似ライブラリが多種あるので,それを用いる)

なお本授業の受講にあたってはProcessingの基礎が既習であることが望ましい.イベント関数などの詳細の説明を省くためである.また,OpenGL ver.2系以降ではC++言語を使用するがglmライブラリを使うための限定的な使用であるので,C++言語を習得している必要はない.

コンテンツ

OpenGL 1.x系

  1. GLUTを使いWindowを作り,イベントをアサインする
  2. OpenGL 1.x系の平行・垂直投影 - 3次元座標系の中での2次元グラフィック描画とアニメーション
  3. OpenGL 1.x系の透視投影 - 3Dグラフィックのプリミティブなオブジェクトの表示とカメラの設定・移動
  4. 3Dグラフィックの座標系と変換・視点の移動

OpenGL 2系以降(本授業資料中のコードはver.3.2を対象とする)

  1. GLUTに代わりGLFWを使う
  2. glmによる変換行列生成と頂点シェーダ
  3. WebGLで書いて実行してみる
  4. フラグメントシェーダとライティング(Phongシェーディング)
  5. モデリングしたオブジェクトのWebGLでの読み込みと表示

授業の下準備

Windowsの場合

まっさらな状態からのMSYS2+GCCでWindows上にC言語とC++の環境を整えるの通りにコンパイラ環境を整える.

その後「MSYS2 MSYS」ショートカットで起動する端末エミュレータから,

$ pacman -S mingw64/mingw-w64-x86_64-freeglut
$ pacman -S mingw64/mingw-w64-x86_64-glfw
$ pacman -S mingw64/mingw-w64-x64_64-glew
$ pacman -S mingw64/mingw-w64-x86_64-glm

の四つのパッケージをインストールしておく.

コンパイルは「MSYS2 MinGW 64-bit」ショートカットで起動する端末エミュレータから,

#GLUTを使ってOpenGL1.x系をコンパイルする
$ /mingw64/bin/x86_64-w64-mingw32-gcc GLプログラムのファイル名.c -o 実行ファイルのファイル名.exe \
    -lopengl32 -lglu32 -lfreeglut -static -static-libgcc -static-libstdc++
#GLFW, GLEW, glmを使ってOpenGL2系以降をコンパイルする
$ /mingw64/bin/x86_64-w64-mingw32-g++ GLプログラムのファイル名.cpp -o 実行ファイルのファイル名.exe \
    -lopengl32 -lglew32 -lglfw3 --std=c++11 -static -static-libgcc -static-libstdc++

とする.(glmはヘッダファイルのみ)

MSYS2の端末エミュレータ外から実行するとき(例えばExplorerからダブルクリックで実行するとき)は,MSYSをインストールした場所\mingw64\binからlibfreeglut.dllglfw3.dllをプログラムを実行する場所にコピーしておく.

OSX, macOSの場合

授業は基本的にWindowsで行うが,プログラムコード自体はOSX, macOSでも動作するようなプログラムを作成する.

標準状態ではGLFWライブラリとglmライブラリ, GLEWライブラリが入っていないので,以下に簡単に手順をまとめる.(ただしOSXの場合自分のコンピュータにのみインストールを行うこと.学校のマシンには入れてはいけない.)

GLFW,GLEW,glmはHomebrew等OSX用パッケージマネージャで入らないわけではないのだが,2017年1月現在は収録されているバージョンが非常に古く本授業資料のコードがコンパイルできないことがあるため,ソースコードから入れる必要がある.

  1. XCodeをインストールした後,(必要があれば)CommandLineToolsをインストールする(El Capitan+XCode8系は必要ない).
  2. GLFW3をダウンロードし,解凍する.
  3. 解凍したディレクトリ内で$ cmake .
  4. $ make
  5. $ sudo make install
  6. glmをダウンロードし,GLFW3と同様に解凍したディレクトリ内で$ cmake .し,$ make$ sudo make installする.
  7. GLEWのページからダウンロードし解凍したディレクトリ内で,$ make GLEW_DEST=/usr/local$ sudo make GLEW_DEST=/usr/local installする.

と,すると,/usr/local/lib/にライブラリが,/usr/local/includeにヘッダファイルが入る.コンパイルは「Terminal.app」から,

#GLUTを使ってOpenGL1.x系のプログラムをコンパイルする
$ clang GLプログラムのファイル名.c -o 実行ファイルのファイル名 \
    -framework OpenGL -framework GLUT -mmacosx-version-min=10.8 
#GLFWとglmを使ってOpenGL3.2のプログラムをコンパイルする
$ clang++ GLプログラムのファイル名.cpp -o 実行ファイルのファイル名 \
    -I/usr/local/include \
    -L/usr/local/lib \
    -lglfw3 -lGLEW \
    -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \
    --std=c++11

とする.

OSXのGLUTは古いバージョンであり(新しいバージョンであるfreeglutが入っていないため,OpenGL ver.2.1までしか対応していない),さらに10.9以降サポート対象外となっているため,-mmacosx-version-min=10.8というオプションをつけて10.8までのコンパイルですよ,と指示している.

WebGLの準備

Chromeウェブブラウザをインストールしておくこと.


[Work/Class/ES2015で動的Webアプリ]

ES2015で動的Webアプリ / 2017-10-18 (水)

この授業の目的

Processingで行ったWebSocket通信を使って,ウェブブラウザ上で動くJavaScriptの2015年版(ECMAScript2015, 通称ES2015, ES6)とProcessingのJavaScript用ライブラリP5.jsを用いて,動的なWebアプリケーションを作るための基礎を学ぶ.

コンテンツ

  1. ES2015の基本 - 変数とそのスコープ,関数定義,Promiseによるコールバック
  2. ES2015の基本 - クラスの定義とインスタンス化
  3. ES2015の基本 - PromiseとXMLHttpRequestを使ったAjaxとfetch
  4. ES2015準拠でP5.jsのインスタンスモード
  5. P5.jsで日本語文字列を表示する
  6. ES2015でWebSocketの基本 - テキストメッセージの受け渡し

授業の下準備

実行環境としてはGoogle Chromeを使うので,最新版を入れておくこと.また描画部分にはP5.js(ProcessingのJavaScript版)を使うので,Processingの描画の復習をしておくこと.

WebSocketサーバ側は,実験用Webサーバ上でコンパイル実行等を行うため,MSYS2やTerminal.appを使ってUnixの基本操作に慣れておくこと.

サーバ側のプログラムとしては,PythonのTornadoライブラリ,C言語(とC++)のlibwebsocketsライブラリの2つを用いる(基本的には理解しやすいTornadoライブラリを使う).ローカルで実行したい場合Anacondaには最初からTornadoが入っている.

ただしC言語のライブラリlibwebsocketsも使う関係上,基本的にはこちらで用意したサーバでサーバ側プログラムを動かすので,無理に入れておく必要はない.


[Work/Class/ES2015で動的Webアプリ/ES2015Basic]

ES2015の基本 - PromiseとXMLHttpRequestを使ったAjaxとfetch / 2017-10-18 (水)

XMLHttpRequestクラスとそのインスタンス

簡単に言えば,インタラクティブに他のページや画像などを持ってきて,今のページに表示させるためのクラスである.

Promiseクラスのオブジェクトを生成するように設計してやると,「読み込み終わって,結果が正しい時に,次のコードを実行する」という処理ができる.

Cross-Originポリシーの関係で,XMLHttpRequestクラスのインスタンスは,そのJavaScriptファイルが置いてあるドメインからしかAjaxできない.(正確には色々やればできるが面倒臭い)

コード例

htmlを取得して,その結果をテキストとしてdivに表示する.

実行ページ

Cross-Originポリシーの関係で,XMLHttpRequestクラスのオブジェクトは,そのJavaScriptファイルが置いてあるドメインからしかAjaxできない.(できるが面倒臭い)

<!DOCTYPE html>
<!-- PromiseAjaxWithClass.html -->
<html lang="ja">
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta charset="utf-8">
    <title>Promiseを使ってAjax</title>
    
    <script type="text/javascript" src="./PromiseAjaxWithClass.js">
    </script>
  </head>
  <body>
    <p>取得したいHTMLのURLを記入</p>
    <form>
      <input type="text" name="target_url" value="http://cad.lolipop.jp/" />
      <input type="button" name="send_button" value="取得" onclick="getHtml(this.form.elements['target_url'].value)" />
    </form>
    <div id="display_iframe">
      ここに取得してきたものが表示されます.
    </div>
  </body>
</html>
// PromiseAjaxWithClass.js
'use strict';
class MyAjax{
    constructor(targetUrl){
	this.targetUrl = targetUrl;
	this.gottenHtmlPage = '';
    }

    setTargetUrl(targetUrl){
	this.targetUrl = targetUrl;
    }

    setDisplayingDivId(divId){
	this.divId = divId;
    }

    // AjaxによるHTMLページの取得を実行する関数 Promiseを返す
    startHttpRequest(){
	return new Promise((resolve, reject) => {
	    let xmlHttpRequest = new XMLHttpRequest();
	    //open(HTTPメソッド, URL, 非同期モードかどうか, ユーザ名, パスワード)
	    xmlHttpRequest.open('GET', this.targetUrl, true);
	    xmlHttpRequest.onload = () => {
		if(xmlHttpRequest.status >= 200 && 
		   xmlHttpRequest.status < 300){
		    resolve(xmlHttpRequest.responseText);
		}
		else{
		    reject(xmlHttpRequest.status);
		}
	    };
	    xmlHttpRequest.onerror = () => {
		reject(xmlHttpRequest.status);
	    };
	    //設定が終わった後にsend(null)を実行すると取得が始まる.
	    xmlHttpRequest.send(null);
	});
    }

    // AjaxによるHTMLページの取得が完了した時に呼び出すコールバック関数
    whenFinishSuceed(result){
	document.getElementById('display_iframe').innerText = result;
    }

    whenFinishFailed(err){
	document.getElementById('display_iframe').innerHTML = 
	    '<p>' + 'Faild to get WebPage. Error code is: ' + err + '</p>';
    }
}

window.getHtml = (targetUrl) => {
    window.myAjax.setTargetUrl(targetUrl);
    window.myAjax.startHttpRequest()
	.then(window.myAjax.whenFinishSuceed)
	.catch(window.myAjax.whenFinishFailed);
};

window.addEventListener('load', () => {
    window.myAjax = new MyAjax('http://cad.lolipop.jp/');
    window.myAjax.setDisplayingDivId('display_iframe');
    window.myAjax.startHttpRequest()
	.then(window.myAjax.whenFinishSuceed)
	.catch(window.myAjax.whenFinishFailed);
});

fetch API

とまぁ,自分で実装すると面倒臭いことこの上ないのだが,Promiseのインスタンス(オブジェクト)を返す全く同じ機能がfetch関数として既に標準ライブラリとして実装されており,Chromeでは問題なくXMLHttpRequestクラスの代わりとして使用できる.

おまけに,Cross-Originポリシーも(サーバが対応していれば)一行設定を書いてやるだけで無視することができる.

コード例

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Fetch API Test</title>
    <script type="text/javascript">
      window.addEventListener('load',  () => {
	  fetch('http://sfdn-w1.sd.tmu.ac.jp/~daichi/', {
	      method: 'GET',
	      mode: 'cors' //Cross-Originポリシーを無視する
	  }).then((response) => {
	      return response.text()
	  }).then((text) => {
	      document.getElementById('for_display').innerText = text;
	  }).catch((error) => {
	      console.log(error);
	  });
      });
    </script>
  </head>
  <body>
    <div id="for_display">
    </div>
  </body>
</html>

[Work/Class/C++11の場合]

C++11の場合

この授業の目的

これまで,ProcessingIDEを用いてJava言語とその標準ライブラリを使ったオブジェクト指向プログラミングを学んできた.ここではC++(11年標準)とその標準ライブラリを使ってどのように書けば良いかを,参考程度に学ぶ.

コンテンツ

  1. ポインタとmalloc,freeによる動的配列確保
  2. クラスの定義とインスタンス化,ヘッダファイルへの分割
  3. 演算子のオーバーロード
  4. auto型宣言とdecltype()
  5. 範囲for文
  6. ラムダ式
  7. Standard Template Library(STL)の基本 - テンプレートの概念
  8. Standard Template Library(STL)の基本 - Collectionとイテレータ

授業の下準備

Windowsの場合

まっさらな状態からのMSYS2+GCCでWindows上にC言語とC++の環境を整えるの通りにコンパイラ環境を整える.

(64bit環境でも32bit環境でも動作する)32bitWindows用バイナリのコンパイルは「MSYS2 MinGW 64-bit」ショートカットで起動する端末エミュレータから,以下のようにする.

 $ /mingw64/bin/x86_64-w64-mingw32-g++ C++プログラムのファイル名.cpp -o 実行ファイルのファイル名.exe --std=c++11 -static -static-libgcc -static-libstdc++

Macの場合

XCodeをインストールした後,(必要があれば)CommandLineToolsをインストールする(El Capitan+XCode8系は必要ない).XCodeをGUI環境で一度実行しておく必要がある.

コンパイルは端末エミュレータ「Terminal.app」から以下のようにする.(Macの場合64bitのみで問題ない)

 $ clang++ C++プログラムのファイル名.cpp -o 実行ファイルのファイル名 --std=c++11 

[Work/Class/C++11の場合/CPPBasic]

C++11の場合 - 範囲for文

範囲for文

Java7でやった拡張for文や,Pythonのlist, arrayを与えるfor文のC++版である.

#include <iostream>

int main(){
  // 拡張for文を静的確保した配列で使う
  int static_array[5] = {1, 2, 3, 4, 5};
  for(auto& item: static_array)
    std::cout << item << std::endl;
  return 0;
}

という形で使う.Java拡張for文やpythonのfor文まんまである.

ちなみにstd::cout << 出力したい変数や文字列 << std::endl;は,printf()関数の代わりに使える.

printf関数では,必ず出力する値の型が明確になっていなければならない(print("%d", int型の変数);など)が,autoを使っていることからわかるように,std::coutはどんな型でも<<で連結して出力することができる.(JavaのSystem.out.println関数内つまりStringで,「+」を使って文字列や変数を連結していくのと同じだと捉えるとよい.正確には演算子のオーバーロードを使って実装されている.)

例えばfor文でstd::endl;を書かないまま回して,for文のループが終了してからstd::out << std::endl;とすれば,行の出力が命令文をまたぐこともできる.

std::vector

ついでなので,可変長配列std::vectorもやっておく.

JavaでいうところのArrayListに相当する.

std::vector<収納する型> vectorの変数名;

で空の配列(要素0)を宣言し,

vectorの変数名.push_back(追加する要素)

で末尾に要素(この場合はint型の数)を追加していく.

もともと範囲for文は,このstd::vectorのために作られたもので,非常に簡単に回すことができる.(JavaのArrayListと拡張for文の組み合わせと同じように使える)

#include <iostream>
#include <vector>

int main(){
  // 可変長配列std::vectorで範囲for文を使う
  int numOfElements = 20;
  std::vector<int> dynamic_array;  
  for(int i=0; i<numOfElements; i++){
    dynamic_array.push_back(rand());
  }

  for(auto& item: dynamic_array)
    std::cout << item << ",";
  std::cout << std::endl;
  return 0;
}

std::vectorの詳しい説明は,STLのCollectionの項で行う.