Tool Development: Resolve, Fusion, Custom Code #2: RGB Mixer
In this blog post, we’re building a custom RGB Mixer tool—a deceptively simple concept that opens up a whole new layer of creative control. It keeps the maths straightforward, but introduces powerful possibilities as we start to manipulate colour channels in relation to one another, all while staying within the familiar world of RGB. Later in this series, we’ll explore other colour models, but for now, there’s a lot we can do right here—by letting our RGB channels interact in new ways.
Up to this point, we’ve treated the red, green, and blue channels as independent—modifying each one separately. This time, we’re going to explore what happens when we mix these channels together, allowing them to influence each other.
So, what does an RGB mixer actually do?
At its core, an RGB mixer allows you to route the strength of one channel into another. For example, if your red channel is particularly strong and your blue channel is weaker, you can “borrow” some of the red and blend it into the blue—helping to rebalance the image or achieve a specific look.
This is the fundamental idea behind RGB mixing: controlled crosstalk between channels. You’re essentially letting the channels bleed into each other in a deliberate, creative way, producing a rewired and often more expressive version of the image.
Before we jump into node trees or implementation, it’s worth understanding the underlying formula. RGB mixers work by adding weighted contributions from other channels into each output channel. The formula typically involves summing channels and then multiplying each contribution by a specific value.
To keep things neutral by default, we start with all contribution values set to zero—so nothing changes at first. Then, as we raise a value, we increase the influence of one channel over another.
For example, the red channel’s formula with no change might look something like this:
red = red + (green * 0) + (blue * 0)
As we begin increasing the strength of those zeros in our formula, we’re effectively increasing the contribution of the green and blue channels into the red channel. And with that core concept in mind, we’re ready to dive into the first of our three environments:
The Colour Page
The most straightforward way to create an RGB mixer in DaVinci Resolve is, of course, to use the built-in RGB Mixer tool. It’s designed by the Resolve developers specifically for this type of channel blending.
But while that’s great for quick adjustments, it doesn’t give us much insight into how the tool actually works under the hood. So instead, we’re going to build our own version—from scratch—using a custom node tree. This might get a little wild, but following the logic will help you deeply understand how the native RGB Mixer actually functions.
Step 1: Split the Image into RGB Channels
To start, we need individual access to the red, green, and blue channels. We can achieve this using a Splitter/Combiner node.
Here’s how:
Go to the Colour menu in the top bar.
Navigate to Nodes → Add Splitter/Combiner Node.
You’ll now see three nodes appear on your Colour page, all connected to an input and output with red, green, and blue symbols. What this has done is split your image into its three colour channels, which we can now manipulate independently.
A Splitter Combiner node.
Step 2: Understand the Structure of the RGB Mixer
If you look at Resolve’s built-in RGB Mixer, you’ll notice it provides nine main controls:
Red in Red, Green in Red, Blue in Red
Red in Green, Green in Green, Blue in Green
Red in Blue, Green in Blue, Blue in Blue
So, we need to recreate each of those control points in our node tree. This is where things start to get a little complex—but if you follow the logic, it’ll all make sense.
Step 3: Rebuild the Red Output
Let’s start with the Red output. By default, the splitter sends the red channel into a node—that’s your Red in Red control. The name comes from the fact that the node is connected to the Red input of the splitter and is feeding into the Red output of the combiner.
Now let’s add the Green in Red contribution.
Select your Red in Red node, and add a Layer Mixer.
This creates a new node connected to a fresh Layer Mixer alongside the original.
Remember our formula: we want to add the green channel and multiply it by a value (starting at 0 for no effect).
To handle the addition part:
Right-click the Layer Mixer, choose Composite Mode, and set it to Add.
This tells Resolve: “Take the outputs of these nodes and add them together.”
Now we need this new node to pull from the green channel, not red. So:
Break the connection between this node and the red input on the splitter.
Reconnect it to the green input.
At this point, your image should look overly red—and that’s expected. You’ve just told Resolve to take the full green channel and add it to the red channel, doubling up and blowing out that part of the image.
To control the intensity of this crossover:
Go to the gain wheel of that green node and reduce it to 0.
This brings the image back to its original state—no visible change—but now you’ve created a manual Green in Red control, just like the slider in Resolve’s RGB Mixer.
Step 4: Complete the Red Row
Now it’s just a matter of repeating the process:
Create another node.
Connect it to the blue input of the splitter.
Feed it into the same Layer Mixer.
Lower the gain to 0.
Now you’ve got Blue in Red.
At this point, your Layer Mixer contains Red in Red, Green in Red, and Blue in Red—each controlled by the gain of their respective nodes.
Red in Red, Green in Red, and Blue in Red nodes.
Step 5: Rebuild the Green and Blue Outputs
You’ll now repeat the exact same structure for the Green output and the Blue output—each with their own Layer Mixers and contributions from all three channels.
I’ll spare you the repetition in text, but I’ve included an image below showing the complete node tree. This rebuilds the RGB mixer entirely and gives you full manual control over all nine crossover points—exactly like the native RGB Mixer tool, but built by hand, with full visibility into what’s actually happening.
Custom RGB Mixer: Blue in Green set to 0.5, and Green in Green set to 0.5 to create red/cyan compression.
2. The Fusion Page
Now let’s rebuild this same RGB Mixer inside Fusion—and the good news is, if you’ve followed along with the previous blog posts and are now familiar with the Custom Tool, this part is going to feel much simpler compared to the complex node tree we just built in the Colour page.
Why? Because instead of piecing together formulas through a series of nodes, we can now just write the logic directly in the Channels tab of the Custom Tool. No more chaining mixers or finessing gain wheels—we can express everything cleanly, mathematically, and in one place.
It will look something like this:
Red = (r1 * n1) + (g1 * n2) + (b1 * n3) Green = (r1 * n4) + (g1 * n5) + (b1 * n6) Blue = (r1 * n7) + (g1 * n8) + (b1 * n9)
Now that we’ve set up our RGB Mixer in Fusion, the next step is to configure the initial values for our sliders.
Default Values
To recreate a neutral, “do-nothing” starting point (just like the native RGB Mixer), we’ll set:
Red in Red to
1
Green in Green to
1
Blue in Blue to
1
All other contributions (e.g. Green in Red, Red in Green, etc.) to
0
This setup ensures that, by default, each output channel is influenced only by its matching input channel.
Fusion's Slider Limitation
However, you may notice a limitation: Fusion's Custom Tool only allows a maximum of 8 sliders. That means we can’t control all 9 contributions individually—the ninth one (n9), which would correspond to Blue in Blue, doesn’t link to any slider and therefore doesn’t work.
So while our formula is correct in principle, Blue in Blue will remain non-functional in this Fusion implementation.
Extending the Tool: Dynamic Colour Crosstalk
Despite that minor hiccup, this setup already replicates the complex node tree from the Colour page with far less effort. But now that we’re in Fusion, we can extend this tool in ways that weren’t possible before.
One such feature is dynamic colour crosstalk—where we let multiple contributions within a channel shift together while maintaining overall image balance.
Manual Example
Let’s model this manually to demonstrate:
Set Red in Green (n4) to 0.5. This increases the red contribution to the green channel.
Set Green in Green (n5) to 0.5. This reduces the green channel’s self-contribution.
The result? You’ll notice an interesting hue shift—but the overall image balance stays intact. It’s a kind of neutral-preserving modification, where desaturated colours are minimally affected, and saturated colours are pulled more strongly—creating a sophisticated, stylised look.
Automating the Crosstalk
While this result is powerful, the current process is tedious. You have to manually increase one slider, and then manually decrease another to match—not ideal for a fast, intuitive grading workflow.
But since we’re working in Fusion, we can take this one step further and automate this inverse relationship directly in our formulas.
The concept is simple:
Whatever value we add to our cross-channel contributions (e.g. Green in Red), we subtract from the dominant channel (e.g. Red in Red).
So, for example:
If Green in Red is set to
0.4
, then Red in Red becomes1 - 0.4 = 0.6
.If Blue in Red is set to
0.2
, then Red in Red becomes1 - 0.2 = 0.8
.
This inverse relationship means we don’t have to manually balance the channels—the maths does it for us. It allows for quick, creative exploration while maintaining image neutrality, which is ideal for colour grading workflows.
Updated Formulas
With this logic in place, we can update the channel formulas to this:
Red = r1 * (1 - n1 - n2) + (g1 * n1) + (b1 * n2) Green = (r1 * n3) + g1 * (1 - n3 - n4) + (b1 * n4) Blue = (r1 * n5) + (g1 * n6) + b1 * (1 - n5 - n6)
This concept might take a moment to fully sink in—it certainly did for me at first! But the core idea is simple: whatever value we assign to one of the cross-talking channels, we subtract that same value from the dominant channel by using 1 - x
.
In other words, we're redistributing influence—if we increase the contribution from green or blue into red, we reduce the red channel’s own weight to compensate.
While this approach means we now have fewer direct controls (we’re no longer adjusting the dominant channel independently), what we gain is far more valuable in this context: a set of dynamic, neutral-preserving controls that allow for powerful and intuitive hue shifts without disrupting overall image balance.
3. Custom Code
As always, we’ll start by laying down our boilerplate code to get things up and running.
__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B){ float3 inRGB = make_float3(p_R, p_G, p_B); float3 out = inRGB; return out; }
Now let’s start with the easy part: creating our sliders. For this cross-talking RGB mixer, we’ll need a total of six sliders, each one controlling the contribution of one channel into another. Here’s the full list:
DEFINE_UI_PARAMS(g_r, Green in Red, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(b_r, Blue in Red, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(r_g, Red in Green, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(b_g, Blue in Green, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(r_b, Red in Blue, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(g_b, Green in Blue, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001)
Now onto the next step—calling our image transform function. Even though we haven’t defined it yet, we can go ahead and call a function named RGBmixer.
One key difference from our previous tools is that instead of passing in just a single variable, we’ll now pass in six—one for each of our cross-channel controls. This allows the user to adjust all six contributions directly through the function, giving us full flexibility within the mixer.
out = RGBmixer(out, g_r, b_r, r_g, b_g, r_b, g_b);
Now let’s work backwards and build the RGBmixer function. To start, we’ll set up the function definition with all six variables included in the parameter list. For now, we’ll keep the function body empty—just a clean structure we can fill in shortly.
It will look something like this:
__DEVICE__ float3 RGBmixer(float3 in, float gr, float br, float rg, float bg, float rb, float gb){ return in; }
Now all that’s left is to add the maths formulas inside the function. As a reminder, we’ll use in.x, in.y, and in.z as stand-ins for the red, green, and blue channels, respectively.
The maths itself follows the same structure we've already discussed—blending the contributions from other channels based on the slider values. Here’s what that looks like inside the function:
__DEVICE__ float3 RGBmixer(float3 in, float gr, float br, float rg, float bg, float rb, float gb){ in.x = in.x * (1 - gr - br) + (in.y * gr) + (in.z * br); in.y = (in.x * rg) + in.y * (1 - rg - bg) + (in.z * bg); in.z = (in.x * rb) + (in.y * gb) + in.z * (1 - rb - gb); return in; }
And that’s it—our RGB Mixer is now fully built and ready to load into Resolve.
By now, you might be noticing a kind of see-saw dynamic across these three methods of tool building. In the earlier tutorials, the Colour Page felt like the most approachable entry point, while DCTL seemed daunting and complex. But as we progress into more advanced tools, that relationship starts to shift.
The Colour Page—while great for prototyping—quickly becomes overly complex and limited when precision or flexibility is needed. In contrast, custom code with DCTL offers complete control and scalability, letting us build exactly what we want without trade-offs. As a result, once the core concepts are in place, DCTL often becomes the fastest, cleanest, and most efficient way to develop tools—especially as they grow in complexity.
__DEVICE__ float3 RGBmixer(float3 in, float gr, float br, float rg, float bg, float rb, float gb){ in.x = in.x * (1 - gr - br) + (in.y * gr) + (in.z * br); in.y = (in.x * rg) + in.y * (1 - rg - bg) + (in.z * bg); in.z = (in.x * rb) + (in.y * gb) + in.z * (1 - rb - gb); return in; } DEFINE_UI_PARAMS(g_r, Green in Red, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(b_r, Blue in Red, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(r_g, Red in Green, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(b_g, Blue in Green, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(r_b, Red in Blue, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) DEFINE_UI_PARAMS(g_b, Green in Blue, DCTLUI_SLIDER_FLOAT, 0, 0, 1, 0.001) __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B){ float3 inRGB = make_float3(p_R, p_G, p_B); float3 out = inRGB; out = RGBmixer(out, g_r, b_r, r_g, b_g, r_b, g_b); return out; }