第 19 章 - ディファード シェーディング
これまで、3D シーンをレンダリングする方法はフォワード レンダリングと呼ばれていました。最初に 3D オブジェクトをレンダリングし、フラグメント シェーダーでテクスチャと照明効果を適用します。この方法は、多数のライトと複雑なエフェクトを含む複雑なフラグメント シェーダー パスがある場合、あまり効率的ではありません。それに加えて、これらの効果を後で深度テストのために破棄される可能性のあるフラグメントに適用することになる場合があります (ただし、初期のフラグメント テストを有効にした場合、これは正確には当てはまりません)。
上記の問題を軽減するために、ディファード シェーディングと呼ばれる技術を使用してシーンをレンダリングする方法を変更することがあります。ディファード シェーディングでは、後の段階で (フラグメント シェーダーで) 必要となるジオメトリ情報を最初にバッファーにレンダリングします。フラグメント シェーダーに必要な複雑な計算は、これらのバッファーに格納されている情報を使用する際に、後の段階に延期されます。
コンセプト
Deferred では、2 つのレンダリング パスを実行する必要があります。1 つ目はジオメトリ パスで、次の情報を含むバッファにシーンをレンダリングします。
・深さの値。
・各位置の拡散色と反射係数。
・各位置のスペキュラー コンポーネント。
・各位置の法線 (ライト ビュー座標系でも)。
そのすべての情報は、G-Buffer と呼ばれるバッファーに格納されます。
2 番目のパスは、ライティング パスと呼ばれます。このパスは、すべての画面を埋めるクワッドを取得し、G バッファーに含まれる情報を使用して各フラグメントの色情報を生成します。ライティング パスを実行するとき、深度テストでは、表示されないすべてのシーン データが既に削除されています。したがって、実行する操作の数は、画面に表示されるものに制限されます。
追加のレンダリング パスを実行するとパフォーマンスが向上するかどうかを尋ねられる場合があります。答えは、場合によるということです。ディファード シェーディングは通常、多数の異なるライト パスがある場合に使用されます。この場合、追加のレンダリング手順は、フラグメント シェーダーで実行される操作の削減によって補われます。
G-バッファ
それでは、コーディングを始めましょう。最初に行う作業は、G-Buffer の新しいクラスを作成することです。という名前のクラスは、GBuffer次のように定義されます。
package org.lwjglb.engine.graph;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryStack;
import org.lwjglb.engine.Window;
import java.nio.*;
import java.util.Arrays;
import static org.lwjgl.opengl.GL30.*;
public class GBuffer {
private static final int TOTAL_TEXTURES = 4;
private int gBufferId;
private int height;
private int[] textureIds;
private int width;
...
}
このクラスは、使用されるバッファーの最大数をモデル化する定数を定義します。G バッファー自体に関連付けられた識別子と、個々のバッファーの配列。テクスチャのサイズも保存されます。
コンストラクターを確認しましょう。
public class GBuffer {
...
public GBuffer(Window window) {
gBufferId = glGenFramebuffers();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gBufferId);
textureIds = new int[TOTAL_TEXTURES];
glGenTextures(textureIds);
this.width = window.getWidth();
this.height = window.getHeight();
for (int i = 0; i < TOTAL_TEXTURES; i++) {
glBindTexture(GL_TEXTURE_2D, textureIds[i]);
int attachmentType;
if (i == TOTAL_TEXTURES - 1) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
(ByteBuffer) null);
attachmentType = GL_DEPTH_ATTACHMENT;
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, (ByteBuffer) null);
attachmentType = GL_COLOR_ATTACHMENT0 + i;
}
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentType, GL_TEXTURE_2D, textureIds[i], 0);
}
try (MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer intBuff = stack.mallocInt(TOTAL_TEXTURES);
for (int i = 0; i < TOTAL_TEXTURES; i++) {
intBuff.put(i, GL_COLOR_ATTACHMENT0 + i);
}
glDrawBuffers(intBuff);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
...
}
最初に行うことは、フレーム バッファーを作成することです。フレーム バッファは、画面にレンダリングする代わりに操作をレンダリングするために使用できる単なる OpenGL オブジェクトであることに注意してください。次に、フレーム バッファに関連付けられる一連のテクスチャ (4 つのテクスチャ) を生成します。
その後、for ループを使用してテクスチャを初期化します。次のタイプがあります。
・位置、法線、拡散コンポーネントなどを格納する「通常のテクスチャ」。
・深度バッファを格納するためのテクスチャ。これが最後のテクスチャになります。
テクスチャが初期化されたら、テクスチャのサンプリングを有効にして、フレーム バッファにアタッチします。各テクスチャは、 で始まる識別子を使用してアタッチされGL_COLOR_ATTACHMENT0ます。各テクスチャはその id によって 1 ずつ増加するため、位置は を使用してアタッチされGL_COLOR_ATTACHMENT0、拡散コンポーネントはGL_COLOR_ATTACHMENT1( GL_COLOR_ATTACHMENT0 + 1) を使用するなどです。
すべてのテクスチャが作成されたら、レンダリングのためにフラグメント シェーダで使用できるようにする必要があります。これはglDrawBuffers呼び出しで行われます。使用するカラー アタッチメントの識別子を含む配列を渡すだけです (GL_COLOR_ATTACHMENT0にGL_COLOR_ATTACHMENT5)。
クラスの残りの部分は、getter メソッドとクリーンアップ メソッドだけです。
public class GBuffer {
...
public void cleanUp() {
glDeleteFramebuffers(gBufferId);
Arrays.stream(textureIds).forEach(GL30::glDeleteTextures);
}
public int getGBufferId() {
return gBufferId;
}
public int getHeight() {
return height;
}
public int[] getTextureIds() {
return textureIds;
}
public int getWidth() {
return width;
}
}
ジオメトリ パス
ジオメトリ パスを実行するときに適用する必要がある変更を調べてみましょう。これらの変更をSceneRenderクラスと関連するシェーダーに適用します。クラスをSceneRender見て、ライト定数とライト ユニフォームを削除する必要があります。これらはこのパスで使用されることに注意してください (単純化するためにマテリアルにアンビエント カラーも使用しません。そのユニフォームも削除する必要があり、選択したものも削除します)。実体のユニフォーム):
public class SceneRender {
private ShaderProgram shaderProgram;
private UniformsMap uniformsMap;
...
private void createUniforms() {
uniformsMap = new UniformsMap(shaderProgram.getProgramId());
uniformsMap.createUniform("projectionMatrix");
uniformsMap.createUniform("modelMatrix");
uniformsMap.createUniform("viewMatrix");
uniformsMap.createUniform("bonesMatrices");
uniformsMap.createUniform("txtSampler");
uniformsMap.createUniform("normalSampler");
uniformsMap.createUniform("material.diffuse");
uniformsMap.createUniform("material.specular");
uniformsMap.createUniform("material.reflectance");
uniformsMap.createUniform("material.hasNormalMap");
}
...
}
メソッドは次のrenderように定義されます。
public class SceneRender {
...
public void render(Scene scene, GBuffer gBuffer) {
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gBuffer.getGBufferId());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, gBuffer.getWidth(), gBuffer.getHeight());
glDisable(GL_BLEND);
shaderProgram.bind();
uniformsMap.setUniform("projectionMatrix", scene.getProjection().getProjMatrix());
uniformsMap.setUniform("viewMatrix", scene.getCamera().getViewMatrix());
uniformsMap.setUniform("txtSampler", 0);
uniformsMap.setUniform("normalSampler", 1);
Collection<Model> models = scene.getModelMap().values();
TextureCache textureCache = scene.getTextureCache();
for (Model model : models) {
List<Entity> entities = model.getEntitiesList();
for (Material material : model.getMaterialList()) {
uniformsMap.setUniform("material.diffuse", material.getDiffuseColor());
uniformsMap.setUniform("material.specular", material.getSpecularColor());
uniformsMap.setUniform("material.reflectance", material.getReflectance());
String normalMapPath = material.getNormalMapPath();
boolean hasNormalMapPath = normalMapPath != null;
uniformsMap.setUniform("material.hasNormalMap", hasNormalMapPath ? 1 : 0);
Texture texture = textureCache.getTexture(material.getTexturePath());
glActiveTexture(GL_TEXTURE0);
texture.bind();
if (hasNormalMapPath) {
Texture normalMapTexture = textureCache.getTexture(normalMapPath);
glActiveTexture(GL_TEXTURE1);
normalMapTexture.bind();
}
for (Mesh mesh : material.getMeshList()) {
glBindVertexArray(mesh.getVaoId());
for (Entity entity : entities) {
uniformsMap.setUniform("modelMatrix", entity.getModelMatrix());
AnimationData animationData = entity.getAnimationData();
if (animationData == null) {
uniformsMap.setUniform("bonesMatrices", AnimationData.DEFAULT_BONES_MATRICES);
} else {
uniformsMap.setUniform("bonesMatrices", animationData.getCurrentFrame().boneMatrices());
}
glDrawElements(GL_TRIANGLES, mesh.getNumVertices(), GL_UNSIGNED_INT, 0);
}
}
}
}
glBindVertexArray(0);
glEnable(GL_BLEND);
shaderProgram.unbind();
}
}
GBufferメソッドのパラメーターとしてインスタンスを受け取っていることがわかります。そのバッファはレンダリングを実行する場所であるため、最初に を呼び出してそのバッファをバインドしglBindFramebufferます。その後、そのバッファをクリアしてブレンドを無効にします。遅延レンダリングを使用する場合、透明なオブジェクトは少し注意が必要です。アプローチは、それらをライト パスでレンダリングするか、ジオメトリ パスで破棄することです。ご覧のとおり、ライトの統一設定コードをすべて削除しました。
頂点シェーダーの唯一の変更点 ( scene.vert) は、ビューの位置が 4 つのコンポーネントのベクターになったことです ( vec4)。
#version 330
...
out vec4 outViewPosition;
...
void main()
{
...
outWorldPosition = modelMatrix * initPos;
outViewPosition = viewMatrix * outWorldPosition;
gl_Position = projectionMatrix * outViewPosition;
outNormal = normalize(modelViewMatrix * initNormal).xyz;
outTangent = normalize(modelViewMatrix * initTangent).xyz;
outBitangent = normalize(modelViewMatrix * initBitangent).xyz;
outTextCoord = texCoord;
}
フラグメント シェーダー ( scene.frag) は大幅に簡素化されています。
#version 330
in vec3 outNormal;
in vec3 outTangent;
in vec3 outBitangent;
in vec2 outTextCoord;
in vec4 outViewPosition;
in vec4 outWorldPosition;
layout (location = 0) out vec4 buffAlbedo;
layout (location = 1) out vec4 buffNormal;
layout (location = 2) out vec4 buffSpecular;
struct Material
{
vec4 diffuse;
vec4 specular;
float reflectance;
int hasNormalMap;
};
uniform sampler2D txtSampler;
uniform sampler2D normalSampler;
uniform Material material;
vec3 calcNormal(vec3 normal, vec3 tangent, vec3 bitangent, vec2 textCoords) {
mat3 TBN = mat3(tangent, bitangent, normal);
vec3 newNormal = texture(normalSampler, textCoords).rgb;
newNormal = normalize(newNormal * 2.0 - 1.0);
newNormal = normalize(TBN * newNormal);
return newNormal;
}
void main() {
vec4 text_color = texture(txtSampler, outTextCoord);
vec4 diffuse = text_color + material.diffuse;
if (diffuse.a < 0.5) {
discard;
}
vec4 specular = text_color + material.specular;
vec3 normal = outNormal;
if (material.hasNormalMap > 0) {
normal = calcNormal(outNormal, outTangent, outBitangent, outTextCoord);
}
buffAlbedo = vec4(diffuse.xyz, material.reflectance);
buffNormal = vec4(0.5 * normal + 0.5, 1.0);
buffSpecular = specular;
}
最も関連性の高い行は次のとおりです。
...
layout (location = 0) out vec4 buffAlbedo;
layout (location = 1) out vec4 buffNormal;
layout (location = 2) out vec4 buffSpecular;
...
これは、このフラグメント シェーダーが書き込むテクスチャを参照している場所です。ご覧のとおり、拡散反射光カラー (マテリアルのコンポーネントに関連付けられたテクスチャのカラー)、スペキュラー コンポーネント、法線、およびシャドウ マップの深度値をダンプするだけです。テクスチャに位置を保存していないことに気付くかもしれません。これは、深度値を使用してフラグメントの位置を再構築できるためです。ライティング パスでこれを行う方法について説明します。
補足:Materialアンビエント カラー コンポーネントを削除して、クラス定義を簡略化しました。
OpenGL デバッガー (RenderDoc など) を使用してサンプルの実行をデバッグすると、ジオメトリ パス中に生成されたテクスチャを表示できます。アルベド テクスチャは次のようになります。
法線の値を保持するテクスチャは次のようになります。
スペキュラ カラーの値を保持するテクスチャは次のようになります。
最後に、深度テクスチャは次のようになります。
照明パス
LightsRenderライティング パスを実行するために、次のように始まる名前の新しいクラスを作成します。
package org.lwjglb.engine.graph;
import org.joml.*;
import org.lwjglb.engine.scene.*;
import org.lwjglb.engine.scene.lights.*;
import java.util.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL14.*;
import static org.lwjgl.opengl.GL30.*;
public class LightsRender {
private static final int MAX_POINT_LIGHTS = 5;
private static final int MAX_SPOT_LIGHTS = 5;
private final ShaderProgram shaderProgram;
private QuadMesh quadMesh;
private UniformsMap uniformsMap;
public LightsRender() {
List<ShaderProgram.ShaderModuleData> shaderModuleDataList = new ArrayList<>();
shaderModuleDataList.add(new ShaderProgram.ShaderModuleData("resources/shaders/lights.vert", GL_VERTEX_SHADER));
shaderModuleDataList.add(new ShaderProgram.ShaderModuleData("resources/shaders/lights.frag", GL_FRAGMENT_SHADER));
shaderProgram = new ShaderProgram(shaderModuleDataList);
quadMesh = new QuadMesh();
createUniforms();
}
public void cleanup() {
quadMesh.cleanup();
shaderProgram.cleanup();
}
...
}
新しいシェーダー プログラムの作成に加えて、QadMeshクラスの新しい属性 (まだ定義されていません) を定義していることがわかります。render メソッドを分析する前に、ライトをどのようにレンダリングするかについて少し考えてみましょう。G-Buffer の内容を使用する必要がありますが、それらを使用するには、まず何かをレンダリングする必要があります。しかし、私たちはすでにシーンを描いているので、何をレンダリングしようとしています. 今?答えは簡単です。すべての画面を満たすクワッドをレンダリングするだけです。そのクワッドの各フラグメントに対して、G バッファーに含まれるデータを使用して、正しい出力カラーを生成します。ここでQuadMeshクラスが機能します。ライティング パスでレンダリングするために使用されるクワッドを定義するだけで、次のように定義されます。
package org.lwjglb.engine.graph;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.*;
import java.nio.*;
import java.util.*;
import static org.lwjgl.opengl.GL30.*;
public class QuadMesh {
private int numVertices;
private int vaoId;
private List<Integer> vboIdList;
public QuadMesh() {
try (MemoryStack stack = MemoryStack.stackPush()) {
vboIdList = new ArrayList<>();
float[] positions = new float[]{
-1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,};
float[] textCoords = new float[]{
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,};
int[] indices = new int[]{0, 2, 1, 1, 2, 3};
numVertices = indices.length;
vaoId = glGenVertexArrays();
glBindVertexArray(vaoId);
// Positions VBO
int vboId = glGenBuffers();
vboIdList.add(vboId);
FloatBuffer positionsBuffer = stack.callocFloat(positions.length);
positionsBuffer.put(0, positions);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, positionsBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
// Texture coordinates VBO
vboId = glGenBuffers();
vboIdList.add(vboId);
FloatBuffer textCoordsBuffer = MemoryUtil.memAllocFloat(textCoords.length);
textCoordsBuffer.put(0, textCoords);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, textCoordsBuffer, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
// Index VBO
vboId = glGenBuffers();
vboIdList.add(vboId);
IntBuffer indicesBuffer = stack.callocInt(indices.length);
indicesBuffer.put(0, indices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
}
public void cleanup() {
vboIdList.stream().forEach(GL30::glDeleteBuffers);
glDeleteVertexArrays(vaoId);
}
public int getNumVertices() {
return numVertices;
}
public int getVaoId() {
return vaoId;
}
}
ご覧のとおり、必要なのは位置とテクスチャの座標属性だけです (G-Buffer テクスチャに適切にアクセスするため)。クラスに戻るとLightsRender、ユニフォームを作成するメソッドが必要です。これにより、以前にクラスで使用されていたライト ユニフォームに加えて、G バッファ テクスチャ ( 、、および)SceneRenderをマップするための新しいユニフォームのセットが復元されます。それに加えて、やなどの深度値からフラグメント位置を計算するための新しいユニフォームが必要になります。シェーダー コードで、それらがどのように使用されるかを確認します。albedoSamplernormalSamplerspecularSamplerdepthSamplerinvProjectionMatrixinvViewMatrix
public class LightsRender {
...
private void createUniforms() {
uniformsMap = new UniformsMap(shaderProgram.getProgramId());
uniformsMap.createUniform("albedoSampler");
uniformsMap.createUniform("normalSampler");
uniformsMap.createUniform("specularSampler");
uniformsMap.createUniform("depthSampler");
uniformsMap.createUniform("invProjectionMatrix");
uniformsMap.createUniform("invViewMatrix");
uniformsMap.createUniform("ambientLight.factor");
uniformsMap.createUniform("ambientLight.color");
for (int i = 0; i < MAX_POINT_LIGHTS; i++) {
String name = "pointLights[" + i + "]";
uniformsMap.createUniform(name + ".position");
uniformsMap.createUniform(name + ".color");
uniformsMap.createUniform(name + ".intensity");
uniformsMap.createUniform(name + ".att.constant");
uniformsMap.createUniform(name + ".att.linear");
uniformsMap.createUniform(name + ".att.exponent");
}
for (int i = 0; i < MAX_SPOT_LIGHTS; i++) {
String name = "spotLights[" + i + "]";
uniformsMap.createUniform(name + ".pl.position");
uniformsMap.createUniform(name + ".pl.color");
uniformsMap.createUniform(name + ".pl.intensity");
uniformsMap.createUniform(name + ".pl.att.constant");
uniformsMap.createUniform(name + ".pl.att.linear");
uniformsMap.createUniform(name + ".pl.att.exponent");
uniformsMap.createUniform(name + ".conedir");
uniformsMap.createUniform(name + ".cutoff");
}
uniformsMap.createUniform("dirLight.color");
uniformsMap.createUniform("dirLight.direction");
uniformsMap.createUniform("dirLight.intensity");
uniformsMap.createUniform("fog.activeFog");
uniformsMap.createUniform("fog.color");
uniformsMap.createUniform("fog.density");
for (int i = 0; i < CascadeShadow.SHADOW_MAP_CASCADE_COUNT; i++) {
uniformsMap.createUniform("shadowMap_" + i);
uniformsMap.createUniform("cascadeshadows[" + i + "]" + ".projViewMatrix");
uniformsMap.createUniform("cascadeshadows[" + i + "]" + ".splitDistance");
}
}
...
}
メソッドは次のrenderように定義されます。
public class LightsRender {
...
public void render(Scene scene, ShadowRender shadowRender, GBuffer gBuffer) {
shaderProgram.bind();
updateLights(scene);
// Bind the G-Buffer textures
int[] textureIds = gBuffer.getTextureIds();
int numTextures = textureIds != null ? textureIds.length : 0;
for (int i = 0; i < numTextures; i++) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, textureIds[i]);
}
uniformsMap.setUniform("albedoSampler", 0);
uniformsMap.setUniform("normalSampler", 1);
uniformsMap.setUniform("specularSampler", 2);
uniformsMap.setUniform("depthSampler", 3);
Fog fog = scene.getFog();
uniformsMap.setUniform("fog.activeFog", fog.isActive() ? 1 : 0);
uniformsMap.setUniform("fog.color", fog.getColor());
uniformsMap.setUniform("fog.density", fog.getDensity());
int start = 4;
List<CascadeShadow> cascadeShadows = shadowRender.getCascadeShadows();
for (int i = 0; i < CascadeShadow.SHADOW_MAP_CASCADE_COUNT; i++) {
glActiveTexture(GL_TEXTURE0 + start + i);
uniformsMap.setUniform("shadowMap_" + i, start + i);
CascadeShadow cascadeShadow = cascadeShadows.get(i);
uniformsMap.setUniform("cascadeshadows[" + i + "]" + ".projViewMatrix", cascadeShadow.getProjViewMatrix());
uniformsMap.setUniform("cascadeshadows[" + i + "]" + ".splitDistance", cascadeShadow.getSplitDistance());
}
shadowRender.getShadowBuffer().bindTextures(GL_TEXTURE0 + start);
uniformsMap.setUniform("invProjectionMatrix", scene.getProjection().getInvProjMatrix());
uniformsMap.setUniform("invViewMatrix", scene.getCamera().getInvViewMatrix());
glBindVertexArray(quadMesh.getVaoId());
glDrawElements(GL_TRIANGLES, quadMesh.getNumVertices(), GL_UNSIGNED_INT, 0);
shaderProgram.unbind();
}
...
}
ライトを更新した後、ジオメトリ パスの結果を保持するテクスチャをアクティブにします。その後、フォグとカスケード シャドウのユニフォームを設定し、クワッドだけを描画します。
では、ライト パスの頂点シェーダーはどのように見えるのでしょうか ( lights.vert)?
#version 330
layout (location=0) in vec3 inPos;
layout (location=1) in vec2 inCoord;
out vec2 outTextCoord;
void main()
{
outTextCoord = inCoord;
gl_Position = vec4(inPos, 1.0f);
}
上記のコードは、頂点を直接ダンプし、テクスチャ座標をフラグメント シェーダーに渡すだけです。フラグメント シェーダー ( lights.frag) は次のように定義されます。
#version 330
const int MAX_POINT_LIGHTS = 5;
const int MAX_SPOT_LIGHTS = 5;
const float SPECULAR_POWER = 10;
const int NUM_CASCADES = 3;
const float BIAS = 0.0005;
const float SHADOW_FACTOR = 0.25;
in vec2 outTextCoord;
out vec4 fragColor;
struct Attenuation
{
float constant;
float linear;
float exponent;
};
struct AmbientLight
{
float factor;
vec3 color;
};
struct PointLight {
vec3 position;
vec3 color;
float intensity;
Attenuation att;
};
struct SpotLight
{
PointLight pl;
vec3 conedir;
float cutoff;
};
struct DirLight
{
vec3 color;
vec3 direction;
float intensity;
};
struct Fog
{
int activeFog;
vec3 color;
float density;
};
struct CascadeShadow {
mat4 projViewMatrix;
float splitDistance;
};
uniform sampler2D albedoSampler;
uniform sampler2D normalSampler;
uniform sampler2D specularSampler;
uniform sampler2D depthSampler;
uniform mat4 invProjectionMatrix;
uniform mat4 invViewMatrix;
uniform AmbientLight ambientLight;
uniform PointLight pointLights[MAX_POINT_LIGHTS];
uniform SpotLight spotLights[MAX_SPOT_LIGHTS];
uniform DirLight dirLight;
uniform Fog fog;
uniform CascadeShadow cascadeshadows[NUM_CASCADES];
uniform sampler2D shadowMap_0;
uniform sampler2D shadowMap_1;
uniform sampler2D shadowMap_2;
vec4 calcAmbient(AmbientLight ambientLight, vec4 ambient) {
return vec4(ambientLight.factor * ambientLight.color, 1) * ambient;
}
vec4 calcLightColor(vec4 diffuse, vec4 specular, float reflectance, vec3 lightColor, float light_intensity, vec3 position, vec3 to_light_dir, vec3 normal) {
vec4 diffuseColor = vec4(0, 0, 0, 1);
vec4 specColor = vec4(0, 0, 0, 1);
// Diffuse Light
float diffuseFactor = max(dot(normal, to_light_dir), 0.0);
diffuseColor = diffuse * vec4(lightColor, 1.0) * light_intensity * diffuseFactor;
// Specular Light
vec3 camera_direction = normalize(-position);
vec3 from_light_dir = -to_light_dir;
vec3 reflected_light = normalize(reflect(from_light_dir, normal));
float specularFactor = max(dot(camera_direction, reflected_light), 0.0);
specularFactor = pow(specularFactor, SPECULAR_POWER);
specColor = specular * light_intensity * specularFactor * reflectance * vec4(lightColor, 1.0);
return (diffuseColor + specColor);
}
vec4 calcPointLight(vec4 diffuse, vec4 specular, float reflectance, PointLight light, vec3 position, vec3 normal) {
vec3 light_direction = light.position - position;
vec3 to_light_dir = normalize(light_direction);
vec4 light_color = calcLightColor(diffuse, specular, reflectance, light.color, light.intensity, position, to_light_dir, normal);
// Apply Attenuation
float distance = length(light_direction);
float attenuationInv = light.att.constant + light.att.linear * distance +
light.att.exponent * distance * distance;
return light_color / attenuationInv;
}
vec4 calcSpotLight(vec4 diffuse, vec4 specular, float reflectance, SpotLight light, vec3 position, vec3 normal) {
vec3 light_direction = light.pl.position - position;
vec3 to_light_dir = normalize(light_direction);
vec3 from_light_dir = -to_light_dir;
float spot_alfa = dot(from_light_dir, normalize(light.conedir));
vec4 color = vec4(0, 0, 0, 0);
if (spot_alfa > light.cutoff)
{
color = calcPointLight(diffuse, specular, reflectance, light.pl, position, normal);
color *= (1.0 - (1.0 - spot_alfa)/(1.0 - light.cutoff));
}
return color;
}
vec4 calcDirLight(vec4 diffuse, vec4 specular, float reflectance, DirLight light, vec3 position, vec3 normal) {
return calcLightColor(diffuse, specular, reflectance, light.color, light.intensity, position, normalize(light.direction), normal);
}
vec4 calcFog(vec3 pos, vec4 color, Fog fog, vec3 ambientLight, DirLight dirLight) {
vec3 fogColor = fog.color * (ambientLight + dirLight.color * dirLight.intensity);
float distance = length(pos);
float fogFactor = 1.0 / exp((distance * fog.density) * (distance * fog.density));
fogFactor = clamp(fogFactor, 0.0, 1.0);
vec3 resultColor = mix(fogColor, color.xyz, fogFactor);
return vec4(resultColor.xyz, color.w);
}
float textureProj(vec4 shadowCoord, vec2 offset, int idx) {
float shadow = 1.0;
if (shadowCoord.z > -1.0 && shadowCoord.z < 1.0) {
float dist = 0.0;
if (idx == 0) {
dist = texture(shadowMap_0, vec2(shadowCoord.xy + offset)).r;
} else if (idx == 1) {
dist = texture(shadowMap_1, vec2(shadowCoord.xy + offset)).r;
} else {
dist = texture(shadowMap_2, vec2(shadowCoord.xy + offset)).r;
}
if (shadowCoord.w > 0 && dist < shadowCoord.z - BIAS) {
shadow = SHADOW_FACTOR;
}
}
return shadow;
}
float calcShadow(vec4 worldPosition, int idx) {
vec4 shadowMapPosition = cascadeshadows[idx].projViewMatrix * worldPosition;
float shadow = 1.0;
vec4 shadowCoord = (shadowMapPosition / shadowMapPosition.w) * 0.5 + 0.5;
shadow = textureProj(shadowCoord, vec2(0, 0), idx);
return shadow;
}
void main()
{
vec4 albedoSamplerValue = texture(albedoSampler, outTextCoord);
vec3 albedo = albedoSamplerValue.rgb;
vec4 diffuse = vec4(albedo, 1);
float reflectance = albedoSamplerValue.a;
vec3 normal = normalize(2.0 * texture(normalSampler, outTextCoord).rgb - 1.0);
vec4 specular = texture(specularSampler, outTextCoord);
// Retrieve position from depth
float depth = texture(depthSampler, outTextCoord).x * 2.0 - 1.0;
if (depth == 1) {
discard;
}
vec4 clip = vec4(outTextCoord.x * 2.0 - 1.0, outTextCoord.y * 2.0 - 1.0, depth, 1.0);
vec4 view_w = invProjectionMatrix * clip;
vec3 view_pos = view_w.xyz / view_w.w;
vec4 world_pos = invViewMatrix * vec4(view_pos, 1);
vec4 diffuseSpecularComp = calcDirLight(diffuse, specular, reflectance, dirLight, view_pos, normal);
int cascadeIndex;
for (int i=0; i<NUM_CASCADES - 1; i++) {
if (view_pos.z < cascadeshadows[i].splitDistance) {
cascadeIndex = i + 1;
break;
}
}
float shadowFactor = calcShadow(world_pos, cascadeIndex);
for (int i=0; i<MAX_POINT_LIGHTS; i++) {
if (pointLights[i].intensity > 0) {
diffuseSpecularComp += calcPointLight(diffuse, specular, reflectance, pointLights[i], view_pos, normal);
}
}
for (int i=0; i<MAX_SPOT_LIGHTS; i++) {
if (spotLights[i].pl.intensity > 0) {
diffuseSpecularComp += calcSpotLight(diffuse, specular, reflectance, spotLights[i], view_pos, normal);
}
}
vec4 ambient = calcAmbient(ambientLight, diffuse);
fragColor = ambient + diffuseSpecularComp;
fragColor.rgb = fragColor.rgb * shadowFactor;
if (fog.activeFog == 1) {
fragColor = calcFog(view_pos, fragColor, fog, ambientLight.color, dirLight);
}
}
ご覧のとおり、見慣れた機能が含まれています。これらは、シーン フラグメント シェーダーの前の章で使用されていました。ここで注意すべき重要な点は、次の行です。
uniform sampler2D albedoSampler;
uniform sampler2D normalSampler;
uniform sampler2D specularSampler;
uniform sampler2D depthSampler;
最初に、現在のフラグメント座標に従って、アルベド、法線マップ ([0, -1] から [-1, 1] の範囲に変換)、およびスペキュラー アタッチメントをサンプリングします。それに加えて、見慣れないコード フラグメントがあります。光の計算を実行するには、フラグメントの位置が必要です。ただし、役職のアタッチメントはありません。ここで、深度アタッチメントと逆投影行列が機能します。その情報を使用して、位置を保存する別のアタッチメントを必要とせずに、世界の位置 (ビュー空間座標) を再構築できます。他のチュートリアルでは、位置に特定のアタッチメントを設定していることがわかりますが、この方法で行う方がはるかに効率的です。遅延アタッチメントによって消費されるメモリが少ないほど良いことを常に覚えておいてください。
コードの残りの部分は、シーン レンダリングのフラグメント シェーダーのものと非常によく似ています。
最後にRender、新しいクラスを使用するようにクラスを更新する必要があります。
public class Render {
...
private GBuffer gBuffer;
...
private LightsRender lightsRender;
...
public Render(Window window) {
...
lightsRender = new LightsRender();
gBuffer = new GBuffer(window);
}
public void cleanup() {
...
lightsRender.cleanup();
gBuffer.cleanUp();
}
private void lightRenderFinish() {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
private void lightRenderStart(Window window) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, window.getWidth(), window.getHeight());
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE);
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer.getGBufferId());
}
public void render(Window window, Scene scene) {
shadowRender.render(scene);
sceneRender.render(scene, gBuffer);
lightRenderStart(window);
lightsRender.render(scene, shadowRender, gBuffer);
skyBoxRender.render(scene);
lightRenderFinish();
guiRender.render(scene);
}
public void resize(int width, int height) {
guiRender.resize(width, height);
}
}
<サンプルコードの実行>
VIDEO