OpenGL.Shader:7-光の学習-通常のベクトル
照明はOpenGLの非常に重要な部分を占めています。照明のシミュレーションはコンピューター分野の主要な研究テーマになり、徐々に改善されるゲームビジョンだけでなく、フィルム、コンピューターイメージング(CGI)などの分野にも反映されているこの分野の影響を見ることができます。
通常、タイプに応じて、さまざまな光源を次のグループに分類できます。
周囲光周囲光
はあらゆる方向から来ているようで、シーン内のすべてが同じ程度に照らされています。これは、空などの大きくて等しい光源から得られる光に似ています。周囲光を使用して、光が目に届く前に多くのオブジェクトで跳ね返る効果を架空にすることもできます。周囲光を通して影が描かれることはありません。黒に。
指向性光
は一方向から来ているようで、光源は非常に原始的な場所にあるようです。これは、太陽や月から得られる光に似ています。
ポイントライト
は近くから投影された光のように見え、光の密度は距離とともに減少します。これは、電球やろうそくのように、すべての方向に光を投射する近くの光源を示すのに適しています。
スポットライトはスポットライトに
似ていますが、制限があり、特定の方向にしか投影できない点が異なります。それは私たちが懐中電灯やスポットライトから得るタイプの光のようなものです。
また、オブジェクトの表面での光の放出方法に応じて、次の2つのカテゴリに分類できます。
拡散反射
とは、光が全方向に均等に広がることを指し、カーペットや外部の混合壁など、表面が研磨されていない材料に適しています。これらの同様の表面には、多くの異なる観測点があるようです。
鏡面反射鏡面反射
は特定の方向に強く放射し、滑らかな金属やワックスを塗ったばかりの車など、磨かれたまたは光沢のある素材に適しています。2
これらの光源はOpenGLで直接シミュレートされません。代わりに、ほとんどのゲームやアプリケーションは、直接シミュレートするのではなく、物事を単純化し、光の働きを高レベルで近似します。これにより、もう1つの重要な概念である通常のベクトルが導入されます。
想像してみてください。平らな面が光に面している場合、それは当然最も明るいです。もちろん、光を反射することができるいくつかの材料面があり、反射光の強度は観察する角度に関連していますが、この記事ではそれらを紹介しません。平面の向きを表すために法線ベクトルを使用します。特定の実装では、各ポイントに法線ベクトルがあります。いわゆる法線ベクトルは、下図に示すように、平面に垂直な3次元ベクトルです。
この図は、2つの通常のベクトル表現方法を示しています。左側は各ポリゴンの各ポイントの通常のベクトル、右側は各ポイントの通常のベクトルです。共有ポイントの通常のベクトルはすべてこのポイントです。平面上の法線ベクトルの合計。通常のベクトルは、常に単位ベクトルに正規化する必要があります。この記事の例では、左側の方法が使用されています。
ここでは、使用されているキューブに基づいており、元のキューブ(位置、テクスチャ)データに通常のベクトルを追加しています。
class CubeIlluminate {
public:
struct V3N3T2 {
float x, y, z; //位置坐标
float nx, ny, nz; //法向量
float u,v; //纹理坐标
};
public:
GLuint mCubeSurfaceTexId;
V3N3T2 _data[36];
void init(const CELL::float3 &halfSize, GLuint tex)
{
V3N3 verts[] =
{
{+halfSize.x, -halfSize.y, +halfSize.z, 0.0f, -1.0f, 0.0f, 0.0f,0.0f},
{-halfSize.x, -halfSize.y, +halfSize.z, 0.0f, -1.0f, 0.0f, 1.0f,0.0f},
{-halfSize.x, -halfSize.y, -halfSize.z, 0.0f, -1.0f, 0.0f, 1.0f,1.0f},
{-halfSize.x, -halfSize.y, +halfSize.z, 0.0f, 0.0f, +1.0f, 0.0f,0.0f},
{+halfSize.x, -halfSize.y, +halfSize.z, 0.0f, 0.0f, +1.0f, 1.0f,1.0f},
{+halfSize.x, +halfSize.y, +halfSize.z, 0.0f, 0.0f, +1.0f, 0.0f,1.0f},
{+halfSize.x, +halfSize.y, -halfSize.z, +1.0f, 0.0f, 0.0f, 1.0f,0.0f},
{+halfSize.x, +halfSize.y, +halfSize.z, +1.0f, 0.0f, 0.0f, 1.0f,1.0f},
{+halfSize.x, -halfSize.y, +halfSize.z, +1.0f, 0.0f, 0.0f, 0.0f,1.0f},
{-halfSize.x, +halfSize.y, +halfSize.z, 0.0f, +1.0f, 0.0f, 1.0f,0.0f},
{+halfSize.x, +halfSize.y, +halfSize.z, 0.0f, +1.0f, 0.0f, 0.0f,1.0f},
{+halfSize.x, +halfSize.y, -halfSize.z, 0.0f, +1.0f, 0.0f, 0.0f,0.0f},
{-halfSize.x, +halfSize.y, +halfSize.z, -1.0f, 0.0f, 0.0f, 0.0f,1.0f},
{-halfSize.x, -halfSize.y, -halfSize.z, -1.0f, 0.0f, 0.0f, 0.0f,0.0f},
{-halfSize.x, -halfSize.y, +halfSize.z, -1.0f, 0.0f, 0.0f, 1.0f,0.0f},
{-halfSize.x, +halfSize.y, +halfSize.z, 0.0f, 0.0f, +1.0f, 0.0f,1.0f},
{-halfSize.x, -halfSize.y, +halfSize.z, 0.0f, 0.0f, +1.0f, 1.0f,0.0f},
{+halfSize.x, +halfSize.y, +halfSize.z, 0.0f, 0.0f, +1.0f, 1.0f,1.0f},
{+halfSize.x, -halfSize.y, -halfSize.z, 0.0f, -1.0f, 0.0f, 1.0f,1.0f},
{+halfSize.x, -halfSize.y, +halfSize.z, 0.0f, -1.0f, 0.0f, 0.0f,1.0f},
{-halfSize.x, -halfSize.y, -halfSize.z, 0.0f, -1.0f, 0.0f, 0.0f,0.0f},
{+halfSize.x, -halfSize.y, -halfSize.z, 0.0f, 0.0f, -1.0f, 1.0f,1.0f},
{-halfSize.x, -halfSize.y, -halfSize.z, 0.0f, 0.0f, -1.0f, 0.0f,0.0f},
{+halfSize.x, +halfSize.y, -halfSize.z, 0.0f, 0.0f, -1.0f, 1.0f,0.0f},
{+halfSize.x, -halfSize.y, -halfSize.z, +1.0f, 0.0f, 0.0f, 1.0f,0.0f},
{+halfSize.x, +halfSize.y, -halfSize.z, +1.0f, 0.0f, 0.0f, 1.0f,1.0f},
{+halfSize.x, -halfSize.y, +halfSize.z, +1.0f, 0.0f, 0.0f, 0.0f,1.0f},
{-halfSize.x, +halfSize.y, -halfSize.z, 0.0f, 0.0f, -1.0f, 1.0f,0.0f},
{+halfSize.x, +halfSize.y, -halfSize.z, 0.0f, 0.0f, -1.0f, 0.0f,1.0f},
{-halfSize.x, -halfSize.y, -halfSize.z, 0.0f, 0.0f, -1.0f, 0.0f,0.0f},
{-halfSize.x, +halfSize.y, -halfSize.z, -1.0f, 0.0f, 0.0f, 0.0f,0.0f},
{-halfSize.x, -halfSize.y, -halfSize.z, -1.0f, 0.0f, 0.0f, 1.0f,0.0f},
{-halfSize.x, +halfSize.y, +halfSize.z, -1.0f, 0.0f, 0.0f, 1.0f,1.0f},
{-halfSize.x, +halfSize.y, -halfSize.z, 0.0f, +1.0f, 0.0f, 0.0f,0.0f},
{-halfSize.x, +halfSize.y, +halfSize.z, 0.0f, +1.0f, 0.0f, 1.0f,1.0f},
{+halfSize.x, +halfSize.y, -halfSize.z, 0.0f, +1.0f, 0.0f, 0.0f,1.0f},
};
memcpy(_data, verts, sizeof(verts));
mCubeSurfaceTexId = tex;
}
// ... ...
};
大量のデータを考慮して、カスタム構造V3N3T2は1点のデータ量を表し、V3N3T2の3つのグループごとに三角形の表面を形成し、残りはもはや言葉ではありません。法線ベクトルは位置情報ではなく方向のみを表すため、法線ベクトルは可能な限り正規化されたデータを使用することに注意してください。上記のデータは、図に示すようにデカルト空間に投影されます
まず、左側の4点の点(1、-1、1)を例にとると、赤い点は左側を右に水平に対応する法線ベクトル(1、0、0)
で、同じ点を底面に置き換えます。(1、-1、1)例として、黄色のベクトルは、垂直に下向きの底面に対応する通常のベクトル(0、-1、0)です。
次に、同じ点(1、-1、1)、青色の前面に置き換えます。色は前面に対応する法線ベクトル(0、0、1)で、水平面は外側を向いています。
上記の説明は、理論的知識を再度証明します。1)正規ベクトルは、位置ではなく方向を表します。2)正規ベクトルは、正規化されたベクトル、つまりMath.sqrt(x * x + y * y + z * z)= 1; 3です。)法線ベクトルは表面に基づいて決定され、方向は平面に垂直です。複数の表面の共通点に対して複数の法線ベクトルが存在する可能性があります。
ここでは基本的に通常のベクトルを紹介します。シェーダープログラムグループに直接進みましょう。
class CubeIlluminateProgram : public ShaderProgram
{
public:
GLint _mvp;
GLint _lightDir;
GLint _lightColor;
GLint _lightDiffuse;
GLint _texture;
GLint _position;
GLint _normal;
GLint _uv;
public:
virtual void initialize()
{
const char* vs = "#version 320 es\n\
uniform mat4 _mvp;\n\
uniform vec3 _lightDir;\n\
uniform vec3 _lightColor;\n\
uniform vec3 _lightDiffuse;\n\
in vec3 _position;\n\
in vec3 _normal;\n\
in vec2 _uv;\n\
out vec2 _outUV;\n\
out vec4 _outComposeColor;\n\
void main()\n\
{\n\
_outUV = _uv; \n\
float lightStrength = max(dot(_normal, -_lightDir), 0.0); \n\
_outComposeColor = vec4(_lightColor * lightStrength + _lightDiffuse, 1);\n\
gl_Position = _mvp * vec4(_position,1.0);\n\
}";
const char* fs = "#version 320 es\n\
precision mediump float;\n\
in vec4 _outComposeColor;\n\
in vec2 _outUV;\n\
uniform sampler2D _texture;\n\
out vec4 _fragColor;\n\
void main()\n\
{\n\
vec4 color = texture(_texture,_outUV);\n\
_fragColor = color * _outComposeColor;\n\
}";
programId = ShaderHelper::buildProgram(vs, fs);
_mvp = glGetUniformLocation(programId, "_mvp");
_lightDir = glGetUniformLocation(programId, "_lightDir");
_lightColor = glGetUniformLocation(programId, "_lightColor");
_lightDiffuse = glGetUniformLocation(programId, "_lightDiffuse");
_position = glGetAttribLocation(programId, "_position");
_normal = glGetAttribLocation(programId, "_normal");
_uv = glGetAttribLocation(programId, "_uv");
_texture = glGetUniformLocation(programId, "_texture");
}
virtual void begin()
{
glEnableVertexAttribArray(_position);
glEnableVertexAttribArray(_normal);
glEnableVertexAttribArray(_uv);
glUseProgram(programId);
}
virtual void end()
{
glDisableVertexAttribArray(_position);
glDisableVertexAttribArray(_normal);
glDisableVertexAttribArray(_uv);
glUseProgram(0);
}
};
まず、頂点シェーダープログラムを分析します。
#version 320 es uniform mat4 _mvp; // 模型视图投影矩阵 uniform vec3 _lightDir; // 光源方向 只是一个方向 uniform vec3 _lightColor; // 环境光源颜色 uniform vec3 _lightDiffuse; // 漫反射 模拟材质补光用 in vec3 _position; // 顶点位置属性 in vec3 _normal; // 顶点法向量 in vec2 _uv; // 纹理坐标 out vec2 _outUV; // 输出片元着色器纹理坐标 out vec4 _outComposeColor; // 输出的混合光 void main() { _outUV = _uv; float lightStrength = max(dot(_normal, -_lightDir), 0.0); _outComposeColor = vec4(_lightColor * lightStrength + _lightDiffuse, 1); gl_Position = _mvp * vec4(_position,1.0); }
コードの2行目 光強度=通常ベクトル*逆正規化後の光源ベクトル 。数式から、光強度は正規ベクトルと逆正規化光源ベクトルのドット積です。逆自然化後の光源は何ですか、下の写真を見てください、とても簡単です。
入力正規ベクトルを直接反転するWebチュートリアルもいくつかあります。これは、数学演算の観点からは一貫していますが、理論的に理解するのは簡単ではありません。この方法はお勧めしません。これが事実であることを知り、他の人々の行動のいくつかを理解することは問題ありません。
光強度を取得した後、コードvec4の3行目(_lightColor * lightStrength + _lightDiffuse、1)、光強度*周囲光の色の値は、この時点で実際に照明効果があり、さらに拡散反射_lightDiffuseは光強度のない場所を防ぐためのものです実際の状況と合わないため、完全に黒くなります。拡散反射には、素材の質感に応じて拡散反射の光強度を計算したり、独自の色の値を追加したりするなど、拡張できる内容がたくさんあります。
version 320 es precision mediump float; in vec4 _outComposeColor; in vec2 _outUV; uniform sampler2D _texture; out vec4 _fragColor; void main() { vec4 color = texture(_texture,_outUV); _fragColor = color * _outComposeColor; }
次に、フラグメントシェーダープログラムに移動します。シェーダープログラムは比較的単純で、テクスチャ座標に従ってテクスチャカラー値を抽出し、テクスチャカラー値に基づいて頂点から出力された混合光カラーを乗算します。これで完了です。
最後に、シェーダープログラムと組み合わせて、CubeIlluminateのレンダリング方法を補完します。
void render(Camera3D& camera)
{
sprogram.begin();
CELL::matrix4 matModel(1);
CELL::matrix4 vp = camera.getProject() * camera.getView();
CELL::matrix4 mvp = (vp * matModel);
glUniformMatrix4fv(sprogram._mvp, 1, GL_FALSE, mvp.data());
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, mCubeSurfaceTexId);
glUniform1i(sprogram._texture, 0);
glUniform3f(sprogram._lightDiffuse, 0.1f, 0.1f, 0.1f); // 漫反射 环境光
glUniform3f(sprogram._lightColor, 1.0f, 1.0f, 1.0f); // 定向光源的颜色
glUniform3f(sprogram._lightDir, // 定向光源的方向 直接使用摄像头到观察点的方向
static_cast<GLfloat>(camera._dir.x),
static_cast<GLfloat>(camera._dir.y),
static_cast<GLfloat>(camera._dir.z));
glVertexAttribPointer(static_cast<GLuint>(sprogram._position), 3, GL_FLOAT, GL_FALSE,
sizeof(CubeIlluminate::V3N3), &_data[0].x);
glVertexAttribPointer(static_cast<GLuint>(sprogram._normal), 3, GL_FLOAT, GL_FALSE,
sizeof(CubeIlluminate::V3N3), &_data[0].nx);
glVertexAttribPointer(static_cast<GLuint>(sprogram._uv), 2, GL_FLOAT, GL_FALSE,
sizeof(CubeIlluminate::V3N3), &_data[0].u);
glDrawArrays(GL_TRIANGLES, 0, 36);
sprogram.end();
}
デモプロジェクトリンクhttps://github.com/MrZhaozhirong/NativeCppApp->LightRenderer.cpp CubeIlluminate.hpp CubeIlluminateProgram.hpp
最後に、これが最も単純なライトノーマル効果です。