1
\$\begingroup\$

I want to generate a color gradient between two colors from x1,y1 to x2,y2. I'm unsure how to do this though. I can use Color.Lerp to get all of the color steps, but the problem is that I don't understand how to then apply that to a plane (the texture pixels) when the line would be at an angle.

If it was a horizontal/vertical line, that would be easy, because it's just plotting the same color along the appropriate axis. But if it's a diagonal line of any degree, that seems way more complicated and I'm unsure how to do calculate it.

As an example, here is a gradient from a picture editor program. It goes from green-to-red. The actual start and end positions are the circles. Therefore, the solid start/end colors continue solid outside of the gradient area.

gradient example

\$\endgroup\$
  • \$\begingroup\$ How's your math skills? Specifically trig and vectors? \$\endgroup\$ – MickLH Jan 10 '14 at 5:11
  • \$\begingroup\$ Not good I'm afraid :( I can only speak in generalities. My first thought of how to do this would be to 1. calculate the angle of the line 2. calculate new intersecting lines at right angles for each step in the original line. These new lines give me lines to copy each color at. I just don't know how to do that. \$\endgroup\$ – Thraka Jan 10 '14 at 5:18
  • \$\begingroup\$ Here I wrote you a shader, good luck! shadertoy.com/view/Xsj3Dw \$\endgroup\$ – MickLH Jan 10 '14 at 6:43
  • \$\begingroup\$ @MickLH Just throws an error for me. \$\endgroup\$ – Mario Jan 10 '14 at 10:42
  • \$\begingroup\$ D'oh! I set that shader to public now, sorry about that :P @Mario \$\endgroup\$ – MickLH Jan 10 '14 at 15:38
2
\$\begingroup\$

Here is one way to do it, the idea behind this method is rotating the whole image to make the line straight while working on it. I am only using half of the matrix because the line is flattened horizontally and so the Y coordinate doesn't matter to us then.

0. Convert your startPoint and endPoint into vectors:

float x1 = (startPoint.X / (float)width)  * 2.0f - 1.0f;
float y1 = (startPoint.Y / (float)height) * 2.0f - 1.0f;
float x2 = (endPoint.X / (float)width)  * 2.0f - 1.0f;
float y2 = (endPoint.Y / (float)height) * 2.0f - 1.0f;

1. Find the angle between (x1,y1) and (x2,y2):

float angle = atan2(x1 - x2, y1 - y2);

2. Create half of a rotation matrix (We only need half for this)

float rx = cos(angle);
float ry = sin(angle);

3. Calculate start and end points in rotated space:

float start = x1 * rx + y1 * ry;
float end =   x2 * rx + y2 * ry;

4.-. For each pixel:

for(int x=0;x<width;x++) {
    for(int y=0;y<height;y++) {

4.1. Rotate the pixel so the line is horizontal

        // but we need vectors from (-1, -1) to (1, 1)
        // instead of pixels from (0, 0) to (width, height)
        float u = (x / (float)width)  * 2.0f - 1.0f;
        float v = (y / (float)height) * 2.0f - 1.0f;

        float here =  u * rx + v * ry;

4.2. Find the ratio between start and end

        float lerp = (start - here) / (start - end);

4.3. Interpolate between your colors and finish the pixel

        SetPixel(x,y, Color.Lerp(color1, color2, lerp));
    }
}
\$\endgroup\$
  • \$\begingroup\$ Mick, this didn't really work for me. It just colored everything the starting color. Hmm. Here is my translated code: pastebin.ca/2532906 \$\endgroup\$ – Thraka Jan 10 '14 at 17:33
  • \$\begingroup\$ "startPoint" and "endPoint" need to be vectors from -1 to 1 also, not pixels. Use the same transformation on them. I'll edit the answer for you. \$\endgroup\$ – MickLH Jan 10 '14 at 18:15
  • \$\begingroup\$ This seems to be working perfectly @MickLH, thanks a bunch! \$\endgroup\$ – Thraka Jan 11 '14 at 6:55
  • \$\begingroup\$ I'm having a problem with the color lerp. It seems to be mixing both colors or the saturation of a color (unsure what is going on) at both ends of the color spectrum. Please look at this question if you could help me with that gamedev.stackexchange.com/questions/69293/… \$\endgroup\$ – Thraka Jan 25 '14 at 21:42
2
\$\begingroup\$

You can project each point of the canvas on the (x1,y1)—(x2,y2) line and get a value between 0 and 1 that you then use for lerping. Here is some pseudocode:

for (y = 0; y < height; ++y)
    for (x = 0; x < width; ++x)
    {
        float A = (x - x1) * (x2 - x1) + (y - y1) * (y2 - y1);
        float B = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
        float U = Clamp(A / B, 0.0, 1.0);

        SetPixel(x, y, Color.Lerp(Color1, Color2, U);
    }
\$\endgroup\$
  • \$\begingroup\$ So far, this solution works best. However, what do I do when I don't want it exactly diagonal? What if it is say a 30% slope? \$\endgroup\$ – Thraka Jan 10 '14 at 17:30
  • \$\begingroup\$ To be clearer, I mean a 30% slope across the entire surface. Say it is a 50x50 grid, instead of 0x0->49x49, I want the angle to be from 20x10->30x30. However, it is the entire surface so each pixel still gets colored according to where it is on the gradient spectrum. \$\endgroup\$ – Thraka Jan 10 '14 at 17:36
  • \$\begingroup\$ @Thraka The x1/y1/x2/y2 values decide the slope of the gradient. Isn’t it what you’re looking for? If not, maybe I’d need a drawing to understand better. \$\endgroup\$ – sam hocevar Jan 11 '14 at 0:26
  • \$\begingroup\$ I've added a picture and paragraph to explain better. Your example works for the area that has been designated with x1,y1 - x2,y2. But I cannot, say create a gradient with it that only takes up part of the entire surface, yet process the entire surface to determine the color each pixel should be, like the picture in my post shows. \$\endgroup\$ – Thraka Jan 11 '14 at 1:25
  • \$\begingroup\$ @Thraka The code above is supposed to give you exactly the effect in your picture. Did you keep the Clamp call? \$\endgroup\$ – sam hocevar Jan 11 '14 at 2:30
0
\$\begingroup\$

To be honest, I wouldn't even start trying to create a texture and update all the pixels. This is far from being efficient, unless you want to do more complicated patterns than just a simple gradient.

Basically, for me there are two valid approaches:

  • Draw a quad with colored vertices (fixed pipeline rendering).
  • Draw a quad and color it using a fragment shader.

To render the quad in both cases, you can just look at this question and answer. Then just add colors to the vertices or enable shader usage.

\$\endgroup\$
  • \$\begingroup\$ I'm not drawing the texture to the screen, I'm doing something else with it, like saving it etc. Thanks for the shader though! \$\endgroup\$ – Thraka Jan 10 '14 at 17:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.