Nodes/ComfyUI CV/CV Vector Displacement Map
ComfyUI Node

CV Vector Displacement Map

Warping with a direction, not just an amount

By bmad4ever·Created 4 months ago·Updated 15 days ago· 1
CV Vector Displacement Map
  • map_x
  • map_y
  • vector_field
  • dx
  • dy
  • map_x
  • map_y
◄strength_x10.0►
◄strength_y10.0►

The pack has two displacement nodes and the difference is one word: vector. DisplacementMap drives both axes from the same grayscale value - you can push pixels further or less far, but always along the same diagonal-and-slope direction, because one number can't say "left" separately from "up". CV Vector Displacement Map adds a per-pixel (dx, dy) field, so every pixel can move in its own direction by its own amount.

That's the difference between a bump and a swirl.

How it works

The node works on the coordinate maps rather than the image. You feed in map_x and map_y - the source-coordinate maps you'd hand to cv2_remap - and it adds the displacement into them:

map_x' = map_x + strength_x * dx
map_y' = map_y + strength_y * dy

Those two outputs then go to cv2_remap (with WARP_RELATIVE_MAP set - see CV Warp Flags). Because you're editing coordinates rather than pixels, these nodes compose: chain an identity map through a wave map, a vector map, a pinch map, and you get all three distortions in one remap, in one resampling. That's a much better deal than warping an image four times and blurring it a little each pass.

The chain always starts from CV Identity Map (absolute coordinates) or CV Relative Identity Map (offsets from zero, which is what WARP_RELATIVE_MAP wants).

Inputs and outputs

map_x / map_y are required, same shape, and are the incoming chain. strength_x / strength_y (default 10 each) are plain multipliers in pixels on the displacement - the knobs you'll actually turn, and they let one displacement source drive different magnitudes on each axis.

Then the displacement source itself, optional and with a precedence rule:

  • vector_field - a multi-channel image. HxWx2 is a direct vector field; HxWx3/HxWx4 is a normal map, using R as dx and G as dy; HxW applies the same value to both axes. uint8 inputs are mapped to [-1, 1], so mid-gray means "don't move" - that's what makes a tangent-space normal map work directly. float inputs are used as raw pixel offsets, no mapping.
  • dx / dy - single-channel HxW arrays, and they override vector_field when connected. This is where optical flow goes if you've computed it: flow's X and Y components are exactly this.

Outputs are map_x and map_y, ready to continue the chain or feed the remap.

That uint8-vs-float distinction is the one detail that decides whether your warp works, so it's worth saying twice. A normal map from any standard source is uint8 with 128 as neutral, and this node handles that. A float displacement field of your own construction is treated as literal pixels of movement. The two are not interchangeable, and a float field containing 0-255 values will send your image roughly 200 pixels sideways.

Where it earns its place

Flow-driven warping. Feed optical flow in and the current frame gets warped toward the next one - frame interpolation's dumb cousin (the pack's CV Flow Interpolate does the real job with a proper blend).

Normal-map-driven relighting. A tangent-space normal map as a displacement field gives the "emboss on a curved surface" look, and separate per-axis strengths let you exaggerate the X shading on its own.

Depth-driven parallax. A depth map whose x-gradient drives dx gives the classic dolly effect on a still - the illusion that the camera moved sideways. Same family as depth-estimation.md: one photo in, depth out, depth used to fake motion that was never filmed.

Installing it

Manager → ComfyUI CV, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart. Python ≥ 3.12, a recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93. No models. GPL-3.0, forked from opencv-comfyui, and the author's standing caution applies: this is LLM-assisted code, several nodes in this pack are genuine algorithm implementations rather than thin cv2 wrappers, and this is one of the remap-family composition nodes you should sanity-check once with a known input before you build a pipeline on it.

Where people get burned

Remapping without WARP_RELATIVE_MAP. Absolute coordinate maps and relative offsets look the same in a graph and behave completely differently. If your output is a smear, uniform shift, or a solid block of border colour, that flag is the first suspect - that's why CV Warp Flags exists as its own node.

Shape mismatches. map_x and map_y must share a shape, and in practice also share the shape of the image you eventually remap. A displacement field sampled at a different resolution than the map chain gives you a mess that's hard to read as an error.

Confusing strength with scale. strength_x = 0 disables X regardless of what the field says, which is a perfectly good way to isolate one axis while debugging.

Four remaps in a row. Each one resamples. Compose the maps (they're just arrays) and remap once - that's the whole design intent of this node family, and it's the difference between a clean warp and mush.

Categoryimage/CV/remap

Inputs (7)

NameTypeDefaultDescription
map_xNPARRAYIncoming source-x coordinate map. Start the chain with 'CV Identity Map' or 'CV Relative Identity Map'.
map_yNPARRAYIncoming source-y coordinate map (same shape as map_x).
strength_xFLOAT10.0-10000–10000Multiplier for the X displacement in pixels.
strength_yFLOAT10.0-10000–10000Multiplier for the Y displacement in pixels.
vector_fieldoptNPARRAY,IMAGE,MASKMulti-channel displacement source: uses ch0 as dx, ch1 as dy. HxWx2 (direct vector field), HxWx3/4 (normal map: R->dx, G->dy), or HxW (same for both axes). uint8 mapped to [-1,1]; float used as-is. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size.
dxoptNPARRAYPer-pixel X displacement (overrides vector_field when connected). Single-channel HxW.
dyoptNPARRAYPer-pixel Y displacement (overrides vector_field when connected). Single-channel HxW.

Outputs (2)

NameTypeDescription
map_xNPARRAYDisplaced source-x coordinate map.
map_yNPARRAYDisplaced source-y coordinate map.