水中の屈折(shader)

水中の屈折(shader)

テクスチャの取得

 描画されている画面をテクスチャで取得し、そのテクスチャを歪ませたのちに水面へ描画することで水中の屈折を表現します。そのためにシェーダ内で画面をテクスチャにしたものを取得する必要があります。以下のコードをサブシェーダ内に追加するだけでテクスチャを取得できます。

次に、このテクスチャを表示するための座標を以下のコードにより取得します。

あとは、取得したテクスチャと座標を用いてフラグメントシェーダ内に以下のコードを追加すると平面に画面をそのまま表示することができます。

シェーダ

 描画されている画面をオブジェクトへ描画するシェーダです。Shiftを調整することでテクスチャをずらすことができます。

実行結果

 青くなっている場所が上記シェーダを適用した平面です。描画された画面がそのまま表示されているため、平面が半透明な色をしているように見えます。

テクスチャをずらすと以下の画像のようになります。平面が半透明ではなく、描画された画面をそのまま表示していることがわかります。

歪みシェーダ

 ノーマルマップをもとに先ほど得られたテクスチャを歪ませます。フラグメントシェーダを以下のように書き換えます。

実行結果

 ノーマルマップによって、テクスチャを歪ませることができました。しかし、オブジェクトが水面から出ている部分まで歪みます。そのため、オブジェクトの後ろからはみ出る部分が生じてしまいます。

深度差シェーダ

 GrabPassによって取得したテクスチャにおいて、オブジェクトが水面より上に出ている部分を歪ませないように処理する必要があります。そのために、カメラの深度テクスチャと平面の深度の差とり、その結果から水面より下か上かを判定します。カメラの深度テクスチャはシェーダ内で_CameraDepthTextureを宣言することで取得できます。

次に平面の深度を取得します。バーテックスシェーダ内で

上記コードより得られた座標をフラグメントシェーダー内で

と処理することで、平面の深度を取得しています。

シェーダ

 平面とその他オブジェクトの深度差を表示するシェーダです。ノーマルマップによってカメラの深度テクスチャを歪ませています。

実行結果

 上記シェーダの実行結果は以下の通りです。

あまり変化がないように見えますが、テクスチャをずらしてみると

となります。これより、オブジェクトが水面より出ている部分が黒くなっていることがわかります。また、ノーマルマップによって歪ませると

深度差が歪んだ状態で求まっていることがわかります。

屈折シェーダ

 深度差を用いて水中部分のみ歪ませるようなシェーダを作成します。深度差が0となっている場所では、ノーマルマップによってuv座標を変えないように処理をすれば良いので

とすれば、水面に出ている部分は歪まなくなります。

シェーダ

 歪みシェーダに深度差を求める処理と上記コードを追加しました。

実行結果

 歪みシェーダではみ出していた部分が消えていることがわかります。

しかし、拡大してみると

このように、オブジェクトの周りに何やら発生しています。

解決方法

原因

 GrabPassによって得られるテクスチャは色が補間されていますが、深度テクスチャは補間されません。そのため、GrabPassによって得られたテクスチャが補間されたことによって、深度テクスチャよりも大きくなる部分ができます。その結果、補間によってはみ出した場所がそのまま出力され、上記のような結果となっているようです。ためしに、歪みシェーダの結果に深度差を乗算した結果を出力したところ

このような結果が得られました。これにより、色の補間によってはみ出でてしまっていることがわかります。

解決方法

 GrabPassによって得られるテクスチャの色の補間をなくす処理(ポイントフィルタ)を追加します。下記に示すAlignWithGrabTexelという関数を作成し、その関数でuv座標を処理することでフィルタリングを行います。

シェーダ

 ポイントフィルターによる処理を追加したシェーダです。

実行結果

 オブジェクトの周りに発生していたドットを消すことができました。

参考サイト

Catlike Coding:Looking Through Water