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/verb-nurbs-web@2.1.3/build/js/verb.js"></script>
<script src="https://unpkg.com/lodash@4.17.20/lodash.js"></script>
<canvas id="c"></canvas>
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/verb-nurbs-web@2.1.3/build/js/verb.js"></script>
<script src="https://unpkg.com/lodash@4.17.20/lodash.js"></script>
<canvas id="c"></canvas>