Anime Aura VFX 技術解説①

概要 この記事について

 UE5 向けの NiagaraSystem アセットである Anime Aura VFX についての技術解説です。
全3回に分けての解説を予定しています(これはボリューム等を見て変更する可能性があります)。

第1回 Anime Aura VFX の原理(本記事)
第2回 パーティクルの投影
第3回 濃度マップ→表現

 このシリーズを通して以下のような知見を得られる可能性があります

  • NiagaraSystem における SimulationStage 活用事例
  • 本アセットのユーザーにとっては
    • 調整のヒント
    • 可能な表現の幅の把握

 第二回は NiagaraSystem、第三回は Material について多く触れることになる予定です。

 実は以前すでにに軽い説明を書いているのですが、これをより具体的にしていく感じになります。
よければ予習にどうぞ(これを読んでいる前提の記事にはしませんのでそこはご安心下さい)。 nkdtr.hatenablog.com

Anime Aura VFX について


 キャラクターの体から発する「気」「オーラ」「呪力」のようなものを表現することを主目的としたものです。
以下のような特徴を併せ持ちます。

  • 2D的な表現(輪郭が存在したりもする)
  • キャラクターのポーズにフィットした形状

www.unrealengine.com

大雑把な原理

一般的な手法で実現できないのかという問題

 この手の表現が必要となった際にゲーム用エフェクトに馴染みのある方がひとまず思いつくのは以下の二つでしょうか。

  • 多数のパーティクルを用いる方法
  • 一枚のスプライトとマテリアルを用いる方法

 前者は UE5 であれば NiagaraSystem を用いることになります。
体表やボーン等からパーティクルを発生させることは容易いですし、実際ゲームではこの種の方法をとる場合が殆どでしょう。 ただ、2D的な表現は難しいかもしれません。
 後者はフリップブック状のテクスチャやマテリアル芸を用いるアプローチです。
こちらはキャラクターのポーズにフィットさせる部分で難を抱えます。
大きく表示するなどポーズに依存しない表現に落とすことが多いでしょうか。
SceneCaptureを使うなどしてポーズを参照することも考えられますが処理負荷は高そうです。

ハイブリッドな手法

 Anime Aura VFX ではこれらのハイブリッド的な方法をとっています。
つまり、

  1. SkeletalMesh を参照してパーティクルを発生させる
     →そのパーティクルを2次元に投影、分布を「濃度マップ」に記録する
  2. 濃度マップを参照する表現用マテリアルを用いて一枚のスプライトを表示

という手順となります。
 それぞれについての具体的な解説を次回以降で行います。

まとめ

 Anime Aura VFX 及びそのメカニズムのさわりについて説明しました。
 次回は NiagaraSystem (SimulationStage) でのパーティクルの投影についてです。
最も重要な部分であり長くなる見込みですので前後編に分かれる可能性があります。

Crystal Renderer 技術解説 補講

概要 この記事について

UE5 向けのプラグインである Crystal Renderer についての技術解説です。
全3回+αに分けての解説を予定しています(これはボリューム等を見て変更する可能性があります)。

第1回:Crystal Renderer の原理
第2回:光の反射と屈折
第3回:形状をマテリアルに取り込む
補講:色収差について (本記事)

本記事は補講です。先に前回までの記事を読んで頂くことをお勧めします。

とりあえずどういうものか

まずは、どのような効果のものであるかを画像で示します。

順に 効果なし/あり のスクリーンショットです。 ふわっとした表現をすれば「色がついた感じ」になります。

いわゆる色収差について

基本的に、色収差というとレンズで像を作る際に色が分解される現象を指します
Wikipediaの記事 へのリンクを貼っておきます)。
第2回で少し触れた屈折率についてですが、この値は光の波長により多少異なります。
多くの場合()において光はたくさんの波長を含みますので、レンズ等を通る際には波長ごとに軌道が異なるということになります。
余談ですが、これに対して同一の波長のみを選択的に増幅させたものがみんな大好きレーザーです)
波長により像を結ぶ点がずれるために視覚的には色がぶれたように映るわけですね。

Crystal Renderer の"色収差"

レンズに限らず一般に光が屈折する際には光が波長毎に分解される現象が起こり、これは分光と呼ばれます。
虹に色が付くことや、ダイヤモンドのような無色の宝石が白い光の下でも色づいて見えることがあるのはこの現象によるものです。
(そういうわけで Crystal Renderer のそれを色収差と呼ぶのは正しくありませんが、エフェクトの名前としては分かりやすいので Aberration と称しています。 )

原理としてはこのようなものですが、残念ながら忠実な色収差を再現するのは現実的ではありません。
現実的な光源は無数の波長成分を含んでいます(この分布を光源スペクトルといいます)。
そして波長毎に異なる軌道をとるわけですから、これを忠実に計算しようとすると計算量が膨大になります。
また、(少なくともゲームにおいて)CGの光源はスペクトルを情報として持ちません。

そういうわけなので、Crystal Renderer においては適当に波長が「長い」「中くらい」「短い」3つの軌道を計算して RGB 成分として合成しています。

まとめ

Crystal Renderer における Aberration (色収差っぽいエフェクト)について解説しました。
ナンバリングせず補講としたのはあまり本質的な部分ではないのと、他と比べてちょっといいかげんな内容だからです。
というわけで、一旦 Crystal Renderer に関する解説シリーズを終わります。

そういえば、UnrealEngine のポストプロセスにも色収差がありますがどのような実装になっているか調べてみるのも面白いかもしれません(他人任せ)。

Crystal Renderer 技術解説③

概要 この記事について

UE5 向けのプラグインである Crystal Renderer についての技術解説です。
全3回+αに分けての解説を予定しています(これはボリューム等を見て変更する可能性があります)。

第1回:Crystal Renderer の原理
第2回:光の反射と屈折
第3回:形状をマテリアルに取り込む(本記事)
補講:色収差について

本記事は第3回です。先に前回までの記事を読んで頂くことをお勧めします。

前置き

前回、レイが平面(無限平面)に衝突したときの計算について解説しました。
しかし、「衝突させるまで」の部分についてが問題として残っています。
視点から発射されたレイが結晶表面に衝突する位置については、ピクセルシェーダーにデフォルトで渡されていますので問題ありません(UE5 の場合は WorldPosition としてマテリアル内でアクセスが可能)。
しかしそこから先、結晶内部を進むレイがどの位置で物体の表面に到達するかを知るには、形状のデータが不可欠です。
マテリアルを扱う方ならお分かりかと思いますが、ここでメッシュ自体を参照するわけにはいかないという点が問題になります。

形状データの表現

結論から提示しますが、形状はテクスチャとして表現されます。
例えばブリリアント・カット(いわゆるダイヤモンドの形状)のメッシュは Crystal Renderer のマテリアル内では以下のようなテクスチャになっています。

勘の良い人であれば何が行われているかを想像できるでしょうか。
今回はこれについての話です。

形状をマテリアルに取り込むにあたって、以下二点の状況・性質を利用します。

  1. 宝石の形状は多くの場合において凸多面体である(正確には曲面を含む場合でも、ポリゴンでは多面体で近似される)
  2. 凸多面体は平面の集合として記述できる

宝石の形状は多くの場合凸多面体である

字面の通りではありますが、凸多面体とは凹みのない多面体のことです。
宝石のカットという工程を動画などで見ると分かると思いますが(検索例)、
平坦なやすりで表面を削る操作によって面を作り出すことを繰り返すのが基本的な操作です。
この操作で凹みが発生することはありませんので、宝石の形状は多くが凸型になります(例外はあります)。
また、ブリリアントカットの周辺部分(ガードルと呼ばれます)など実物の宝石には曲面も存在することがありますが、
ゲーム用のCGにおいては多面体(ポリゴン)に落とし込まれるため曲面ではなくなります。

凸多面体は平面の集合として記述できる

「平面で削り取る」操作を繰り返して作られた形状は、削り取った平面の集合によって充分に表現できるはずです。 「同じ組み合わせの平面で削れば同じ形状になる」と表現すればより納得しやすいでしょうか。
前回の記事で少し触れたように、境界のない無限平面は四次元ベクトルで記述できます。
四次元ベクトルはRGBAカラーに変換できますので、一面あたり1ピクセルのテクスチャとして平面の集合を表現できることになります。
注意点として、ベクトルの成分は負の数や程度大きい数を含むことになります。
したがってそれを許容するようなフォーマットを用いるか、何らかの正規化によって0.0~1.0の範囲内に値が収まるようにする必要があります。 Crystal Renderer では RGBA16F(16ビット浮動小数)フォーマットのテクスチャを用いています。

メッシュを読み取って得た三角形の集合を平面の集合に直し、以下のような関数によってテクスチャ化しています。

UTexture2D *UCrystalRendererBPLibrary::CreateConvexMeshFromSurfaces(TArray<FVector4> &Surfaces, int &PlaneNum, int &TextureSizeW, UObject *TextureOwner)
{
    if (Surfaces.Num() <= 0)
    {
        UE_LOG(LogTemp, Error, TEXT("Invalid surface list"));
        return nullptr;
    }

    // merge surfaces with (apploximately) same plane
    {
        TArray<int> RemoveList;
        for (int BaseIndex = 0; BaseIndex < Surfaces.Num() - 1; ++BaseIndex)
        {
            for (int AnotherIndex = BaseIndex + 1; AnotherIndex < Surfaces.Num(); ++AnotherIndex)
            {
                if (Surfaces[BaseIndex].Equals(Surfaces[AnotherIndex], 0.01f))
                {
                    RemoveList.AddUnique(AnotherIndex);
                }
            }
        }
        RemoveList.Sort();

        for (int i = RemoveList.Num() - 1; i >= 0; --i)
        {
            Surfaces.RemoveAt(RemoveList[i]);
        }
        PlaneNum = Surfaces.Num();
    }

    // decide texture size
    {
        int Width = 4;
        while (Width*Width < PlaneNum)
        {
            Width *= 2;
        }
        TextureSizeW = Width;
    }

    UTexture2D* Retval = CreateTexture(TextureOwner, TextureSizeW);
    
    TArray<FFloat16Color> Pixels;
    Pixels.SetNum(TextureSizeW*TextureSizeW);

    for (int i = 0; i < Surfaces.Num(); ++i)
    {
        Pixels[i] = FLinearColor(Surfaces[i]);
    }

    void *BulkData = Retval->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
    FMemory::Memcpy(BulkData, Pixels.GetData(), sizeof(FFloat16Color)*PlaneNum);
    Retval->GetPlatformData()->Mips[0].BulkData.Unlock();
#if WITH_EDITORONLY_DATA
    Retval->Source.Init(TextureSizeW, TextureSizeW, 1, 1, ETextureSourceFormat::TSF_RGBA16F, (const uint8*)Pixels.GetData());
#endif
    Retval->UpdateResource();

#if WITH_EDITOR
    Retval->PostEditChange();
#endif

    Retval->MarkPackageDirty();

    return Retval;
}

多角形の面は同一平面上のトライアングルを含みますので、これを除外する処理をついでに行っています。
これは衝突検出時の無駄な繰り返しを減らし、シェーダーの実行速度を有意に向上させる効果があります
(もちろん形状によりますが、宝石の面には四角形がけっこう多いためそれなりの効果が期待できます)。

形状データと衝突検出

さて、平面の集合として表現された形状とレイとの衝突をどのように計算すればよいでしょうか。
ふつうのポリゴンの場合は三角形とレイとの衝突判定を繰り返すことになりますが、
四次元ベクトルで表される(無限)平面の集合は頂点やエッジの情報を持ちません。
計算によりそれらを得ることも理屈上は可能でしょうが、死ぬほど効率が悪いということは想像に難くありません。
さいわい、これに関しては頓智めいた方法が存在します。
レイが形状の内部より開始するという条件が付くものの、各平面とレイとの衝突のうちもっとも早いものを採用するだけで凸多面体自体とレイとの衝突を求めることができます。
次の図を見ればこのことが直観的に分かるかと思います(もちろん実際には三次元空間を扱いますが、理屈は同じことです)。

(茶色い線が実際の形状データ、黒い線が元の形状、白い線がレイ、青グレーの丸がレイと平面との交点、黄色い丸がレイと多面体との交点を表します)

まとめ

レイの軌跡を計算するために形状をマテリアルに取り込む方法、及び取り込んだ形状の利用方法について説明しました。
なかなかに都合の良い話なのですが、凸多面体という条件下においてはこのような計算が成り立ちます。
Crystal Renderer の核となる理論についてはこれでおしまいです。
次は補講と称して色収差オプションについて少し書きます。

(おまけ)凸型でない形状

その場合においては軌跡の計算が正確ではなくなるため、ストアでの説明上は「凸型形状のメッシュに結晶風のマテリアルを適用するもの」としてあります。
正確でない計算の結果どうなるかと言えば、例えば結晶内部に透明な境界があるように見えたりします。
劇的に色が変わるわけではないので遠目にて気になる可能性は低いと思われますが、よく観察すれば違和感に気付くでしょう。

Crystal Renderer 技術解説②

概要 この記事について

UE5 向けのプラグインである Crystal Renderer についての技術解説です。
全3回+αに分けての解説を予定しています(これはボリューム等を見て変更する可能性があります)。

第1回:Crystal Renderer の原理
第2回:光の反射と屈折(本記事)
第3回:形状をマテリアルに取り込む
補講:色収差について

本記事は第2回です。先に前回の記事を読んで頂くことをお勧めします。
今回は物理(光)と数学(ベクトル)の話がメインです。

基本情報

光の反射と屈折を計算するときに、レイおよび平面という二つの概念を扱いますのでこれについて確認しておく必要があります。
説明不要という方はこの部分を飛ばして下さい。

レイについて

レイとは仮想的な光線であり、「開始点の座標」及び「進行方向のベクトル」を情報として持ちます
(プログラミング的にはこれを構造体としても良いですが、片方しか必要としない計算も多いため Crystal Renderer ではバラして扱っています)。
レイは「何かが直進したときにどこにぶつかるのか」を調べる際に頻繁に使われる概念ですのでご存知の方も多いでしょう。
ゲーム開発の場面においてはレイキャストという熟語で触れることが多いかもしれません。
検索すればどういうものかは分かると思いますので、ここでの説明は割愛します。

平面について

ここで言う平面とは無限に続く平面を指します。
平面は「法線ベクトル」および「平面上の座標と法線ベクトルとの内積スカラー値)」で表されますので、プログラム上は四次元ベクトルに格納することができます。
このあたりで何を言ってるか分からないという方は次の記事などを読むとよいかもしれません。
ゲーム作りとかCGとかに関わる数学(初歩)① #数学 - Qiita
UE5 においては平面を表現するために FPlane(中身は TPlane)が定義されていますが、コンストラクタ等からお察しできるようにデータの内容としてはやはり四次元ベクトルと同等です。

光の反射と屈折


屈折率の異なる物質の境界に当たった光は一部が反射して戻り、一部が屈折して進入します。
Crystal Renderer では以下の関数でこれらの経路を計算しています(多少編集しつつ抜粋)。

/**
* separate ray into refraction and reflection with weights
*/
void CollideRayWithPlane(float3 rayNormalized, float4 plane, float startSideRelativeRefraction, out float reflectionRate, out float3 reflection, out float3 refraction )
{
    float3 rayVertical = dot(plane.xyz, rayNormalized) * plane.xyz;
    reflection = rayNormalized - rayVertical*2.0;

    float3 rayHorizontal = rayNormalized - rayVertical;
    float3 refractHorizontal = rayHorizontal * startSideRelativeRefraction;
    float horizontalElementSquared = dot(refractHorizontal, refractHorizontal);             
    float borderDot;

    if( startSideRelativeRefraction > 1.0 )
    {
        borderDot = sqrt(1.0-1.0f/(startSideRelativeRefraction*startSideRelativeRefraction));
    }
    else
    {
        borderDot = 0.0;
    }

    // full reflection
    if( horizontalElementSquared >= 1.0 )
    {
        reflectionRate = 1.0;
        refraction = plane.xyz;

        return;
    }               
            
    float verticalSizeSquared = 1-horizontalElementSquared;
    float3 refractVertical = rayVertical * sqrt( verticalSizeSquared / dot(rayVertical, rayVertical) );
    float BaseReflection = pow((1.0-startSideRelativeRefraction)/(1.0+startSideRelativeRefraction), 2);

    refraction = refractHorizontal + refractVertical;
    reflectionRate = CalcReflectionRate(rayNormalized, plane.xyz, BaseReflection, borderDot);

    return;
}

反射について

光が平面上で反射するときの経路について考えます。
こういったことをプログラミングするときは、「まっすぐ跳ね返る」「鏡のように跳ね返る」といった漠然としたイメージではなく数学的な表現で考える必要があります。
この場合、反射後の進行方向は「元の進行方向ベクトルのうち、平面の法線方向成分のみが反転した向き」のように表現できます。
自信のない方は写経でも良いので一度ご自身でプログラミングしておくことをお勧めしますが、以下のように簡単なコードで計算できます(上のものからの抜粋)。
法線方向成分を抽出して二つ分引き算すれば反転するということですね。

float3 rayVertical = dot(plane.xyz, rayNormalized) * plane.xyz;
reflection = rayNormalized - rayVertical*2.0;

屈折について

屈折についてはもう少し複雑な計算になります。
屈折時の進行方向についてはスネルの法則というものに従います。
スネルの法則 - Wikipedia
ようは、屈折前後の進行方向ベクトルについて平面に対して水平な成分の比が屈折率の比と反比例するということです。
進行方向ベクトルが正規化(長さを1にすること)されていれば、
①元の進行方向の水平成分に屈折率の比を乗算して屈折後の水平成分を算出
②長さが1となるよう垂直成分を補う
という手順で屈折後の進行方向を得ることができます。
上に示したコードから間引きながら関係する部分を抜粋したものを以下に示します。

   float3 rayVertical = dot(plane.xyz, rayNormalized) * plane.xyz;
    float3 rayHorizontal = rayNormalized - rayVertical;
    float3 refractHorizontal = rayHorizontal * startSideRelativeRefraction;    // 屈折後の水平成分

    float horizontalElementSquared = dot(refractHorizontal, refractHorizontal);
    float verticalSizeSquared = 1-horizontalElementSquared;
    float3 refractVertical = rayVertical * sqrt( verticalSizeSquared / dot(rayVertical, rayVertical) );    // 屈折後の垂直成分
    refraction = refractHorizontal + refractVertical;

反射と屈折との割合

この割合は光の当たる角度(入射角)によって異なります。
基本的には浅い角度であるほど反射の割合が高くなることが知られています(俗にフレネル反射とか言われる効果です)。
また、屈折率の高い物質から低い物質への境界においては、入射角が一定以上の場合に「全反射」が起こります。
屈折率の高いダイヤモンドの中に複雑な反射が見えるのはこのことと関係があります。
反射の割合についてはフレネルの式というもので計算できます。
フレネルの式 - Wikipedia
実際の光には「波の振動方向」というものがあり、それに応じて反射率が異なるために複雑な式となります(ですので、水面やガラスで反射した光は偏光します)が、通常のコンピューターグラフィックスにおける光にそんなものはありませんので近似式を用います。
ページの下の方にあるコレ↓ですね。

この式にならい、Crystal Renderer では以下のような関数によって反射率を計算しています。

float CalcReflectionRate(float3 normal, float3 ray, float baseReflection, float borderDot)
{
    float normalizedDot = clamp( (abs(dot(normal,ray)) - borderDot) / ( 1.0 - borderDot ), 0.0, 1.0);
    return baseReflection + (1.0-baseReflection)*pow(1.0-normalizedDot, 5);
}

反射するのがこの割合で、残りは屈折すると考えます。

反射・屈折を繰り返すことについて

結晶内部で反射した光は別の位置で表面に到達し、ふたたび反射・屈折が起こります。
これが繰り返されることによって複雑なパターンが表示されることになります。
理論上反射は無限に起こりますが、計算の都合上適当な回数で中断することになります。
当然ですが、この反射回数に応じて表示内容が変化します。
このように繰り返しが多いほど緻密な表示になりますが、10→20では劇的な変化がないことが見て取れるかと思います。
再帰的な処理になりますのでコードもそれに従いたいところですが、シェーダーコンパイラーがこれに対応していません(多分)ので通常のループとして記述することになります。
Crystal Renderer では以下のようなループになっています。
これまでの説明では意味の分からない部分を幾らか含んでいると思いますが、
反射・屈折を繰り返す光の経路をたどりながら計算し必要な情報を配列へと保存しています。

 LOOP for( uint i = 0; i<MAX_REFLECTION; ++i )
    {
        if( i>= condition.MaxReflection )
        {
            break;
        }
        float hitTime=1000000.0;
        float4 hitPlane=float4(1,0,0,1);
        CheckCollideRayWithAllPlanes(tmpRayStart, tmpRayDirection, condition, hitPlane, hitTime);

        hitTimes[i] = hitTime;
        hitPlanes[i] = hitPlane;

        if (hitTime < 0.0)
        {
            badRay = 1;
        }
                                        
        float3 rayEnd = tmpRayStart + tmpRayDirection*hitTime;
                                
        float reflectionRate;
        float3 reflectionRay;
        float3 refractionRay;

        CollideRayWithPlane(tmpRayDirection, hitPlane, refractiveIndex, reflectionRate, reflectionRay, refractionRay);

        reflectionRates[i] = reflectionRate;            
        refractionColors[i] = SampleEnvironment(refractionRay, condition);
        depthColors[i] = float4(CalcColorCoefByDistance(hitTime, condition.MaterialColor),1);

        tmpRayStart = tmpRayStart + tmpRayDirection * hitTime;
        tmpRayDirection = reflectionRay;
    }

この結果を参照し、最後に環境マップからサンプリングした色の加重平均によって表示色を算出します。

まとめ

反射および屈折の計算方法について説明しました。
Crystal Renderer のシェーダーに関して全てではないものの、これが核心ではあります。
ただ、鋭い方はお気付きかもしれませんが、この計算を成立させるために重要な要素がまだ足りていません(冒頭にネタバレがありますが)。
それについてはまた次回をお待ちください。

Crystal Renderer 技術解説①

Models by :

left : 3D Jewels(3djewels.pro/jewelry-3d-mod)/Adapted, CC0

right : Evan(sketchfab.com/3d-models/the-)/Adapted, CC4.0

宝石(ダイヤモンド)部分が CrystalRenderer により描画されています。

 

 

概要 この記事について

Crytal Renderer(次項にて説明)で使用されている技術についての解説です。

一見するとなかなか複雑なエフェクトですが、実際には基礎的な知識(高校生程度+多少の UnrealEngine)を使って作られています。

汎用性のある基礎を応用する例として、これを実現するロジックについてご紹介するシリーズです。

※)ようはタネ明かしです。ある程度エンジン自体の心得や基礎がある人であれば似たものを作れるようになるくらいのつもりで書きます。「作ってみた」報告が届くと嬉しいです。

※)UE5 向けのプラグインについての事例となりますが、理屈についてはゲームエンジン等の環境によらず通じます(すでに公開をやめていますが、過去にはほぼ同様の機能をUnity向けに販売していました)ので、エンジニア・テクニカルアーティスト一般に有益な内容になると思っています。

 

全3回+αに分けての解説を予定しています(これはボリューム等を見て変更する可能性があります)。

第1回:Crystal Renderer の原理(本記事)

第2回:光の反射と屈折

第3回:形状をマテリアルに取り込む

補講:色収差について

 

Crystal Renderer について

Crystal Renderer は UE5 用のプラグインです。

結晶内での光の反射および出入り時の屈折をシミュレートし、リアリティの高いレンダリングを実現します。

主に透明な宝石のような物体に適しています。

おまけとして、メッシュを生成するための補助機能を備えています。

www.unrealengine.com

以下、本題です。

Crystal Renderer の原理

上で少し触れたように、Crystal Renderer は光の経路を計算することをが機能の核です。

光の経路を計算というと「レイトレーシング」という言葉が思い浮かぶかもしれません。Crystal Renderer は物凄く大雑把に言えば「状況を大幅に限定することで通常のピクセルシェーダーに落とし込んだレイトレーシングっぽい何か」・・・と言うとまさかりが飛んでくるかもしれませんが、概ねそのような雰囲気のものです。

以下に模式図を示します。

この図の意味するところについて説明していきます。

光の性質について

まず、光について知っておくべき基本的な性質が二つほどあります。

光の経路は可逆である

光の基本的な性質の一つに、経路を逆に辿れるというものがあります。

つまり、視点から発射した光が最終的にどこに辿り着くかを調べることによって、その方向から届くはずの光について知ることができるというわけです。

これはレイトレーシングの基本的な考え方ですので、このワードで検索すると詳しい説明が見つかると思います。

反射と屈折

光が屈折率の異なる物質との境界に当たったとき、反射と屈折が起こります。

ここで反射と屈折の割合や方向については一定の法則があります。

詳しくは次回の記事で計算式やコードとともに触れる予定ですが、これが Crystal Renderer の核です。

光の経路の計算手順

上の二点を踏まえると、結晶の外界を環境マップとして用意する必要はあるものの以下のような方法で結晶の表面に映る色を計算することができそうです。

 

①視点から放たれたレイ(仮想的な、計算上の「光線」)が結晶表面に当たる。

 ここで反射(外界に離脱)したレイと屈折(結晶内部に進入)したレイとに分かれる。

②反射したレイについて、その方向を用いて環境マップから色をサンプリング

③進入したレイが結晶表面に当たる。

 ここで反射(結晶内部に留まる)レイと屈折(外界に離脱)レイとに分かれる。

④屈折したレイについて、その方向を用いて環境マップから色をサンプリング

⑤反射したレイについて、③に戻って再計算

 

理屈の上では無限に続く計算になりますが、計算資源は有限ですので繰り返しは適当なところで切り上げます(反射を起こらなくします)。Crystal Renderer においては切り上げる回数をパラメータ化し、負荷を調整できるようにしてあります。

 

⑥最終的に②と④で得られる色を重みづけして足しこみ、色を決定する

 

こういうことですので、環境マップからサンプリングする位置及び重みを計算するためにレイを使っているという見方もできますね。

 

まとめ

ここまでの説明で「あーなるほどね」と感じた方は、実装を試してみるのも良いでしょう(時間はかかると思いますが)。

それで詰まるようなことがあれば、次以降の記事が助けになるかもしれません。

次回の公開までしばらく時間がかかると思いますが、気長にお待ちいただければと思います。

AnimToSprite Manual -Afterimage Effect-

AnimToSprite Manual -Afterimage Effect-

A2S Version 1.9 provides a component to add afterimage effects.

It is a bit heavy because it spawns Actors, but it is a useful effect.

 

Caution :

This is only for AnimSpriteRender component (provided by A2S).

Paper2D will be supported in a later version.

 

How to use

1. Attach component

Add BP_SpriteAfterImageSpawner to an actor which has AnimSpriteRender component.

 

2. Activate

This component is not activated by default.

Use Activate function or check "Auto Activate" in the Details.

 

Customization

You can control the appearance of afterimages by creating materials or changing the properties.

Properties

-CaptureMaterial:

The material used to create textures for afterimages.

This is less likely to be modified.

-SpawnRate

Number of spawned afterimage sprite per second.

-AfterImageMaterial

The material that determines the appearance of the afterimage sprites.

-LifeSpan

-ColorOverTime

Used in the standard (provided) AfterImageMaterial to define the colors and opacities of afterimage sprites over time.

-AutoActivate

If checked, the afterimage effect will be active when the game starts.

Examples

1. Default

 

2. Spawn Rate

Spawn rate can be modified in the Details of the component.

 

3. Color

You need to create a pair of LinearColorCurve and CurveAtlas.

Of course, replacing AfterImageMaterial also works.

 

4. Using sprite colors

(The latest version of A2S contains this material as "M_SpriteAfterImage_Colored").

You need to your customized material (based on provided M_SpriteAfterImage).

Use the RGB channel of the "Texture" in some way.

AnimToSprite Manual -Wake Sprite Option-

Anim To Sprite "Wake-up Sprite Option"

Summary

Intersections between 3D backgrounds and camera-facing sprites are often problematic, especially in Top-View games.

 

The Wake-up option eliminates this problem to some extent, but should be implemented with caution because it does not work well from a near-vertical perspective and other side effects are to be expected.

 

How to setup

When using material instances based on embedded materials (M_AnimSprite/M_AnimSprite_Toon), check "bWakeSprite" to enable this option.

"WakeRate" determines the strength of its effect.

 

If you use your original material, just immitate M_AnimSprite.

MF_WakeSprite does the calculation.

 

Then check "Use Subdivided Mesh" in the details of AnimSpriteRender component.

 

Technical Explanation

This section describes the actual effect of this option.
This may help in analyzing the side effects that would occur.

 

Wake Sprite option offsets the vertices toward the camera so that the mesh stands vertically.

This requires a subdivided mesh to deter image distortion.

A2S provides a quad mesh subdivided into 8x8 rectangles.