0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】自作したシャドウマップにソフトシャドウをつけてみる

Posted at

はじめに

最近シャドウマップの実装を勉強しています。
元々カスケードシャドウの実装方法を勉強したかったのですがそもそもシャドウマップがどのように作成されるのかを理解しないとカスケードシャドウの実装を読めなかったりし、1からシャドウマップやシャドウについて勉強しています。

今回は比較的簡単にソフトシャドウを作ることのできる方法について勉強したので記事にしてみます。

そもそもシャドウマップを作る方法についてはこちら

の記事のソースコードを参考にしています。
こちらの記事を参考に作ったシャドウマップを改造してソフトシャドウにします。

ソースコード

ソフトシャドウについて

まず、地面に影を落とすためにはシャドウマップが必要です。

image.png

シャドウマップというのはこのようなテクスチャのことです。(見やすくなるようにLevelsを調整しています。)
Lightから見たオブジェクトを深度値としてテクスチャに保存しています。
この保存された深度値と描画するオブジェクトのカメラから見た深度値を比較することで地面に影を落としています。

詳しくはこちらの記事に書かれています。

この保存されているシャドウマップの情報を使うだけではパキっとしたドット感のあるハードシャドウになってしまいます。これをふわっとしたシャドウにするのがソフトシャドウです。

Pasted image 20250713212423.png
ハードシャドウになっている様子。

Pasted image 20250713204839.png
フチがボケてソフトシャドウになっている様子。

ぼかす方法

今回使う手法はPercentageCloserFilteringという方法を使います。略すとPCFとなります。

シャドウマップをサンプリングする際にサンプリングする点とその周囲のピクセルを調べ、平均を取ります。
その平均を取ることでボケます。

image.png
平均を取ることで本当にボケるのか試してみます。
このようなテクスチャがあるとします。

image.png
自分のピクセルの周りの平均というのは青色の場合は赤色の領域になります。
この場合、(1+1+1+1+1+1+1+1+1)/9=1となり、白色です。

image.png
自分含めて周りが白いピクセルはこの水色範囲なのでここに関しては白色にしかなりません(1pxごと説明するのを省くためにこのように説明します。)

image.png
次にこのピクセルについて考えます。黒いマスが1つあるので(1+1+1+1+1+1+1+1+0)/9=0.888888889となり、薄い灰色になります。

image.png
次にこのピクセルについて考えます。黒いマスが2つあるので(1+1+1+1+1+1+1+0+0)/9=0.777777778となり、先ほどより少し濃い灰色になります。

image.png
さらに隣のピクセルも黒は2つしかなので同じになります。
この作業を続けていくと

image.png
このようになります。
平均を取ることでボケました。

float SampleCustomShadow_PCF(float depth, float2 shadowCoord, float2 texelSize)
{
    static const int2 OFF[9] = {
        int2(-1,-1), int2( 0,-1), int2( 1,-1),
        int2(-1, 0), int2( 0, 0), int2( 1, 0),
        int2(-1, 1), int2( 0, 1), int2( 1, 1)
    };

    float sum = 0.1;
    [unroll]
    for(int i = 0; i < 9; i++)
    {
        float2 uv = shadowCoord.xy + OFF[i] * texelSize;

        // テクセル外はライトに当たってることにする
        if (any(uv < 0) || any(uv > 1))
        {
            sum += 1;
            continue;
        }

        float shadowMapDepth;
        shadowMapDepth = SAMPLE_TEXTURE2D(_CharacterShadowMapTexture, sampler_CharacterShadowMapTexture, uv).r;

        #if UNITY_REVERSED_Z
        shadowMapDepth = 1.0 - shadowMapDepth;
        #endif

        // 比較する
        sum += step(depth, shadowMapDepth);
    }
    return sum / 9.0;      // 平均を取る
}

この作業をコードにするとこのようになります。

static const int2 OFF[9] = {
        int2(-1,-1), int2( 0,-1), int2( 1,-1),
        int2(-1, 0), int2( 0, 0), int2( 1, 0),
        int2(-1, 1), int2( 0, 1), int2( 1, 1)
    };

ここで自分のピクセルと調べたいその周囲の方向を決めています。

[unroll]
for(int i = 0; i < 9; i++)

平均を取るのでその数だけループさせます。

float2 uv = shadowCoord.xy + OFF[i] * texelSize;

OFF[9]の値を直接使うとUVよりも大きい範囲を見に行ってしまうので、texelSizeを使い1px分だけその方向にずれたピクセルを見ます。
Unityの_テクスチャ名_TexelSizeのx,y要素には1/width、1/heightが入っており、この値が1テクセル分の移動になります。(例:1/1024 = 0.0009765625)

あとはこのuvでシャドウマップを見ます。

    sum += step(depth, shadowMapDepth);
}
return sum / 9.0;

平均を取るのでループの中で加算をし、ループの後に9で割ります。

終わりに

平均を取るだけでボケを作り、ソフトシャドウを作ることができました。
UnityのソフトシャドウでもPCFを使用していますが内部実装を確認したところ別の計算方法でぼかしているようでした。
詳しく説明している記事はこちらにありました。(中国の記事です)

TentFilterというフィルター方法を使用しているようです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?