Releases: routersys/YMM4-PerspectiveShadow
v1.0.0
v1.0.0 - 影(立体投影)for YMM4
YukkuriMovieMaker4 向けの透視投影落ち影エフェクトプラグインの初回リリースです。
仮想点光源と接地面を定義し、透視変換に基づく立体的な落ち影を GPU 上でリアルタイム生成します。
HLSL ピクセルシェーダーによる逆投影サンプリング・距離連動動的ブラー・黄金角スパイラルサンプリング・
距離減衰・シルエットしきい値によるソフトシルエット生成・
全パラメータのキーフレームアニメーション対応・ビューポートコントローラーポイント・
8 言語対応 UI を備えた映像エフェクトプラグインです。
新機能
1. エフェクト定義(PerspectiveShadowEffect)
PerspectiveShadowEffect は VideoEffectBase を継承します。
[VideoEffect] 属性は以下のパラメーターで宣言されます。
- 表示名:
Texts.PerspectiveShadowEffectName(ローカライズキー) - カテゴリー:
VideoEffectCategories.Decoration - 検索タグ:
"影(立体投影)"・"透視投影"・"3D影"・"落ち影"・"影" IsAviUtlSupported = falseにより AviUtl 向け EXO 出力は非対応です。ResourceType = typeof(Texts)でローカライズリソースを指定します。
LabelプロパティはTexts.PerspectiveShadowEffectNameを返却します。
公開プロパティは以下のとおりです。
光源グループ(Texts.PerspectiveShadowGroupLight)
-
LightX(Animation、デフォルト0、内部範囲VerySmallValue〜VeryLargeValue):
[AnimationSlider("F1", "px", -1000d, 1000d)]でスライダー操作範囲 −1000〜1000 px として表示されます。
光源の水平位置を指定します。 -
LightY(Animation、デフォルト-500、内部範囲VerySmallValue〜VeryLargeValue):
[AnimationSlider("F1", "px", -1000d, 1000d)]でスライダー操作範囲 −1000〜1000 px として表示されます。
光源の垂直位置を指定します。 -
LightHeight(Animation、デフォルト2000、内部範囲1〜VeryLargeValue):
[AnimationSlider("F1", "px", 50d, 2000d)]でスライダー操作範囲 50〜2000 px として表示されます。
光源の仮想的な高さを指定します。小さいほど影が長く伸びます。 -
GroundY(Animation、デフォルト0、内部範囲VerySmallValue〜VeryLargeValue):
[AnimationSlider("F1", "px", -1000d, 1000d)]でスライダー操作範囲 −1000〜1000 px として表示されます。
影が落ちる接地面の Y 位置を素材下端からのオフセット (px) で指定します。
影の見え方グループ(Texts.PerspectiveShadowGroupAppearance) -
Opacity(Animation、デフォルト75、内部範囲0〜100):
[AnimationSlider("F1", "%", 0d, 100d)]でスライダー操作範囲 0〜100 % として表示されます。
影全体の不透明度を指定します。 -
Falloff(Animation、デフォルト40、内部範囲0〜100):
[AnimationSlider("F1", "%", 0d, 100d)]でスライダー操作範囲 0〜100 % として表示されます。
影の先端に向かって薄くなる強さを指定します。 -
BlurRadius(Animation、デフォルト2、内部範囲0〜256):
[AnimationSlider("F1", "px", 0d, 32d)]でスライダー操作範囲 0〜32 px として表示されます。
影のソフトエッジ基本半径を指定します。 -
Spread(Animation、デフォルト0.30、内部範囲0〜1):
[AnimationSlider("F2", "", 0d, 1d)]でスライダー操作範囲 0〜1 として表示されます。
距離に応じてブラーエッジが拡散する強さを指定します。 -
AlphaThreshold(Animation、デフォルト0.05、内部範囲0〜1):
[AnimationSlider("F2", "", 0d, 1d)]でスライダー操作範囲 0〜1 として表示されます。
シルエットとして扱う不透明度のしきい値を指定します。 -
ShadowColor(Color、デフォルトColor.FromArgb(255, 0, 0, 0)):
[ColorPicker]でカラーピッカーとして表示されます。デフォルト値は不透明黒(A=255, R=0, G=0, B=0)です。
プロパティ変更時はSetメソッドで変更通知が発行されます。
CreateExoVideoFiltersは空のIEnumerable<string>を返却し、EXO 向けフィルター出力は実装されていません。
CreateVideoEffect(IGraphicsDevicesAndContext devices) は new PerspectiveShadowEffectProcessor(devices, this) を返却します。
GetAnimatables は LightX・LightY・LightHeight・GroundY・Opacity・Falloff・BlurRadius・Spread・AlphaThreshold の 9 アニメーションプロパティを返却します。
ShadowColor は Color 型であり、GetAnimatables に含まれずキーフレームアニメーションの対象外です。
2. エフェクトプロセッサー(PerspectiveShadowEffectProcessor)
PerspectiveShadowEffectProcessor は VideoEffectProcessorBase を継承し、
コンストラクターで IGraphicsDevicesAndContext devices と PerspectiveShadowEffect item を受け取ります。
内部フィールドとして effect(PerspectiveShadowCustomEffect?)・isFirst(bool)・
lightX・lightY・lightHeight・groundY・opacity・falloff・blurRadius・spread・alphaThreshold(各 double)・
shadowColor(Color)を保持します。
CreateEffect メソッド
CreateEffect(IGraphicsDevicesAndContext devices) は以下の手順でエフェクトを構築します。
new PerspectiveShadowCustomEffect(devices)でシェーダーエフェクトを生成します。IsEnabledがfalseの場合はDispose()してnullを返却します。- 有効と判断した場合は
disposer.Collect(effect)で破棄管理に登録します。 effect.Outputを取得・disposer.Collect後、これを戻り値として返却します。
本エフェクトはシングルパス構成です。トライトーンのような複数エフェクトチェーン(Composite / Blend / CrossFade)は持たず、シェーダー単体で影の生成・合成をすべて完結させます。
setInput / ClearEffectChain メソッド
setInput(ID2D1Image? input) は effect?.SetInput(0, input, true) でスロット 0 に入力を設定します。
ClearEffectChain は effect?.SetInput(0, null, true) でスロット 0 の入力を解除します。
Update メソッド
Update(EffectDescription effectDescription) は毎フレーム呼び出されます。
IsPassThroughEffect が true、または effect が null の場合は effectDescription.DrawDescription をそのまま返却します。
effectDescription から frame(ItemPosition.Frame)・length(ItemDuration.Frame)・fps(FPS)を取得し、各アニメーション値を評価します。
-
lightX:item.LightX.GetValue(frame, length, fps) -
lightY:item.LightY.GetValue(frame, length, fps) -
lightHeight:item.LightHeight.GetValue(frame, length, fps) -
groundY:item.GroundY.GetValue(frame, length, fps) -
opacity:item.Opacity.GetValue(frame, length, fps) -
falloff:item.Falloff.GetValue(frame, length, fps) -
blurRadius:item.BlurRadius.GetValue(frame, length, fps) -
spread:item.Spread.GetValue(frame, length, fps) -
alphaThreshold:item.AlphaThreshold.GetValue(frame, length, fps) -
shadowColor:item.ShadowColorを直接参照します。
isFirstがtrueの場合、または前フレームからの差分がある場合に限り、以下の更新を行います。 -
effect.LightX = (float)lightX -
effect.LightY = (float)lightY -
effect.LightHeight = (float)lightHeight -
effect.GroundY = (float)groundY -
effect.Opacity = (float)opacity / 100f(0〜100 → 0〜1 正規化) -
effect.Falloff = (float)falloff / 100f(0〜100 → 0〜1 正規化) -
effect.BlurRadius = (float)blurRadius -
effect.Spread = (float)spread -
effect.AlphaThreshold = (float)alphaThreshold -
effect.ShadowColor = new Vector4(shadowColor.R / 255f, shadowColor.G / 255f, shadowColor.B / 255f, shadowColor.A / 255f)
更新後はisFirst = falseをセットし、全キャッシュフィールドを現在値で上書きします。
ビューポートコントローラーポイント
Update の最後に VideoEffectController を生成し、DrawDescription の Controllers に追加して返却します。
コントローラーポイントは 1 点で、座標は (lightX, lightY, 0f) です。
ドラッグ操作時のデルタは item.LightX.AddToEachValues(arg.Delta.X) および item.LightY.AddToEachValues(arg.Delta.Y) として全キーフレームに一括適用されます。
3. カスタムシェーダーエフェクト(PerspectiveShadowCustomEffect)
PerspectiveShadowCustomEffect は D2D1CustomShaderEffectBase を継承します。
コンストラクターは Create<EffectImpl>(devices) を呼び出します。
公開プロパティとして LightX・LightY・LightHeight・GroundY・Opacity・Falloff・
BlurRadius・Spread・ShadowColor(Vector4)・AlphaThreshold の 10 個のプロパティを持ちます。
float 型プロパティは SetValue((int)EffectImpl.Properties.XXX, value) / GetFloatValue(...) で、
Vector4 型の ShadowColor は SetValue((int)EffectImpl.Properties.ShadowColor, value) / GetVector4Value(...) で Direct2D プロパティシステムと通信します。
EffectImpl 内部クラス
EffectImpl は D2D1CustomShaderEffectImplBase<EffectImpl> を継承し、
[CustomEffect(1)] 属性により入力テクスチャ 1 枚のカスタムエフェクトとして宣言されます。
ConstantBuffer 構造体
[StructLayout(LayoutKind.Sequential)] で宣言された ConstantBuffer 構造体は
12 個の float フィールドおよび 1 個の Vector4 フィールドを以下の順序で保持します。
| フィールド | レジスター | 役割 |
|---|---|---|
LightX |
c0.x | 光源の水平位置 |
LightY |
c0.y | 光源の垂直位置 |
LightHeight |
c0.z | 光源の仮想高さ |
GroundY |
c0.w | 解決済み接地線 Y 座標(絶対座標) |
Opacity |
c1.x | 影の不透明度(0〜1) |
Falloff |
c1.y | 距離減衰強度(0〜1) |
BlurRadius |
c1.z | ぼかし基本半径 |
Spread |
c1.w | 距離連動ブラー拡散率 |
ShadowColor |
c2.xyzw | 影の色(RGBA、0〜1 正規化) |
AlphaThreshold |
c3.x | シルエットしきい値(0〜1) |
Pad0 |
c3.y | パディング |
Pad1 |
c3.z | パディング |
Pad2 |
c3.w | パディング |
この配置は HLSL 定数バッファーの packoffset 宣言と厳密に対応します。
Properties 列挙型
Properties 列挙型はパディングを除く 10 フィールドを int 値 0〜9 でマッピングします。
| 列挙値 | 値 |
|---|---|
LightX |
0 |
LightY |
1 |
LightHeight |
2 |
GroundY |
3 |
Opacity |
4 |
Falloff |
5 |
BlurRadius |
6 |
Spread |
7 |
ShadowColor |
8 |
AlphaThreshold |
9 |
各プロパティは [CustomEffectProperty(PropertyType.Float, (int)Properties.XXX)] で修飾されます。
ShadowColor のみ PropertyType.Vector4 で宣言されます。
入力値のクランプ
C# 側のセッターは以下のクランプ処理を施します。
LightHeight:Math.Max(value, 1f)(最小 1 px)Opacity:Math.Clamp(value, 0f, 1f)Falloff:Math.Clamp(value, 0f, 1f)BlurRadius:Math.Max(value, 0f)Spread:Math.Clamp(value, 0f, 1f)AlphaThreshold:Math.Clamp(value, 0f, 1f)
LightX・LightY・GroundY・ShadowColorはクランプなしで直接設定されます。
コンストラクター
base(ShaderResourceUri.Get("PerspectiveShadow")) を呼び出します。
これによりコンパイル済みシェーダーオブジェクト(.cso)が WPF リソースとして読み込まれます。
UpdateConstants
drawInformation?.SetPixelShaderConstantBuffer(_cb) を呼び出して
ConstantBuffer 構造体をピクセルシェーダーの定数バッファーレジスター b0 へ転送します。
MapInputRectsToOutputRect(出力矩形計算)
本エフェクトは影の投影先を包含するよう出力矩形を動的に拡張します。処理は以下の手順で行われます。
_resolvedGroundY = inputRect.Bottom + _groundYOffsetとして接地線の絶対座標を確定し、_cb.GroundYを更新します。- 素材の四隅(左上・右上・左下・右下)それぞれについて
ProjectToGroundで影の投影先座標を計算し、
その座標の最小・最大値を累積して出力矩形の候補範囲を求めます。 - 各コーナーで
shadowDistとspreadからComputeDynamicBlurを評価し、最大のブラー量maxDynBlurを求めます。 blurMargin = ceil(maxDynBlur) + 2を余白として四辺に加算します。- 出力矩形は上記の拡張範囲を素材矩形から最大 4096 px の範囲に制限して確定します。
ProjectToGroundは以下の射影計算を行います。
t = min(H / max(H - h, ε), 10.0) ただし h = max(0, G - Q.Y)
Sx = lightX + (Q.X - lightX) × t
Sy = lightY + (G - lightY) × t
t は最大 10.0 にクランプされ、影が無限遠に伸びることを防ぎます。
ComputeDynamicBlur は以下の式でブラー量を算出します。
expansion = min(shadowDist / max(lightHeight, 1) × spread, 3.0)
dynamicBlur = blurRadius × (1 + expansion)
MapOutputRectToInputRects(入力矩形計算)
MapOutputRectToInputRects は出力矩形の境界上の点群を UnprojectFromShadow で逆変換し、
必要な入力矩形を計算します。処理は以下の手順で行われます。
- 出力矩形の境界を
4 × 4(Subdivisions = 4)のグリッドに分割した境界上の点(内部点を除く辺上の点のみ)を列挙します。 - 各点を
UnprojectFromShadowで逆変換し、有効な投影元座標 Q を得ます。 - Q の位置に
ComputeDynamicBlurのブラー余白を加えた範囲で入力矩形を拡張します。 - 光源座標が出力矩形内に含まれる場合は、入力矩形全体を素材の境界まで拡張します(光源が領域内にある場合、任意の座標が素材全体を参照しうるため)。
- 最終的な入力矩形に ±2 px のマージンを付加して返却します。
4. HLSL ピクセルシェーダー(PerspectiveShadow.hlsl)
シェーダーモデル ps_5_0・エントリーポイント main でコンパイルされます。
リソース宣言
- `Texture2D InputTex...