Fragment Shaders

The fragment shader is called for every pixel to be rendered. In this chapter, we will develop a small red lens which will increase the red color channel value of the source.

Setting up the scene

First, we set up our scene, with a grid centered in the field and our source image be displayed.

  1. import QtQuick
  2. Rectangle {
  3. width: 480; height: 240
  4. color: '#1e1e1e'
  5. Grid {
  6. anchors.centerIn: parent
  7. spacing: 20
  8. rows: 2; columns: 4
  9. Image {
  10. id: sourceImage
  11. width: 80; height: width
  12. source: '../../assets/tulips.jpg'
  13. }
  14. }
  15. }

image

A red shader

Next, we will add a shader, which displays a red rectangle by providing for each fragment a red color value.

  1. #version 440
  2. layout(location=0) in vec2 qt_TexCoord0;
  3. layout(location=0) out vec4 fragColor;
  4. layout(std140, binding=0) uniform buf {
  5. mat4 qt_Matrix;
  6. float qt_Opacity;
  7. } ubuf;
  8. layout(binding=1) uniform sampler2D source;
  9. void main() {
  10. fragColor = vec4(1.0, 0.0, 0.0, 1.0) * ubuf.qt_Opacity;
  11. }

In the fragment shader we simply assign a vec4(1.0, 0.0, 0.0, 1.0), representing the color red with full opacity (alpha=1.0), to the fragColor for each fragment, turning each pixel to a solid red.

image

A red shader with texture

Now we want to apply the red color to each texture pixel. For this, we need the texture back in the vertex shader. As we don’t do anything else in the vertex shader the default vertex shader is enough for us. We just need to provide a compatible fragment shader.

  1. #version 440
  2. layout(location=0) in vec2 qt_TexCoord0;
  3. layout(location=0) out vec4 fragColor;
  4. layout(std140, binding=0) uniform buf {
  5. mat4 qt_Matrix;
  6. float qt_Opacity;
  7. } ubuf;
  8. layout(binding=1) uniform sampler2D source;
  9. void main() {
  10. fragColor = texture(source, qt_TexCoord0) * vec4(1.0, 0.0, 0.0, 1.0) * ubuf.qt_Opacity;
  11. }

The full shader contains now back our image source as variant property and we have left out the vertex shader, which if not specified is the default vertex shader.

In the fragment shader, we pick the texture fragment texture(source, qt_TexCoord0) and apply the red color to it.

image

The red channel property

It’s not really nice to hard code the red channel value, so we would like to control the value from the QML side. For this we add a redChannel property to our shader effect and also declare a float redChannel inside the uniform buffer of the fragment shader. That is all that we need to do to make a value from the QML side available to the shader code.

TIP

Notice that the redChannel must come after the implicit qt_Matrix and qt_Opacity in the uniform buffer, ubuf. The order of the parameters after the qt_ parameters is up to you, but qt_Matrix and qt_Opacity must come first and in that order.

  1. #version 440
  2. layout(location=0) in vec2 qt_TexCoord0;
  3. layout(location=0) out vec4 fragColor;
  4. layout(std140, binding=0) uniform buf {
  5. mat4 qt_Matrix;
  6. float qt_Opacity;
  7. float redChannel;
  8. } ubuf;
  9. layout(binding=1) uniform sampler2D source;
  10. void main() {
  11. fragColor = texture(source, qt_TexCoord0) * vec4(ubuf.redChannel, 1.0, 1.0, 1.0) * ubuf.qt_Opacity;
  12. }

To make the lens really a lens, we change the vec4 color to be vec4(redChannel, 1.0, 1.0, 1.0) so that the other colors are multiplied by 1.0 and only the red portion is multiplied by our redChannel variable.

image

The red channel animated

As the redChannel property is just a normal property it can also be animated as all properties in QML. So we can use QML properties to animate values on the GPU to influence our shaders. How cool is that!

  1. ShaderEffect {
  2. id: effect4
  3. width: 80; height: width
  4. property variant source: sourceImage
  5. property real redChannel: 0.3
  6. visible: root.step>3
  7. NumberAnimation on redChannel {
  8. from: 0.0; to: 1.0; loops: Animation.Infinite; duration: 4000
  9. }
  10. fragmentShader: "red3.frag.qsb"
  11. }

Here the final result.

image

The shader effect on the 2nd row is animated from 0.0 to 1.0 with a duration of 4 seconds. So the image goes from no red information (0.0 red) over to a normal image (1.0 red).

Baking

Again, we need to bake the shaders. The following commands from the command line does that:

  1. qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -o red1.frag.qsb red1.frag
  2. qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -o red2.frag.qsb red2.frag
  3. qsb --glsl 100es,120,150 --hlsl 50 --msl 12 -o red3.frag.qsb red3.frag