光の反射と屈折(スネルの法則)

光の反射と屈折は、幾何光学の最も基本的な現象です。光が異なる媒質の境界に到達したとき、一部は反射し、一部は屈折して透過します。これらの法則はフェルマーの原理という単一の原理から導出でき、光ファイバーからカメラレンズまで幅広い応用の基盤となります。

本記事の内容

  • フェルマーの原理
  • 反射の法則の導出
  • スネルの法則(屈折の法則)の導出
  • 全反射と臨界角
  • 分散とプリズム
  • Pythonでの可視化

前提知識

この記事を読む前に、以下の知識があると理解が深まります。

  • 微分の基礎(極値条件)
  • 三角関数の基本的な性質

フェルマーの原理

フェルマーの原理(Fermat’s principle)は次のように述べられます。

光は、ある点から別の点へ進むとき、光路長(optical path length)が停留値をとる経路を通る。

光路長 $L$ は、屈折率 $n$ と幾何学的経路長 $ds$ の積分で定義されます。

$$ L = \int n \, ds $$

均一媒質中では光速は $v = c / n$ ですから、光路長は光が進むのに要する時間に比例します。つまり、フェルマーの原理は「光は最短時間の経路を通る」と言い換えることもできます(より正確には停留値)。

反射の法則の導出

平面鏡での反射を考えます。点 A から出た光が鏡面上の点 P で反射して点 B に到達するとします。

A の座標を $(0, h_1)$、B の座標を $(d, h_2)$、P の座標を $(x, 0)$ とします。媒質の屈折率は一様に $n$ とします。

光路長は:

$$ L(x) = n\left(\sqrt{x^2 + h_1^2} + \sqrt{(d-x)^2 + h_2^2}\right) $$

フェルマーの原理より、$L(x)$ が停留値をとる条件は:

$$ \frac{dL}{dx} = 0 $$

微分を計算します。

$$ \begin{align} \frac{dL}{dx} &= n\left(\frac{x}{\sqrt{x^2 + h_1^2}} + \frac{-(d-x)}{\sqrt{(d-x)^2 + h_2^2}}\right) \\ &= n(\sin\theta_i – \sin\theta_r) = 0 \end{align} $$

ここで $\theta_i$ は入射角、$\theta_r$ は反射角です。したがって:

$$ \boxed{\theta_i = \theta_r} $$

これが反射の法則です。入射角と反射角は等しくなります。

スネルの法則(屈折の法則)の導出

屈折率 $n_1$ の媒質1と屈折率 $n_2$ の媒質2の境界面を考えます。点 A $(0, h_1)$ が媒質1に、点 B $(d, -h_2)$ が媒質2にあり、境界面上の点 P $(x, 0)$ を光が通過するとします。

光路長は:

$$ L(x) = n_1 \sqrt{x^2 + h_1^2} + n_2 \sqrt{(d-x)^2 + h_2^2} $$

フェルマーの原理より:

$$ \frac{dL}{dx} = \frac{n_1 x}{\sqrt{x^2 + h_1^2}} – \frac{n_2 (d-x)}{\sqrt{(d-x)^2 + h_2^2}} = 0 $$

入射角 $\theta_1$、屈折角 $\theta_2$ を用いると:

$$ \frac{n_1 x}{\sqrt{x^2 + h_1^2}} = n_1 \sin\theta_1, \quad \frac{n_2 (d-x)}{\sqrt{(d-x)^2 + h_2^2}} = n_2 \sin\theta_2 $$

よって:

$$ \boxed{n_1 \sin\theta_1 = n_2 \sin\theta_2} $$

これがスネルの法則(Snell’s law)です。

全反射と臨界角

光が光学的に密な媒質($n_1 > n_2$)から疎な媒質へ進む場合、スネルの法則より:

$$ \sin\theta_2 = \frac{n_1}{n_2}\sin\theta_1 $$

$n_1 > n_2$ のとき $n_1/n_2 > 1$ なので、$\theta_1$ がある角度を超えると $\sin\theta_2 > 1$ となり、屈折光が存在しなくなります。この臨界的な入射角を臨界角 $\theta_c$ といいます。

$$ \sin\theta_c = \frac{n_2}{n_1} $$

$$ \boxed{\theta_c = \arcsin\left(\frac{n_2}{n_1}\right)} $$

$\theta_1 > \theta_c$ のとき、光は全て反射されます。これが全反射(total internal reflection)です。光ファイバーはこの原理を利用して光を閉じ込め、長距離を伝搬させます。

具体例

ガラス($n_1 = 1.50$)から空気($n_2 = 1.00$)への場合:

$$ \theta_c = \arcsin\left(\frac{1.00}{1.50}\right) = \arcsin(0.667) \approx 41.8° $$

分散(プリズム)

屈折率 $n$ は一般に波長 $\lambda$ に依存します。これを分散(dispersion)といいます。コーシーの分散公式は:

$$ n(\lambda) = A + \frac{B}{\lambda^2} + \frac{C}{\lambda^4} $$

短い波長(青色光)ほど屈折率が大きいため、プリズムを通過した白色光は虹色に分離されます。

Pythonでの実装

スネルの法則の可視化

import numpy as np
import matplotlib.pyplot as plt

# --- スネルの法則: 屈折の可視化 ---
n1 = 1.0   # 空気
n2 = 1.5   # ガラス

theta1_deg = np.linspace(0, 89, 200)
theta1 = np.radians(theta1_deg)
sin_theta2 = (n1 / n2) * np.sin(theta1)
theta2 = np.arcsin(sin_theta2)

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 左: 入射角 vs 屈折角
axes[0].plot(theta1_deg, np.degrees(theta2), 'b-', linewidth=2)
axes[0].plot([0, 89], [0, 89], 'k--', alpha=0.3, label=r'$\theta_1 = \theta_2$')
axes[0].set_xlabel(r'Incident angle $\theta_1$ [deg]', fontsize=12)
axes[0].set_ylabel(r'Refracted angle $\theta_2$ [deg]', fontsize=12)
axes[0].set_title(f'Snell\'s law: $n_1={n1}$ to $n_2={n2}$', fontsize=13)
axes[0].legend(fontsize=11)
axes[0].grid(True, alpha=0.3)

# 右: 屈折の光線図
theta_i = np.radians(45)
theta_r = np.arcsin((n1 / n2) * np.sin(theta_i))

# 境界面
axes[1].axhline(y=0, color='gray', linewidth=2)
axes[1].axvline(x=0, color='gray', linewidth=1, linestyle='--', alpha=0.5)
axes[1].fill_between([-3, 3], [-3, -3], [0, 0], alpha=0.1, color='blue', label=f'Medium 2 (n={n2})')
axes[1].fill_between([-3, 3], [0, 0], [3, 3], alpha=0.1, color='yellow', label=f'Medium 1 (n={n1})')

# 入射光
length = 2.5
x_in = -length * np.sin(theta_i)
y_in = length * np.cos(theta_i)
axes[1].annotate('', xy=(0, 0), xytext=(x_in, y_in),
                 arrowprops=dict(arrowstyle='->', color='red', lw=2))

# 屈折光
x_out = length * np.sin(theta_r)
y_out = -length * np.cos(theta_r)
axes[1].annotate('', xy=(x_out, y_out), xytext=(0, 0),
                 arrowprops=dict(arrowstyle='->', color='blue', lw=2))

# 反射光
x_ref = -length * np.sin(theta_i)
y_ref_end = length * np.cos(theta_i)
axes[1].annotate('', xy=(-x_in, y_in), xytext=(0, 0),
                 arrowprops=dict(arrowstyle='->', color='green', lw=2, ls='--'))

axes[1].set_xlim(-3, 3)
axes[1].set_ylim(-3, 3)
axes[1].set_aspect('equal')
axes[1].set_title('Ray diagram (θ_i = 45°)', fontsize=13)
axes[1].legend(fontsize=10, loc='lower right')

plt.tight_layout()
plt.show()

全反射の臨界角

import numpy as np
import matplotlib.pyplot as plt

# --- 全反射と臨界角 ---
n1 = 1.5  # ガラス
n2 = 1.0  # 空気

theta_c = np.degrees(np.arcsin(n2 / n1))
print(f"臨界角: {theta_c:.1f}°")

theta1_deg = np.linspace(0, 89, 500)
theta1 = np.radians(theta1_deg)
sin_theta2 = (n1 / n2) * np.sin(theta1)

# 全反射領域と屈折領域を分離
mask_refract = sin_theta2 <= 1.0
mask_total = sin_theta2 > 1.0

fig, ax = plt.subplots(figsize=(8, 5))

# 屈折領域
theta2_refract = np.degrees(np.arcsin(sin_theta2[mask_refract]))
ax.plot(theta1_deg[mask_refract], theta2_refract, 'b-', linewidth=2, label='Refracted angle')

# 全反射領域
ax.axvspan(theta_c, 90, alpha=0.2, color='red', label='Total internal reflection')
ax.axvline(x=theta_c, color='red', linestyle='--', linewidth=1.5,
           label=f'Critical angle = {theta_c:.1f}°')

ax.set_xlabel(r'Incident angle $\theta_1$ [deg]', fontsize=12)
ax.set_ylabel(r'Refracted angle $\theta_2$ [deg]', fontsize=12)
ax.set_title(f'Total internal reflection ($n_1={n1}$ → $n_2={n2}$)', fontsize=13)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 90)
ax.set_ylim(0, 100)

plt.tight_layout()
plt.show()

プリズムによる分散

import numpy as np
import matplotlib.pyplot as plt

# --- プリズムの分散 ---
# コーシーの分散公式(BK7ガラスの近似値)
A, B = 1.5046, 0.00420  # 簡略化した係数(μm単位)

wavelengths = np.array([0.400, 0.450, 0.500, 0.550, 0.600, 0.650, 0.700])  # μm
colors_name = ['violet', 'blue', 'cyan', 'green', 'orange', 'red', 'darkred']
n_values = A + B / wavelengths**2

apex_angle = np.radians(60)  # プリズム頂角

fig, ax = plt.subplots(figsize=(10, 6))

for wl, n, col in zip(wavelengths, n_values, colors_name):
    # 最小偏角条件: 入射角
    theta_i = np.arcsin(n * np.sin(apex_angle / 2))
    # 最小偏角
    delta_min = 2 * theta_i - apex_angle

    # プリズム入射面での屈折
    theta_r1 = np.arcsin(np.sin(theta_i) / n)

    # 簡易光線描画
    x_start = -2
    y_start = 0.5
    # 入射光
    ax.plot([x_start, 0], [y_start, 0], color=col, linewidth=1.5)
    # 出射光(偏角を反映)
    x_end = 2 * np.cos(np.degrees(delta_min) * np.pi / 180 * 0.3)
    y_end = -2 * np.sin(delta_min)
    ax.plot([0, 2], [0, -np.degrees(delta_min) * 0.04], color=col, linewidth=2,
            label=f'{wl*1000:.0f} nm (n={n:.4f})')

ax.set_xlim(-2.5, 2.5)
ax.set_ylim(-2, 2)
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Dispersion by prism (Cauchy model)', fontsize=13)
ax.legend(fontsize=9, loc='upper right')
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

まとめ

本記事では、光の反射と屈折について解説しました。

  • フェルマーの原理: 光は光路長が停留値をとる経路を通る
  • 反射の法則: 入射角と反射角は等しい($\theta_i = \theta_r$)
  • スネルの法則: $n_1 \sin\theta_1 = n_2 \sin\theta_2$
  • 全反射: $\theta_1 > \theta_c = \arcsin(n_2/n_1)$ で全ての光が反射される
  • 分散: 屈折率の波長依存性により、白色光がスペクトルに分離される

次のステップとして、以下の記事も参考にしてください。