Post
Rainbow Text in WebGL2
We have our wavy text, now we want our rainbow text. Why? Because we can.
Let's start where we left off in the last article. To refresh our minds, we have a simple texture, with a black background and white text. Can we pick those white pixels and add color without having to recreate the texture? The answer is yes, and the fragment shader is our best friend in this case.
A pixel, usually, has 3 color components, the famous RGB. By changing
the values each said component, we can get different colors. Let's do
that, and let's stick a sin() function in there:
vec3 color = vec3((sin(u_time * u_speed) + 1.0) / 2.0);This is our base. Colors go from [0, 1], so we need to shift our
sin() function (which goes from [-1, 1]) by adding 1, and
then dividing by 2. If you were to try this right now, all you'll see is
the text fading in and out. Nice, but not our goal.
We need to shift things again. Since we have 3 components, and
sin() is pretty friendly with 360 degree angles, let's
shift the phase of our components by 120 degrees (= 360/3):
color.r = (sin(u_time * u_speed + radians( 0.0)) + 1.0) / 2.0;
color.g = (sin(u_time * u_speed + radians(120.0)) + 1.0) / 2.0;
color.b = (sin(u_time * u_speed + radians(240.0)) + 1.0) / 2.0;As you can see, we convert our degrees into radians by calling the
radians() function. Hooray, for GLSL! If you don't know
why, or how radians and degrees are related to each other, you should
study some trigonomotry. Or just trust me, things'll work out.
Set color compression to 0 in the demo below to see how things look in this part.
Alright, things are improving, now we can see it's already, rainbow-ing. But we aren't happy. We want to see more of the rainbow, we want to squash it, compress it. So let's perform some horizontal scaling. First, we add another uniform:
gl.uniform1f(gl.getUniformLocation(shaderProgram, "u_compression"), 20.0)And then we take a step back. Let's remove time from the equation, so
we're left with (sin(radians(120.0) + 1.0) / 2.0. Our text
is colored with a plain color (set color compression to zero in the demo
below, you'll see). We want to see the whole spectrum of the rainbow.
Let's phase our color again by using the x-coordinate of our pixel
(multiplied by u_compression, so we can play with the
intensity of this parameter):
color.r = (sin(radians( 0.0) + uv.x * u_compression) + 1.0) / 2.0;
color.g = (sin(radians(120.0) + uv.x * u_compression) + 1.0) / 2.0;
color.b = (sin(radians(240.0) + uv.x * u_compression) + 1.0) / 2.0;Uncheck enable time in the demo below to see how things look in this part.
Great! Let's re-add the time:
color.r = (sin(u_time * u_speed + radians( 0.0) + uv.x * u_compression) + 1.0) / 2.0;
color.g = (sin(u_time * u_speed + radians(120.0) + uv.x * u_compression) + 1.0) / 2.0;
color.b = (sin(u_time * u_speed + radians(240.0) + uv.x * u_compression) + 1.0) / 2.0;We're almost there. We now need to apply our calculated color only to the pixels that have something besides pitch black, that have some brightness. Let's get our pixel:
vec4 pixel = texture(u_texture, uv);And let's create a brightness variable for tidyness:
float brightness = pixel.r;I chose the red component of our pixel to represent the brightness. Since we're working with black and white, any component would've sufficed (they're all equal, at least in theory).
And finally, let's multiply our color by our brightness. Anything
that's completely black or rgb(0, 0, 0) will stay black,
and everything else, will get the correct tonality of the rainbow
color.
outColor = vec4(color * brightness, 1.0);We're done. As the last time, I invite you to check how things look in context.
Wishing you well,
0x4a41