Support multiple vortices in latent swirl noise
This commit is contained in:
@@ -38,7 +38,7 @@ Generates smooth octave-based Perlin noise and adds it to the latent. Tune the b
|
||||
|
||||
### Latent Swirl Noise
|
||||
|
||||
Warps the latent around a seeded vortex using `grid_sample`. Control the maximum rotation (in radians), the falloff radius, centre jitter, and blend strength to inject painterly whirlpools or gentle twisting motion into the underlying features.
|
||||
Warps the latent around one or more seeded vortices using `grid_sample`. Control the vortex count, maximum rotation (in radians), falloff radius, centre jitter, and blend strength to inject painterly whirlpools or gentle twisting motion into the underlying features.
|
||||
|
||||
### Conditioning (Add Noise)
|
||||
|
||||
@@ -82,7 +82,7 @@ Image exploration rarely means chasing a single "correct" answer. These nodes ar
|
||||
- Follow the noise with `Conditioning (Gaussian Blur)` to soften abrupt emphasis changes or to merge multi-prompt blends into a single vibe. Blurring after noise often produces dreamlike, painterly shifts instead of chaotic drift.
|
||||
- Use `Conditioning (Scale)` as a volume knob while iterating: dial the factor down to 0.3–0.5 when you want the image to respond mostly to the recovered latent, then crank it above 1.5 when the textual guidance should take the lead.
|
||||
- Pair `Latent Gaussian Blur` with `Conditioning (Gaussian Blur)` for holistic smoothing—latent blur calms texture while conditioning blur calms prompt pacing. Reintroduce energy by sprinkling in `Add Latent Noise` or ramping the conditioning scale afterwards.
|
||||
- Sculpt structured detail by layering `Latent Perlin Fractal Noise` (shared mode for broad waves, per-channel for colour shifts) before blending back with `Latent Swirl Noise` to twist the resulting features into fluid patterns.
|
||||
- Sculpt structured detail by layering `Latent Perlin Fractal Noise` (shared mode for broad waves, per-channel for colour shifts) before blending back with `Latent Swirl Noise`—increase `vortices` for multi-spiral motion, or keep it at 1 for a single focal swirl.
|
||||
- Split a latent with `Latent Frequency Split`, sharpen or noise the high band while blurring the low band, then recombine via `Latent Mixer`/`Add` nodes to weave sharp detail onto soft composition.
|
||||
- Carve prompts with `Conditioning (Frequency Split)` and return them through `Conditioning (Frequency Merge)`: keep low-band text steady for the story while remapping the high band through noise, blur, or scale to emphasize only certain fragments.
|
||||
- Swap seeds between the inverter, forward diffusion, and noise nodes as you iterate. Small tweaks here can shift the interplay between latent detail and prompt guidance, rewarding playful experimentation.
|
||||
|
||||
+38
-18
@@ -216,17 +216,26 @@ def _normalized_meshgrid(height, width, device, dtype):
|
||||
return torch.meshgrid(ys, xs, indexing="ij")
|
||||
|
||||
|
||||
def _swirl_grid(base_y, base_x, center_x, center_y, strength, radius, direction):
|
||||
radius = max(radius, 1e-3)
|
||||
dx = base_x - center_x
|
||||
dy = base_y - center_y
|
||||
r = torch.sqrt(dx * dx + dy * dy)
|
||||
falloff = torch.exp(-torch.square(r / radius))
|
||||
angle = direction * strength * falloff
|
||||
sin_a = torch.sin(angle)
|
||||
cos_a = torch.cos(angle)
|
||||
x_new = dx * cos_a - dy * sin_a + center_x
|
||||
y_new = dx * sin_a + dy * cos_a + center_y
|
||||
def _swirl_grid(base_y, base_x, vortices):
|
||||
total_dx = torch.zeros_like(base_x)
|
||||
total_dy = torch.zeros_like(base_y)
|
||||
|
||||
for center_x, center_y, strength, radius, direction in vortices:
|
||||
radius = max(radius, 1e-3)
|
||||
dx = base_x - center_x
|
||||
dy = base_y - center_y
|
||||
r = torch.sqrt(dx * dx + dy * dy)
|
||||
falloff = torch.exp(-torch.square(r / radius))
|
||||
angle = direction * strength * falloff
|
||||
sin_a = torch.sin(angle)
|
||||
cos_a = torch.cos(angle)
|
||||
rotated_dx = dx * cos_a - dy * sin_a
|
||||
rotated_dy = dx * sin_a + dy * cos_a
|
||||
total_dx = total_dx + (rotated_dx - dx)
|
||||
total_dy = total_dy + (rotated_dy - dy)
|
||||
|
||||
x_new = base_x + total_dx
|
||||
y_new = base_y + total_dy
|
||||
grid = torch.stack((x_new, y_new), dim=-1)
|
||||
return torch.clamp(grid, -1.0, 1.0)
|
||||
|
||||
@@ -758,6 +767,12 @@ class LatentSwirlNoise:
|
||||
"required": {
|
||||
"latent": ("LATENT",),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"vortices": ("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 16,
|
||||
"tooltip": "Number of independent vortex centres to spawn per latent."
|
||||
}),
|
||||
"strength": ("FLOAT", {
|
||||
"default": 0.75,
|
||||
"min": 0.0,
|
||||
@@ -800,7 +815,7 @@ class LatentSwirlNoise:
|
||||
FUNCTION = "add_swirl_noise"
|
||||
CATEGORY = "Latent/Noise"
|
||||
|
||||
def add_swirl_noise(self, latent, seed, strength, radius, center_spread, direction_bias, mix):
|
||||
def add_swirl_noise(self, latent, seed, vortices, strength, radius, center_spread, direction_bias, mix):
|
||||
if strength == 0.0 or mix == 0.0:
|
||||
return (latent,)
|
||||
|
||||
@@ -823,19 +838,24 @@ class LatentSwirlNoise:
|
||||
center_spread = max(0.0, min(center_spread, 1.0))
|
||||
mix = max(0.0, min(mix, 1.0))
|
||||
bias_threshold = max(0.0, min(1.0, (direction_bias + 1.0) * 0.5))
|
||||
vortex_count = max(1, int(vortices))
|
||||
|
||||
cpu_generator = torch.Generator(device="cpu").manual_seed(seed)
|
||||
|
||||
total = working.shape[0]
|
||||
|
||||
for idx in range(total):
|
||||
rand_vals = torch.rand(3, generator=cpu_generator)
|
||||
offsets = (rand_vals[:2] * 2.0 - 1.0) * center_spread
|
||||
center_y = offsets[0].item()
|
||||
center_x = offsets[1].item()
|
||||
direction = 1.0 if rand_vals[2].item() < bias_threshold else -1.0
|
||||
rand_vals = torch.rand((vortex_count, 3), generator=cpu_generator)
|
||||
vortex_params = []
|
||||
|
||||
grid = _swirl_grid(base_y, base_x, center_x, center_y, strength, radius, direction).unsqueeze(0)
|
||||
for vortex_idx in range(vortex_count):
|
||||
offsets = (rand_vals[vortex_idx, :2] * 2.0 - 1.0) * center_spread
|
||||
center_y = offsets[0].item()
|
||||
center_x = offsets[1].item()
|
||||
direction = 1.0 if rand_vals[vortex_idx, 2].item() < bias_threshold else -1.0
|
||||
vortex_params.append((center_x, center_y, float(strength), float(radius), direction))
|
||||
|
||||
grid = _swirl_grid(base_y, base_x, vortex_params).unsqueeze(0)
|
||||
|
||||
sample = working[idx:idx + 1]
|
||||
warped = F.grid_sample(
|
||||
|
||||
Reference in New Issue
Block a user