Pixel Bender: HDR Brighten

Pixel Bender: HDR Brighten

I've started experimenting with the awesome Pixel Bender Toolkit (a sort-of shader for 2D images) and since I'm obsessed with HDR-esque imagery, well my first 'hello-world' shader I decided to write would be one that gives pictures an HDR-like highlight effect Wink. The result is far from perfect, there are noticeable artifacts, but it is reasonable. The preview comparison image shows the result as-is, without any post-processing (except the logo and the split, of course).

Kernel implementation

For each pixel, sample neighbouring pixels and compute their 'brightness' (in this case, average of R, G, B values). Bright pixels are further brightened, whereas darker pixels are suppressed (using combination of bright and acctweak parameters). The outputted pixel then is

output pixel = original pixel + accumulated contribution of neighbouring pixels * weight

The contribution is further affected by the brightness of the original pixel itself. For simplicity, the sampled pixels are placed in a grid.

Parameters

There are several parameters used for tweaking. Not all of them have sensible names, I'll work on it in the future, but for now:

  • factor: overall effect on the image. 0 means no effect. You can go over 1.0 for extra brightness.
  • power: brightness contribution from the pixel itself. Use to brighten up the whole image.
  • rings: number of sampling 'rings' to accumulate pixels from
  • radius: separation of sampling 'rings' from each other in pixels
  • bright: scales the luminance on accumulated pixels. Higher means brighter
  • acctweak: parameter for tweaking the contribution of accumulated pixels. Lower values spread brightness across the image, whereas higher values give localised bright areas.

The code

Due to the lack of support for loops in the language, any loops better be expanded. The shader is also available for download at the end of this page.

<languageVersion : 1.0;>
kernel Brighten
<   namespace : "micron_developers";
    vendor : "micron_developers";
    version : 1;
    description : "Brights up an image based on high luminance pixels";
>
{
    parameter float factor <
        minValue: float(0.0);
        maxValue: float(2.0);
        defaultValue: float(0.85);
    >;
    parameter float bright <
        minValue: float(1.0);
        maxValue: float(4.0);
        defaultValue: float(1.5);
    >;
    parameter float power <
        minValue: float(0.0);
        maxValue: float(5.0);
        defaultValue: float(4.4);
    >;
    parameter float acctweak <
        minValue: float(0.0);
        maxValue: float(5.0);
        defaultValue: float(1.3);
    >;
    parameter int rings <
        minValue: int(1);
        maxValue: int(5);
        defaultValue: int(4);
    >;
    parameter float radius <
        minValue: float(1.0);
        maxValue: float(15.0);
        defaultValue: float(3.0);
    >;
    // ring weights
    dependent float w1, w2, w3, w4, w5;
 
    input image4 src;
    output pixel4 dst;
 
    void evaluateDependents() {
        w1 = 10.1;
        w2 = 6.75;
        w3 = 4.5;
        w4 = 3.0;
        w5 = 2.0;
    }
 
    // customizable "luminance" function
    float lum(float4 s) {
        return (s.r + s.g + s.b)/3.0;
    }
 
    // evaluates the accumulation contribution
    float4 evalAcc(float4 s) {
        float l = lum(s);
        return s*pow(l*bright, acctweak);
    }
 
    // evaluatePixel
    void evaluatePixel()
    {
    float4 s = sampleNearest(src, outCoord());
    float h = lum(s);
    // find contribution from other pixels
    float4 acc = float4(0.0);
    float4 acc1 = float4(0.0), acc2 = float4(0.0), acc3 = float4(0.0), acc4 = float4(0.0), acc5 = float4(0.0);
    float w = 0.00001;
    if (rings > 0) {
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-1.0, -1.0))); 
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-1.0, 0.0))) * 2.0;
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-1.0, 1.0))); 
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, -1.0))) * 2.0; 
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, 1.0))) * 2.0; 
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(1.0, -1.0))); 
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(1.0, 0.0))) * 2.0;
        acc1 += evalAcc(sampleNearest(src, outCoord() + radius*float2(1.0, 1.0)));
        acc1 /= 12.0;
        acc1 *= w1;
        w += w1;
    }
    if (rings > 1) {
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-2.0, -2.0))); 
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-2.0, 0.0))) * 2.0;
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-2.0, 2.0))); 
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, -2.0))) * 2.0; 
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, 2.0))) * 2.0; 
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(2.0, -2.0))); 
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(2.0, 0.0))) * 2.0;
        acc2 += evalAcc(sampleNearest(src, outCoord() + radius*float2(2.0, 2.0)));
        acc2 /= 12.0;
        acc2 *= w2;
        w += w2;
    }
    if (rings > 2) {
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-3.0, -3.0))); 
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-3.0, 0.0))) * 2.0;
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-3.0, 3.0))); 
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, -3.0))) * 2.0; 
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, 3.0))) * 2.0; 
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(3.0, -3.0))); 
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(3.0, 0.0))) * 2.0;
        acc3 += evalAcc(sampleNearest(src, outCoord() + radius*float2(3.0, 3.0)));
        acc3 /= 12.0;
        acc3 *= w3;
        w += w3;
    }
    if (rings > 3) {
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-4.0, -4.0))); 
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-4.0, 0.0))) * 2.0;
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-4.0, 4.0))); 
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, -4.0))) * 2.0; 
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, 4.0))) * 2.0; 
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(4.0, -4.0))); 
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(4.0, 0.0))) * 2.0;
        acc4 += evalAcc(sampleNearest(src, outCoord() + radius*float2(4.0, 4.0)));
        acc4 /= 12.0;
        acc4 *= w4;
        w += w4;
    }
    if (rings > 4) {
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-5.0, -5.0))); 
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-5.0, 0.0))) * 2.0;
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(-5.0, 5.0))); 
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, -5.0))) * 2.0; 
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(0.0, 5.0))) * 2.0; 
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(5.0, -5.0))); 
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(5.0, 0.0))) * 2.0;
        acc5 += evalAcc(sampleNearest(src, outCoord() + radius*float2(5.0, 5.0)));
        acc5 /= 12.0;
        acc5 *= w5;
        w += w5;
    }
    // aggregate acc
    acc = (acc1+acc2+acc3+acc4+acc5)/w;
    // account pixel luminance
    acc *= pow(h, 5.0-power);
    // overall factor
    acc *= factor;
    // add
    dst = s+acc;
    }    
}
AttachmentSize
brighten.pbk5.95 KB

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <b> <i> <p> <blockquote>
  • Lines and paragraphs break automatically.
  • Textual smileys will be replaced with graphical ones.

More information about formatting options