この方法の特徴は、これらの光源が量または形状によって制限されないということです。
![](https://habrastorage.org/getpro/habr/post_images/549/c23/af4/549c23af47771f8b71ba8567616145ae.gif)
従来、アルゴリズムは2つのコンポーネントに分割できます。2Dオブジェクトの照明と光源の表示形式です。
照明
報道の一部はこの投稿に書かれています。
ネタバレ
同じテクニックを使用しているので、この投稿を見て驚いたこともあります。 もちろん、法線マッピングは長い間知られていましたが、私が知る限り、ごく最近ピクセルアートで使用されるようになりました。これは最初の投稿の1つと言えます。
各ピクセルの照明強度を決定するには、このピクセルの法線と光源への方向ベクトルを知る必要があります。 実際、これは私の投稿が2つの部分に分割されている場所です。(現在のオブジェクトの)ピクセルの法線を取得する場所と、照明の方向ベクトルを計算する方法です。
原則として、現在のオブジェクトのピクセル法線は法線マップから取得されます 。
法線マップはさまざまな方法で取得できます(そのうちの1つは上記の投稿で説明されています)。次のように作成します。
スプライトが描画されます:
![](https://habrastorage.org/getpro/habr/post_images/d90/7d4/c73/d907d4c73a10661d320c9fa9a37caba1.png)
次に、彼のために高さマップが描かれます。 私の場合、スプライト自体は高さマップとして解釈できます。 高さマップとは何か、一般的にはバンプマッピングについては、 こちらをご覧ください 。
高さマップを使用すると、すでに法線マップを作成できます。 これを実行できるユーティリティがいくつかあります。 私はGIMPのプラグインを使用しました(ここにいくつかの優れたプラグインがありますが、標準のリポジトリには多くの問題があるようです)。
![](https://habrastorage.org/getpro/habr/post_images/43d/ef4/e66/43def4e66842f94a3e4237be2cd49881.png)
したがって、3次元オブジェクトの効果を作成する両方のスプライトがあります。 これらの2つのスプライトを使用するシェーダーを考えてみましょう。光源の方向がピクセルの強度を決定します。この段階では、 前の投稿とまったく同じです。
コード
// varying vec4 texCoord; void main(){ gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; texCoord = gl_MultiTexCoord0; } // uniform sampler2D colorMap; uniform sampler2D normalMap; varying vec4 texCoord; uniform vec2 light; uniform vec2 screen; uniform float dist; void main() { vec3 normal = texture2D(normalMap, texCoord.st).rgb; normal = 2.0*normal-1.0; vec3 n = normalize(normal); vec3 l = normalize(vec3((gl_FragCoord.xy-light.xy)/screen, dist)); float a = dot(n, l); gl_FragColor = a*texture2D(colorMap, texCoord.st); }
光源
このテクノロジーは、遅延シェーディングを漠然と連想させます。
主なアイデアは、各ピクセルがフレーム内の対応するピクセルの光度の値を格納する、個別のライティングバッファーを作成することです。 つまり、これは2Dシーンの通常のライトマップです。
ライトマップを作成するには、すべての光源をレンダリングする必要があります。 このアプローチの利点:
- 光源の数は鉄によってのみ制限されます。 たとえば、1000個の光源は1000個のスプライトです。 1000個のスプライトをレンダリングすることは、モバイルGPUでも難しくありません。2Dシーンに1000個のソースが必要ですか?
- 光源は異なる色と異なる透明度を持つことができます-これは一般的なテクスチャであるため
- 光源の形状は任意です
ここで、たとえば、溶岩のあるシーンのライトマップ:
![](https://habrastorage.org/getpro/habr/post_images/e0f/855/315/e0f8553158ef141e46a5dadd8b284c88.png)
これは新しい照明技術ではなく、マイナス(方向ベクトルの欠如)があります。 ただし、このベクトルを決定するアルゴリズムを考え出すことができます。
最初に、光源とは何か、その特性は何かを判断しましょう。 物理学の教科書から複雑な公式や引用を出さない-これはすべて退屈で面白くない。 私は母に説明するように説明しようとします。
そのため、光線が遠くに出るほど、強度が弱まります。 この観測を使用して、光線の方向ベクトルを決定できます。 つまり、2つの隣接するピクセルがあり、最初のピクセルで光の値が0.5で、2番目のピクセルで0.25の場合、光線のベクトルは最初のピクセルから2番目のピクセルに向けられていると結論付けることができます。
この場合、照明ベクトルを計算する簡単な式は次のようになります。
v [cx] [cy] .x = p [cx] [cy] .x-p [cx + 1] [cy] .x
v [cx] [cy] .y = p [cx] [cy] .y-p [cx] [cy + 1] .y
ここで、cx、cyは問題のピクセルの座標です
ただし、2つの隣接するピクセルの差は非常に小さい可能性があるため、ベクトルの長さも小さく、正確ではない可能性があります。 この問題に対する2つの解決策を見つけました。結果に特定の係数を掛けるか、ピクセルを1ピクセル以上離すことです。 2番目のケースでは、照明の細部を犠牲にします。 最終的に、これらのメソッドの両方を組み合わせて、最終的な式は次のようになります。
v [cx] [cy] .x =(p [cx-d / 2] [cy] .x-p [cx + d / 2] [cy] .x)* k
v [cx] [cy] .y =(p [cx] [cy-d / 2] .y-p [cx] [cy + d / 2] .y)* k
ここで、kは光の方向ベクトルのゲイン、dは方向ベクトルが考慮される基準となるピクセル間の距離です。
これらの新しい値は、個別のライティング法線マップに書き込まれるか、ライトマップを使用して結果フレームのレンダリング中にオンザフライで計算されます。 2番目のオプションを選択しました。
シェーダー
// varying vec4 texCoord; varying vec4 nmTexCoord; varying vec2 lightMapTexCoord; // varying vec2 lightMapTexCoordX1; // varying vec2 lightMapTexCoordX2; // varying vec2 lightMapTexCoordY1; // varying vec2 lightMapTexCoordY2; // //, , . uniform vec2 fieldSize; // const float spriteSize = 16.0; // void main() { gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; texCoord = gl_MultiTexCoord0; nmTexCoord = gl_MultiTexCoord1; // . lightMapTexCoordX1 = vec2(gl_Vertex.x/(fieldSize.x-1.0/spriteSize), gl_Vertex.y/fieldSize.y); lightMapTexCoordX2 = vec2(gl_Vertex.x/(fieldSize.x+1.0/spriteSize), gl_Vertex.y/fieldSize.y); lightMapTexCoordY1 = vec2(gl_Vertex.x/fieldSize.x, gl_Vertex.y/(fieldSize.y-1.0/spriteSize)); lightMapTexCoordY2 = vec2(gl_Vertex.x/fieldSize.x, gl_Vertex.y/(fieldSize.y+1.0/spriteSize)); lightMapTexCoord = vec2(gl_Vertex.x/fieldSize.x, gl_Vertex.y/fieldSize.y); } //--------------------------------------------------------------------------------------------------------------- // varying vec4 texCoord; varying vec4 nmTexCoord; varying vec2 lightMapTexCoord; varying vec2 lightMapTexCoordX1; varying vec2 lightMapTexCoordX2; varying vec2 lightMapTexCoordY1; varying vec2 lightMapTexCoordY2; uniform sampler2D colorMap; // uniform sampler2D lightMap; uniform float ambientIntensity; // uniform float lightIntensity; // const float shadowIntensity = 8.0; // const vec3 av = vec3(0.33333); // void main() { vec4 lmc = texture2D(lightMap, lightMapTexCoord)*2,0; // . , 0.5, 1.0 (). , , . , . // x y - float x = (dot(texture2D(lightMap, lightMapTexCoordX1).rgb, av)- dot(texture2D(lightMap, lightMapTexCoordX2).rgb, av))*shadowIntensity; float y = (dot(texture2D(lightMap, lightMapTexCoordY2).rgb, av)- dot(texture2D(lightMap, lightMapTexCoordY1).rgb, av))*shadowIntensity; float br = dot(lmc.rgb, av); // - vec3 l = vec3(x, y, br); // , z , , , l = normalize(l)*br; // vec3 normal = 2.0*texture2D(colorMap, nmTexCoord.st).rgb-1.0; float a = dot(normal, l)*lightIntensity; a = max(a, 0.0); vec4 c = texture2D(colorMap, texCoord.st); c = a*min(c, lmc)+ambientIntensity*c; // float m = 0.0; // , , (. gif ). . m = max(m, cr); m = max(m, cg); m = max(m, cb); gl_FragColor = c+max(0.0, m-1.0); // . }
効果を示すビデオ: 光源は任意の形状のスプライトであり 、 各溶岩粒子は光源です 。