0
votes

I am currently in the process of learning a bit of graphics programming / glsl using shader-school, which works great. However I am stuck at the second lesson about vertex shaders.

The triangle asked for the solution seems to display a triangle filled with the differents hues of the YUV color space:
YUV-Wheel

I know that it's possible to calculate the conversion between RGB and YUV, however I am not quite sure about how the coordinate space in the vertex or fragment shader works.

My vertex shader looks like this:

precision highp float;

attribute vec4 position;
attribute vec3 color;

varying vec4 fragPosition;
void main() {
  gl_Position = position;
  fragPosition = position; // send the position to the fragment shader
}

while my fragment shader looks like this:

precision highp float;

varying vec4 fragPosition; // is set by vertex shader

void main() {
  gl_FragColor = fragPosition.rgba;
}

And the rendered triangle looks like this (left part is the problem to solve, right part is my solution so far):

shader-school lesson about vertex shaders #2

fragPosition.a is continuously 1.0; why does the fading look so weird? How exactly does the coordinate space inside the triangle? I can't wrap my head around it and without breakpoints or print statements it's not that easy to figure out. Why is the bottom left corner completely black, and why is there no blue spot?

2

2 Answers

1
votes

The default space for opengl is the NDC space. Its a unit cube in range of [-1,1]. Where x=-1 is at the left side of screen, x=1 is at the right side of screen, y=-1 bottom, y=+1 top, z=-1 at the near plane, and z=+1 at the far plane.

What you are basically doing in your shader right now is displaying the fragment coordinate as a color.

The top of your triangle is green because the coordinate is something like [0,1,0,1] and when interpreted as a color, that gives it a green color. The right of triangle has the coordinate [1,-1,0,1] which gives it a red color. The bottom is likely to be something like [0,-1,0,1] which is black.

Btw, I am not sure if fragment.a is actually 1.0, you can check by doing gl_FragColor = fragPosition.aaaa;. If .a is 1.0 then you should only see white, if .a is ~0.5, it should be gray and so on.

I am guessing here, I think what you are suppose to do in this lesson is to use color inputs instead (attribute vec3 color;) and implement the rgba to yuv conversion in the shader.

1
votes

I see 3 different solutions here

First is do entire calculation in FS(fragment shader). This kind of solutions are good for postprocessing for example, but with high performance impact.

Design idea: You pass only white triangle via VS(vertex shader), needs one attribute, and do all colors in FS based on position.

Second solution is to do colors in VS and use interpolation to achieve result in FS. Overal this solutions are the fastest, but not all kinds of problems contain simple interpolation.

Design idea: Needs one attribute and one varying. You pass data for one triangle from js to VS, where you establish colors in corners and get result in FS from interpolated color values.

Third solution is to initalize colors in JS, pass it to VS where each corner has one color, use interpolation and get result from FS. This last solution offers high modularity and might allow interaction. Cost is longer code and might be slower.

Design idea: Use two attributes and one varying, pass postions and colorsYUV to VS, then do interpolation just for colors and the rest of result in FS.

I simplecoded last solution, because you could learn the most from it.

// stackoverflow purposes

// initalize canvas and webgl
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");

gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;

// create shaders
var vsSource = `
        precision mediump float;

        attribute vec2 a_position;
        attribute vec3 a_color_yuv;

        varying vec3 v_color_yuv;
        
        void main() {
            v_color_yuv = a_color_yuv;
            gl_Position = vec4(a_position, 0., 1.);
        }
        `;

var fsSource = `
        precision mediump float;

        varying vec3 v_color_yuv; // is set by vertex shader

        void main() {
            float r = v_color_yuv.x + 1.4075 * (v_color_yuv.z);
            float g = v_color_yuv.x - 0.3455 * (v_color_yuv.y) - (0.7169 * (v_color_yuv.z));
            float b = v_color_yuv.x + 1.7790 * (v_color_yuv.y);
            
            gl_FragColor = vec4(r, g, b, 1.);
        }
        `;

var vs = gl.createShader(gl.VERTEX_SHADER);
var fs = gl.createShader(gl.FRAGMENT_SHADER);

// replace removes nonascii chars from shaders sources, because I created them with http://es6-features.org/#StringInterpolation
gl.shaderSource(vs, vsSource.replace(/[^\x00-\x7F]/g, ""));
gl.shaderSource(fs, fsSource.replace(/[^\x00-\x7F]/g, ""));

gl.compileShader(vs);
gl.compileShader(fs);

// create webgl program
var shaderProgram = gl.createProgram();

gl.attachShader(shaderProgram, vs);
gl.attachShader(shaderProgram, fs);

gl.linkProgram(shaderProgram);

gl.useProgram(shaderProgram);

// set uniform and attribute locations
var a_position = gl.getAttribLocation(shaderProgram, "a_position");
gl.enableVertexAttribArray(a_position);

var a_color_yuv = gl.getAttribLocation(shaderProgram, "a_color_yuv");
gl.enableVertexAttribArray(a_color_yuv);

// set attribute buffers
var trianglePositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, trianglePositionBuffer);
var positions = [
  -1.0, -1.0,
  1.0, -1.0,
  1.0, 1.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
trianglePositionBuffer.itemSize = 2;

var triangleColorYUVBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, triangleColorYUVBuffer);
// modify this
var colorsYUV = [
  0.5, -0.5, -0.5,
  0.5, 0.0, 0.0,
  0.5, -0.5, 0.5
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorsYUV), gl.STATIC_DRAW);
triangleColorYUVBuffer.itemSize = 3;

var bufferSize = 3;

// final draw
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);

gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

gl.bindBuffer(gl.ARRAY_BUFFER, trianglePositionBuffer);
gl.vertexAttribPointer(a_position, trianglePositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, triangleColorYUVBuffer);
gl.vertexAttribPointer(a_color_yuv, triangleColorYUVBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.drawArrays(gl.TRIANGLES, 0, bufferSize);
<canvas id="c" style="border: none;" width="500" height="500"></canvas>

Explinations

fragPosition.a is continuously 1.0; why does the fading look so weird?

If we imagine that left picture is from YUV colors, then Y is probably 0.5 because there is grey color in right bottom corner.

This has nothing to do with fading, but your Y is 0 all the time, which mean black color, not grey. And so colors are darker.

How exactly does the coordinate space inside the triangle?

Lets say I use YUV colors in ranges <0,1> for Y and <-0.5,0.5> for U and V

Lets iterate trhu all corners of the triangle.

  • Start with left bottom corner.
  • What coordinate is in this corner? -1, -1
  • What color does it have? green
  • What is this color in YUV? Well, Y might be somewhere in range <0,1> (but I guess its 0.5), U must be -0.5 and V must be -0.5

Lets go next. Right bottom corner

  • What coordinate is in this corner? 1, -1
  • What color does it have? grey
  • What is this color in YUV? U and V must be both 0, while Y is 0.5

Last corner, right top:

  • What coordinate is in this corner? 1, 1
  • What color does it have? orange
  • What is this color in YUV? U must be -0.5 while V is 0.5 and Y is in range <0,1> (but I guess its 0.5)

In your shader code, you are trying to use positions as colors. Well lets say we have YUV colors. And we have XY positions. We set Y as constant (you used 0, but it should be 0.5). But then XY!=constant*UV + constant (no matter what constants are) because desired triangle has twisted color system from YUV color system. In the left bottom is green, which is ok. But in right bottom is grey instead of blue. Right top is orange not violet. And left top we dont know, and we dont care. So there are two solutions for this. Create color buffer which will assign color for each corner or create your owen color system (in fragment shader). I used first solution so I kept classical YUV system.

Why is the bottom left corner completely black

You use coordinate as colors, so your gl_FragColor.rgba=position.xyzw, which is [-1, -1, 0, 1] in left bottom corner, which will transform into [0,0,0,1] = black color

why is there no blue spot

On the left picture, author probably doesnt want blue. On your picture, blue is Z coordinate for you, which is 0 for entire time = no blue anywhere.