Welcome back! This is part two of my series detailing the step-by-step creation of a night-vision-style fragment shader. You can check out part one here if you haven’t yet. We’ll be building on the code from previous article, but it’s not very complicated at this point.
Let’s add some code to crank up the contrast. As an added bonus, we’ll add a flag to pick green or grayscale rendering, to give us some options when defining the look of our games.
The contrast formula is pretty straight-forward:
(contrastyValue - 0.5) = contrastAmount * (inputValue - 0.5)
Solving for the output:
contrastyValue = contrastAmount * (inputValue - 0.5) + 0.5
In this form, we can more easily see what’s going on: we translate the input value to the range [-0.5, 0.5], multiply by our contrast coefficient, and then shift it back up. Setting the contrast strength > 1.0 means that dark values (less than 50%) will be amplified toward 0 (black) and light values (greater than 50%) will be amplified toward 1.0 (white). Depending on the exact input values and contrast strength used, the result could potentially be greater than one or less than zero. So, we have to remember to clamp the result of the expression. Setting the contrast strength < 1.0 will likewise push all colors toward 0.5 (gray).
We’ll also add a new uniform float value for the contrast coefficient, uContrast
. Integrating everything into our monochrome shader, we get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #version 100 precision mediump float; uniform vec4 uColor; uniform sampler2D uTexture; uniform float uContrast; varying vec4 vColor; varying vec2 vTexCoord; void main() { vec4 color = uColor * vColor * texture2D(uTexture, vTexCoord); // choose monochrome value float v = max(max(color.r, color.g), color.b); // adjust contrast v = clamp(uContrast * (v - 0.5) + 0.5, 0.0, 1.0); gl_FragColor = vec4(0.0, v, 0.0, color.a); } |
Easy-peasy. Now lets look at choosing monochrome green vs. grayscale.
A simple if-else block with two versions of the final color vector should suffice—one green and one gray. We’ll add a new integer uniform as the flag value, uColorMode
. Let’s adopt the convention that a value of 1 means “green mode” and a value of 2 means “grayscale mode”. Since we only have two color modes right now, we really only have to test for one of them. Putting it all together:
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 | #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); // choose monochrome value float v = max(max(color.r, color.g), color.b); // adjust contrast v = clamp(uContrast * (v - 0.5) + 0.5, 0.0, 1.0); // choose green or greyscale vec4 monochrome; if(uColorMode == GREEN_MODE) { monochrome = vec4(0.0, v, 0.0, color.a); } else { monochrome = vec4(v, v, v, color.a); } gl_FragColor = monochrome; } |
For comparison, here’s the unprocessed results, and the monochrome shader with no contrast:
And here’s what it looks like with contrasty green, and contrasty grayscale:
You may have noticed that the total energy output is not conserved as the texels are pumped through the shader. That is, the luminosity is not properly taken into account. In fact, it’s not consistent between the green and grayscale modes. We’ll address these in a future article, but for now we’re just going for “looks pretty cool”, rather than “100% physically accurate”. After all, this is a quick-and-dirty night-vision shader, not a rigorous-and-correct night-vision shader.
See y’all for part three! 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: // <http://code.google.com/p/rogue-op/wiki/LICENSE> |
Pingback: Quick and Dirty Night-Vision Shader: Part 3 | Ionoclast Laboratories