読者です 読者をやめる 読者になる 読者になる

しゅみぷろ

プログラミングとか

雨の日のカメラを作る

はじめに

カメラのレンズに雨が付着した感を出してみました。

ささっと作ったのですが、ちょっといい感じになったので自分で驚いてます。 雨粒が付着した部分は背景の映り込みが歪むようになっています。

Unity Standerd Assetに含まれるガラスシェーダーを使いまわしたので、10分くらいで実装することが出来ました。

実装方法

UnityTexturePaintを使います。

github.com

まずは以下の画像のように Assets -> Import Package -> Effectsを選択して、Glass Refractionをインポートします。 Glass Refractionでは、GrabPassで取得したテクスチャと法線マップを使い、写り込んだ背景を歪ませる処理が行われています。 このシェーダーをそのまま利用し、透明な状態で法線情報だけを更新していき、水滴が付着したように見せるという寸法です。

f:id:es_program:20170403212428p:plain:w600

次に、カメラ正面にQuadを置き、Quadのマテリアルを先程インポートしたGlassRefractiveに変更し、テクスチャを真っ白なものに差し替えます。QuadのコンポーネントにDynamicCanvasとHeightFluidをアタッチしたら準備は完了です。

f:id:es_program:20170403213207p:plain:w600

あとはTexturePaintを使って、Quadの適用な位置にHeight(液体の付着量)情報をペイントしてやります。

具体的には

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Es.TexturePaint;

public class RainPainter : MonoBehaviour
{
    [SerializeField]
    private DynamicCanvas canvas;

    [SerializeField]
    private PaintBrush brush;

    [SerializeField]
    private float minSize;

    [SerializeField]
    private float maxSize;

    [SerializeField, Range(0, 100)]
    private float aboutCallPercentOnFrame = 10;

    private void OnWillRenderObject()
    {
        if(Random.Range(0f, 100f) > aboutCallPercentOnFrame)
            return;

        for(int i=Random.Range(1, 10);i>0;--i){
            brush.Scale = Random.Range(minSize, maxSize);
            canvas.PaintUVDirect(brush, new Vector2(Random.Range(0f, 1f), Random.Range(0f, 1f)));
        }
    }
}

こんなスクリプトを書いてQuadに取り付けて、BrushTextureとBrushHeightTextureにこんな感じの画像を与えてやります(透過済みの画像です)。

f:id:es_program:20170403213616p:plain:w200

サンプルの動画では設定はこんな感じに。

f:id:es_program:20170403213745p:plain:w400

あとは実行すれば、適当な位置に水滴がついたような効果が断続的に出現します。

最後に

1粒1粒の水滴がつくたびに描画命令を発行するため、今回のままではパフォーマンス的によろしくありません。 改善する方法としては、水滴がたくさん付いているようなテクスチャを入力として、1回で複数の雨粒を書き込んでやる、などが挙げられます。

法線に影響される液垂れの表現

はじめに

液垂れ表現についてです。 最近Switchばっかやってて全然更新してなかったので久しぶりにネタ消化。

液垂れの基本的な部分は過去記事で。

esprog.hatenablog.com

esprog.hatenablog.com

今回は法線マップに影響させる部分について軽くメモしていきます。

f:id:es_program:20170401115307g:plain:w600

f:id:es_program:20170401135622g:plain:w600

法線に応じて流れやすさを変える

Shaderに法線マップを入力として取らせ、接空間上で法線と液体の付着量から、力の働いている方向にどの程度流れるかをそれっぽく計算してやります。

float3 normal = normalize(UnpackNormal(tex2D(_NormalMap, i.uv)).xyz);

で法線マップの法線を取ってきて

float VITIATE_Z = pow(normal.b, 2) - normal.y * 0.2;
float VITIATE_X = 0.1 * rand(float3(i.uv.xy, i.uv.x + i.uv.y)) * (1 + normal.b * 30);

どのくらい縦、横の液体を考慮するかの係数(VATATE_ZとVITATE_X)を計算します。 式自体に特に深い意味はなく、それっぽく流れてくれるよう、法線が上向きなら流れにくく・・・とかやってるだけです。

この係数を使って液体マップの周囲(液体が垂れてくると予想される場所)をサンプリングし、付着量を出します。

float2 shiftZ = float2(_FlowDirection.x * _MainTex_TexelSize.x, _FlowDirection.y * _MainTex_TexelSize.y) * _ScaleFactor * _Viscosity * VITIATE_Z;
float2 shiftX = float2(_MainTex_TexelSize.x * _FlowDirection.y, _MainTex_TexelSize.y * _FlowDirection.x) * _ScaleFactor * _Viscosity * VITIATE_X;
float2 shiftx = -shiftX;

float4 texZ = tex2D(_MainTex, clamp(i.uv.xy + shiftZ, 0, 1));
float4 texx = tex2D(_MainTex, clamp(i.uv.xy + shiftx + shiftZ, 0, 1));
float4 texX = tex2D(_MainTex, clamp(i.uv.xy + shiftX + shiftZ, 0, 1));

float amountUp = (texZ.a + texx.a + texX.a) * 0.3333;

この付着量が粘性係数を上回る場合は液垂れ計算が行われ、下回る場合は変化なしということになります。

最後に

Texture basedなので、勿論uv依存です。 使用用途として考えているのは壁等のシンプルなオブジェクトへの液体の付着表現(血の表現、Splatoonのようなインク表現)なので、uvが複雑になるモデルには不向きです。

今はまだ試作段階なので、係数のパラメーター化や色の減法混色なども実装していこうと思います。