【UE5で気分転換!セルルック×ライティングでキャラを魅せたい】第3回:背面法とポストプロセスなアウトラインを描いてみた – Unreal Engine 5.6

Unreal Engine

水ノ茉(こおり)の宣伝

叡智でえっちな同人作品を予定中…

Ci-en R18

同人作品の宣伝

  • 準 備 中 . . .

プラグインの宣伝

シリーズ説明

本シリーズで最終的に検証したい内容は、DMXで制御されたバカみたいな量のライト、それらすべてをキャラクターに当てても破綻しないレンダリングパイプラインの構築です。

実装の手段は選ばないため、普段展開している【UE5】◯◯◯ – Unreal Engine ◯.◯と比べると多少敷居が高く、また、実装の再現性を提供しないこともあるため、いつも以上に趣味嗜好が全開となります。

https://kafues511.jp/2025/06/14/4249/

始まり

セルルックで超重要なアウトライン、輪郭線を作っていきます。

当たり前ですが前回同様にバチボコにエンジン改造をしているため再現性は提供していません。

再現性が欲しい場合は別の記事を参照してください。
自分のコンテンツだけで循環できるの気分いいですわ。

背面法なアウトライン

UEで背面法なアウトラインを作る場合は、オーバーレイマテリアルを利用するか、SkeletalMeshComponentを複製するかの2択が一般的だと思います。

前者のオーバーレイマテリアルは実装は簡単ですが、ひとつのマテリアルですべてのアウトラインを制御しないといけないので、拡張性が低いです。

後者のSkeletalMeshComponentはマテリアルスロットごとにマテリアルをアタッチできるため、拡張性は高いのですが、実装を気を付けて組まないと処理が重複します。仮に気を付けても重複する箇所は設計上、絶対に発生するため、処理負荷的に懸念されます。

というのがオーバーレイマテリアルが誕生してから5.5までの常識でした。

5.6からはMaterial Slots Overlay Materialが追加され、遂に、オーバーレイマテリアルもマテリアルスロットごとに指定できるようになりました。

これは嬉しいアプデですね。

筆者はエンジン改造するのが好きですが、元を辿ればUEの描画機能が不足しているから、仕方がなく追加しているだけに過ぎないので、実装作業をサボれるなら別に喜んでサボります。プライベートなら後学のために車輪の再発明をするのも悪くないですが、実務では無駄な労力は避けられるなら全力で避けるべきですからね。

さてと、機能追加の事実は大変嬉しいのですが、今回のケースでは残念ながらエンジン改造なのです。

シリーズ説明に記載の通り趣味嗜好がほとんどですが、わざわざエンジン改造を選択しているのには、純正では達成することが出来ない or 難しい or ボトルネックになる、という最低限の理由はあります。

5.6から追加されたMaterial Slots Overlay Materialを使うことでマテリアルスロットごとにマテリアルパラメータを調整可能にはなりますが、敢えて悪く言うと通常のマテリアルアウトラインマテリアル2つを作る必要があります。

メッシュに割り当てられるマテリアルが両手で収まるのであれば、別に大したことのない労力なのですが、それ以上を考えてみてください。意外と地獄なんですわ。その地獄を味わうのは普通はアーティストでエンジニアではないため、管轄違いとして別にいいや♪(すまん)と投げ捨てることも可能です。

な・の・で・す・が

プライベートなので当然、独りで作業をするのです。

自分で実装して自分で地獄みたいな環境を作るアホは居ないでしょう。
筆者さんバカですけどそこまで底なしのバカではないわ。たぶん。

ということで、ひとつのマテリアルから通常のパラメータとアウトライン用のパラメータ、両方を指定できるようにしつつ、トゥーンパス専用のアウトラインメッシュをフラグをポチッとするだけで投下できるように実装していきます。

ToonOutlineフラグの追加

アウトラインメッシュを投下したり、シェーダーをアウトライン用のバリエーションに切り替えるためのフラグを追加します。

5.4から5.5のタイミングで、この部分の実装方法のテンプレートが少し変わったんですよね。マクロな記法は置換前の実装を把握している人間からしたらコード量が減るのでありがたいのですが、新規参入者からしたら可読性がまぁゴミなので、なんともいえないです。ドンマイですね。

Outline World Position Offsetピンの追加

アウトライン専用のWorldPositionOffsetピンを作成します。

Customノード内でバリエーションをしても静的分岐な挙動は満たされるのですが、ピンの接続状態を分けておきたい理由があるので、今回は新設で進めます。ちなみに普段公開してるピンの追加方法はピクセルシェーダーです。この子は頂点シェーダーなので、ちょっと作業場所が違います。愚直にリネームするだけでは地味に動作しないのが面倒なところです。

アウトラインメッシュの投下

先ほどのToonOutlineフラグを元にアウトライン用のメッシュを投下させていきます。

ドローコールまわりは5.4から5.5のタイミングでかなり分かりにくく変わったので地味に大変でした。最適化が理由なので受け入れはするのですが、動線が結構複雑になったのがネックですね。あとキャッシュがすんごく効くようになったのでタイミングを逃すとブレークに永遠と到達しなくなりました。最適化の代償として動作把握のイテレーションがかなり低下したような気がします。たぶんコンソール変数で切れるとは思うんだけど、その部分は探していないです。把握し終わったから別にいいやというアレです。

PIXで確認するとドローコールが2倍になっているので、フラグを元にアウトライン用のメッシュを投下できていますね。

現状だと、どれが通常メッシュで、どれがアウトラインメッシュなのかが分からないため、ドローコマンドの部分もちょっといじります。5.5からはMaterialNameに格納が廃止されちゃったんですよね。使用時にプロキシからGetMaterialName()しているので、おそらくFStringのメモリを削りたかったのでしょう。でもTESTとSHIPPINGではバリエーションが畳まれる部分なので製品には影響が出ないんですよね。だからそこまで極限に削らなくても良かったのにと思うのです。別にいいんだけどさ。

アウトラインメッシュなドローコールには末尾にOutlineを付けました。これで視認性向上ですね。

アウトラインバリエーションを追加

ドローコールの対応が終わったので次はシェーダーバリエーションです。

SetDefineに指定の通りTOON_OUTLINEバリエーションを作成しています。

template<bool bToonOutline>
class TToonVelocityVS : public FMeshMaterialShader
{
public:
	DECLARE_SHADER_TYPE(TToonVelocityVS, MeshMaterial);

	static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
	{
		FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);

		OutEnvironment.SetDefine(TEXT("TOON_OUTLINE"), bToonOutline ? 1 : 0);
	}

BasePassほど組み合わせが多くないのでPipelineも用意しています。

using FToonVelocityVS = TToonVelocityVS<false>;
using FToonVelocityPS = TToonVelocityPS<false>;

using FToonOutlineVelocityVS = TToonVelocityVS<true>;
using FToonOutlineVelocityPS = TToonVelocityPS<true>;

IMPLEMENT_SHADER_TYPE(template<>, FToonVelocityVS, TEXT("/Engine/Private/Toon/ToonVelocityVertexShader.usf"), TEXT("MainVS"), SF_Vertex);
IMPLEMENT_SHADER_TYPE(template<>, FToonVelocityPS, TEXT("/Engine/Private/Toon/ToonVelocityPixelShader.usf"), TEXT("MainPS"), SF_Pixel);
IMPLEMENT_SHADERPIPELINE_TYPE_VSPS(ToonVelocityPipeline, FToonVelocityVS, FToonVelocityPS, true);

IMPLEMENT_SHADER_TYPE(template<>, FToonOutlineVelocityVS, TEXT("/Engine/Private/Toon/ToonVelocityVertexShader.usf"), TEXT("MainVS"), SF_Vertex);
IMPLEMENT_SHADER_TYPE(template<>, FToonOutlineVelocityPS, TEXT("/Engine/Private/Toon/ToonVelocityPixelShader.usf"), TEXT("MainPS"), SF_Pixel);
IMPLEMENT_SHADERPIPELINE_TYPE_VSPS(ToonOutlineVelocityPipeline, FToonOutlineVelocityVS, FToonOutlineVelocityPS, true);

バリエーションはシェーダー定義にマクロを多用したり、ゴリゴリにテンプレート記法をしたり、色々と趣向が分かれる部分です。筆者はエンジンアップデートの輪廻に囚われ続け、そこで得た持論としては、愚直に読みやすい実装方法でした。ゴリゴリな書き方はかっこよくて最初のうちは可読性も良いのですが、数か月後に読み直すと、大体ブチ切れます。読みにくくて、意味分からんくて。なので、愚直に読みやすい実装方法を採用するに落ち着きました。前提として、エンジンの保守なんて怠い作業は、独りでやるのが普通なはずなので、未来の自分が読めさえすればなんでも良いとは思います。

template<bool bToonOutline>
bool GetToonVelocityPassShaders(
	const FMaterial& Material,
	const FVertexFactoryType* VertexFactoryType,
	TShaderRef<TToonVelocityVS<bToonOutline>>& VertexShader,
	TShaderRef<TToonVelocityPS<bToonOutline>>& PixelShader)
{
	return false;
}

template<>
bool GetToonVelocityPassShaders<false>(
	const FMaterial& Material,
	const FVertexFactoryType* VertexFactoryType,
	TShaderRef<TToonVelocityVS<false>>& VertexShader,
	TShaderRef<TToonVelocityPS<false>>& PixelShader)
{
	FMaterialShaderTypes ShaderTypes;
	ShaderTypes.PipelineType = &ToonVelocityPipeline;
	ShaderTypes.AddShaderType<FToonVelocityVS>();
	ShaderTypes.AddShaderType<FToonVelocityPS>();

	...省略...
}

template<>
bool GetToonVelocityPassShaders<true>(
	const FMaterial& Material,
	const FVertexFactoryType* VertexFactoryType,
	TShaderRef<TToonVelocityVS<true>>& VertexShader,
	TShaderRef<TToonVelocityPS<true>>& PixelShader)
{
	FMaterialShaderTypes ShaderTypes;
	ShaderTypes.PipelineType = &ToonOutlineVelocityPipeline;
	ShaderTypes.AddShaderType<FToonOutlineVelocityVS>();
	ShaderTypes.AddShaderType<FToonOutlineVelocityPS>();

	...省略...
}

template<bool bToonOutline>
bool FToonVelocityMeshProcessor::Process(
	const FMeshBatch& MeshBatch,
	uint64 BatchElementMask,
	int32 StaticMeshId,
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
	const FMaterial& RESTRICT MaterialResource,
	ERasterizerFillMode MeshFillMode,
	ERasterizerCullMode MeshCullMode)
{
	const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;

	TMeshProcessorShaders<
		TToonVelocityVS<bToonOutline>,
		TToonVelocityPS<bToonOutline>> ToonVelocityPassShaders;

	if (!GetToonVelocityPassShaders<bToonOutline>(
		MaterialResource,
		VertexFactory->GetType(),
		ToonVelocityPassShaders.VertexShader,
		ToonVelocityPassShaders.PixelShader))
	{
		return false;
	}

	...省略...

ランタイムの改修が終わったら残りはシェーダー部分の対応です。

この静的分岐をするためだけにピンを追加したり、バリエーションを追加したり、ドローコールを複製したり、色々としていたのです。最適化というのは得られる結果に対する作業量が稀にバグってます。これも醍醐味なんですけどね。

#if TOON_OUTLINE
	TranslatedWorldPosition.xyz += GetMaterialOutlineWorldPositionOffset(VertexParameters);
#else
	TranslatedWorldPosition.xyz += GetMaterialWorldPositionOffset(VertexParameters);
#endif  // TOON_OUTLINE

ToonVelocityで動作確認

実装も終わったので、適当に頂点法線方向に膨張させてみます。

できました。

忘れていましたが、カリングを反転させないと深度テスト的にアウトラインしか描画されないですね。とりあえずはアウトラインで一般的なフロントカリングで固定にしておきます。

const ERasterizerCullMode MeshCullMode = bToonOutline ? ERasterizerCullMode::CM_CCW : ComputeMeshCullMode(*Material, OverrideSettings);

それっぽくなりましたね。アウトラインが透明になっているのはベースパスのバリエーション対応が抜けているからです。

ToonBasePassの対応と動作確認

ベースパスにも同様のバリエーション対応を入れて、ついでに専用のアウトラインカラーを出力できるようにします。

#if TOON_OUTLINE
	// 最終的にはライティングするからGバッファ格納だけど、いまはエミッシブで簡易表現
	float3 Emissive = GetMaterialOutlineColor(PixelMaterialInputs);
#else
	float3 Emissive = GetMaterialEmissive(PixelMaterialInputs);
#endif  // TOON_OUTLINE

できました。

速度を書き込んでいるというのにTSR特有のノイズが常時発生している謎現象に遭遇したと思ったら、前フレームのワールドポジションオフセットを加算している箇所のアウトライン対応が抜けていました。オフセットなしとオフセットありの差分で速度を求めていたら、そりゃ常時動き回っているのと同じですわ。うっかりです。

うっかり発動中
うっかり修正後
#if TOON_OUTLINE
	// ここ忘れてた
	PrevTranslatedWorldPosition.xyz += GetMaterialPreviousOutlineWorldPositionOffset(VertexParameters);
#else
	PrevTranslatedWorldPosition.xyz += GetMaterialPreviousWorldPositionOffset(VertexParameters);
#endif

ひとつのマテリアルから通常と輪郭のパラメータ調整

ひとつのマテリアルから通常メッシュとアウトラインメッシュの両方に対してパラメータを反映できました。

アウトラインバリエーションで制御しているので、通常メッシュでアウトラインのノードが展開されちゃうという、純正ではコンパイラの解釈によって最適化の有無が決定される危うい部分も#if TOON_OUTLINEで囲っているので安心です。ある程度分かっている方なら『FPixelMaterialInputsから取ってきてる部分だけ囲っても無意味では?』と思うことでしょう。安心してください。WPOと同様な展開に改変しているのでバリエーションを反映可能なのです。抜かりありませんわ♪

最初に追加したToonOutlineフラグも正常に動作していますね。

素体と衣装はマスターのインスタンスのインスタンスを別々にしているので、一括で切り替わらないのは正常動作です。

改造とは関係ないですけど、頂点法線からハードエッジを計算する簡易対応だけで毛先が綺麗になるのはコスパ良いですね。これもいつかプラグインで公開しようかと思っているんですが、漬け続けています。マジで自分を複製して一緒に作業を進めたい。ネタ帳の追加に対して消費が永遠と追い付かないです。

ポストプロセスなアウトライン

背面法は綺麗な輪郭線を出せるのですが唯一の欠点があります。

それは綺麗な線を引くためには法線調整を手動で行った方が良い結果を得られるということです。

こっそり適用したハードエッジの自動計算にも限度があり、周辺の法線方向がすべてそっぽを向かれると正規化したところで向く先はそっぽのままです。

前髪を例に出すとエッジの法線が正面から見た時に左右に伸び切っておらず、真横から見た際に前方に強く出ています。この状態だと頂点法線でもハードエッジな計算を適用しても左右の伸びが足りずに正面から見た際に前髪に輪郭が引かれないのです。

エッジの左右が弱い
エッジの前方が強すぎる

アーティストなら黙ってDCCツールと向き合ってもらうのが自然な流れですが、私はエンジニアなのです。プラグイン提供や効率化なら黙ってやりますが、法線調整はあまりにも管轄違いの作業なのです。

めんどうなのよさ。

ということでポストプロセスなアウトラインと併用して、背面法が苦手な部分はポストの方で頑張ってもらいます。

アウトライン用のToonVelocityを作成

ポストエフェクトなアウトラインを計算する際に最も基本的な方法は深度差分です。

他にも法線差分やGバッファを参照した方法などもありますが、それらはどれもこれも、背面法と併用する場合にはちょっと不都合があります。それは背面法を書き込んだ後の深度やバッファを元に線を引くと、背面法の更に外側に線が引かれるため、線が太くなってしまうことです。

一番簡単な抑制、回避方法として、周辺に背面法が引かれている場合にはクリップするというのがありますが、これは背面法のメリットをほぼ殺してしまいます。表現力の低下と計算量の無駄を招くだけです。

ポストなアウトラインのメリットを最大限引き出しつつ、上記のデメリットを防ぐために、背面法を書き込む前に深度バッファをコピーします。コピーした深度バッファを元にアウトラインを引くことで背面法の外側に引かれるという結果を回避できます。

ついでに、アウトラインのWPOが通常のWPO判定に影響を及ぼしていたので、内部で判定を切り分けて、最適化が効くようにしました。軽量版シェーダーの適用有無はPassProcessorで決定されるため、ここだけ変えるのが一番コスパが良いのです。WPOの処理を読んだことある方なら分かるでしょうが、あれコンパイラまわりにも繋がっているので、通常とアウトラインのそれぞれでWPO判定を行うと変更量がとてつもないのです。

const bool bModifiesMeshPosition = bToonOutline ? DoMaterialAndPrimitiveModifyMeshPosition(*Material, PrimitiveSceneProxy) : DoMaterialAndPrimitiveModifyMeshNonOutlinePosition(*Material, PrimitiveSceneProxy);

結果は背面法の太さに依存しますが、比較すると結構変わっていることが分かるでしょう。

唯一の懸念としては描画パスを区切るので若干のスパイクが発生します。ですが、品質と速度はトレードオフなので受け入れましょう。これらを両方満たせる手法というのは稀有です。基本的には良く見せたいなら相応のGPUコストは支払いましょう。そのコストを最小限にするために最適化というフェーズがあるのです。

背面法とポストの併用

併用前後でパラメータの調整が異なるので純粋な比較ではないですが、ポストなアウトラインを乗せることで、今まで線が引けていなかった前髪やその他の部分にも綺麗に引けるようになりました。

SMAAのエッジ検出パスを組み込めば破線を排除でき、より綺麗な線を表現できますが、そういう品質向上はまた今度気が向いた時にこっそり進めるとします。まずは土台であるパイプライン構築を爆速で進めたいのです。

アウトラインのマスク

アウトラインを引きたくない箇所の対応をします。

独自パス群でGバッファへのアクセスやシェーダーの書き換えが自由に出来るので簡単なのです。

rcpを使っていますが普通にハードコードで良いと思います。0.0未満がdiscardされるのですが、GetMaterialOutlineOpacityGetMaterialOpacitysaturateするのが基本設計なので、その結果に1.0/255.0を減算して0.0未満を作り出している感じです。このあたりの計算仕様はUEに準拠しているので、私が敢えて面倒なフローを踏んでいるとかではないです。

#if TOON_OUTLINE
	float MaterialOpacity = GetMaterialOutlineOpacity(PixelMaterialInputs);
	clip(MaterialOpacity - rcp(255.0));
#else
	float MaterialOpacity = GetMaterialOpacity(PixelMaterialInputs);
	clip(MaterialOpacity - rcp(255.0));
#endif  // TOON_OUTLINE

試しに目元のアウトラインを消してみます。問題なさそうですね。

アウトラインのゴースティング対策

背面法の計算はUnityのスクリーンスペースな実装を参考にしています。

オーバーレイマテリアルの記事はアンチエイリアスにSMAAを使用していました。本環境はTSRです。なにが言いたいかというと上記で実装したCustomノードを流用すると、カノギで触れたように、前フレームのWPO計算に不備が発生します。

マテリアルエディタで組んだノードから展開されたHLSLを一部抜粋するとこんな感じです。赤枠で囲ったところは関数名や変数名の頭にPrevが付いています。このPrevは名前から想像に容易いと思いますが、前フレームの行列や変数を返してくれています。このPrevがあることで前フレームにそのキャラ(描画物)が画面上のどこにいたかを復元できます。その復元された座標と現座標の差分から移動方向、速度が求まります。この速度はTSRの履歴参照で用いられるため、ゴースティングの抑制に置いて最も重要なのです。マジで重要。これがズレるとゴースティングの始まりです。

通常のノードであればこのPrevが自動的に付与されるのですが、Customノードで書かれた実装はこれが適用されません。ちなみに適用されないことが正しいです。内部で意図的にPrev変数を参照しないことも考えられるので、勝手に書き換えられたら回避不可能な迷惑仕様なのです。

Customノードでこの仕様を満たすにはPreviousFrameSwitchノードで明示的に実装をすれば良いです。ちゃんと用意されているのは有難いですね。

ただし、スクリーンスペースな頂点オフセットの改変は、これだけだと場合によっては対策不十分です。

PreviousFrame対応をした後ですが動き出しの最初の数フレーム間、少しだけアウトラインがズレていることが視認できます。動きが一定になってくると人間の眼が勝手に補間しちゃって問題を認識できなくなります。

この程度であれば気にならないかもしれませんが、あくまで人間が気にならないだけです。TSRは重みに応じて履歴に書き込みます。つまりその場面では多少の不一致でも、それは黒歴史のようにある一定の時間が過ぎるまで履歴に残り続けるので、ゴースティングの温床になるのです。

あくまで経験則ですが、スクリーンスペースな実装はキャラやカメラの左右移動にはある程度耐えられます。ただし、奥行き方向になると途端に動画のような問題が現れます。シンプルな膨張ではないのでおそらくそもそもの座標が不適切なんでしょうね。『再帰速度が不十分だからでは?』と疑うかもしれませんが、動作環境は120FPSです。若干過剰な設定としてモーションブラーの量を1.0にしていますが、仮に0.0にしても知覚が難しいだけで、現象自体は発生しています。

この現象を純正で直すのはかなり難しいので、素直にエンジンをいじって解消しちゃいます。同様に120FPSでモーションブラーの量が1.0ですが、限りなく不一致が抑制できていることが確認できます。

実はこの対策方法、設計だけ考えて実装をしたのはCoffeeLiveが初です。想定通りの結果が出たのでかなりご機嫌です。むふふ。

ゲームとかであれば多少のズレは許容されてもいいと思いますが、魅せるという前提環境では、スクショが存在します。えぇ。崩壊した場面を撮られるのは大変まずいのです。キャプチャをキャッチしてTSRの結果が安定するまで回し続けるという回避方法も存在しますが、実装が地味に面倒なのと、根本的な原因を潰したかったので、これを機に試した感じでした。

Velocityバッファを操作する際の注意点

速度の書き込みは行わないケースがあります。

UEさんは描画まわりの最適化、一部場所ではかなり攻めています。

そのひとつにVelocityバッファがあります。

画面にStaticなオブジェクトだけ、エフェクトがなかったり、スケルタルメッシュがない場合は、速度が書き込まれることはありません。その場合、速度バッファのクリアパスを削減できます。参照するバッファをダミーにしたり、バリエーションで速度がない版を実行して最適化している感じです。つまり、コンポジットする時やフェッチする時、なにも考えずに取ろうとすると、クリアされていないバッファを参照することになります。

クリアされていないバッファをフェッチすると、赤枠のような見た目で分かりやすい不具合が発生します。プラットフォームによってはそもそもクラッシュします。

ゼロ除算と同じように、クリアしていない特有の症状が現れるので、原因推定は容易ですが、稀に忘れることもありますのでね。ゼロ除算は強制発光だから大体ピカピカとかね。

速度バッファはプラグイン拡張でも弄れちゃうので、触る際はこんな感じでクリアされているかを確認しておきましょう。

const bool bWriteVelocity = HasBeenProduced(SceneTextures.Velocity);

この辺りはいっぱいバグらせて、いろんな子と遭遇して、症状と対処法を紐付けしておくと、仮に起きちゃっても爆速で原因推定と修正が出来るようになります。

おわり!!!

アウトラインは色々と苦しめられたのでその恨みから結構ナレッジを持っている方です。たぶん。

嫌なことは回避したいのですがアウトラインは回避できないので、極力触れる時間を少なくするために瞬間火力を焚いて関わる時間を最小限に抑えるという手段を取っています。

このムッてお口。可愛いよね。いぇーい。ぴーす。

次回はシャドウするかマルチパスするかライティングするか迷い中です。

だいぶ欲求を発散できたのでちょっと下火です。

あとこの後作業する内容は大体面倒な領域なので億劫なのです。

好きでも面倒は面倒なのですよ。

セルルック関連

https://kafues511.jp/2025/06/14/4249/

店舗特典

各店舗特典が公開されましたね。迷います。

ライムライト・レモネードジャム|ゆずソフト
ゆずソフト最新作ライムライト・レモネードジャムのPC版公式サイトです。

価格帯に慣れているとなんとも思いませんが、数字に起こすとやっぱり高いですね。

とはいえこの値段帯は大衆の誤進入を防止する防波堤も兼ねていると思っているので、結構守ってほしいラインです。

無闇にお手頃価格にすると文化に理解の無い大衆が入ってくる恐れがあるのであんまり好かんのですよね。産業保護という観点では母数を増やすことが重要なのは理解していますが、その母数に面倒な輩が居ると文化が劣化するので嫌なのです。

エロゲは母数の極端な増加が早々あり得ない文化なので、そういった観点で居心地が良いのです。というか極端に増えたら世も末なのです。

ま、筆者さんは外の世界の情報をあんまり取り入れない閉鎖空間を心掛けているので、ぶっちゃけ世界が変わろうが関係ないんですがね。クレカもJCBだし。海外サイトはPayPal経由しているので意外となくても問題なし。現世では通勤と生活必需以外ではお外でない引きこもりだし。

プレ版は公式WEB通販で予約したので通常+店舗特典に絞って漁りますか。

げっちゅ屋とアニメイトは画像公開されてから決めようと思います。

残りはポチポチです。

地味な目標に前回購入した店舗数より最低+1するというのがあります。無事達成できてよかったです。これが未達になる時は転職とボーダーを定めています。自分の中でボーダーを引いておくとそれ以外は気に留めなくなるので、精神衛生上結構おすすめです。まぁ気分で転職活動しかけたこともつい最近ありましたが、そこは気分屋さんなので仕方がないのです。なんなら飽き性を抱えている割に4年も居続けていることが奇跡です。そろそろ限界な気はするのですが、いま下手に動くと個人開発に時間を割けなくなるので、タイミングが難しいなと思う今日この頃です。

好きなものに散財も出来たし、実装も順調だし、今日も良い日でした。