Search code examples
javascriptwebglnurbs

Using Nurbs Surface (Verb) in webGL, indices are always wrong unless I use corners instead of controlPoints


I am trying to make Verb's Nurbs Surface to work with vanilla webGL (cheating a bit with webgl-utils though). PIXI.js also yields the same results, corners work, but not the control points.

I could do it using corners verb.geom.NurbsSurface.byCorners however when I try to use the controlPoints using verb.geom.NurbsSurface.byKnotsControlPointsWeights the surface gets messed up, most probably the indices are wrong!

There is an example of using verb with THREE.js but even when I try the same functions used in the example, the results are the same.

I have commented out the function based on the corners, you can see that it works. I console logged srf and from the _data object I could see Verb is generating those control points under the hood, but the same points would not work if I try them with byKnotsControlPointsWeights

Apart from a working solution, I also would like to understand why the code does not work, and which part in the THREE.js is different from my vanilla code that makes it work with THREE but not here.

const flatten = _.flatten;
// const corners = [
//   [100, 100], // top left
//   [450, 50], // top right
//   [650, 650], // bottom right
//   [0, 750] // bottom left
// ];
// var srf = verb.geom.NurbsSurface.byCorners(...corners);

const degreeU = 3;
const degreeV = 3;
const knotsU = [0, 0, 0, 0, 1, 1, 1, 1];
const knotsV = [0, 0, 0, 0, 1, 1, 1, 1];
const controlPoints = [
  [
    [0, 0, 1],
    [0, 249, 1],
    [0, 500, 1],
    [0, 750, 1]
  ],
  [
    [249, 0, 1],
    [249, 249, 1],
    [249, 500, 1],
    [249, 750, 1]
  ],
  [
    [500, 0, 1],
    [500, 249, 1],
    [500, 500, 1],
    [500, 750, 1]
  ],
  [
    [750, 0, 1],
    [750, 249, 1],
    [750, 500, 1],
    [750, 750, 1]
  ]
];

var srf = verb.geom.NurbsSurface.byKnotsControlPointsWeights(
  degreeU,
  degreeV,
  knotsU,
  knotsV,
  controlPoints
);

// tesselate the nurface and get the triangles
var tess = srf.tessellate();
console.log(tess);

const vertexSource = `
  attribute vec2 a_position;
  attribute vec2 a_texCoord;

  uniform vec2 u_resolution;

  varying vec2 v_texCoord;

  void main() {
    vec2 zeroToOne = a_position / u_resolution;
    vec2 zeroToTwo = zeroToOne * 2.0;
    vec2 clipSpace = zeroToTwo - 1.0;

    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
    v_texCoord = a_texCoord;
  }
`;

const fragmentSource = `
  precision mediump float;

  // our texture
  uniform sampler2D u_image;

  // the texCoords passed in from the vertex shader.
  varying vec2 v_texCoord;

  void main() {
    gl_FragColor = texture2D(u_image, v_texCoord);
  }
`;

function main() {
  var image = new Image();
  image.crossOrigin = "anonymous";
  image.onload = function () {
    render(image);
  };
  image.src = "https://pixijs.io/examples/examples/assets/bg_scene_rotate.jpg";
}

function render(image) {
  // Get A WebGL context
  /** @type {HTMLCanvasElement} */
  var canvas = document.getElementById("c");
  var gl = canvas.getContext("webgl");

  // setup GLSL program
  var program = webglUtils.createProgramFromSources(gl, [
    vertexSource,
    fragmentSource
  ]);

  // look up where the vertex data needs to go.
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");

  // Create a buffer to put three 2d clip space points in
  var positionBuffer = gl.createBuffer();

  // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Set a rectangle the same size as the image.
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(flatten(tess.points)),
    gl.STATIC_DRAW
  );

  // provide texture coordinates for the rectangle.
  var texcoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(flatten(tess.uvs)),
    gl.STATIC_DRAW
  );

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we can render any size image.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

  // lookup uniforms
  var resolutionLocation = gl.getUniformLocation(program, "u_resolution");

  // resize canvas to display size
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  // Tell WebGL how to convert from clip space to pixels
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

  // Clear the canvas
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Tell it to use our program (pair of shaders)
  gl.useProgram(program);

  // index buffer
  const indexBuffer = gl.createBuffer();
  // make this buffer the current 'ELEMENT_ARRAY_BUFFER'
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  // Fill the current element array buffer with data

  const indices = new Uint16Array(flatten(tess.faces));
  gl.bufferData(
    gl.ELEMENT_ARRAY_BUFFER,
    new Uint16Array(indices),
    gl.STATIC_DRAW
  );

  // Turn on the position attribute
  gl.enableVertexAttribArray(positionLocation);

  // Bind the position buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  var size = 2; // 2 components per iteration
  var type = gl.FLOAT; // the data is 32bit floats
  var normalize = false; // don't normalize the data
  var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  var offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionLocation,
    size,
    type,
    normalize,
    stride,
    offset
  );

  // Turn on the texcoord attribute
  gl.enableVertexAttribArray(texcoordLocation);

  // bind the texcoord buffer.
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);

  gl.vertexAttribPointer(
    texcoordLocation,
    size,
    type,
    normalize,
    stride,
    offset
  );

  // set the resolution
  gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

  // Draw the rectangle.
  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}

main();
body { margin: 0; }
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://unpkg.com/[email protected]/build/js/verb.js"></script>
<script src="https://unpkg.com/[email protected]/lodash.js"></script>
<canvas id="c"></canvas>


Solution

  • I don't know what it's supposed to look like but the code was passing 2 for the size of the positions when calling gl.vertexAttribPointer but the data has 3 (x, y, z). Setting that to 3 certainly gets a different image. Still need to make sure UVs are set to a size of 2

    const flatten = _.flatten;
    // const corners = [
    //   [100, 100], // top left
    //   [450, 50], // top right
    //   [650, 650], // bottom right
    //   [0, 750] // bottom left
    // ];
    // var srf = verb.geom.NurbsSurface.byCorners(...corners);
    
    const degreeU = 3;
    const degreeV = 3;
    const knotsU = [0, 0, 0, 0, 1, 1, 1, 1];
    const knotsV = [0, 0, 0, 0, 1, 1, 1, 1];
    const controlPoints = [
      [
        [0, 0, 1],
        [0, 249, 1],
        [0, 500, 1],
        [0, 750, 1]
      ],
      [
        [249, 0, 1],
        [249, 249, 1],
        [249, 500, 1],
        [249, 750, 1]
      ],
      [
        [500, 0, 1],
        [500, 249, 1],
        [500, 500, 1],
        [500, 750, 1]
      ],
      [
        [750, 0, 1],
        [750, 249, 1],
        [750, 500, 1],
        [750, 750, 1]
      ]
    ];
    
    var srf = verb.geom.NurbsSurface.byKnotsControlPointsWeights(
      degreeU,
      degreeV,
      knotsU,
      knotsV,
      controlPoints
    );
    
    // tesselate the nurface and get the triangles
    var tess = srf.tessellate();
    console.log(tess);
    
    const vertexSource = `
      attribute vec2 a_position;
      attribute vec2 a_texCoord;
    
      uniform vec2 u_resolution;
    
      varying vec2 v_texCoord;
    
      void main() {
        vec2 zeroToOne = a_position / u_resolution;
        vec2 zeroToTwo = zeroToOne * 2.0;
        vec2 clipSpace = zeroToTwo - 1.0;
    
        gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
        v_texCoord = a_texCoord;
      }
    `;
    
    const fragmentSource = `
      precision mediump float;
    
      // our texture
      uniform sampler2D u_image;
    
      // the texCoords passed in from the vertex shader.
      varying vec2 v_texCoord;
    
      void main() {
        gl_FragColor = texture2D(u_image, v_texCoord);
      }
    `;
    
    function main() {
      var image = new Image();
      image.crossOrigin = "anonymous";
      image.onload = function () {
        render(image);
      };
      image.src = "https://pixijs.io/examples/examples/assets/bg_scene_rotate.jpg";
    }
    
    function render(image) {
      // Get A WebGL context
      /** @type {HTMLCanvasElement} */
      var canvas = document.getElementById("c");
      var gl = canvas.getContext("webgl");
    
      // setup GLSL program
      var program = webglUtils.createProgramFromSources(gl, [
        vertexSource,
        fragmentSource
      ]);
    
      // look up where the vertex data needs to go.
      var positionLocation = gl.getAttribLocation(program, "a_position");
      var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
    
      // Create a buffer to put three 2d clip space points in
      var positionBuffer = gl.createBuffer();
    
      // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    
      // Set a rectangle the same size as the image.
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(flatten(tess.points)),
        gl.STATIC_DRAW
      );
    
      // provide texture coordinates for the rectangle.
      var texcoordBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(flatten(tess.uvs)),
        gl.STATIC_DRAW
      );
    
      // Create a texture.
      var texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);
    
      // Set the parameters so we can render any size image.
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    
      // Upload the image into the texture.
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    
      // lookup uniforms
      var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
    
      // resize canvas to display size
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
    
      // Tell WebGL how to convert from clip space to pixels
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    
      // Clear the canvas
      gl.clearColor(0, 0, 0, 0);
      gl.clear(gl.COLOR_BUFFER_BIT);
    
      // Tell it to use our program (pair of shaders)
      gl.useProgram(program);
    
      // index buffer
      const indexBuffer = gl.createBuffer();
      // make this buffer the current 'ELEMENT_ARRAY_BUFFER'
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
      // Fill the current element array buffer with data
    
      const indices = new Uint16Array(flatten(tess.faces));
      gl.bufferData(
        gl.ELEMENT_ARRAY_BUFFER,
        new Uint16Array(indices),
        gl.STATIC_DRAW
      );
    
      // Turn on the position attribute
      gl.enableVertexAttribArray(positionLocation);
    
      // Bind the position buffer.
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    
      // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
      var size = 3; // 2 components per iteration
      var type = gl.FLOAT; // the data is 32bit floats
      var normalize = false; // don't normalize the data
      var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
      var offset = 0; // start at the beginning of the buffer
      gl.vertexAttribPointer(
        positionLocation,
        size,
        type,
        normalize,
        stride,
        offset
      );
    
      // Turn on the texcoord attribute
      gl.enableVertexAttribArray(texcoordLocation);
    
      // bind the texcoord buffer.
      gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
    
      gl.vertexAttribPointer(
        texcoordLocation,
        2, //size,
        type,
        normalize,
        stride,
        offset
      );
    
      // set the resolution
      gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
    
      // Draw the rectangle.
      gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
    }
    
    main();
    body { margin: 0; }
    <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
    <script src="https://unpkg.com/[email protected]/build/js/verb.js"></script>
    <script src="https://unpkg.com/[email protected]/lodash.js"></script>
    <canvas id="c"></canvas>