[C言語] OpenGLとは?描画などの基本的な使い方を解説

OpenGLは、2Dおよび3Dグラフィックスを描画するためのクロスプラットフォームなAPIです。C言語を使用してOpenGLを操作することで、リアルタイムのグラフィックスアプリケーションを開発できます。

OpenGLの基本的な使い方には、ウィンドウの作成、コンテキストの設定、シェーダーのコンパイル、頂点データのバッファリング、描画コマンドの発行などがあります。

これらの操作を通じて、ポリゴンの描画やテクスチャの適用、光源の設定など、さまざまなグラフィックス効果を実現できます。

この記事でわかること
  • OpenGLの概要と特徴
  • OpenGLの基本的なセットアップ方法
  • OpenGLを使用した描画の基本フロー
  • 3Dグラフィックスの描画における重要な概念
  • OpenGLの応用例と実践的な活用方法

目次から探す

OpenGLとは何か

OpenGLの概要

OpenGL(Open Graphics Library)は、2Dおよび3Dグラフィックスを描画するためのクロスプラットフォームなAPI(アプリケーションプログラミングインターフェース)です。

OpenGLは、グラフィックスハードウェアを直接操作するための標準化されたインターフェースを提供し、さまざまなプラットフォームで一貫したグラフィックス描画を可能にします。

これにより、開発者は異なるオペレーティングシステムやデバイス上で同じコードを使用してグラフィックスを描画することができます。

OpenGLの歴史

OpenGLは1992年にSilicon Graphics, Inc.(SGI)によって開発されました。

当初は高性能な3Dグラフィックスを提供するためのAPIとして設計され、主にワークステーション向けに使用されていました。

その後、OpenGLはKhronos Groupによって管理されるようになり、さまざまなバージョンアップを経て、現在では幅広いプラットフォームで利用されています。

OpenGLの特徴

OpenGLの主な特徴は以下の通りです。

スクロールできます
特徴説明
クロスプラットフォームWindows、macOS、Linuxなど、さまざまなOSで動作します。
拡張性ベンダー独自の拡張機能を追加することが可能です。
リアルタイムレンダリング高速なグラフィックス描画を実現します。
オープンスタンダードKhronos Groupによって管理され、広く普及しています。

OpenGLと他のグラフィックスAPIの比較

OpenGLは他のグラフィックスAPIと比較して、以下のような違いがあります。

スクロールできます
API特徴主な用途
OpenGLクロスプラットフォーム、オープンスタンダードゲーム開発、科学技術計算
DirectXWindows専用、マイクロソフトによる開発ゲーム開発、マルチメディアアプリケーション
Vulkan高性能、低レベルアクセスゲーム開発、リアルタイムアプリケーション

OpenGLは、特にクロスプラットフォームでの開発が求められるプロジェクトにおいて、その柔軟性と広範なサポートにより選ばれることが多いです。

DirectXはWindows環境に特化しており、Vulkanはより低レベルな制御を提供するため、パフォーマンスが重要なアプリケーションで使用されます。

OpenGLの基本概念

コンテキストとは

OpenGLのコンテキストは、OpenGLの状態やリソースを管理するための環境を指します。

コンテキストは、描画を行うために必要な情報を保持し、OpenGLの関数呼び出しがどのように動作するかを決定します。

具体的には、ウィンドウシステムと連携して、描画対象のウィンドウやフレームバッファを管理します。

OpenGLプログラムを実行する際には、まずこのコンテキストを作成し、アクティブにする必要があります。

シェーダーの役割

シェーダーは、GPU上で実行される小さなプログラムで、グラフィックスパイプラインの特定のステージでデータを処理します。

OpenGLでは、主に以下の2種類のシェーダーが使用されます。

  • 頂点シェーダー: 頂点データを処理し、頂点の位置や属性を計算します。
  • フラグメントシェーダー: ピクセルごとの色やテクスチャを計算し、最終的なピクセルの色を決定します。

シェーダーはGLSL(OpenGL Shading Language)で記述され、プログラム内でコンパイルおよびリンクされて使用されます。

バッファとその種類

OpenGLでは、データを効率的に管理するためにバッファを使用します。

バッファは、GPUメモリ上にデータを格納するための領域で、以下のような種類があります。

  • 頂点バッファオブジェクト(VBO): 頂点データを格納します。
  • インデックスバッファオブジェクト(IBO): 頂点の描画順序を格納します。
  • フレームバッファオブジェクト(FBO): 描画結果を格納し、オフスクリーンレンダリングに使用します。

これらのバッファを使用することで、データ転送のオーバーヘッドを減らし、描画のパフォーマンスを向上させることができます。

パイプラインの流れ

OpenGLのグラフィックスパイプラインは、データがどのように処理されて最終的な画像が生成されるかを示す一連のステージで構成されています。

主なステージは以下の通りです。

  1. 頂点シェーダー: 頂点データを処理し、変換を行います。
  2. ジオメトリシェーダー(オプション): プリミティブを生成または変換します。
  3. ラスタライゼーション: プリミティブをフラグメントに変換します。
  4. フラグメントシェーダー: フラグメントごとの色を計算します。
  5. フレームバッファ: 最終的なピクセルデータを格納します。

このパイプラインを通じて、OpenGLは効率的にグラフィックスを描画し、リアルタイムでのレンダリングを実現します。

OpenGLのセットアップ

必要なライブラリとツール

OpenGLを使用して開発を始めるには、いくつかのライブラリとツールが必要です。

以下は、一般的に使用されるものです。

スクロールできます
ライブラリ/ツール説明
OpenGLグラフィックスAPIそのもの。
GLFWウィンドウの作成や入力処理を簡単にするライブラリ。
GLEWOpenGLの拡張機能を簡単に使用するためのライブラリ。
GLMOpenGL向けの数学ライブラリ。ベクトルや行列の計算をサポート。
Visual Studio / GCCC言語の開発環境。WindowsではVisual Studio、LinuxではGCCが一般的。

これらのライブラリを使用することで、OpenGLのプログラミングがより効率的になります。

開発環境の構築

OpenGLの開発環境を構築する手順は、使用するOSによって異なりますが、一般的な流れは以下の通りです。

  1. コンパイラのインストール: WindowsではVisual Studio、LinuxではGCCをインストールします。
  2. ライブラリのダウンロード: GLFW、GLEW、GLMなどのライブラリを公式サイトからダウンロードします。
  3. プロジェクトの設定: 開発環境で新しいプロジェクトを作成し、ダウンロードしたライブラリをプロジェクトに追加します。
  4. リンク設定: ライブラリのヘッダーファイルとリンクファイルをプロジェクトに設定します。

これらの手順を完了することで、OpenGLを使用したプログラミングが可能になります。

初めてのOpenGLプログラム

OpenGLを使った最初のプログラムとして、ウィンドウを作成し、背景色を設定する簡単な例を示します。

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdio.h>
int main() {
    // GLFWの初期化
    if (!glfwInit()) {
        fprintf(stderr, "GLFWの初期化に失敗しました\n");
        return -1;
    }
    // ウィンドウの作成
    GLFWwindow* window = glfwCreateWindow(640, 480, "OpenGL Window", NULL, NULL);
    if (!window) {
        fprintf(stderr, "ウィンドウの作成に失敗しました\n");
        glfwTerminate();
        return -1;
    }
    // コンテキストの設定
    glfwMakeContextCurrent(window);
    // GLEWの初期化
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "GLEWの初期化に失敗しました\n");
        return -1;
    }
    // メインループ
    while (!glfwWindowShouldClose(window)) {
        // 背景色の設定
        glClearColor(0.0f, 0.5f, 0.5f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        // バッファの入れ替え
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    // 終了処理
    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}

このプログラムを実行すると、640×480ピクセルのウィンドウが表示され、背景色がシアン色に設定されます。

ウィンドウを閉じるまで、メインループが実行され続けます。

この基本的なプログラムを基に、さらに複雑なグラフィックスを描画することができます。

OpenGLでの描画の基本

ウィンドウの作成

OpenGLで描画を行うためには、まず描画対象となるウィンドウを作成する必要があります。

ウィンドウの作成には、GLFWライブラリを使用するのが一般的です。

以下は、GLFWを使用してウィンドウを作成する基本的な手順です。

  1. GLFWの初期化: glfwInit()を呼び出してGLFWを初期化します。
  2. ウィンドウの作成: glfwCreateWindow()を使用してウィンドウを作成します。

ウィンドウの幅、高さ、タイトルを指定します。

  1. コンテキストの設定: glfwMakeContextCurrent()で作成したウィンドウのコンテキストをアクティブにします。

以下は、ウィンドウを作成するコードの例です。

GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Window", NULL, NULL);
if (!window) {
    fprintf(stderr, "ウィンドウの作成に失敗しました\n");
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

描画の基本フロー

OpenGLでの描画は、以下の基本フローに従って行われます。

  1. 初期化: 必要なリソース(シェーダー、バッファなど)を初期化します。
  2. 描画ループ: ウィンドウが閉じられるまで、以下のステップを繰り返します。
  • 入力処理: ユーザーの入力を処理します。
  • 描画コマンドの発行: 描画するオブジェクトを指定し、描画コマンドを発行します。
  • バッファのスワップ: glfwSwapBuffers()を呼び出して、描画結果を画面に表示します。
  1. 終了処理: 使用したリソースを解放し、GLFWを終了します。

頂点バッファオブジェクト(VBO)の使用

頂点バッファオブジェクト(VBO)は、頂点データをGPUメモリに格納するためのバッファです。

VBOを使用することで、描画のパフォーマンスを向上させることができます。

以下は、VBOを使用する基本的な手順です。

  1. バッファの生成: glGenBuffers()を使用してバッファを生成します。
  2. バッファのバインド: glBindBuffer()でバッファをバインドします。
  3. データの転送: glBufferData()を使用してデータをバッファに転送します。

以下は、VBOを使用して三角形を描画するコードの例です。

GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
GLfloat vertices[] = {
    0.0f,  0.5f, 0.0f,
   -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
};
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

シェーダープログラムの作成と使用

シェーダープログラムは、GPU上で実行されるプログラムで、頂点やフラグメントの処理を行います。

OpenGLでは、シェーダープログラムを作成し、使用するために以下の手順を行います。

  1. シェーダーの作成: glCreateShader()を使用してシェーダーオブジェクトを作成します。
  2. シェーダーのソースコードを設定: glShaderSource()でシェーダーのソースコードを設定します。
  3. シェーダーのコンパイル: glCompileShader()を呼び出してシェーダーをコンパイルします。
  4. プログラムオブジェクトの作成: glCreateProgram()でプログラムオブジェクトを作成します。
  5. シェーダーのリンク: glAttachShader()でシェーダーをプログラムにアタッチし、glLinkProgram()でリンクします。
  6. プログラムの使用: glUseProgram()でプログラムを使用します。

以下は、シェーダープログラムを作成し、使用するコードの例です。

const char* vertexShaderSource = "#version 330 core\n"
    "layout(location = 0) in vec3 aPos;\n"
    "void main() {\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "}
const char* vertexShaderSource = "#version 330 core\n"
    "layout(location = 0) in vec3 aPos;\n"
    "void main() {\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "}\0";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glLinkProgram(shaderProgram);
glUseProgram(shaderProgram);
"; GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glLinkProgram(shaderProgram); glUseProgram(shaderProgram);

シェーダープログラムを使用することで、頂点の位置や色を柔軟に制御することができ、より複雑なグラフィックスを描画することが可能になります。

3Dグラフィックスの描画

カメラと視点の設定

3Dグラフィックスでは、カメラの設定が重要です。

カメラは、シーンをどのように見るかを決定します。

OpenGLでは、カメラの位置や方向を設定するためにビュー行列を使用します。

ビュー行列は、カメラの位置、注視点、上方向を指定して作成されます。

GLMライブラリを使用すると、glm::lookAt関数で簡単にビュー行列を作成できます。

GLMライブラリはC++用です。C++対応コンパイラが必要です。

glm::mat4 view = glm::lookAt(
    glm::vec3(0.0f, 0.0f, 3.0f), // カメラの位置
    glm::vec3(0.0f, 0.0f, 0.0f), // 注視点
    glm::vec3(0.0f, 1.0f, 0.0f)  // 上方向
);

モデル変換とビュー変換

3Dオブジェクトを描画する際には、モデル変換とビュー変換を行います。

モデル変換は、オブジェクトの位置、回転、スケールを設定するための行列です。

ビュー変換は、カメラの視点を設定するための行列です。

これらの行列を組み合わせて、オブジェクトの最終的な位置を決定します。

glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(1.0f, 0.0f, 0.0f)); // オブジェクトを右に移動
model = glm::rotate(model, glm::radians(45.0f), glm::vec3(0.0f, 1.0f, 0.0f)); // Y軸周りに45度回転

投影変換の実装

投影変換は、3Dシーンを2Dスクリーンに投影するための変換です。

OpenGLでは、透視投影と正射影の2種類の投影方法があります。

透視投影は、遠くのオブジェクトが小さく見えるようにするために使用されます。

GLMライブラリを使用すると、glm::perspective関数で透視投影行列を作成できます。

glm::mat4 projection = glm::perspective(
    glm::radians(45.0f), // 視野角
    800.0f / 600.0f,     // アスペクト比
    0.1f, 100.0f         // 近クリップ面と遠クリップ面
);

照明とマテリアルの基本

3Dグラフィックスでは、照明とマテリアルの設定が重要です。

照明は、シーン内のオブジェクトにどのように光が当たるかを決定します。

OpenGLでは、フラグメントシェーダーを使用して照明計算を行います。

基本的な照明モデルには、アンビエント、ディフューズ、スペキュラの3つの成分があります。

  • アンビエント: 環境光。

全体的な明るさを設定します。

  • ディフューズ: 拡散光。

光源からの直接的な光をシミュレートします。

  • スペキュラ: 鏡面反射光。

光沢のある表面での反射を表現します。

マテリアルは、オブジェクトの表面特性を定義します。

マテリアルの設定により、オブジェクトがどのように光を反射するかを制御できます。

以下は、シェーダー内での基本的な照明計算の例です。

vec3 ambient = lightColor * ambientStrength;
vec3 norm = normalize(normal);
vec3 lightDir = normalize(lightPos - fragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = lightColor * diff * diffuseStrength;
vec3 viewDir = normalize(viewPos - fragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
vec3 specular = lightColor * spec * specularStrength;
vec3 result = ambient + diffuse + specular;

このように、照明とマテリアルを適切に設定することで、リアルな3Dグラフィックスを描画することができます。

テクスチャの使用

テクスチャの読み込み

テクスチャは、3Dオブジェクトの表面に貼り付ける画像データです。

OpenGLでテクスチャを使用するには、まず画像ファイルを読み込んでテクスチャオブジェクトを作成する必要があります。

一般的に、stb_imageライブラリを使用して画像を読み込みます。

以下は、テクスチャを読み込む基本的な手順です。

  1. 画像の読み込み: stbi_load()を使用して画像ファイルを読み込みます。
  2. テクスチャオブジェクトの生成: glGenTextures()でテクスチャオブジェクトを生成します。
  3. テクスチャのバインド: glBindTexture()でテクスチャをバインドします。
  4. テクスチャデータの設定: glTexImage2D()でテクスチャデータを設定します。
int width, height, nrChannels;
unsigned char *data = stbi_load("path/to/texture.jpg", &width, &height, &nrChannels, 0);
if (data) {
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
} else {
    fprintf(stderr, "テクスチャの読み込みに失敗しました\n");
}
stbi_image_free(data);

テクスチャのマッピング

テクスチャマッピングは、3Dオブジェクトの頂点にテクスチャ座標を割り当て、テクスチャをオブジェクトに貼り付けるプロセスです。

テクスチャ座標は通常、0.0から1.0の範囲で指定されます。

頂点シェーダーでテクスチャ座標を受け取り、フラグメントシェーダーでテクスチャをサンプリングして色を決定します。

以下は、テクスチャマッピングの基本的なコード例です。

// 頂点データにテクスチャ座標を追加
GLfloat vertices[] = {
    // 位置          // テクスチャ座標
    0.5f,  0.5f, 0.0f,  1.0f, 1.0f,
    0.5f, -0.5f, 0.0f,  1.0f, 0.0f,
   -0.5f, -0.5f, 0.0f,  0.0f, 0.0f,
   -0.5f,  0.5f, 0.0f,  0.0f, 1.0f
};
// フラグメントシェーダーでのテクスチャサンプリング
uniform sampler2D ourTexture;
out vec4 FragColor;
void main() {
    FragColor = texture(ourTexture, TexCoord);
}

テクスチャフィルタリングとラッピング

テクスチャフィルタリングとラッピングは、テクスチャがオブジェクトにどのように適用されるかを制御します。

  • フィルタリング: テクスチャが拡大または縮小されたときに、どのようにピクセルを補間するかを決定します。

一般的なフィルタリングモードには、GL_NEAREST(最近傍補間)とGL_LINEAR(線形補間)があります。

  • ラッピング: テクスチャ座標が0.0から1.0の範囲を超えたときに、どのようにテクスチャを繰り返すかを決定します。

一般的なラッピングモードには、GL_REPEAT(繰り返し)、GL_MIRRORED_REPEAT(鏡像繰り返し)、GL_CLAMP_TO_EDGE(端にクランプ)があります。

以下は、フィルタリングとラッピングを設定するコードの例です。

glBindTexture(GL_TEXTURE_2D, texture);
// フィルタリングの設定
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// ラッピングの設定
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

これらの設定を適切に行うことで、テクスチャがオブジェクトに自然に適用され、視覚的に魅力的な結果を得ることができます。

応用例

2Dゲームの開発

OpenGLは、2Dゲームの開発においても非常に有用です。

2Dゲームでは、スプライトと呼ばれる画像を使用してキャラクターや背景を描画します。

OpenGLのテクスチャマッピング機能を利用することで、スプライトを効率的に描画できます。

また、シェーダーを活用することで、スプライトに対する特殊効果(例:影、光の反射、アニメーション)を実現することも可能です。

2Dゲーム開発の基本的な流れは以下の通りです。

  1. スプライトの読み込み: テクスチャとしてスプライト画像を読み込みます。
  2. ゲームループの実装: ゲームのメインループを実装し、入力処理、ゲームロジックの更新、描画を行います。
  3. 衝突判定: キャラクターやオブジェクト間の衝突を検出し、ゲームの進行に応じた処理を行います。

OpenGLを使用することで、2Dゲームの描画が高速化され、スムーズなゲームプレイを実現できます。

3Dモデリングソフトの作成

3Dモデリングソフトは、3Dオブジェクトを作成、編集、表示するためのツールです。

OpenGLは、リアルタイムで3Dオブジェクトを描画するための強力な機能を提供します。

モデリングソフトでは、以下のような機能を実装することが一般的です。

  1. オブジェクトの描画: モデルデータを読み込み、OpenGLを使用して3D空間に描画します。
  2. ユーザーインターフェース: マウスやキーボードを使用して、オブジェクトの移動、回転、スケーリングを行います。
  3. シェーディングとライティング: シェーダーを使用して、オブジェクトにリアルな光と影を適用します。

OpenGLの柔軟性とパフォーマンスにより、複雑な3Dシーンをリアルタイムで操作することが可能です。

科学技術計算の可視化

科学技術計算の分野では、計算結果を視覚的に表現することが重要です。

OpenGLは、大量のデータを効率的に描画し、複雑なシミュレーション結果を可視化するためのツールとして利用されます。

以下は、科学技術計算の可視化におけるOpenGLの応用例です。

  1. データの読み込みと処理: 計算結果をデータとして読み込み、OpenGLで描画可能な形式に変換します。
  2. グラフやチャートの描画: 2Dおよび3Dのグラフやチャートを描画し、データの傾向やパターンを視覚化します。
  3. シミュレーションのアニメーション: 時間の経過に伴うデータの変化をアニメーションとして表示し、動的なシミュレーションを実現します。

OpenGLを使用することで、科学技術計算の結果を直感的に理解しやすくなり、研究や開発の効率を向上させることができます。

よくある質問

OpenGLはどのプラットフォームで使用できますか?

OpenGLはクロスプラットフォームなAPIであり、Windows、macOS、Linuxなどの主要なオペレーティングシステムで使用できます。

また、モバイルデバイス向けにはOpenGL ES(Embedded Systems)が提供されており、iOSやAndroidでも利用可能です。

この広範なプラットフォームサポートにより、OpenGLは多くの開発者にとって魅力的な選択肢となっています。

OpenGLとDirectXの違いは何ですか?

OpenGLとDirectXはどちらもグラフィックスAPIですが、いくつかの違いがあります。

OpenGLはクロスプラットフォームであり、さまざまなOSで動作しますが、DirectXは主にWindows専用です。

OpenGLはオープンスタンダードであり、Khronos Groupによって管理されています。

一方、DirectXはマイクロソフトによって開発されており、Windows環境に最適化されています。

これにより、DirectXはWindows上でのゲーム開発において高いパフォーマンスを発揮しますが、OpenGLは異なるプラットフォーム間での移植性に優れています。

OpenGLの学習におすすめのリソースはありますか?

OpenGLを学ぶためのリソースは多く存在します。

以下はおすすめのリソースです。

  • 公式ドキュメント: Khronos Groupの公式サイトで提供されているOpenGLの仕様書やチュートリアル。
  • オンラインチュートリアル: “Learn OpenGL”(https://learnopengl.com/)は、初心者から上級者までを対象とした詳細なチュートリアルを提供しています。
  • 書籍: “OpenGL Programming Guide”(通称「赤本」)は、OpenGLの基本から応用までを網羅した書籍です。
  • フォーラムとコミュニティ: Stack OverflowやOpenGLの公式フォーラムで質問や情報交換を行うことができます。

これらのリソースを活用することで、OpenGLの理解を深め、実践的なスキルを身につけることができます。

まとめ

OpenGLは、クロスプラットフォームで動作する強力なグラフィックスAPIであり、2Dおよび3Dグラフィックスの描画に広く利用されています。

この記事では、OpenGLの基本概念からセットアップ、描画の基本、応用例までを解説しました。

OpenGLの特徴や他のグラフィックスAPIとの違いを理解し、実際の開発に役立てることができるでしょう。

これを機に、OpenGLを使ったプロジェクトに挑戦し、さらなるスキルアップを目指してみてください。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

  • URLをコピーしました!
目次から探す