--[[pixelshader@PS_FlowHQ: // -------- Resources -------- Texture2D CurrColor : register(t0); // 現フレーム Texture2D PrevColor : register(t1); // 前フレーム SamplerState LinearClamp : register(s0); // -------- Parameters -------- cbuffer FlowHQParams : register(b0) { float2 InvResolution; // 1 / resolution float KernelRadiusF; // 2〜4 推奨 (例: 3 → 7x7) float GradientScale; // 勾配スケール補正 float MaxFlow; // 最大フロー長 (UV) float Epsilon; // 行列安定化用 }; // -------- Utility -------- float Luma(float3 c) { return dot(c, float3(0.299, 0.587, 0.114)); } float SampleLuma(Texture2D tex, float2 uv) { return Luma(tex.SampleLevel(LinearClamp, uv, 0).rgb); } // -------- Gradient -------- void ComputeGradients(float2 uv, out float Ix, out float Iy, out float It) { float dx = InvResolution.x; float dy = InvResolution.y; float c = SampleLuma(CurrColor, uv); float cx1 = SampleLuma(CurrColor, uv + float2(dx, 0)); float cx0 = SampleLuma(CurrColor, uv - float2(dx, 0)); float cy1 = SampleLuma(CurrColor, uv + float2(0, dy)); float cy0 = SampleLuma(CurrColor, uv - float2(0, dy)); Ix = (cx1 - cx0) * 0.5 * GradientScale; Iy = (cy1 - cy0) * 0.5 * GradientScale; float p = SampleLuma(PrevColor, uv); It = c - p; } float2 EstimateOpticalFlowHQ(float2 uv) { int KernelRadius = int(KernelRadiusF); float sumIx2 = 0.0; float sumIy2 = 0.0; float sumIxIy = 0.0; float sumIxIt = 0.0; float sumIyIt = 0.0; // 多点サンプリング(局所ウィンドウ) for (int y = -KernelRadius; y <= KernelRadius; y++) { for (int x = -KernelRadius; x <= KernelRadius; x++) { float2 suv = uv + float2(x * InvResolution.x, y * InvResolution.y); float Ix, Iy, It; ComputeGradients(suv, Ix, Iy, It); sumIx2 += Ix * Ix; sumIy2 += Iy * Iy; sumIxIy += Ix * Iy; sumIxIt += Ix * It; sumIyIt += Iy * It; } } // 正規方程式の解 float det = sumIx2 * sumIy2 - sumIxIy * sumIxIy; float2 flow = 0; if (abs(det) > Epsilon) { flow.x = (-sumIy2 * sumIxIt + sumIxIy * sumIyIt) / det; flow.y = ( sumIxIy * sumIxIt - sumIx2 * sumIyIt) / det; } // 制限 float len = length(flow); if (len > MaxFlow) flow *= MaxFlow / max(len, 1e-5); return flow; } struct PSInput { float4 pos : SV_Position; float2 uv : TEXCOORD0; }; float4 PS_FlowHQ(PSInput input) : SV_Target { float2 flow = EstimateOpticalFlowHQ(input.uv); return float4(flow, 0.0, 1.0); } ]] --[[pixelshader@PS_MotionBlur: // ============================================================ // Motion Blur Pass using Optical Flow Texture // 入力: CurrColor + Flow Texture // ============================================================ // -------- Resources -------- Texture2D CurrColor : register(t0); // 現フレーム Texture2D FlowTex : register(t1); // Flow Texture (RG) SamplerState LinearClamp : register(s0); // -------- Parameters -------- cbuffer MotionBlurParams : register(b0) { float BlurScale; float SampleCountF; // 8〜24推奨 }; struct PSInput { float4 pos : SV_Position; float2 uv : TEXCOORD0; }; float4 PS_MotionBlur(PSInput input) : SV_Target { float2 uv = input.uv; float2 flow = FlowTex.SampleLevel(LinearClamp, uv, 0).xy * BlurScale; float4 accum = 0.0; float weightSum = 0.0; int SampleCount = int(SampleCountF); [loop] for (int i = 0; i < SampleCount; i++) { float t = (i / (SampleCount - 1.0)) - 0.5; float w = 1.0 - abs(t) * 2.0; float2 suv = uv + flow * t; float4 c = CurrColor.Sample(LinearClamp, suv); accum += c * w; weightSum += w; } float4 result = accum / max(weightSum, 1e-4); return float4(result); } ]] --[[pixelshader@PS_TimeBlur: Texture2D CurrColor : register(t0); Texture2D PrevColor : register(t1); SamplerState LinearClamp : register(s0); cbuffer TimeBlurParams : register(b0) { float Mix; } struct PSInput { float4 pos : SV_Position; float2 uv : TEXCOORD0; }; float4 PS_TimeBlur(PSInput input) : SV_Target { float4 curr = CurrColor.Sample(LinearClamp, input.uv); float4 prev = PrevColor.Sample(LinearClamp, input.uv); return curr * (1 - Mix) + prev * Mix; } ]] --filter --label:ぼかし --track@blur_scale:強度,0.0,100.0,10.0 --track@_history_blend:速度,0.0,100.0,50.0 --group:高度な設定(上級者向け) --check@show_flow:フロー表示,false --track@max_flow:最大フロー長,0.0,1.0,0.05,0.1 --track@sample_count:サンプル数,4,64,64,1 --track@kernel_radius:カーネル半径,1,10,8,1 --track@epsilon:しきい値,0.0001,1.0,0.1,0.1 --track@gradient_scale:勾配スケール,0.1,2.0,120,0.01 local history_blend = _history_blend / 100 --math.exp(-3 * history_blend / 100) local id = obj.id local cache_a = "cache:ofmb_a" .. id local cache_b = "cache:ofmb_b" .. id ofmb = ofmb or {} ofmb[id] = ofmb[id] or {flow=nil,prev=nil} local prev = ofmb[id].prev local flow = ofmb[id].flow local w, h = obj.getpixel() obj.clearbuffer(cache_a, w, h) obj.clearbuffer(cache_b, w, h) if prev then obj.putpixeldata(cache_a, prev, w, h) end obj.pixelshader( "PS_FlowHQ", cache_b, { "object", cache_a }, { 1 / w, 1 / h, kernel_radius, gradient_scale, max_flow, epsilon }, "copy", "clamp" ) obj.pixelshader( "PS_TimeBlur", cache_a, { "object", cache_a }, { history_blend }, "copy", "clamp" ) ofmb[id].prev, _, _ = obj.getpixeldata(cache_a) obj.clearbuffer(cache_a, w, h) if flow then obj.putpixeldata(cache_a, flow, w, h) end obj.pixelshader( "PS_TimeBlur", cache_b, { cache_b, cache_a }, { history_blend }, "copy", "clamp" ) ofmb[id].flow, _, _ = obj.getpixeldata(cache_b) obj.pixelshader( "PS_MotionBlur", "object", { "object", cache_b }, { blur_scale / 100, sample_count }, "copy", "clamp" ) if show_flow then obj.copybuffer("object", cache_b) end