diff --git a/main.c b/main.c index 259f26d..5a97397 100755 --- a/main.c +++ b/main.c @@ -176,6 +176,118 @@ GLuint buildProgram(const char *vertexPath, const char *fragmentPath) { return program; } +// テクスチャの大きさ(テクセル) +#define TEX_SIZE 256 + +// テスト用のテクスチャをCで作る(256×256、RGBA、1チャンネル8ビット) +// 1行目(y = 0)がテクスチャの一番下(v = 0)になる +GLuint createTestTexture(void) { + unsigned char *pixels = (unsigned char *)malloc(TEX_SIZE * TEX_SIZE * 4); + if (!pixels) { + puts("テクスチャのメモリ確保に失敗しました"); + return 0; + } + + for (int y = 0; y < TEX_SIZE; y++) { + for (int x = 0; x < TEX_SIZE; x++) { + // グラデーション:右に行く程赤く、上に行く程緑になる + float r = (float)x / (TEX_SIZE - 1); + float g = (float)y / (TEX_SIZE - 1); + float b = 0.6f; + + // 32×32テクセルのチェッカーボード(暗いマスは暗くする) + if ((x / 32 + y / 32) % 2 == 1) { + r *= 0.35f; + g *= 0.35f; + b *= 0.35f; + } + + // 「F」の文字(上下左右が反転したらすぐ分かる様に) + int fx = x - 80, fy = y - 64; + int inF = (fx >= 0 && fx < 24 && fy >= 0 && fy < 128) // 縦棒 + || (fx >= 0 && fx < 96 && fy >= 104 && fy < 128) // 上の横棒 + || (fx >= 0 && fx < 72 && fy >= 56 && fy < 80); // 真ん中の横棒 + + // 右上の白い輪(フィルタリングの違いが見える様に) + float dx = x + 0.5f - 216.0f, dy = y + 0.5f - 216.0f; + float d = sqrtf(dx * dx + dy * dy); + int inRing = d > 20.0f && d < 24.0f; + + if (inF || inRing) r = g = b = 1.0f; + + unsigned char *p = &pixels[(y * TEX_SIZE + x) * 4]; + p[0] = (unsigned char)(r * 255.0f + 0.5f); + p[1] = (unsigned char)(g * 255.0f + 0.5f); + p[2] = (unsigned char)(b * 255.0f + 0.5f); + p[3] = 255; + } + } + + // テクスチャオブジェクトを作り、ピクセルをGPUに送る + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEX_SIZE, TEX_SIZE, 0, + GL_RGBA, GL_UNSIGNED_BYTE, pixels); + free(pixels); + + // ミップマップ(縮小版の画像の列)を自動で作る + glGenerateMipmap(GL_TEXTURE_2D); + + // 範囲外の色(GL_CLAMP_TO_BORDERの時に使う) + float border[] = { 1.0f, 0.0f, 1.0f, 1.0f }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); + return texture; +} + +// フィルタリングの種類(Fキーで切り替え) +typedef struct { + GLint minFilter; + GLint magFilter; + const char *name; +} FilterMode; + +static const FilterMode filterModes[] = { + { GL_NEAREST, GL_NEAREST, "GL_NEAREST" }, + { GL_LINEAR, GL_LINEAR, "GL_LINEAR" }, + { GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, "GL_LINEAR_MIPMAP_LINEAR(ミップマップ)" }, +}; + +// ラップモードの種類(Wキーで切り替え) +typedef struct { + GLint mode; + const char *name; +} WrapMode; + +static const WrapMode wrapModes[] = { + { GL_REPEAT, "GL_REPEAT" }, + { GL_MIRRORED_REPEAT, "GL_MIRRORED_REPEAT" }, + { GL_CLAMP_TO_EDGE, "GL_CLAMP_TO_EDGE" }, + { GL_CLAMP_TO_BORDER, "GL_CLAMP_TO_BORDER" }, +}; + +// 今バインドされているテクスチャにフィルタリングを設定 +void setFilter(int mode) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterModes[mode].minFilter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterModes[mode].magFilter); + printf("フィルタリング: %s\n", filterModes[mode].name); +} + +// 今バインドされているテクスチャにラップモードを設定(S = u方向、T = v方向) +void setWrap(int mode) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapModes[mode].mode); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapModes[mode].mode); + printf("ラップモード: %s\n", wrapModes[mode].name); +} + +// キーが押された瞬間だけ1を返す(押しっぱなしでは繰り返さない) +int keyPressed(GLFWwindow *window, int key, int *wasDown) { + int down = glfwGetKey(window, key) == GLFW_PRESS; + int pressed = down && !*wasDown; + *wasDown = down; + return pressed; +} + int main(int argc, char **argv) { (void)argc; (void)argv; @@ -261,16 +373,35 @@ int main(int argc, char **argv) { glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)offset); glEnableVertexAttribArray(1); + // テスト用のテクスチャを作る(最初はミップマップ付きのGL_LINEARとGL_REPEAT) + GLuint texture = createTestTexture(); + int filterMode = 2; + int wrapMode = 0; + setFilter(filterMode); + setWrap(wrapMode); + int reloadWasDown = 0; + int filterWasDown = 0; + int wrapWasDown = 0; // メインレンダリングループ while (!glfwWindowShouldClose(window)) { // ESCキーで終了 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, 1); + // Fキーでフィルタリング、Wキーでラップモードを切り替える + glBindTexture(GL_TEXTURE_2D, texture); + if (keyPressed(window, GLFW_KEY_F, &filterWasDown)) { + filterMode = (filterMode + 1) % 3; + setFilter(filterMode); + } + if (keyPressed(window, GLFW_KEY_W, &wrapWasDown)) { + wrapMode = (wrapMode + 1) % 4; + setWrap(wrapMode); + } + // Rキーでシェーダーを再読み込み(失敗したら古いシェーダーのまま) - int reloadDown = glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS; - if (reloadDown && !reloadWasDown) { + if (keyPressed(window, GLFW_KEY_R, &reloadWasDown)) { GLuint newProgram = buildProgram(vertexPath, fragmentPath); if (newProgram) { glDeleteProgram(shaderProgram); @@ -280,7 +411,6 @@ int main(int argc, char **argv) { puts("再読み込みに失敗しました。前のシェーダーを使い続けます"); } } - reloadWasDown = reloadDown; // 画面をクリア glClearColor(0.0f, 0.0f, 0.0f, 1.0f); @@ -302,6 +432,11 @@ int main(int argc, char **argv) { glUniform2f(glGetUniformLocation(shaderProgram, "uMouse"), (float)mouseX * scale, (float)(windowH - mouseY) * scale); + // テクスチャをユニット0にバインドし、サンプラーにユニットの番号を教える + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glUniform1i(glGetUniformLocation(shaderProgram, "uTexture"), 0); + // クアッドを描画 glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); @@ -315,6 +450,7 @@ int main(int argc, char **argv) { glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); + glDeleteTextures(1, &texture); glDeleteProgram(shaderProgram); glfwDestroyWindow(window); glfwTerminate(); diff --git a/shader/playground.glsl b/shader/playground.glsl index 8ec93da..a9dd86b 100644 --- a/shader/playground.glsl +++ b/shader/playground.glsl @@ -3,20 +3,22 @@ in vec2 vUv; out vec4 out_color; -uniform float uTime; // 起動してからの秒数 -uniform vec2 uResolution; // 画面の大きさ(ピクセル) -uniform vec2 uMouse; // マウスの位置(ピクセル、左下が原点) +uniform float uTime; +uniform vec2 uResolution; +uniform sampler2D uTexture; void main() { - // ピクセル座標を0〜1に正規化 - vec2 uv = gl_FragCoord.xy / uResolution; + vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y; - // 時間で動くグラデーション - vec3 color = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0.0, 2.0, 4.0)); + // 床の上の点:地平線(p.y = 0)に近い程遠い(距離は 1 / -p.y) + float depth = 1.0 / max(-p.y, 0.001); + vec2 uv = vec2(p.x * depth, depth) * 0.5; + uv.y += uTime * 0.25; // 前に進む - // マウスの周りに白い円 - float d = length(gl_FragCoord.xy - uMouse); - color = mix(color, vec3(1.0), 1.0 - smoothstep(18.0, 20.0, d)); + // texture()はifの外で呼ぶ(理由は本文で) + vec3 floorColor = texture(uTexture, uv).rgb; + vec3 skyColor = vec3(0.45, 0.6, 0.85); + vec3 color = p.y < 0.0 ? floorColor : skyColor; out_color = vec4(color, 1.0); }