Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebGL - Variables array sizes over vertex shader calls

Context

I'm trying to draw Bezier curves in a canvas. I achieved to draw quadratic and cubic curves from within the shader, but I did have a uniform variable for each and every control point so far.

So, I click on my canvas, add points and when I have enough (respectively 3 and 4), I draw my curve.

Now I'm trying to generalize Bezier curves. Though I achieved to accomplish this on the JavaScript side, I feel like it would be better to do this from the shader side, as the speed of the rendering would be greatly increased.

So, I would like to draw my curve as soon as I have at least two points. But I could keep adding points and draw my curve using every point, in order as control points.

Explanation

So I know it's not possible to set a dynamic array in GLSL, but is it possibliy to dynamically declare a GLSL array based on a JS variable ?

If my question is unclear (I know I have troubles formulating things right straight away), let me explain with an example.

uniform vec2 uMyPoints[<length>];

So this is what I would like to achieve, but of course, the array size must be a constant, according to glsl specifications.

However, from my point of view, I feel like I should be able to set up length from a JS variable. My intuition is that the array size in GLSL would be constant for the duration of the execution of the different shaders during the render, but could change from one rendering to another.

Question

So my question to you is : Based on these, do you know any good way or tricks to be able set a constant in GLSL from a javascript variable ?

If it appears to be possible, this would help me greatly. Thanks for your consideration.

As a matter of comparison : How could we set "numLights" in this example from JS ?

Answer

String substitution suits my needs nicely. Though it's a bit tricky, it will do just fine. Further research led me to know that implicit array size is available Open GL ES version 3, which should be used by future versions of WebGL, but not right now.

In the other hand, the second suggestion does not suits my needs as I precisely wanted to avoid having N points in the shader, as that quantity of points may change.

Thanks for the answer ;)

like image 878
CrumbleZ Avatar asked Oct 23 '15 10:10

CrumbleZ


People also ask

How many times a vertex shader is called?

Your shader is called once per vertex. Each time it's called you are required to set the special global variable, gl_Position to some clip space coordinates. Vertex shaders need data. They can get that data in 3 ways. Attributes (data pulled from buffers)

Do shaders work in WebGL?

WebGL is based on OpenGL, and uses the same shader language. That's right, shader programs are written in a language of their own, GLSL, which stands for Graphics Library Shader Language.

What is vertex shader in WebGL?

WebGL runs on the GPU on your computer. As such you need to provide the code that runs on that GPU. You provide that code in the form of pairs of functions. Those 2 functions are called a vertex shader and a fragment shader and they are each written in a very strictly typed C/C++ like language called GLSL.

What is gl_Position in WebGL?

gl_Position is the predefined variable which is available only in the vertex shader program. It contains the vertex position. In the above code, the coordinates attribute is passed in the form of a vector. As vertex shader is a per-vertex operation, the gl_position value is calculated for each vertex.


1 Answers

String substitution works

<script id="vs" type="notjs">    
uniform vec2 uMyPoints[<length>];
...
</script>

js

var numPoints = 10;
var vSrc = document.getElementById("vs").text;
vSrc = vSrc.replace(/<length>/g, numPoints);

This is what most complex programs do for shaders. They generate the shaders with string manipulation.

Of course you might want to use a nicer function to do the string substitution. For example maybe something like

  /**
   * Replace %(id)s in strings with values in objects(s)
   *
   * Given a string like `"Hello %(name)s from $(user.country)s"`
   * and an object like `{name:"Joe",user:{country:"USA"}}` would
   * return `"Hello Joe from USA"`.
   *
   * @function
   * @param {string} str string to do replacements in
   * @param {Object|Object[]} params one or more objects.
   * @returns {string} string with replaced parts
   * @memberOf module:Strings
   */
  var replaceParams = (function() {
    var replaceParamsRE = /%\(([^\)]+)\)s/g;

    return function(str, params) {
      if (!params.length) {
        params = [params];
      }

      return str.replace(replaceParamsRE, function(match, key) {
        var keys = key.split('.');
        for (var ii = 0; ii < params.length; ++ii) {
          var obj = params[ii];
          for (var jj = 0; jj < keys.length; ++jj) {
            var part = keys[jj];
            obj = obj[part];
            if (obj === undefined) {
              break;
            }
          }
          if (obj !== undefined) {
            return obj;
          }
        }
        console.error("unknown key: " + key);
        return "%(" + key + ")s";
      });
    };
  }());

now if you're shader looks like this

uniform Lights u_lights[%(numLights)s];
uniform vec2 u_points[%(numPoints)s];

you can substitute with

vSrc = replaceParams(vsrc, {
   numLights: 4,
   numPoints: 10,
});

You can also of course use `#define in the shader

#define NUM_LIGHTS %(numLights)s
#define NUM_POINTS %(numPoints)s

uniform Lights u_lights[NUM_LIGHTS];
uniform vec2 u_points[NUM_POINTS];

void main() {
  for (int i = 0; i < NUM_LIGHTS; ++i) {
    ...
  }
}

etc..

But, honestly most people wouldn't pass bezier control points as uniforms because there is a severe restriction on the number of uniforms. Most people would pass bezier control points in attributes. You can probably even set the stride and offset when calling gl.vertexAttribPointer so that if your points go

 [pt0, pt1, pt2, pt3, pt4, pt5, pt6, pt7, pt8, ..]

You can make 4 attributes

attribute vec2 p0;
attribute vec2 p1;
attribute vec2 p2;
attribute vec2 p3;

And point all of them with an offset and stride to set your 4 attributes so the points get pulled out

p0 = pt0, p1 = pt1, p2 = pt2, p3 = pt3,
p0 = pt1, p1 = pt2, p2 = pt3, p3 = pt4,
p0 = pt2, p1 = pt3, p2 = pt4, p3 = pt5,
p0 = pt3, p1 = pt4, p2 = pt5, p3 = pt6,

etc..

like image 75
gman Avatar answered Oct 17 '22 14:10

gman