diff --git a/include/matrix.h b/include/matrix.h new file mode 100644 index 0000000..7ffc3a1 --- /dev/null +++ b/include/matrix.h @@ -0,0 +1,149 @@ +#pragma once + +// 小さな行列ライブラリ(線型代数シリーズと同じ約束事) +// ・列優先(column-major):m[列 * 4 + 行] +// ・右手座標系、カメラは-Z方向を向く +// ・ベクトルは列ベクトル:v' = M * v + +#include +#include + +typedef struct { + float m[16]; +} Matrix4; + +typedef struct { + float m[9]; +} Matrix3; + +// ベクトルの補助関数 +static inline Vector3 vec3Sub(Vector3 a, Vector3 b) { + return (Vector3){ { a.x - b.x, a.y - b.y, a.z - b.z } }; +} + +static inline float vec3Dot(Vector3 a, Vector3 b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +static inline Vector3 vec3Cross(Vector3 a, Vector3 b) { + return (Vector3){ { a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x } }; +} + +static inline Vector3 vec3Normalize(Vector3 v) { + float len = sqrtf(vec3Dot(v, v)); + if (len == 0.0f) return v; + return (Vector3){ { v.x / len, v.y / len, v.z / len } }; +} + +// 行row・列colの要素(0始まり) +static inline float *mat4At(Matrix4 *a, int row, int col) { + return &a->m[col * 4 + row]; +} + +// 単位行列 +static inline Matrix4 mat4Identity(void) { + Matrix4 r = { { 0 } }; + r.m[0] = r.m[5] = r.m[10] = r.m[15] = 1.0f; + return r; +} + +// 行列の積 r = a * b(bを先に適用する) +static inline Matrix4 mat4Multiply(Matrix4 a, Matrix4 b) { + Matrix4 r; + for (int col = 0; col < 4; col++) { + for (int row = 0; row < 4; row++) { + float sum = 0.0f; + for (int k = 0; k < 4; k++) sum += a.m[k * 4 + row] * b.m[col * 4 + k]; + r.m[col * 4 + row] = sum; + } + } + return r; +} + +// 平行移動 +static inline Matrix4 mat4Translate(float x, float y, float z) { + Matrix4 r = mat4Identity(); + r.m[12] = x; // 4列目 + r.m[13] = y; + r.m[14] = z; + return r; +} + +// 拡大縮小 +static inline Matrix4 mat4Scale(float x, float y, float z) { + Matrix4 r = mat4Identity(); + r.m[0] = x; + r.m[5] = y; + r.m[10] = z; + return r; +} + +// X軸周りの回転(ラジアン、右手の法則で反時計回り) +static inline Matrix4 mat4RotateX(float angle) { + float c = cosf(angle), s = sinf(angle); + Matrix4 r = mat4Identity(); + *mat4At(&r, 1, 1) = c; *mat4At(&r, 1, 2) = -s; + *mat4At(&r, 2, 1) = s; *mat4At(&r, 2, 2) = c; + return r; +} + +// Y軸周りの回転 +static inline Matrix4 mat4RotateY(float angle) { + float c = cosf(angle), s = sinf(angle); + Matrix4 r = mat4Identity(); + *mat4At(&r, 0, 0) = c; *mat4At(&r, 0, 2) = s; + *mat4At(&r, 2, 0) = -s; *mat4At(&r, 2, 2) = c; + return r; +} + +// ビュー行列:カメラがeyeからtargetを見る +static inline Matrix4 mat4LookAt(Vector3 eye, Vector3 target, Vector3 up) { + Vector3 f = vec3Normalize(vec3Sub(target, eye)); // 前 + Vector3 r = vec3Normalize(vec3Cross(f, up)); // 右 + Vector3 u = vec3Cross(r, f); // 上 + + Matrix4 m = mat4Identity(); + *mat4At(&m, 0, 0) = r.x; *mat4At(&m, 0, 1) = r.y; *mat4At(&m, 0, 2) = r.z; + *mat4At(&m, 1, 0) = u.x; *mat4At(&m, 1, 1) = u.y; *mat4At(&m, 1, 2) = u.z; + *mat4At(&m, 2, 0) = -f.x; *mat4At(&m, 2, 1) = -f.y; *mat4At(&m, 2, 2) = -f.z; + *mat4At(&m, 0, 3) = -vec3Dot(r, eye); + *mat4At(&m, 1, 3) = -vec3Dot(u, eye); + *mat4At(&m, 2, 3) = vec3Dot(f, eye); + return m; +} + +// 透視投影(OpenGL:NDCのzは-1〜1)、fovYは縦の視野角(ラジアン) +static inline Matrix4 mat4Perspective(float fovY, float aspect, float n, float f) { + float t = 1.0f / tanf(fovY * 0.5f); + Matrix4 m = { { 0 } }; + *mat4At(&m, 0, 0) = t / aspect; + *mat4At(&m, 1, 1) = t; + *mat4At(&m, 2, 2) = -(f + n) / (f - n); + *mat4At(&m, 2, 3) = -(2.0f * f * n) / (f - n); + *mat4At(&m, 3, 2) = -1.0f; + return m; +} + +// 法線行列:モデル行列の左上3×3の逆行列の転置 +// 逆行列の転置 = 余因子行列 / 行列式 なので、余因子を直接計算する +static inline Matrix3 mat3NormalMatrix(Matrix4 a) { + // 左上3×3の要素(行, 列) + float m00 = a.m[0], m10 = a.m[1], m20 = a.m[2]; + float m01 = a.m[4], m11 = a.m[5], m21 = a.m[6]; + float m02 = a.m[8], m12 = a.m[9], m22 = a.m[10]; + + // 余因子 + float c00 = m11 * m22 - m12 * m21, c01 = m12 * m20 - m10 * m22, c02 = m10 * m21 - m11 * m20; + float c10 = m02 * m21 - m01 * m22, c11 = m00 * m22 - m02 * m20, c12 = m01 * m20 - m00 * m21; + float c20 = m01 * m12 - m02 * m11, c21 = m02 * m10 - m00 * m12, c22 = m00 * m11 - m01 * m10; + + float det = m00 * c00 + m01 * c01 + m02 * c02; + float inv = det != 0.0f ? 1.0f / det : 0.0f; + + // 結果の(行i, 列j) = c_ij / det、列優先で格納 + Matrix3 r; + r.m[0] = c00 * inv; r.m[1] = c10 * inv; r.m[2] = c20 * inv; + r.m[3] = c01 * inv; r.m[4] = c11 * inv; r.m[5] = c21 * inv; + r.m[6] = c02 * inv; r.m[7] = c12 * inv; r.m[8] = c22 * inv; + return r; +} diff --git a/main.c b/main.c index 259f26d..56a1fe2 100755 --- a/main.c +++ b/main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include // ファイルパスを構築(プラットフォームに応じて) @@ -176,6 +177,220 @@ GLuint buildProgram(const char *vertexPath, const char *fragmentPath) { return program; } +#define PI 3.14159265358979f + +// メッシュ(頂点配列オブジェクトとインデックスの数) +typedef struct { + GLuint vao, vbo, ebo; + GLsizei indexCount; +} Mesh; + +// 頂点の形式:位置(3)、法線(3)、UV(2)=8個のfloat +#define VERTEX_FLOATS 8 + +// 頂点とインデックスの配列からメッシュを作る +Mesh createMesh(const float *vertices, int vertexCount, const GLuint *indices, int indexCount) { + Mesh mesh; + mesh.indexCount = indexCount; + glGenVertexArrays(1, &mesh.vao); + glGenBuffers(1, &mesh.vbo); + glGenBuffers(1, &mesh.ebo); + + glBindVertexArray(mesh.vao); + glBindBuffer(GL_ARRAY_BUFFER, mesh.vbo); + glBufferData(GL_ARRAY_BUFFER, vertexCount * VERTEX_FLOATS * sizeof(float), vertices, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * sizeof(GLuint), indices, GL_STATIC_DRAW); + + // 頂点属性:位置(location 0)、法線(location 1)、UV(location 2) + GLsizei stride = VERTEX_FLOATS * sizeof(float); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void *)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void *)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void *)(6 * sizeof(float))); + glEnableVertexAttribArray(2); + + glBindVertexArray(0); + return mesh; +} + +void drawMesh(const Mesh *mesh) { + glBindVertexArray(mesh->vao); + glDrawElements(GL_TRIANGLES, mesh->indexCount, GL_UNSIGNED_INT, 0); +} + +void destroyMesh(Mesh *mesh) { + glDeleteVertexArrays(1, &mesh->vao); + glDeleteBuffers(1, &mesh->vbo); + glDeleteBuffers(1, &mesh->ebo); +} + +// 立方体(一辺1、中心が原点):各面に4頂点(面ごとに法線が違う為) +Mesh createCube(void) { + // 各面:法線n、右方向u、上方向v(u × v = n なので外から見て反時計回り) + const float faces[6][9] = { + { 1, 0, 0, 0, 0, -1, 0, 1, 0 }, // +X + { -1, 0, 0, 0, 0, 1, 0, 1, 0 }, // -X + { 0, 1, 0, 1, 0, 0, 0, 0, -1 }, // +Y + { 0, -1, 0, 1, 0, 0, 0, 0, 1 }, // -Y + { 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // +Z + { 0, 0, -1, -1, 0, 0, 0, 1, 0 }, // -Z + }; + const float corners[4][2] = { { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, 1 } }; + + float vertices[24 * VERTEX_FLOATS]; + GLuint indices[36]; + for (int f = 0; f < 6; f++) { + const float *n = faces[f], *u = faces[f] + 3, *v = faces[f] + 6; + for (int c = 0; c < 4; c++) { + float s = corners[c][0], t = corners[c][1]; + float *out = &vertices[(f * 4 + c) * VERTEX_FLOATS]; + for (int k = 0; k < 3; k++) out[k] = 0.5f * (n[k] + s * u[k] + t * v[k]); // 位置 + for (int k = 0; k < 3; k++) out[3 + k] = n[k]; // 法線 + out[6] = 0.5f * (s + 1.0f); // UV + out[7] = 0.5f * (t + 1.0f); + } + GLuint base = f * 4; + GLuint quad[6] = { base, base + 1, base + 2, base + 2, base + 3, base }; + memcpy(&indices[f * 6], quad, sizeof(quad)); + } + return createMesh(vertices, 24, indices, 36); +} + +// UV球(半径1):slices=経度の分割数、stacks=緯度の分割数 +Mesh createSphere(int slices, int stacks) { + int vertexCount = (slices + 1) * (stacks + 1); + int indexCount = slices * stacks * 6; + float *vertices = malloc(vertexCount * VERTEX_FLOATS * sizeof(float)); + GLuint *indices = malloc(indexCount * sizeof(GLuint)); + + float *out = vertices; + for (int i = 0; i <= stacks; i++) { + float phi = PI * (float)i / (float)stacks; // 0(北極)〜π(南極) + for (int j = 0; j <= slices; j++) { + float theta = 2.0f * PI * (float)j / (float)slices; + float x = sinf(phi) * sinf(theta); + float y = cosf(phi); + float z = sinf(phi) * cosf(theta); + // 単位球なので、位置と法線は同じ + out[0] = x; out[1] = y; out[2] = z; + out[3] = x; out[4] = y; out[5] = z; + out[6] = (float)j / (float)slices; + out[7] = 1.0f - (float)i / (float)stacks; + out += VERTEX_FLOATS; + } + } + + GLuint *idx = indices; + for (int i = 0; i < stacks; i++) { + for (int j = 0; j < slices; j++) { + GLuint a = i * (slices + 1) + j; // 左上 + GLuint b = a + slices + 1; // 左下 + *idx++ = a; *idx++ = b; *idx++ = a + 1; + *idx++ = a + 1; *idx++ = b; *idx++ = b + 1; + } + } + + Mesh mesh = createMesh(vertices, vertexCount, indices, indexCount); + free(vertices); + free(indices); + return mesh; +} + +// XY平面上の格子(幅w、高さh、左下が原点):nx × ny個の四角形 +Mesh createGrid(int nx, int ny, float w, float h) { + int vertexCount = (nx + 1) * (ny + 1); + int indexCount = nx * ny * 6; + float *vertices = malloc(vertexCount * VERTEX_FLOATS * sizeof(float)); + GLuint *indices = malloc(indexCount * sizeof(GLuint)); + + float *out = vertices; + for (int j = 0; j <= ny; j++) { + for (int i = 0; i <= nx; i++) { + float u = (float)i / (float)nx, v = (float)j / (float)ny; + out[0] = u * w; out[1] = v * h; out[2] = 0.0f; + out[3] = 0.0f; out[4] = 0.0f; out[5] = 1.0f; + out[6] = u; out[7] = v; + out += VERTEX_FLOATS; + } + } + + GLuint *idx = indices; + for (int j = 0; j < ny; j++) { + for (int i = 0; i < nx; i++) { + GLuint a = j * (nx + 1) + i; // 左下 + GLuint b = a + nx + 1; // 左上 + *idx++ = a; *idx++ = a + 1; *idx++ = b + 1; + *idx++ = b + 1; *idx++ = b; *idx++ = a; + } + } + + Mesh mesh = createMesh(vertices, vertexCount, indices, indexCount); + free(vertices); + free(indices); + return mesh; +} + +// 行列のユニフォームを設定(列優先のままなのでtransposeはGL_FALSE) +void setMat4(GLuint program, const char *name, Matrix4 m) { + glUniformMatrix4fv(glGetUniformLocation(program, name), 1, GL_FALSE, m.m); +} + +void setMat3(GLuint program, const char *name, Matrix3 m) { + glUniformMatrix3fv(glGetUniformLocation(program, name), 1, GL_FALSE, m.m); +} + +// シェーダープログラムの一覧(Rキーで全部再読み込み) +typedef struct { + const char *vertexPath; + const char *fragmentPath; + GLuint id; +} Program; + +enum { PROGRAM_SKY, PROGRAM_PHONG, PROGRAM_GOURAUD, PROGRAM_FLAG, PROGRAM_COUNT }; + +static Program gPrograms[PROGRAM_COUNT] = { + { "shader/vertex_fullscreen.glsl", "shader/frag_sky.glsl", 0 }, + { "shader/vertex_mesh.glsl", "shader/frag_phong.glsl", 0 }, + { "shader/vertex_gouraud.glsl", "shader/frag_gouraud.glsl", 0 }, + { "shader/vertex_flag.glsl", "shader/frag_phong.glsl", 0 }, +}; + +// 全てのプログラムを作り直す(失敗した物は前のままにする)、失敗の数を返す +int loadPrograms(void) { + int failed = 0; + for (int i = 0; i < PROGRAM_COUNT; i++) { + GLuint program = buildProgram(gPrograms[i].vertexPath, gPrograms[i].fragmentPath); + if (program) { + if (gPrograms[i].id) glDeleteProgram(gPrograms[i].id); + gPrograms[i].id = program; + } else { + failed++; + } + } + return failed; +} + +// 照明付きの物体を描く(モデル行列から法線行列も計算する) +void drawLit(GLuint program, const Mesh *mesh, Matrix4 model, Vector3 color, int pattern) { + setMat4(program, "uModel", model); + setMat3(program, "uNormalMatrix", mat3NormalMatrix(model)); + glUniform3f(glGetUniformLocation(program, "uColor"), color.r, color.g, color.b); + glUniform1i(glGetUniformLocation(program, "uPattern"), pattern); + drawMesh(mesh); +} + +// 全てのプログラムで共通のユニフォーム +void setCommonUniforms(GLuint program, Matrix4 view, Matrix4 proj, Vector3 eye, float time) { + glUseProgram(program); + setMat4(program, "uView", view); + setMat4(program, "uProj", proj); + glUniform3f(glGetUniformLocation(program, "uCameraPos"), eye.x, eye.y, eye.z); + glUniform3f(glGetUniformLocation(program, "uLightDir"), -0.5f, 0.8f, 0.6f); // 光の方へ向かう方向 + glUniform1f(glGetUniformLocation(program, "uTime"), time); +} + int main(int argc, char **argv) { (void)argc; (void)argv; @@ -202,7 +417,7 @@ int main(int argc, char **argv) { #endif // ウィンドウを作成 - GLFWwindow *window = glfwCreateWindow(800, 600, "GLSLシェーダープレイグラウンド", NULL, NULL); + GLFWwindow *window = glfwCreateWindow(800, 600, "GLSLシェーダー:頂点シェーダーと3D", NULL, NULL); if (!window) { puts("GLFWウィンドウの作成に失敗しました"); glfwTerminate(); @@ -224,44 +439,31 @@ int main(int argc, char **argv) { glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); // シェーダーを読み込む - const char *vertexPath = "shader/vertex.glsl"; - const char *fragmentPath = "shader/playground.glsl"; - GLuint shaderProgram = buildProgram(vertexPath, fragmentPath); - if (!shaderProgram) { + if (loadPrograms() > 0) { puts("シェーダーの読み込みに失敗しました"); glfwDestroyWindow(window); glfwTerminate(); return -1; } - // 頂点データ(フルスクリーンクアッド)を設定 - float vertices[] = { - -1.0f, -1.0f, 0.0f, 0.0f, // 左下(位置、UV) - 1.0f, -1.0f, 1.0f, 0.0f, // 右下 - 1.0f, 1.0f, 1.0f, 1.0f, // 右上 - -1.0f, 1.0f, 0.0f, 1.0f // 左上 - }; - GLuint indices[] = { 0, 1, 2, 2, 3, 0 }; - - GLuint VAO, VBO, EBO; - glGenVertexArrays(1, &VAO); - glGenBuffers(1, &VBO); - glGenBuffers(1, &EBO); + // メッシュを作る + Mesh cube = createCube(); + Mesh sphere = createSphere(16, 12); + Mesh flag = createGrid(32, 20, 1.4f, 0.9f); - glBindVertexArray(VAO); - glBindBuffer(GL_ARRAY_BUFFER, VBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + // 頂点バッファなしで描く為の空のVAO(コアプロファイルではVAOが必須) + GLuint emptyVAO; + glGenVertexArrays(1, &emptyVAO); - // 頂点属性:位置(location 0)、UV(location 1) - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); - glEnableVertexAttribArray(0); - size_t offset = 2 * sizeof(float); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)offset); - glEnableVertexAttribArray(1); + // 深度テストと裏面カリング + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); int reloadWasDown = 0; + int gouraudWasDown = 0; + int useGouraud = 0; // メインレンダリングループ while (!glfwWindowShouldClose(window)) { @@ -271,40 +473,72 @@ int main(int argc, char **argv) { // Rキーでシェーダーを再読み込み(失敗したら古いシェーダーのまま) int reloadDown = glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS; if (reloadDown && !reloadWasDown) { - GLuint newProgram = buildProgram(vertexPath, fragmentPath); - if (newProgram) { - glDeleteProgram(shaderProgram); - shaderProgram = newProgram; - puts("シェーダーを再読み込みしました"); - } else { - puts("再読み込みに失敗しました。前のシェーダーを使い続けます"); - } + if (loadPrograms() == 0) puts("シェーダーを再読み込みしました"); + else puts("再読み込みに失敗しました。前のシェーダーを使い続けます"); } reloadWasDown = reloadDown; - // 画面をクリア - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - // シェーダープログラムを使用 - glUseProgram(shaderProgram); - - // マウスの位置(ピクセル、左下が原点になる様にyを反転) - double mouseX, mouseY; - glfwGetCursorPos(window, &mouseX, &mouseY); - int windowW, windowH; - glfwGetWindowSize(window, &windowW, &windowH); - float scale = windowW > 0 ? (float)gWidth / (float)windowW : 1.0f; + // Gキーで頂点ごとのライティング(グーロー)とピクセルごと(フォン)を切り替える + int gouraudDown = glfwGetKey(window, GLFW_KEY_G) == GLFW_PRESS; + if (gouraudDown && !gouraudWasDown) { + useGouraud = !useGouraud; + puts(useGouraud ? "頂点ごとのライティング(グーロー)" : "ピクセルごとのライティング(フォン)"); + } + gouraudWasDown = gouraudDown; - // ユニフォームを設定 - glUniform1f(glGetUniformLocation(shaderProgram, "uTime"), (float)glfwGetTime()); - glUniform2f(glGetUniformLocation(shaderProgram, "uResolution"), (float)gWidth, (float)gHeight); - glUniform2f(glGetUniformLocation(shaderProgram, "uMouse"), - (float)mouseX * scale, (float)(windowH - mouseY) * scale); + float time = (float)glfwGetTime(); - // クアッドを描画 - glBindVertexArray(VAO); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + // 画面と深度バッファをクリア + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // 背景:頂点バッファなしのフルスクリーン三角形(深度テストなし) + glDisable(GL_DEPTH_TEST); + GLuint sky = gPrograms[PROGRAM_SKY].id; + glUseProgram(sky); + glUniform2f(glGetUniformLocation(sky, "uResolution"), (float)gWidth, (float)gHeight); + glBindVertexArray(emptyVAO); + glDrawArrays(GL_TRIANGLES, 0, 3); + glEnable(GL_DEPTH_TEST); + + // カメラ:ビュー行列と投影行列 + Vector3 eye = { { 0.0f, 1.0f, 5.0f } }; + Vector3 target = { { 0.0f, 0.1f, 0.0f } }; + Vector3 up = { { 0.0f, 1.0f, 0.0f } }; + Matrix4 view = mat4LookAt(eye, target, up); + float aspect = gHeight > 0 ? (float)gWidth / (float)gHeight : 1.0f; + Matrix4 proj = mat4Perspective(50.0f * PI / 180.0f, aspect, 0.1f, 100.0f); + + // 立方体と球(Gキーでシェーダーを切り替え) + GLuint lit = useGouraud ? gPrograms[PROGRAM_GOURAUD].id : gPrograms[PROGRAM_PHONG].id; + setCommonUniforms(lit, view, proj, eye, time); + + // 立方体:回転してから左へ移動(M = T * R) + Matrix4 cubeModel = mat4Multiply(mat4Translate(-1.7f, 0.1f, 0.0f), + mat4Multiply(mat4RotateY(time * 0.7f), mat4RotateX(0.6f))); + Vector3 cubeColor = { { 0.9f, 0.45f, 0.1f } }; + drawLit(lit, &cube, cubeModel, cubeColor, 1); + + // 球:わざと潰してから回転(M = T * R * S)、法線行列が必要になる + Matrix4 sphereModel = mat4Multiply(mat4Translate(0.0f, 0.1f, 0.0f), + mat4Multiply(mat4RotateY(time * 0.5f), + mat4Scale(0.9f, 0.6f, 0.9f))); + Vector3 sphereColor = { { 0.2f, 0.45f, 0.9f } }; + drawLit(lit, &sphere, sphereModel, sphereColor, 1); + + // 旗竿:細長く伸ばした立方体 + Matrix4 poleModel = mat4Multiply(mat4Translate(1.2f, 0.0f, 0.0f), mat4Scale(0.05f, 2.2f, 0.05f)); + Vector3 poleColor = { { 0.8f, 0.8f, 0.75f } }; + drawLit(lit, &cube, poleModel, poleColor, 0); + + // 旗:頂点シェーダーで波打たせる(両面を描くのでカリングなし) + GLuint flagProgram = gPrograms[PROGRAM_FLAG].id; + setCommonUniforms(flagProgram, view, proj, eye, time); + glDisable(GL_CULL_FACE); + Vector3 flagColor = { { 0.95f, 0.95f, 0.92f } }; + Matrix4 flagModel = mat4Multiply(mat4Translate(1.2f, 0.2f, 0.0f), mat4RotateY(0.6f)); + drawLit(flagProgram, &flag, flagModel, flagColor, 2); + glEnable(GL_CULL_FACE); // バッファをスワップし、イベントを処理 glfwSwapBuffers(window); @@ -312,10 +546,11 @@ int main(int argc, char **argv) { } // クリーンアップ - glDeleteVertexArrays(1, &VAO); - glDeleteBuffers(1, &VBO); - glDeleteBuffers(1, &EBO); - glDeleteProgram(shaderProgram); + destroyMesh(&cube); + destroyMesh(&sphere); + destroyMesh(&flag); + glDeleteVertexArrays(1, &emptyVAO); + for (int i = 0; i < PROGRAM_COUNT; i++) glDeleteProgram(gPrograms[i].id); glfwDestroyWindow(window); glfwTerminate(); diff --git a/shader/frag_gouraud.glsl b/shader/frag_gouraud.glsl new file mode 100644 index 0000000..bf3d282 --- /dev/null +++ b/shader/frag_gouraud.glsl @@ -0,0 +1,8 @@ +#version 450 core + +in vec3 vColor; // ラスタライザーが三角形の中で補間した色 +out vec4 out_color; + +void main() { + out_color = vec4(pow(vColor, vec3(1.0 / 2.2)), 1.0); +} diff --git a/shader/frag_phong.glsl b/shader/frag_phong.glsl new file mode 100644 index 0000000..4154898 --- /dev/null +++ b/shader/frag_phong.glsl @@ -0,0 +1,43 @@ +#version 450 core + +in vec3 vWorldPos; +in vec3 vNormal; +in vec2 vUv; +out vec4 out_color; + +uniform vec3 uColor; // 物体の色(リニア) +uniform vec3 uLightDir; // 表面から光の方へ向かう方向(ワールド空間) +uniform vec3 uCameraPos; // カメラの位置(ワールド空間) +uniform int uPattern; // 0:単色、1:チェッカー、2:日の丸 + +// UVから物体の色を決める +vec3 albedo() { + if (uPattern == 1) { + vec2 cell = floor(vUv * 4.0); + float checker = mod(cell.x + cell.y, 2.0); + return uColor * mix(0.55, 1.0, checker); + } + if (uPattern == 2) { + vec2 p = (vUv - 0.5) * vec2(1.4 / 0.9, 1.0); // 旗の縦横比 + float disc = 1.0 - smoothstep(0.29, 0.31, length(p)); + return mix(uColor, vec3(0.75, 0.02, 0.05), disc); + } + return uColor; +} + +void main() { + vec3 n = normalize(vNormal); + if (!gl_FrontFacing) n = -n; // 裏面(旗の裏側)では法線を反転 + vec3 l = normalize(uLightDir); + vec3 v = normalize(uCameraPos - vWorldPos); + vec3 h = normalize(l + v); // ハーフベクトル(Blinn-Phong) + + float diffuse = max(dot(n, l), 0.0); + float specular = diffuse > 0.0 ? pow(max(dot(n, h), 0.0), 64.0) : 0.0; + + // 空と地面の色の半球ライト(アンビエント) + vec3 ambient = mix(vec3(0.05, 0.04, 0.03), vec3(0.10, 0.13, 0.20), n.y * 0.5 + 0.5); + + vec3 color = albedo() * (ambient + diffuse) + vec3(0.6) * specular; + out_color = vec4(pow(color, vec3(1.0 / 2.2)), 1.0); // ガンマ補正 +} diff --git a/shader/frag_sky.glsl b/shader/frag_sky.glsl new file mode 100644 index 0000000..9036085 --- /dev/null +++ b/shader/frag_sky.glsl @@ -0,0 +1,14 @@ +#version 450 core + +in vec2 vUv; +out vec4 out_color; + +uniform vec2 uResolution; + +void main() { + float t = gl_FragCoord.y / uResolution.y; // 0(下)〜1(上) + vec3 horizon = vec3(0.32, 0.30, 0.36); + vec3 zenith = vec3(0.07, 0.10, 0.20); + vec3 color = mix(horizon, zenith, smoothstep(0.0, 1.0, t)); + out_color = vec4(color, 1.0); +} diff --git a/shader/vertex_flag.glsl b/shader/vertex_flag.glsl new file mode 100644 index 0000000..6d179b8 --- /dev/null +++ b/shader/vertex_flag.glsl @@ -0,0 +1,41 @@ +#version 450 core + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aUv; + +out vec3 vWorldPos; +out vec3 vNormal; +out vec2 vUv; + +uniform mat4 uModel; +uniform mat4 uView; +uniform mat4 uProj; +uniform mat3 uNormalMatrix; +uniform float uTime; + +const float AMPLITUDE = 0.15; // 波の高さ(旗の端で) +const float WAVE_K = 5.0; // 波数(x方向に1あたり何ラジアン) +const float WAVE_Y = 1.5; // 斜めの波にする為のy方向の波数 +const float SPEED = 4.0; // 角速度(ラジアン/秒) +const float WIDTH = 1.4; // 旗の幅(C側の格子と同じ) + +void main() { + vec3 p = aPosition; + + // 竿(x = 0)では動かず、端に行く程大きく揺れる + float a = AMPLITUDE * p.x / WIDTH; + float phase = WAVE_K * p.x + WAVE_Y * p.y - SPEED * uTime; + p.z = a * sin(phase); + + // 法線:曲面 z = h(x, y) の偏微分から (-dh/dx, -dh/dy, 1) + float dhdx = AMPLITUDE / WIDTH * sin(phase) + a * WAVE_K * cos(phase); + float dhdy = a * WAVE_Y * cos(phase); + vec3 normal = normalize(vec3(-dhdx, -dhdy, 1.0)); + + vec4 worldPos = uModel * vec4(p, 1.0); + vWorldPos = worldPos.xyz; + vNormal = uNormalMatrix * normal; + vUv = aUv; + gl_Position = uProj * (uView * worldPos); +} diff --git a/shader/vertex_fullscreen.glsl b/shader/vertex_fullscreen.glsl new file mode 100644 index 0000000..0add1b8 --- /dev/null +++ b/shader/vertex_fullscreen.glsl @@ -0,0 +1,11 @@ +#version 450 core + +out vec2 vUv; + +// 頂点バッファなしで画面全体を覆う大きな三角形 +// gl_VertexID = 0, 1, 2 → (-1, -1), (3, -1), (-1, 3) +void main() { + vec2 uv = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); + vUv = uv; + gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/shader/vertex_gouraud.glsl b/shader/vertex_gouraud.glsl new file mode 100644 index 0000000..31481b5 --- /dev/null +++ b/shader/vertex_gouraud.glsl @@ -0,0 +1,30 @@ +#version 450 core + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aUv; + +out vec3 vColor; // 頂点で計算したライティングの結果 + +uniform mat4 uModel; +uniform mat4 uView; +uniform mat4 uProj; +uniform mat3 uNormalMatrix; +uniform vec3 uColor; +uniform vec3 uLightDir; +uniform vec3 uCameraPos; + +void main() { + vec4 worldPos = uModel * vec4(aPosition, 1.0); + vec3 n = normalize(uNormalMatrix * aNormal); + vec3 l = normalize(uLightDir); + vec3 v = normalize(uCameraPos - worldPos.xyz); + vec3 h = normalize(l + v); + + float diffuse = max(dot(n, l), 0.0); + float specular = diffuse > 0.0 ? pow(max(dot(n, h), 0.0), 64.0) : 0.0; + vec3 ambient = mix(vec3(0.05, 0.04, 0.03), vec3(0.10, 0.13, 0.20), n.y * 0.5 + 0.5); + + vColor = uColor * (ambient + diffuse) + vec3(0.6) * specular; + gl_Position = uProj * (uView * worldPos); +} diff --git a/shader/vertex_mesh.glsl b/shader/vertex_mesh.glsl new file mode 100644 index 0000000..8b74bcb --- /dev/null +++ b/shader/vertex_mesh.glsl @@ -0,0 +1,22 @@ +#version 450 core + +layout(location = 0) in vec3 aPosition; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aUv; + +out vec3 vWorldPos; // ワールド空間の位置 +out vec3 vNormal; // ワールド空間の法線 +out vec2 vUv; + +uniform mat4 uModel; // オブジェクト空間 → ワールド空間 +uniform mat4 uView; // ワールド空間 → ビュー空間 +uniform mat4 uProj; // ビュー空間 → クリップ空間 +uniform mat3 uNormalMatrix; // 法線用(モデル行列の逆行列の転置) + +void main() { + vec4 worldPos = uModel * vec4(aPosition, 1.0); + vWorldPos = worldPos.xyz; + vNormal = uNormalMatrix * aNormal; + vUv = aUv; + gl_Position = uProj * (uView * worldPos); +}