uniform structを使ってシェーダー内で構造体を使う(4-Ex2.)

# はじめに

[複数のスポットライトのライティングを実装する(4-11.相当)](https://qiita.com/uechoco@github/items/609bd631ce9edbfd78f8) では、個々のuniform変数を配列で扱うように変更し、複数のスポットライトを実装していました。

c++を使い慣れたプログラマーであれば、スポットライトのパラメータを構造体でまとめたいと思ったかもしれません。今回はuniform変数に構造体を導入します。

# 1. シェーダー内での構造体の定義の仕方

シェーダー内で構造体を定義するには、 `uniform struct` 構文を使います。 `uniform` というキーワードが付いていなければ、C言語の構造体にそっくりです。
実際に構造体を用いる変数を宣言するには、構造体の名前で `uniform` 変数を宣言します。配列として扱いたい場合は変数名の後ろにさらに要素数を付け加えます。

```glsl
uniform struct LightSource {
    vec3 light_pos;
    float light_attenuation;
    vec3 spot_dir;
    float spot_phi;
    float spot_theta;
    float spot_falloff;
    vec4 diffuse_color;
    vec4 ambient_color;
    vec4 specular_color;
    float specular_shininess;
};
uniform LightSource lights[LIGHT_NUM];
```

構造体内のメンバ変数にアクセスする方法もC言語と一緒で、 `lights[i].light_pos` のように記述します。

実は構造化されたuniform変数にはメモリレイアウトをどうするかという問題を考えなければいけないケースが出てきます。 `std140` といったキーワードがそれに当たります。今回は取り扱いませんが、気になる方は調べてみてください。

# 2. 構造体として定義したuniform変数にプログラムから値を渡す

4-11. では、uniform変数を配列化したときは、添字付きのuniform変数名でlocationを指定することで、配列の要素に値を渡すことができました。
uniform変数を構造体にしたときは、おそらく想像通りかと思いますが、ドット区切りのuniform変数名でlocationを指定します。

```cpp
// uniform変数を配列化したとき
program->SetUniform("light_pos[0]", lightPos1);
// uniform変数を構造体にしたとき
program->SetUniform("light.light_pos", lightPos1);
// uniform変数を構造体の配列にしたとき
program->SetUniform("lights[0].light_pos", lightPos1);
```

# 3. 実装

変更後のプログラムを掲載します。

```glsl:myshader.fsh
#version 410

in vec3 v_position;
in vec3 v_normal;
in vec4 v_color;
const int LIGHT_NUM = 3;
uniform struct LightSource {
    vec3 light_pos;
    float light_attenuation;
    vec3 spot_dir;
    float spot_phi;
    float spot_theta;
    float spot_falloff;
    vec4 diffuse_color;
    vec4 ambient_color;
    vec4 specular_color;
    float specular_shininess;
};
uniform LightSource lights[LIGHT_NUM];
uniform vec3 eye_dir;
uniform mat4 pvm_mat;
uniform mat4 model_mat;
uniform vec4 emissive_color;
layout (location=0) out vec4 frag_color;

void main()
{
    vec4 diffuse_total = vec4(0);
    vec4 ambient_total = vec4(0);
    vec4 specular_total = vec4(0);
    for (int i = 0; i < LIGHT_NUM; ++i)
    {
        vec3 light_vec_dir = v_position - lights[i].light_pos;
        float light_len = length(light_vec_dir);
        float attenuation = 1.0 / (lights[i].light_attenuation * light_len * light_len);

        vec3 light_vec_dirN = normalize(light_vec_dir);
        vec3 spor_dirN = normalize(lights[i].spot_dir);
        float cos_alpha = dot(light_vec_dirN, spor_dirN);
        float cos_half_theta = cos(lights[i].spot_theta / 2.0);
        float cos_half_phi = cos(lights[i].spot_phi / 2.0);
        if (cos_alpha <= cos_half_phi)
        {
            // out-range
            // attenuation * 0.f;
            ambient_total += lights[i].ambient_color;
        }
        else
        {
            if (cos_alpha > cos_half_theta)
            {
                // inner corn
                // attenuation * 1.f
            }
            else
            {
                // outer corn
                attenuation *= pow((cos_alpha - cos_half_phi)/(cos_half_theta - cos_half_phi), lights[i].spot_falloff);
            }
            vec3 normal = normalize((vec4(v_normal, 0.0) * model_mat).xyz);
            vec3 light = -light_vec_dirN;
            float diffuse_power = clamp(dot(normal, light), 0.0, 1.0);
            vec3 eye = -normalize(eye_dir);
            vec3 half_vec = normalize(light + eye);
            float specular = pow(clamp(dot(normal, half_vec), 0.0, 1.0), lights[i].specular_shininess);

            diffuse_total += lights[i].diffuse_color * diffuse_power * attenuation;
            ambient_total += lights[i].ambient_color;
            specular_total += lights[i].specular_color * specular;
        }
        frag_color = v_color * diffuse_total + ambient_total + specular_total + emissive_color;
    }
}
```

```cpp:Game.cpp

void Game::Render()
{
    /*
    if (Input::GetKey(KeyCode::A)) {
        cameraPos.x -= 2.0f * Time::deltaTime;
        bCameraDirty = true;
    } else if (Input::GetKey(KeyCode::D)) {
        cameraPos.x += 2.0f * Time::deltaTime;
        bCameraDirty = true;
    } else if (Input::GetKey(KeyCode::W)) {
        cameraPos.z -= 2.0f * Time::deltaTime;
        bCameraDirty = true;
    } else if (Input::GetKey(KeyCode::S)) {
        cameraPos.z += 2.0f * Time::deltaTime;
        bCameraDirty = true;
    } else if (Input::GetKey(KeyCode::Q)) {
        cameraPos.y -= 2.0f * Time::deltaTime;
        bCameraDirty = true;
    } else if (Input::GetKey(KeyCode::E)) {
        cameraPos.y += 2.0f * Time::deltaTime;
        bCameraDirty = true;
    } else if (Input::GetKey(KeyCode::P)) {
        bPerspective = !bPerspective;
        bCameraDirty = true;
    }
    */

    cameraPos.x = -cosf(Time::time * 0.6f) * 5.0f;
    cameraPos.z = sinf(Time::time * 0.6f) * 5.0f;
    bCameraDirty = true;
    // レンダリングに使用するシェーダをセット
    program->Use();

    GLKVector3 cameraTarget = GLKVector3Make(0.0f, 0.0f, 0.0f);
    if (bCameraDirty)
    {
        GLKMatrix4 viewMat = GLKMatrix4MakeLookAt(cameraPos.x, cameraPos.y, cameraPos.z,
                                              cameraTarget.x, cameraTarget.y, cameraTarget.z,
                                              0.0f, 1.0f, 0.0f);

        GLKMatrix4 projMat = bPerspective
            ? GLKMatrix4MakePerspective(GLKMathDegreesToRadians(60.0f), 640.f / 480.f, 0.00001f, 50.f)
            : GLKMatrix4MakeOrtho(-8.f, 8.f, -6.f, 6.f, 0.0001f, 50.f);
        projViewMat = GLKMatrix4Multiply(projMat, viewMat);

        bCameraDirty = false;
    }
    GLKVector3 eyeDir = GLKVector3Subtract(cameraTarget, cameraPos);
    eyeDir = GLKVector3Normalize(eyeDir);
    program->SetUniform("eye_dir", eyeDir);


    // 背景の上書き
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    {
        // ライト1
        GLKVector3 lightPos = GLKVector3Make(-1.f, 4.f, 2.0f);
        GLKVector3 spotDir = GLKVector3Make(0.27f, -1.0f, -0.6f);
        program->SetUniform("lights[0].light_pos", lightPos);
        program->SetUniform("lights[0].light_attenuation", 0.01f);
        program->SetUniform("lights[0].spot_dir", spotDir);
        program->SetUniform("lights[0].spot_phi", GLKMathDegreesToRadians(45.f));
        program->SetUniform("lights[0].spot_theta", GLKMathDegreesToRadians(30.f));
        program->SetUniform("lights[0].spot_falloff", 1.f);

        program->SetUniform("lights[0].diffuse_color", GLKVector4Make(0.9f, 0.1f, 0.1f, 1.0f));
        program->SetUniform("lights[0].ambient_color", GLKVector4Make(0.3f, 0.0f, 0.0f, 1.0f));
        program->SetUniform("lights[0].specular_color", GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f));
        program->SetUniform("lights[0].specular_shininess", 50.0f);
    }

    {
        // ライト2
        GLKVector3 lightPos = GLKVector3Make(-3.f, 4.f, -1.0f);
        GLKVector3 spotDir = GLKVector3Make(0.8f, -1.f, 0.3f);
        program->SetUniform("lights[1].light_pos", lightPos);
        program->SetUniform("lights[1].light_attenuation", 0.01f);
        program->SetUniform("lights[1].spot_dir", spotDir);
        program->SetUniform("lights[1].spot_phi", GLKMathDegreesToRadians(45.f));
        program->SetUniform("lights[1].spot_theta", GLKMathDegreesToRadians(30.f));
        program->SetUniform("lights[1].spot_falloff", 1.f);

        program->SetUniform("lights[1].diffuse_color", GLKVector4Make(0.1f, 0.9f, 0.1f, 1.0f));
        program->SetUniform("lights[1].ambient_color", GLKVector4Make(0.0f, 0.1f, 0.0f, 1.0f));
        program->SetUniform("lights[1].specular_color", GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f));
        program->SetUniform("lights[1].specular_shininess", 50.0f);
    }

    {
        // ライト3
        GLKVector3 lightPos = GLKVector3Make(3.f, 4.f, 2.0f);
        GLKVector3 spotDir = GLKVector3Make(-0.8f, -1.f, -0.6f);
        program->SetUniform("lights[2].light_pos", lightPos);
        program->SetUniform("lights[2].light_attenuation", 0.01f);
        program->SetUniform("lights[2].spot_dir", spotDir);
        program->SetUniform("lights[2].spot_phi", GLKMathDegreesToRadians(45.f));
        program->SetUniform("lights[2].spot_theta", GLKMathDegreesToRadians(30.f));
        program->SetUniform("lights[2].spot_falloff", 1.f);

        program->SetUniform("lights[2].diffuse_color", GLKVector4Make(0.1f, 0.1f, 0.9f, 1.0f));
        program->SetUniform("lights[2].ambient_color", GLKVector4Make(0.0f, 0.0f, 0.1f, 1.0f));
        program->SetUniform("lights[2].specular_color", GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f));
        program->SetUniform("lights[2].specular_shininess", 50.0f);
    }

    GLKMatrix4 modelMat = GLKMatrix4Identity;
    modelMat = GLKMatrix4Translate(modelMat, 0.0f, -2.0f, 0.0f);
    modelMat = GLKMatrix4Scale(modelMat, 20.0f, 20.0f, 20.0f);
    program->SetUniform("model_mat", modelMat);
    GLKMatrix4 pvmMat = GLKMatrix4Multiply(projViewMat, modelMat);
    program->SetUniform("pvm_mat", pvmMat);

    program->SetUniform("emissive_color", GLKVector4Make(0.f, 0.f, 0.4f, 0.f));
    mesh->Draw();

    program->SetUniform("emissive_color", GLKVector4Make(0.f, 0.f, 0.0f, 0.f));
    planeMesh->Draw();
}
```

※[macOSでOpenGLを勝手に勉強する(目次)](https://qiita.com/uechoco@github/items/655806d9252c035f9efc)