diff --git a/main.c b/main.c index 259f26d..f450302 100755 --- a/main.c +++ b/main.c @@ -262,6 +262,8 @@ int main(int argc, char **argv) { glEnableVertexAttribArray(1); int reloadWasDown = 0; + int gammaOn = 1; // ガンマ補正(Gキーで切り替え) + int gammaWasDown = 0; // メインレンダリングループ while (!glfwWindowShouldClose(window)) { @@ -282,6 +284,14 @@ int main(int argc, char **argv) { } reloadWasDown = reloadDown; + // Gキーでガンマ補正を切り替える(比較用) + int gammaDown = glfwGetKey(window, GLFW_KEY_G) == GLFW_PRESS; + if (gammaDown && !gammaWasDown) { + gammaOn = !gammaOn; + printf("ガンマ補正: %s\n", gammaOn ? "オン" : "オフ"); + } + gammaWasDown = gammaDown; + // 画面をクリア glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -301,6 +311,7 @@ int main(int argc, char **argv) { glUniform2f(glGetUniformLocation(shaderProgram, "uResolution"), (float)gWidth, (float)gHeight); glUniform2f(glGetUniformLocation(shaderProgram, "uMouse"), (float)mouseX * scale, (float)(windowH - mouseY) * scale); + glUniform1i(glGetUniformLocation(shaderProgram, "uGamma"), gammaOn); // クアッドを描画 glBindVertexArray(VAO); diff --git a/shader/playground.glsl b/shader/playground.glsl index 9282bc9..3288431 100644 --- a/shader/playground.glsl +++ b/shader/playground.glsl @@ -6,8 +6,9 @@ out vec4 out_color; uniform float uTime; // 起動してからの秒数 uniform vec2 uResolution; // 画面の大きさ(ピクセル) uniform vec2 uMouse; // マウスの位置(ピクセル、左下が原点) +uniform bool uGamma; // trueならガンマ補正する(Gキーで切り替え) -// 1にするとマウスで光を動かす、0にすると時間で光が回る +// 1にするとスポットライトがマウスを追う、0にすると時間で動く #define FOLLOW_MOUSE 0 // 表面の材質 @@ -17,15 +18,54 @@ struct Material { float shininess; // 光沢度(大きい程ハイライトが小さく鋭い) }; +// 光源 +struct Light { + vec3 position; // 位置 + vec3 color; // 色と明るさ + vec3 attenuation; // 減衰の定数(定数項、1次、2次) +}; + const float RADIUS = 0.6; // 球の半径 const float WALL_Z = -RADIUS; // 壁の位置(球の真後ろ) -const float AMBIENT = 0.1; // アンビエントの強さ -const vec3 LIGHT_COLOR = vec3(1.0, 0.95, 0.85) * 3.0; // 光の色と明るさ -const vec3 ATTENUATION = vec3(1.0, 0.7, 1.8); // 減衰の定数(定数項、1次、2次) const Material SPHERE = Material(vec3(0.8, 0.2, 0.3), 0.5, 64.0); const Material WALL = Material(vec3(0.7, 0.7, 0.75), 0.1, 8.0); +// 減衰の定数(第9章の表から) +const vec3 SHORT_RANGE = vec3(1.0, 0.7, 1.8); // 近くだけを照らす +const vec3 MEDIUM_RANGE = vec3(1.0, 0.35, 0.44); // 中くらい +const vec3 LONG_RANGE = vec3(1.0, 0.09, 0.032); // 遠くまで届く + +// スポットライト +const vec3 SPOT_POS = vec3(-0.8, 0.8, 1.6); // 位置(左上手前) +const vec3 SPOT_COLOR = vec3(1.0, 0.95, 0.8) * 1.0; // 色と明るさ +const float COS_INNER = cos(radians(8.0)); // 内側の円錐(此処までは全力) +const float COS_OUTER = cos(radians(12.0)); // 外側の円錐(此処から外は真っ暗) + +// ヘミスフィアライト +const vec3 SKY = vec3(0.04, 0.05, 0.08); // 上から来る光(空) +const vec3 GROUND = vec3(0.02, 0.015, 0.01); // 下から来る光(地面) +const vec3 UP = vec3(0.0, 1.0, 0.0); + +// 回るポイントライト(赤、緑、青) +const int NUM_LIGHTS = 3; +const vec3 LIGHT_COLORS[NUM_LIGHTS] = vec3[NUM_LIGHTS]( + vec3(1.0, 0.15, 0.1) * 2.0, + vec3(0.1, 1.0, 0.15) * 2.0, + vec3(0.15, 0.3, 1.0) * 2.0 +); + +// チューブライト(AからBまでの線分の光源) +const vec3 TUBE_A = vec3(-0.5, 0.4, 0.9); +const vec3 TUBE_B = vec3(0.5, 0.4, 0.9); +const vec3 TUBE_COLOR = vec3(1.0, 0.9, 0.7) * 0.6; +const int TUBE_SAMPLES = 16; + +// 距離dでの減衰(kは定数項、1次、2次) +float attenuate(float d, vec3 k) { + return 1.0 / (k.x + k.y * d + k.z * d * d); +} + // 1つの光からの拡散反射と鏡面反射(ブリン・フォン) // n:法線、v:目への向き、l:光への向き(全て単位ベクトル) vec3 blinnPhong(vec3 n, vec3 v, vec3 l, Material m, vec3 lightColor) { @@ -38,20 +78,102 @@ vec3 blinnPhong(vec3 n, vec3 v, vec3 l, Material m, vec3 lightColor) { return (m.albedo * diffuse + m.specular * specular) * lightColor; } -// ポイントライト:光の位置から向きと減衰を求める -vec3 pointLight(vec3 pos, vec3 n, vec3 v, Material m, vec3 lightPos, vec3 lightColor) { - vec3 toLight = lightPos - pos; - float d = length(toLight); // 光までの距離 - vec3 l = toLight / d; // normalize(toLight)と同じ - float attenuation = 1.0 / (ATTENUATION.x + ATTENUATION.y * d + ATTENUATION.z * d * d); - return blinnPhong(n, v, l, m, lightColor) * attenuation; +// ポイントライト +vec3 pointLight(vec3 pos, vec3 n, vec3 v, Material m, Light light) { + vec3 toLight = light.position - pos; + float d = length(toLight); + vec3 l = toLight / d; + return blinnPhong(n, v, l, m, light.color) * attenuate(d, light.attenuation); +} + +// スポットライト:ポイントライトを円錐の中だけにする +// dir:光軸の向き(単位ベクトル) +vec3 spotLight(vec3 pos, vec3 n, vec3 v, Material m, Light light, vec3 dir) { + vec3 l = normalize(light.position - pos); + float cosAngle = dot(-l, dir); // 光軸と「光から表面への向き」の間の角度のコサイン + float cone = smoothstep(COS_OUTER, COS_INNER, cosAngle); + return pointLight(pos, n, v, m, light) * cone; +} + +// ヘミスフィアライト:法線が上を向く程空の色、下を向く程地面の色 +vec3 hemisphere(vec3 n) { + float t = 0.5 + 0.5 * dot(n, UP); + return mix(GROUND, SKY, t); +} + +// チューブライト(AからBまでの線分) +vec3 tubeLight(vec3 pos, vec3 n, vec3 v, Material m, vec3 a, vec3 b, vec3 lightColor) { + // 拡散反射:線分上に並べたTUBE_SAMPLES個のポイントライトの平均 + float diffuse = 0.0; + for (int i = 0; i < TUBE_SAMPLES; ++i) { + float t = (float(i) + 0.5) / float(TUBE_SAMPLES); + vec3 toLight = mix(a, b, t) - pos; + float d = length(toLight); + diffuse += max(dot(n, toLight / d), 0.0) * attenuate(d, MEDIUM_RANGE); + } + diffuse /= float(TUBE_SAMPLES); + + // 鏡面反射:反射ベクトルに一番近い線分上の点(代表点)だけを使う + vec3 r = reflect(-v, n); + vec3 l0 = a - pos; + vec3 ld = b - a; + float rd = dot(r, ld); + float t = (dot(r, l0) * rd - dot(l0, ld)) / max(dot(ld, ld) - rd * rd, 1e-6); + vec3 toLight = l0 + clamp(t, 0.0, 1.0) * ld; + float d = length(toLight); + vec3 l = toLight / d; + float specular = 0.0; + if (dot(n, l) > 0.0) specular = pow(max(dot(n, normalize(l + v)), 0.0), m.shininess); + specular *= attenuate(d, MEDIUM_RANGE); + + return (m.albedo * diffuse + m.specular * specular) * lightColor; +} + +// マウスの位置をpと同じ座標にする +vec2 mousePos() { + return (2.0 * uMouse - uResolution) / uResolution.y; +} + +// i番目のポイントライトの位置(120°ずつずれて回る) +vec3 orbit(int i) { + float a = uTime * 1.25 + float(i) * radians(120.0); + return vec3(0.9 * cos(a), 0.6 * sin(a), 0.7); +} + +// スポットライトが狙う壁の点 +vec3 spotTarget() { +#if FOLLOW_MOUSE + return vec3(mousePos(), WALL_Z); +#else + return vec3(0.8 * sin(uTime * 0.7), 0.4 * sin(uTime * 1.1), WALL_Z); +#endif +} + +// 線分abまでの距離(第6章) +float sdSegment(vec2 p, vec2 a, vec2 b) { + vec2 pa = p - a; + vec2 ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h); } // 表面の1点の色 -vec3 shade(vec3 pos, vec3 n, Material m, vec3 lightPos) { +vec3 shade(vec3 pos, vec3 n, Material m) { vec3 v = vec3(0.0, 0.0, 1.0); // 目への向き - vec3 color = m.albedo * AMBIENT; - color += pointLight(pos, n, v, m, lightPos, LIGHT_COLOR); + if (uGamma) m.albedo = pow(m.albedo, vec3(2.2)); // sRGBの色を線型にする + + vec3 color = m.albedo * hemisphere(n); + + for (int i = 0; i < NUM_LIGHTS; ++i) { + Light light = Light(orbit(i), LIGHT_COLORS[i], SHORT_RANGE); + color += pointLight(pos, n, v, m, light); + } + + Light spot = Light(SPOT_POS, SPOT_COLOR, LONG_RANGE); + vec3 spotDir = normalize(spotTarget() - SPOT_POS); + color += spotLight(pos, n, v, m, spot, spotDir); + + color += tubeLight(pos, n, v, m, TUBE_A, TUBE_B, TUBE_COLOR); return color; } @@ -59,27 +181,31 @@ void main() { vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / uResolution.y; float px = 2.0 / uResolution.y; - // 光の位置(zは球より手前) -#if FOLLOW_MOUSE - vec2 mouse = (2.0 * uMouse - uResolution) / uResolution.y; // pと同じ座標にする - vec3 lightPos = vec3(mouse, 0.7); -#else - vec3 lightPos = vec3(0.9 * cos(uTime * 1.25), 0.6 * sin(uTime * 1.25), 0.7); -#endif - // 球 float z = sqrt(max(RADIUS * RADIUS - dot(p, p), 0.0)); vec3 spherePos = vec3(p, z); - vec3 sphere = shade(spherePos, spherePos / RADIUS, SPHERE, lightPos); + vec3 sphere = shade(spherePos, spherePos / RADIUS, SPHERE); // 壁(z = WALL_Zの平面、法線は手前を向く) - vec3 wall = shade(vec3(p, WALL_Z), vec3(0.0, 0.0, 1.0), WALL, lightPos); + vec3 wall = shade(vec3(p, WALL_Z), vec3(0.0, 0.0, 1.0), WALL); float mask = 1.0 - smoothstep(RADIUS - px, RADIUS, length(p)); vec3 color = mix(wall, sphere, mask); - // 光の位置に小さな白い点を描く - float marker = 1.0 - smoothstep(0.03 - px, 0.03, length(p - lightPos.xy)); + // トーンマッピング(ラインハルト):0〜∞を0〜1に収める + color = color / (1.0 + color); + + // ガンマ補正:線型の値をモニター用のsRGBにする + if (uGamma) color = pow(color, vec3(1.0 / 2.2)); + + // 光源の印(ポイントライトは点) + float marker = 0.0; + for (int i = 0; i < NUM_LIGHTS; ++i) { + marker = max(marker, 1.0 - smoothstep(0.03 - px, 0.03, length(p - orbit(i).xy))); + } + // チューブライトは線 + float tube = sdSegment(p, TUBE_A.xy, TUBE_B.xy); + marker = max(marker, 1.0 - smoothstep(0.015 - px, 0.015, tube)); color = mix(color, vec3(1.0), marker); out_color = vec4(color, 1.0);