Welcome to part three of my series on creating cool looking shaders of the not-quite-night-vision-but-still-looks-pretty-good variety. You can go check out part 1 or part 2 if you missed either of those. Go ahead, I’ll wait…
Hi again! Let’s jump right in, shall we? This time, we’re going to put a little effort into maintaining the overall energy-per-pixel to the greatest extent that is convenient and easy. After all, we are pretty damn lazy. Adjusting the contrast will skew things, but the main point is that full-color, gray, and green modes should otherwise all look approximately as bright as each other.
The first change we’ll make is to go from taking the dominant color value to calculating the overall intensity of the color. Because the green component of an RGB color adds way more brightness than the blue component than the blue, and red is somewhere in the middle, we want to weight these components accordingly. Researchers have found that a reasonable approximation is 30% red, 59% green, and 11% blue, adding up to 100%. We’ll use these as coefficients and dot them with the input RGB values.
1 2 | const vec3 proportions = vec3(0.30, 0.59, 0.11); float intentisy = dot(color.rgb, proportions); |
You’ll notice that for pure white, this comes out to 0.3 + 0.59 + 0.11 == 1.0 (full intensity); for pure black it’s 0.0 + 0.0 + 0.0 == 0.0 (null intensity). This is exactly what we want, and it’s what we get with very little code and very little runtime overhead.
The contrast calculation will stay the same, coming across from the last version of the shader. Setting the gray output color is basically the same too, except instead of the value, we set the red, green, and blue channels to our intensity color. The next notable change will be when we set the output green channel for monochrome green mode.
To keep things mathematically consistent when setting the green channel only, we must divide the intensity by 0.59. This means that if we take the intensity of the color triplet we generated, it will give us back the original intensity value we started with: 0.3*0.0 + 0.59(intensity/0.59) + 0.11*0.0 == intensity. Unfortunately, when I do this, things come out way too bright. If I try setting just the green channel to the unadjusted intensity, it’s obviously too dark. So I decided to split the difference and divide the intensity by 0.8. It’s not perfect, but it looks pretty good, with minimal fuss.
1 2 | float green = clamp(intentisy / 0.8, 0.0, 1.0); monochrome = vec4(0.0, green, 0.0, color.a); |
You may want to adjust that fudge value to your liking.
In order to make it more obvious what’s going on, I decided to go with a higher-resolution, full-color texture to test it on. I used a photo I took on a hike near my house. I took this photo near noon, and is completely unadjusted going into the shader, except to resize it. I have included screen capture of green and gray modes, with and without contrast.
And here’s the complete shader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #version 100 precision mediump float; #define GREEN_MODE 1 #define GRAYSCALE_MODE 2 uniform vec4 uColor; uniform sampler2D uTexture; uniform float uContrast; uniform int uColorMode; varying vec4 vColor; varying vec2 vTexCoord; void main() { vec4 color = uColor * vColor * texture2D(uTexture, vTexCoord); // get the color's intensity const vec3 proportions = vec3(0.30, 0.59, 0.11); float intentisy = dot(color.rgb, proportions); // adjust contrast intentisy = clamp(uContrast * (intentisy - 0.5) + 0.5, 0.0, 1.0); // choose green or greyscale vec4 monochrome; if(uColorMode == GREEN_MODE) { float green = clamp(intentisy / 0.8, 0.0, 1.0); monochrome = vec4(0.0, green, 0.0, color.a); } else { monochrome = vec4(intentisy, intentisy, intentisy, color.a); } gl_FragColor = monochrome; } |
I’m starting to like how this is looking, but it’s still way too clean. Next time we’ll some noise, or maybe blur? You can add suggestions in the comments. Otherwise I’ll just decide when I start writing.
See y’all next time! Please do feel free to comment and share! <3
// Copyright © 2013-2014 Brigham Toskin // This software is meant to be educational only. It is distributable // under the terms of a modified MIT License. You can find a copy the license: // |