C/C++の言語仕様は気に食わない、でもライブラリは使いたい...
そんな自分はD言語を始めました。しかしやってみるとわかるが あまりにも日本語のちゃんとした文献が少ない!ということでメモっておきます。自分はゲーム開発目的のため、IDEが欲しかったのでVisual Dを入れましたが、必要なければコンソールでも大丈夫だと思います。また、D言語でのゲーム開発にはDerelictとかいうC/C++のいろんなライブ ラリをラップしたやつを使うのが良さげとのことだったのでそれを使います。OpenGLを使うにあたってGLUTを使いたかったのですが、いざ調べてやっ てみたら「glut32.lib is not valid library」的なこと言われてコンパイルできなかったので、諦めてGLFWを使うことにしました。ちなみに現在は2015年12月2日。
Visual Studioのインストール
D 言語のIDE(統合開発環境)であるVisual DはVisual Studioのプラグインで、Visual Studio2005~2015,Visual Studio Community 2013,2015,その他Visual Studio Shellで動きます(Expressでは動かないので注意!)。該当するものが既にPCにインストールされていたらここはパス。入っていなければ
- ここを開く。
- 上のバーから「製品情報->Visual Studio 2015」をダウンロード。
- 指示にしたがってインストール
DMDのインストール
DMD(Digital Mars D compiler)をインストールしましょう
こちらから自分のOSに合ったものをダウンロードしてインストールしてください。
Visual Dのインストール
リンクはこちら。うまいことインストールしましょう。
ここまでは日本語の文献もあるのですが、ここから先が資料が少なかったり古かったりして難儀しました。
Derelict3の準備
- gitのレポジトリを開き、右下のほうにある「Download ZIP」からzipをダウンロード
- 適当に解凍
- コマンドプロンプトを開き、解凍してできたフォルダ(自分の場合はDerelict3-masterでした)/buildに移動
- dmd build.dと入力
- 多分Error: module std.array import 'endsWith' not foundみたいなことが出る
- もし出ていたらbuild.dを開き、import std.array : endsWith;の行を削除
- もっかいdmd build.dと入力
- build.dと同じフォルダにbuild.exeができているのを確認
- build.exeを実行
- Derelict3-master/lib/dmdに.libファイルがたくさんできているのを確認
GLFWのダウンロード
- http://www.glfw.org/download.htmlを開く
- Windows pre-compiled binariesの右のほうのボタンからダウンロード
- 適当な場所に解凍
プロジェクト作成
- Visual Studioを開く
- 左上のファイルから「新規作成->プロジェクト->Console Application」を選び、適当なプロジェクト名をつけ、OKを押す
- 右のソリューションエクスプローラーからプロジェエクト名を右クリック->「エクスプローラーからフォルダを開く」
- 先ほど準備したDerelict3-master/import/derelictをderelictフォルダごとそこにコピー
- Derelict3-master/libをlibフォルダごとそこにコピー
- 先ほど準備したglfwのフォルダからlib-vc[自分のVisual Studioのバージョン]を開き、glfw3.dllをそこにコピー
- Visual Studioの上のデバッグから「[プロジェクト名]のプロパティ->構成プロパティ->Linker->General」を選び、Library Filesに
lib/dmd/DerelictUtil.lib lib/dmd/DerelictSDL2.lib lib/dmd/DerelictGL3.lib lib/dmd/DerelictGLFW3.lib
と入力してOKを押す
(注)2.でConsole Application DMD/GDC を選ぶと何故かHello worldさえ動かなかったので気をつけましょう。
ここまでで準備は完了です。今度は実際にコードを書いていきましょう。
import std.stdio;
import std.string;
import std.conv;
import std.file;
import std.mathspecial;
import derelict.opengl3.gl;
import derelict.glfw3.glfw3;
void main()
{
//Derelictの初期化。必要なもののみload
DerelictGL.load();
DerelictGLFW3.load();
if (!glfwInit()) {
writeln("Failed to initialize GLFW");
return;
}
//ウインドウを生成。
const int window_width = 600;
const int window_height = 600;
auto window = glfwCreateWindow(window_width, window_height,"Hello, D world!", null, null);
if(!window){
writeln("Failed to create window");
return;
}
//GLFWとウインドウを紐付け
glfwMakeContextCurrent(window);
//リロード
auto glver = DerelictGL.reload();
if(glver < derelict.opengl3.gl3.GLVersion.GL40){
return;
}
//シェーダを生成
auto vsID = glCreateShader(GL_VERTEX_SHADER);
auto fsID = glCreateShader(GL_FRAGMENT_SHADER);
//生成したシェーダとGLSLソースコードを紐付け
string vsPath = ((cast(const char[])read("TestShader.vert"))).idup;
string fsPath = ((cast(const char[])read("TestShader.frag"))).idup;
auto str = vsPath.toStringz;
int len = vsPath.length;
glShaderSource(vsID, 1, &str, &len);
str = fsPath.toStringz;
len = fsPath.length;
glShaderSource(fsID, 1, &str, &len);
//GLSLソースをコンパイル
glCompileShader(vsID);
glCompileShader(fsID);
//コンパイルエラーを確認
int result;
glGetShaderiv(vsID, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE) {
int logLength;
glGetShaderiv(vsID, GL_INFO_LOG_LENGTH, &logLength);
char[] log = new char[logLength];
int a;
glGetShaderInfoLog(vsID, logLength, &a, &log[0]);
("Compile Error in \"" ~ vsPath ~ "\"\n" ~ to!string(log)).writeln;
return;
}
glGetShaderiv(fsID, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE) {
int logLength;
glGetShaderiv(fsID, GL_INFO_LOG_LENGTH, &logLength);
char[] log = new char[logLength];
int a;
glGetShaderInfoLog(fsID, logLength, &a, log.ptr);
("Compile Error in \"" ~ fsPath ~ "\"\n" ~ to!string(log)).writeln;
return;
}
//シェーダプログラムを生成
int programID = glCreateProgram();
//シェーダプログラムと各シェーダを紐付け
glAttachShader(programID, vsID);
glAttachShader(programID, fsID);
//シェーダプログラムとシェーダをリンク
glLinkProgram(programID);
//リンクエラーを確認
glGetProgramiv(programID, GL_LINK_STATUS, &result);
if (result == GL_FALSE) {
int logLength;
glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &logLength);
char[] log = new char[logLength];
int a;
glGetProgramInfoLog(programID, logLength, &a, log.ptr);
("Link Error\n" ~ to!string(log)).writeln;
return;
}
//準備完了したシェーダプログラムの使用を宣言
glUseProgram(programID);
//頂点シェーダのattribute変数のアドレスを取得
int vLoc = glGetAttribLocation(programID, "mVertexPosition");
//VAOを作成
uint[1] vao;
glGenVertexArrays(1, vao.ptr);
glBindVertexArray(vao[0]);
//VBOを作成
uint[2] vboID;
glGenBuffers(2, vboID.ptr);
//頂点データを送信
glBindBuffer(GL_ARRAY_BUFFER, vboID[0]);
auto position = [
-1.0f, -1.0f, 0.5f,
1.0f, -1.0f, 0.5f,
-1.0f, 1.0f, 0.5f,
1.0f, 1.0f, 0.5f,
];
glBufferData(GL_ARRAY_BUFFER, position.length * GLfloat.sizeof, position.ptr, GL_STATIC_DRAW);
glEnableVertexAttribArray(vLoc);
glVertexAttribPointer(vLoc, 3, GL_FLOAT, GL_FALSE, 3 * float.sizeof, null);
//フラグメントシェーダのUniform変数のアドレスを取得
int xLoc = glGetUniformLocation(programID, "xvec");
int yLoc = glGetUniformLocation(programID, "yvec");
int eLoc = glGetUniformLocation(programID, "eye");
//ビューポート設定
glViewport(0, 0, window_width ,window_height);
//闇の準備活動
const double cameraDistance = 500;
const double cameraRotationSpeed = 0.05;
float[] eye = [0, 0, cameraDistance];
double[] target = new double[3];
double[] n = [1, 1, 1];
double nLen = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
n[] /= nLen;
double c = cos(cameraRotationSpeed);
double s = sin(cameraRotationSpeed);
float matrix[][];
matrix.length = 3;
matrix[0].length = 3;
matrix[1].length = 3;
matrix[2].length = 3;
matrix[0][0] = n[0]*n[0]*(1-c)+c;
matrix[0][1] = n[0]*n[1]*(1-c)-n[2]*s;
matrix[0][2] = n[2]*n[0]*(1-c)+n[1]*s;
matrix[1][0] = n[0]*n[1]*(1-c)+n[2]*s;
matrix[1][1] = n[1]*n[1]*(1-c)+c;
matrix[1][2] = n[1]*n[2]*(1-c)-n[0]*s;
matrix[2][0] = n[2]*n[0]*(1-c)-n[1]*s;
matrix[2][1] = n[1]*n[2]*(1-c)+n[0]*s;
matrix[2][2] = n[2]*n[2]*(1-c)+c;
float[] xvec = [1, 0, 0];
float[] yvec = [0, 1, 0];
//メインループ回し回し
while (!glfwWindowShouldClose(window))
{
//画面をクリア
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
//Uniform変数に代入
glUniform3fv(xLoc, 1, xvec.ptr);
glUniform3fv(yLoc, 1, yvec.ptr);
glUniform3fv(eLoc, 1, eye.ptr);
//VAOをバインド
glBindVertexArray(vao[0]);
//バインドしたVAOで四角を描画
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//バッファを更新
glfwSwapBuffers(window);
glfwPollEvents();
//eye, xvec, yvecを回転
double sx, sy, sz;
sx = matrix[0][0] * eye[0] + matrix[0][1] * eye[1] + matrix[0][2] * eye[2];
sy = matrix[1][0] * eye[0] + matrix[1][1] * eye[1] + matrix[1][2] * eye[2];
sz = matrix[2][0] * eye[0] + matrix[2][1] * eye[1] + matrix[2][2] * eye[2];
eye[0] = sx; eye[1] = sy; eye[2] = sz;
sx = matrix[0][0] * xvec[0] + matrix[0][1] * xvec[1] + matrix[0][2] * xvec[2];
sy = matrix[1][0] * xvec[0] + matrix[1][1] * xvec[1] + matrix[1][2] * xvec[2];
sz = matrix[2][0] * xvec[0] + matrix[2][1] * xvec[1] + matrix[2][2] * xvec[2];
xvec[0] = sx; xvec[1] = sy; xvec[2] = sz;
sx = matrix[0][0] * yvec[0] + matrix[0][1] * yvec[1] + matrix[0][2] * yvec[2];
sy = matrix[1][0] * yvec[0] + matrix[1][1] * yvec[1] + matrix[1][2] * yvec[2];
sz = matrix[2][0] * yvec[0] + matrix[2][1] * yvec[1] + matrix[2][2] * yvec[2];
yvec[0] = sx; yvec[1] = sy; yvec[2] = sz;
}
//後始末
glfwTerminate();
}
#version 400
attribute vec3 mVertexPosition;
varying vec2 tc;
void main()
{
tc = mVertexPosition.xy;
gl_Position=vec4(mVertexPosition, 1.0);
}
#version 400
out vec4 oColor;
varying vec2 tc;
uniform vec3 xvec;
uniform vec3 yvec;
uniform vec3 eye;
const float epsilon = 0.01;
const float screen_width = 100;
const float screen_height = 100;
float distBox(vec3 p, vec3 s) {
return length(max(vec3(0, 0, 0), abs(p) - s));
}
float dist(vec3 p) {
const float R = 30;
const float r = 10;
p.y = -p.y;
const float depth = 5;
const float c = cos(45);
const float s = sin(45);
float r1 = distBox(p+vec3(25, 0, 0), vec3(2.5, 35, depth));
float r2 = distBox(p+vec3(25, 20, 0), vec3(17, 2.5, depth));
float nx = p.x * c - p.y * s;
float ny = p.x * s + p.y * c;
float r3 = distBox(vec3(nx+30, ny+30, p.z), vec3(9, 2.5, depth));
nx = p.x * c + p.y * s;
ny =-p.x * s + p.y * c;
float r4 = distBox(vec3(nx, ny-15, p.z), vec3(9, 2.5, depth));
float r5 = distBox(p + vec3(-25, 0, 0), vec3(2.5, 25, depth));
float r6 = distBox(p + vec3(-25, 23, 0), vec3(12, 2.5, depth));
float r7 = distBox(p + vec3(-25, 0, 0), vec3(17, 2.5, depth));
float r8 = distBox(p + vec3(-33, -23, 0), vec3(10, 2.5, depth));
return min(r1, min(r2, min(r3, min(r4, min(r5, min(r6, min(r7, r8)))))));
}
vec3 getNormal(vec3 p) {
vec3 result;
result.x = dist(p + vec3(epsilon, 0, 0)) - dist(p - vec3(epsilon, 0, 0));
result.y = dist(p + vec3(0, epsilon, 0)) - dist(p - vec3(0, epsilon, 0));
result.z = dist(p + vec3(0, 0, epsilon)) - dist(p - vec3(0, 0, epsilon));
return normalize(result);
}
void main() {
//Ray Marching
vec3 c;//camera vector
c = normalize(screen_width * tc.x * xvec + screen_height * tc.y * yvec - eye);
vec3 p = eye;
for (int j = 0; j < 80; j++) {
float d = dist(p);
p += d * c;
if (-epsilon < d && d < epsilon)
break;
}
float d = dist(p);
if (d < epsilon) {
vec3 normal = getNormal(p);
float dot = -dot(normal, c);
oColor = vec4(vec3(1, 1, 1) * dot, 1.0);
} else {
oColor = vec4(0, 0, 0, 1);
}
}
これで回るホモが出て来れば成功です。ソースコードの解説についてはコメントとして書いておきました。シェーダの中身はレイマーチングです。