第13章 霧
この章では、ゲーム エンジンでフォグ エフェクトを作成する方法を確認します。その効果を使用して、遠くのオブジェクトが薄暗くなり、濃霧に消えていくように見える様子をシミュレートします。
コンセプト
まず、フォグを定義する属性を調べてみましょう。1つ目は霧の色です。現実の世界では、霧は灰色ですが、この効果を使用して、さまざまな色の霧が侵入する広い領域をシミュレートできます。アトリビュートはフォグの密度です。
したがって、フォグ エフェクトを適用するには、3D シーン オブジェクトがカメラから遠く離れている限り、フォグ カラーにフェードする方法を見つける必要があります。カメラに近いオブジェクトは霧の影響を受けませんが、遠くにあるオブジェクトは区別できません。そのため、その効果をシミュレートするために、フォグ カラーと各フラグメント カラーをブレンドするために使用できる係数を計算できる必要があります。その要因は、カメラまでの距離に依存する必要があります。
その要因を呼びましょう
フォググファクター
、その範囲を 0 から 1 に設定します。
フォググファクター
が 1 の場合、オブジェクトがフォグの影響を受けない、つまり近くのオブジェクトであることを意味します。とき
フォググファクター
値が 0 の場合、オブジェクトが完全にフォグに隠れることを意味します。したがって、フォグ カラーの計算に必要な式は次のとおりです。
フォグ カラーの計算に必要な式
霧効果を適用した結果の色です。
フォグ カラーとフラグメント カラーのブレンド方法を制御するパラメータです。基本的にオブジェクトの可視性を制御します。
霧の色です。
フォグ効果を適用していないフラグメントの色です。
次に、計算方法を見つける必要があります
距離にもよる。さまざまなモデルを選択できますが、最初のモデルは線形モデルを使用することです。これは、距離が与えられると、fogFactor 値を直線的に変化させるモデルです。線形モデルは、次のパラメーターによって定義できます。
: フォグ エフェクトが適用され始める距離。fogStart
: フォグ エフェクトが最大値に達する距離。fogFinish
: カメラまでの距離。distance
以下の距離にあるオブジェクトの場合
単に設定するだけです
に
. 次のグラフは、
距離で変わります。
線形モデルは計算が簡単ですが、あまり現実的ではなく、霧の密度が考慮されていません。実際には、霧はより滑らかに成長する傾向があります。したがって、次の適切なモデルは指数モデルです。そのモデルの式は次のとおりです。
作用する新しい変数は次のとおりです。フォグの厚さまたは密度をモデル化します。
これは、霧が距離とともに増加する速度を制御するために使用されます。
次の図は、指数のさまざまな値に対する上記の式の 2 つのグラフを示しています (青い線は $$2$$、
このコードでは、指数の値を 2 に設定する数式を使用します (別の値を使用するように例を簡単に変更できます)。
実装
理論が説明されたので、それを実践することができます。scene.frag必要なすべての変数がそこにあるので、シーン フラグメント シェーダー ( ) にエフェクトを実装します。フォグ属性をモデル化する構造体を定義することから始めます。
基本的な処理は以下の通りです。
- Fogクラスを作成
- Fogモデルのロード
- シーンへの描画を行う
今回の「フォグ」に関しては、エフェクトという形のモデル?を追加しているので、シェーダーとのやり取りが多くなっています。
...
struct Fog
{
int activeFog;
vec3 color;
float density;
};
...
このactiveアトリビュートは、フォグ エフェクトを有効または無効にするために使用されます。フォグは、 という名前の別のユニフォームを介してシェーダーに渡されfogます。
...
uniform Fog fog;
...
calcFogこのように定義されているという名前の関数を作成します。
...
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);
}
...
ご覧のとおり、最初に頂点までの距離を計算します。頂点座標はpos変数で定義されており、長さを計算するだけです。次に、指数が 2 の指数モデルを使用してフォグ ファクターを計算します (これは、2 倍するのと同じです)。fogFactor間の範囲にクランプします
0と1
機能を使用しmixます。GLSL では、mix関数はフォグ カラーとフラグメント カラー (変数で定義color) をブレンドするために使用されます。これは、次の方程式を適用することと同じです。
また、元の色の透明度である w コンポーネントも保持します。フラグメントはその透過性レベルを維持する必要があるため、このコンポーネントが影響を受けることは望ましくありません。
フラグメント シェーダーの最後で、すべてのライト エフェクトを適用した後、フォグがアクティブな場合は、返された値をフラグメント カラーに割り当てるだけです。
...
if (fog.activeFog == 1) {
fragColor = calcFog(outPosition, fragColor, fog, ambientLight.color, dirLight);
}
...
Fogフォグ属性を含む別の POJO (Plain Old Java Object)という名前の新しいクラスも作成します。
package org.lwjglb.engine.scene;
import org.joml.Vector3f;
public class Fog {
private boolean active;
private Vector3f color;
private float density;
public Fog() {
active = false;
color = new Vector3f();
}
public Fog(boolean active, Vector3f color, float density) {
this.color = color;
this.density = density;
this.active = active;
}
public Vector3f getColor() {
return color;
}
public float getDensity() {
return density;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public void setColor(Vector3f color) {
this.color = color;
}
public void setDensity(float density) {
this.density = density;
}
}
Fogクラスにインスタンスを追加しますScene。
public class Scene {
...
private Fog fog;
...
public Scene(int width, int height) {
...
fog = new Fog();
}
...
public Fog getFog() {
return fog;
}
...
public void setFog(Fog fog) {
this.fog = fog;
}
...
}
ここで、これらすべての要素をクラスに設定する必要があります。最初に、構造SceneRenderに均一な値を設定します。Fog
public class SceneRender {
...
private void createUniforms() {
...
uniformsMap.createUniform("fog.activeFog");
uniformsMap.createUniform("fog.color");
uniformsMap.createUniform("fog.density");
}
...
}
このrenderメソッドでは、最初にブレンドを有効にしてからFogユニフォームを設定する必要があります。
public class SceneRender {
...
public void render(Scene scene) {
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
shaderProgram.bind();
...
Fog fog = scene.getFog();
uniformsMap.setUniform("fog.activeFog", fog.isActive() ? 1 : 0);
uniformsMap.setUniform("fog.color", fog.getColor());
uniformsMap.setUniform("fog.density", fog.getDensity());
...
shaderProgram.unbind();
glDisable(GL_BLEND);
}
...
}
最後に、Main霧を設定するようにクラスを変更し、霧の効果を示すためにスケーリングされた地形として単一のクワッドを使用します。
public class Main implements IAppLogic {
...
public static void main(String[] args) {
...
Engine gameEng = new Engine("chapter-13", new Window.WindowOptions(), main);
...
}
...
public void init(Window window, Scene scene, Render render) {
String terrainModelId = "terrain";
Model terrainModel = ModelLoader.loadModel(terrainModelId, "resources/models/terrain/terrain.obj",
scene.getTextureCache());
scene.addModel(terrainModel);
Entity terrainEntity = new Entity("terrainEntity", terrainModelId);
terrainEntity.setScale(100.0f);
terrainEntity.updateModelMatrix();
scene.addEntity(terrainEntity);
SceneLights sceneLights = new SceneLights();
AmbientLight ambientLight = sceneLights.getAmbientLight();
ambientLight.setIntensity(0.5f);
ambientLight.setColor(0.3f, 0.3f, 0.3f);
DirLight dirLight = sceneLights.getDirLight();
dirLight.setPosition(0, 1, 0);
dirLight.setIntensity(1.0f);
scene.setSceneLights(sceneLights);
SkyBox skyBox = new SkyBox("resources/models/skybox/skybox.obj", scene.getTextureCache());
skyBox.getSkyBoxEntity().setScale(50);
scene.setSkyBox(skyBox);
scene.setFog(new Fog(true, new Vector3f(0.5f, 0.5f, 0.5f), 0.95f));
scene.getCamera().moveUp(0.1f);
}
...
public void update(Window window, Scene scene, long diffTimeMillis) {
// Nothing to be done here
}
}
強調すべき重要な点の 1 つは、霧の色を賢く選択する必要があるということです。スカイボックスがなく固定色の背景がある場合、これはさらに重要です。霧の色をクリアの色と同じになるように設定する必要があります。スカイボックスをレンダリングするコードのコメントを外してサンプルを再実行すると、このような結果が得られます。
ようなものが表示されるはずです。