I am able to write out by hand the full boilerplate to WebGL2 pretty much, and have this much working.
const canvas = document.createElement('canvas')
document.body.appendChild(canvas)
const gl = canvas.getContext('webgl2', { antialias: true })
const width = 800
const height = 500
canvas.width = width
canvas.height = height
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(vertexShader, `#version 300 es
in vec3 position;
in vec4 color;
out vec4 thecolor;
void
main() {
gl_Position = vec4(position, 1.0);
thecolor = color;
}
`)
gl.shaderSource(fragmentShader, `#version 300 es
precision mediump float;
in vec4 thecolor;
out vec4 color;
void
main() {
color = thecolor;
}
`)
gl.compileShader(vertexShader)
var success = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)
if (!success) throw new Error(gl.getShaderInfoLog(vertexShader))
gl.compileShader(fragmentShader)
var success = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)
if (!success) throw new Error(gl.getShaderInfoLog(fragmentShader))
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
const positionAttribute = gl.getAttribLocation(program, 'position')
const colorAttribute = gl.getAttribLocation(program, 'color')
gl.viewport(0, 0, width, height)
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
// I don't know what the purpose of this is.
const positionVAO = gl.createVertexArray()
gl.bindVertexArray(positionVAO)
const vertexBuffer = gl.createBuffer()
const indexBuffer = gl.createBuffer()
const vertexArray = [
// don't know how to structure this on my own.
]
const indexArray = [
// don't know how to structure this either.
]
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexArray), gl.DYNAMIC_DRAW)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), gl.STATIC_DRAW)
gl.enableVertexAttribArray(positionAttribute)
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(colorAttribute)
gl.vertexAttribPointer(colorAttribute, 4, gl.FLOAT, false, 0, 0)
gl.drawElements(gl.TRIANGLES, indexArray.length, gl.UNSIGNED_SHORT, 0)
However, there are 3 comments in there.
I don't know what the purpose of gl.createVertexArray and gl.bindVertexArray are. This explains it.
Don't know how to structure the vertices in vertexArray.
Don't know how to structure the indices in indexArray.
I've gone through many tutorials but they usually gloss over the creation and definition of the the vertices/indices. They don't really explain how they designed them or structured them or why it's like that, so I haven't really been able to reconstruct it on my own yet. I would like to use drawElements with the indices instead of drawArrays.
Wondering if one could show how to draw 3 rectangles each with a different color (which gets passed in through the vertexArray). I was imagining interleaving the positions/colors in the vertexArray, but I don't know how to do that properly, and also don't know how to associate the data with the indexArray. By "properly", I mean I don't understand intuitively yet what goes into the Float32Array for vertices and the Uint32Array for indices. If it is x, y, or x, y, r, g, b, a in this case, or what. I don't understand how the rectangle closes and its "surface" gets colored. Wondering if one could help explain and demonstrate this drawing of 3 rectangles of different colors. That would help solidify how to draw in WebGL!
My attempt at drawing them is this:
const vertexArray = [
1, 1, 1, 1, 1, 1, // x y r g b a
0, 1, 1, 1, 1, 1,
1, 0, 1, 1, 1, 1,
0, 0, 1, 1, 1, 1
]
const indexArray = [
1,
2,
3,
4
]
But it doesn't do anything.
The key to this is the are the last 2 parameters of gl.vertexAttribPointer.
The 5th parameter specifies the byte offset between the sets of consecutive generic vertex attributes. In your case each set of attributes consists of 6 values (x y r g b a) with type float. So the byte offset is 6*4 = 24.
The 6th parameter specifies the byte offset of the first component of the first generic vertex attribute in the array (In case when a named array buffer object is bound).
The offset for the vertex coordinates is 0, since this are the first 2 values.
The offset for the color attribute is 2*4 = 8, since the color attribute starts at the 3rd position.
So the specification of the vertex array has to be:
const vertexArray = [
1, 1, 1, 1, 1, 1, // x y r g b a
0, 1, 1, 1, 1, 1,
1, 0, 1, 1, 1, 1,
0, 0, 1, 1, 1, 1
]
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexArray), gl.DYNAMIC_DRAW)
gl.enableVertexAttribArray(positionAttribute)
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 6*4, 0)
gl.enableVertexAttribArray(colorAttribute)
gl.vertexAttribPointer(colorAttribute, 4, gl.FLOAT, false, 6*4, 2*4)
You want to draw 2 triangles:
2 0
+--------+ 0: (1, 1)
| /| 1: (0, 1)
| / | 2: (1, 0)
| / | 3: (0, 0)
+ -------+
3 1
each triangle consists of 3 indices, so the array of indices has to be:
const indexArray = [ 0, 2, 3, 0, 3, 1 ]
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), gl.STATIC_DRAW)
If you draw this using the primitive type TRIANGLES,
gl.drawElements(gl.TRIANGLES, indexArray.length, gl.UNSIGNED_SHORT, 0)
then this forms the 2 triangles with the coordinates:
1st : (1, 1) -> (1, 0) -> (0, 0)
2nd : (1, 1) -> (0, 0) -> (0, 1)
Of course it is possible to draw a triangles strip (TRIANGLE_STRIP) or triangle fan (TRIANGLE_FAN) instead:
const indexArray = [ 2, 0, 3, 1 ]
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), gl.STATIC_DRAW)
gl.drawElements(gl.TRIANGLE_STRIP, indexArray.length, gl.UNSIGNED_SHORT, 0)
const indexArray = [ 0, 2, 3, 1 ]
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), gl.STATIC_DRAW)
gl.drawElements(gl.TRIANGLE_FAN, indexArray.length, gl.UNSIGNED_SHORT, 0)
var canvas = document.getElementById('my_canvas');
const gl = canvas.getContext('webgl2', { antialias: true })
const width = 800
const height = 500
canvas.width = width
canvas.height = height
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(vertexShader, `#version 300 es
in vec3 position;
in vec4 color;
out vec4 thecolor;
void
main() {
gl_Position = vec4(position, 1.0);
thecolor = color;
}
`)
gl.shaderSource(fragmentShader, `#version 300 es
precision mediump float;
in vec4 thecolor;
out vec4 color;
void
main() {
color = thecolor;
}
`)
gl.compileShader(vertexShader)
var success = gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)
if (!success) throw new Error(gl.getShaderInfoLog(vertexShader))
gl.compileShader(fragmentShader)
var success = gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)
if (!success) throw new Error(gl.getShaderInfoLog(fragmentShader))
const program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
gl.useProgram(program)
const positionAttribute = gl.getAttribLocation(program, 'position')
const colorAttribute = gl.getAttribLocation(program, 'color')
gl.viewport(0, 0, width, height)
gl.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
// I don't know what the purpose of this is.
const positionVAO = gl.createVertexArray()
gl.bindVertexArray(positionVAO)
const vertexBuffer = gl.createBuffer()
const indexBuffer = gl.createBuffer()
const vertexArray = [
1, 1, 1, 1, 0, 1, // x y r g b a
0, 1, 1, 0, 1, 1,
1, 0, 0, 1, 1, 1,
0, 0, 1, 1, 0, 1
]
const indexArray = [0, 2, 3, 0, 3, 1]
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexArray), gl.DYNAMIC_DRAW)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), gl.STATIC_DRAW)
gl.enableVertexAttribArray(positionAttribute)
gl.vertexAttribPointer(positionAttribute, 2, gl.FLOAT, false, 6*4, 0)
gl.enableVertexAttribArray(colorAttribute)
gl.vertexAttribPointer(colorAttribute, 4, gl.FLOAT, false, 6*4, 2*4)
gl.drawElements(gl.TRIANGLES, indexArray.length, gl.UNSIGNED_SHORT, 0)
<canvas id="my_canvas"></canvas>
Related
I was going through this website when I came across the following code in the snippet.
I got lost in the code where positionBuffer was created, and gl.ARRAY_BUFFER was bound to it. It seems that no data was loaded onto the newly created buffer, but instead, the buffer was bound to the attribute a_postion. Later on, a new buffer texCoordBuffer was created and some data was written onto it. In the results, it seems like this same data had been loaded by webGL into the a_position attribute as well. My doubt is, how did this happen? When a new buffer was created and the buffer pointer gl.ARRAY_BUFFER was pointed to the new array, how did the positionBuffer get the same data?
// WebGL2 - 2D image 3x3 convolution
// from https://webgl2fundamentals.org/webgl/webgl-2d-image-3x3-convolution.html
"use strict";
var vertexShaderSource = `#version 300 es
// an attribute is an input (in) to a vertex shader.
// It will receive data from a buffer
in vec2 a_position;
in vec2 a_texCoord;
// Used to pass in the resolution of the canvas
uniform vec2 u_resolution;
// Used to pass the texture coordinates to the fragment shader
out vec2 v_texCoord;
// all shaders have a main function
void main() {
// convert the position from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points.
v_texCoord = a_texCoord;
}
`;
var fragmentShaderSource = `#version 300 es
// fragment shaders don't have a default precision so we need
// to pick one. highp is a good default. It means "high precision"
precision highp float;
// our texture
uniform sampler2D u_image;
// the convolution kernal data
uniform float u_kernel[9];
uniform float u_kernelWeight;
// the texCoords passed in from the vertex shader.
in vec2 v_texCoord;
// we need to declare an output for the fragment shader
out vec4 outColor;
void main() {
vec2 onePixel = vec2(1) / vec2(textureSize(u_image, 0));
vec4 colorSum =
texture(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
texture(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
texture(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
texture(u_image, v_texCoord + onePixel * vec2(-1, 0)) * u_kernel[3] +
texture(u_image, v_texCoord + onePixel * vec2( 0, 0)) * u_kernel[4] +
texture(u_image, v_texCoord + onePixel * vec2( 1, 0)) * u_kernel[5] +
texture(u_image, v_texCoord + onePixel * vec2(-1, 1)) * u_kernel[6] +
texture(u_image, v_texCoord + onePixel * vec2( 0, 1)) * u_kernel[7] +
texture(u_image, v_texCoord + onePixel * vec2( 1, 1)) * u_kernel[8] ;
outColor = vec4((colorSum / u_kernelWeight).rgb, 1);
}
`;
var image = new Image();
image.src = "https://webgl2fundamentals.org/webgl/resources/leaves.jpg"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
render(image);
};
function render(image) {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromSources(gl,
[vertexShaderSource, fragmentShaderSource]);
// look up where the vertex data needs to go.
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
var texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
var imageLocation = gl.getUniformLocation(program, "u_image");
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
// Create a vertex array object (attribute state)
var vao = gl.createVertexArray();
// and make it the one we're currently working with
gl.bindVertexArray(vao);
// Create a buffer and put a single pixel space rectangle in
// it (2 triangles)
var positionBuffer = gl.createBuffer();
// Turn on the attribute
gl.enableVertexAttribArray(positionAttributeLocation);
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the 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(
positionAttributeLocation, size, type, normalize, stride, offset);
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]), gl.STATIC_DRAW);
// Turn on the attribute
gl.enableVertexAttribArray(texCoordAttributeLocation);
// Tell the attribute how to get data out of texCoordBuffer (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(
texCoordAttributeLocation, size, type, normalize, stride, offset);
// Create a texture.
var texture = gl.createTexture();
// make unit 0 the active texture uint
// (ie, the unit all other texture commands will affect
gl.activeTexture(gl.TEXTURE0 + 0);
// Bind it to texture unit 0's 2D bind point
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we don't need mips and so we're not filtering
// and we don't repeat at the edges.
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.
var mipLevel = 0; // the largest mip
var internalFormat = gl.RGBA; // format we want in the texture
var srcFormat = gl.RGBA; // format of data we are supplying
var srcType = gl.UNSIGNED_BYTE; // type of data we are supplying
gl.texImage2D(gl.TEXTURE_2D,
mipLevel,
internalFormat,
srcFormat,
srcType,
image);
// Bind the position buffer so gl.bufferData that will be called
// in setRectangle puts data in the position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
setRectangle(gl, 0, 0, image.width, image.height);
// Define several convolution kernels
var kernels = {
normal: [
0, 0, 0,
0, 1, 0,
0, 0, 0,
],
gaussianBlur: [
0.045, 0.122, 0.045,
0.122, 0.332, 0.122,
0.045, 0.122, 0.045,
],
gaussianBlur2: [
1, 2, 1,
2, 4, 2,
1, 2, 1,
],
gaussianBlur3: [
0, 1, 0,
1, 1, 1,
0, 1, 0,
],
unsharpen: [
-1, -1, -1,
-1, 9, -1,
-1, -1, -1,
],
sharpness: [
0, -1, 0,
-1, 5, -1,
0, -1, 0,
],
sharpen: [
-1, -1, -1,
-1, 16, -1,
-1, -1, -1,
],
edgeDetect: [
-0.125, -0.125, -0.125,
-0.125, 1, -0.125,
-0.125, -0.125, -0.125,
],
edgeDetect2: [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1,
],
edgeDetect3: [
-5, 0, 0,
0, 0, 0,
0, 0, 5,
],
edgeDetect4: [
-1, -1, -1,
0, 0, 0,
1, 1, 1,
],
edgeDetect5: [
-1, -1, -1,
2, 2, 2,
-1, -1, -1,
],
edgeDetect6: [
-5, -5, -5,
-5, 39, -5,
-5, -5, -5,
],
sobelHorizontal: [
1, 2, 1,
0, 0, 0,
-1, -2, -1,
],
sobelVertical: [
1, 0, -1,
2, 0, -2,
1, 0, -1,
],
previtHorizontal: [
1, 1, 1,
0, 0, 0,
-1, -1, -1,
],
previtVertical: [
1, 0, -1,
1, 0, -1,
1, 0, -1,
],
boxBlur: [
0.111, 0.111, 0.111,
0.111, 0.111, 0.111,
0.111, 0.111, 0.111,
],
triangleBlur: [
0.0625, 0.125, 0.0625,
0.125, 0.25, 0.125,
0.0625, 0.125, 0.0625,
],
emboss: [
-2, -1, 0,
-1, 1, 1,
0, 1, 2,
],
};
var initialSelection = 'edgeDetect2';
// Setup UI to pick kernels.
var ui = document.querySelector("#ui");
var select = document.createElement("select");
for (var name in kernels) {
var option = document.createElement("option");
option.value = name;
if (name === initialSelection) {
option.selected = true;
}
option.appendChild(document.createTextNode(name));
select.appendChild(option);
}
select.onchange = function() {
drawWithKernel(this.options[this.selectedIndex].value);
};
ui.appendChild(select);
drawWithKernel(initialSelection);
function computeKernelWeight(kernel) {
var weight = kernel.reduce(function(prev, curr) {
return prev + curr;
});
return weight <= 0 ? 1 : weight;
}
function drawWithKernel(name) {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// 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 | gl.DEPTH_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Bind the attribute/buffer set we want.
gl.bindVertexArray(vao);
// Pass in the canvas resolution so we can convert from
// pixels to clipspace in the shader
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
// Tell the shader to get the texture from texture unit 0
gl.uniform1i(imageLocation, 0);
// set the kernel and it's weight
gl.uniform1fv(kernelLocation, kernels[name]);
gl.uniform1f(kernelWeightLocation, computeKernelWeight(kernels[name]));
// Draw the rectangle.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2,
]), gl.STATIC_DRAW);
}
#import url("https://webgl2fundamentals.org/webgl/resources/webgl-tutorials.css");
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="canvas"></canvas>
<div id="uiContainer">
<div id="ui"></div>
</div>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webgl2fundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webgl2fundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webgl2fundamentals.org/webgl/resources/webgl-utils.js"></script>
Given I spent more time, I see that a_position is an attribute that takes the coordinates of the screen, but a_texCoord takes clipspace coordinates of the texture.
However, down in the code (#181),
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
the buffer pointer is attached to the positionBuffer and then the setRectangle() function attaches data to the attribute a_position.
To add more to the answer, the line (#115),
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
, is put at the top while defining and binding the vertex array object vao is because the gl.vertexAttribPointer() function needs a bound buffer pointer so that the current ARRAY_BUFFER can be attached to the attribute that it is working on.
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>
This is the prelude/simple example to kickstart my bigger idea.
Question: How can we deform the cube's vertices using a sine wave while the cube is scaling, translating or rotating.
Note: Maybe there's some post processing effect I'm not aware of for this and therefore, animating vertices are not best suited for this.
Note 2: My final goal is to push music/audio through geometry/mesh so it has more of an effect like so:
Just to clarify I would like the effect of this image above and I would also like it to be animated and be a piece of 3d geometry not 2d rastered image.
but I fear adding this audio feature is too much for one question.
That being said heres a cube being translated,scaled,rotated. The cube has a light source using normals and color:
var gl,
shaderProgram,
vertices,
matrix = mat4.create(),
vertexCount,
indexCount,
q = quat.create(),
translate =[-3, 0, -10],
scale = [1,1,1],
pivot = [0,0,0];
translate2 = [0, 0, -8],
scale2 = [3,3,3],
pivot2 = [1,1,1]
initGL();
createShaders();
createVertices();
draw();
function initGL() {
var canvas = document.getElementById("canvas");
gl = canvas.getContext("webgl");
gl.enable(gl.DEPTH_TEST);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(1, 1, 1, 1);
}
function createShaders() {
var vertexShader = getShader(gl, "shader-vs");
var fragmentShader = getShader(gl, "shader-fs");
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
}
function createVertices() {
vertices = [
[-1, -1, -1, 1, 0, 0, 1], // 0
[ 1, -1, -1, 1, 1, 0, 1], // 1
[-1, 1, -1, 0, 1, 1, 1], // 2
[ 1, 1, -1, 0, 0, 1, 1], // 3
[-1, 1, 1, 1, 0.5, 0, 1], // 4
[1, 1, 1, 0.5, 1, 1, 1], // 5
[-1, -1, 1, 1, 0, 0.5, 1], // 6
[1, -1, 1, 0.5, 0, 1, 1], // 7
];
var normals = [
[0, 0, 1], [0, 1, 0], [0, 0, -1],
[0, -1, 0], [-1, 0, 0], [1, 0, 0] ];
var indices = [
[0, 1, 2, 1, 2, 3],
[2, 3, 4, 3, 4, 5],
[4, 5, 6, 5, 6, 7],
[6, 7, 0, 7, 0, 1],
[0, 2, 6, 2, 6, 4],
[1, 3, 7, 3, 7, 5]
];
var attributes = []
for(let side=0; side < indices.length; ++side) {
for(let vi=0; vi < indices[side].length; ++vi) {
attributes.push(...vertices[indices[side][vi]]);
attributes.push(...normals[side]);
}
}
vertexCount = attributes.length / 10;
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(attributes), gl.STATIC_DRAW);
var coords = gl.getAttribLocation(shaderProgram, "coords");
gl.vertexAttribPointer(coords, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, 0);
gl.enableVertexAttribArray(coords);
var colorsLocation = gl.getAttribLocation(shaderProgram, "colors");
gl.vertexAttribPointer(colorsLocation, 4, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, Float32Array.BYTES_PER_ELEMENT * 3);
gl.enableVertexAttribArray(colorsLocation);
var normalLocation = gl.getAttribLocation(shaderProgram, "normal");
gl.vertexAttribPointer(normalLocation, 3, gl.FLOAT, false, Float32Array.BYTES_PER_ELEMENT * 10, Float32Array.BYTES_PER_ELEMENT * 7);
gl.enableVertexAttribArray(normalLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
var lightColor = gl.getUniformLocation(shaderProgram, "lightColor");
gl.uniform3f(lightColor, 1, 1, 1);
var lightDirection = gl.getUniformLocation(shaderProgram, "lightDirection");
gl.uniform3f(lightDirection, 0.5, 0.5, -1);
var perspectiveMatrix = mat4.create();
mat4.perspective(perspectiveMatrix, 1, canvas.width / canvas.height, 0.1, 11);
var perspectiveLoc = gl.getUniformLocation(shaderProgram, "perspectiveMatrix");
gl.uniformMatrix4fv(perspectiveLoc, false, perspectiveMatrix);
}
function draw(timeMs) {
requestAnimationFrame(draw);
let interval = timeMs / 3000
let t = interval - Math.floor(interval);
let trans_t = vec3.lerp([], translate, translate2, t);
let scale_t = vec3.lerp([], scale, scale2, t);
let pivot_t = vec3.lerp([], pivot, pivot2, t);
let quat_t = quat.slerp(quat.create(), q, [1,0,1,1], t /2);
mat4.fromRotationTranslationScaleOrigin(matrix, quat_t, trans_t, scale_t, pivot_t);
var transformMatrix = gl.getUniformLocation(shaderProgram, "transformMatrix");
gl.uniformMatrix4fv(transformMatrix, false, matrix);
gl.clear(gl.COLOR_BUFFER_BIT);
//gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_BYTE, 0);
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
}
/*
* https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
*/
function getShader(gl, id) {
var shaderScript, theSource, currentChild, shader;
shaderScript = document.getElementById(id);
if (!shaderScript) {
return null;
}
theSource = "";
currentChild = shaderScript.firstChild;
while (currentChild) {
if (currentChild.nodeType == currentChild.TEXT_NODE) {
theSource += currentChild.textContent;
}
currentChild = currentChild.nextSibling;
}
if (shaderScript.type == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderScript.type == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
// Unknown shader type
return null;
}
gl.shaderSource(shader, theSource);
// Compile the shader program
gl.compileShader(shader);
// See if it compiled successfully
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
<canvas id="canvas" width="600" height="600"></canvas>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec4 coords;
uniform mat4 transformMatrix;
attribute vec3 normal;
attribute vec4 colors;
uniform vec3 lightColor;
uniform vec3 lightDirection;
varying vec4 varyingColors;
uniform mat4 perspectiveMatrix;
void main(void) {
vec3 norm = normalize(normal);
vec3 ld = normalize(lightDirection);
float dotProduct = max(dot(norm, ld), 0.0);
vec3 vertexColor = lightColor * colors.rgb * dotProduct;
varyingColors = vec4(vertexColor, 1);
gl_Position = perspectiveMatrix * transformMatrix * coords;
}
</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 color;
varying vec4 varyingColors;
void main(void) {
gl_FragColor = varyingColors;
}
</script>
I think reasonable way to do it is vertex displacement.
That is adding an offset to vertex position.
To make it work you need to tesselate your cube or take some other mesh with high polygon count. Then you can tie your sine phase to position.
Sort of:
float amplitude = 0.1;
vec3 offset = vec3(sin(globalTime + coords.y * 10.0), 0.0, 0.0) * amplitude;
gl_Position = perspectiveMatrix * transformMatrix * (coords + offset);
For using audio as input you can get a spectrum from WebAudio api and use some bar value as amplitude. Low frequency value initially works well since that's there kicks sound at. Consider asking api for low detail frequency data (few wide bars).
Also at that point some spectrum filtering might be required to smooth visual effect. For example interpolating spectrum data across few last frames.
Using multiple freq bars as input can result in nice equalizer effect. To make it work you can bake bar index as geometry attribute and displace based on that bar value.
I'm trying to display multiple frames from different streams in a single canvas, using viewports and calling draw function on every frame I have to render. What I'm trying to replicate is a camera videowall that uses only one canvas and one webgl context. The problem is that, every time I render a frame into a specific viewport, then the other frames I rendered before in a different viewport, it disappear.
I even tried to initiate the webgl context setting "preserveDrawingBuffer" attribute to true, but it does not solved.
Following the code I'm using:
this.drawNextOuptutPictureGL = function (par) {
var gl = this.contextGL;
var texturePosBuffer = this.texturePosBuffer;
var uTexturePosBuffer = this.uTexturePosBuffer;
var vTexturePosBuffer = this.vTexturePosBuffer;
var yTextureRef = this.yTextureRef;
var uTextureRef = this.uTextureRef;
var vTextureRef = this.vTextureRef;
var width = this.width;
var height = this.height;
var yData = par.yData;
var uData = par.uData;
var vData = par.vData;
var yDataPerRow = par.yDataPerRow || width;
var yRowCnt = par.yRowCnt || height;
var uDataPerRow = par.uDataPerRow || (width / 2);
var uRowCnt = par.uRowCnt || (height / 2);
var vDataPerRow = par.vDataPerRow || uDataPerRow;
var vRowCnt = par.vRowCnt || uRowCnt;
var viewportRow = par.viewportRow;
var viewportColumn = par.viewportColumn;
// Calculate coordinates basing on a square matrix
var square = Math.sqrt(this.viewports.length);
var x = (this.canvasElement.width / square) * (viewportColumn - 1);
var y = (this.canvasElement.height / square) * (square - viewportRow);
gl.viewport(x, y, width, height);
var tTop = 0;
var tLeft = 0;
var tBottom = height / yRowCnt;
var tRight = width / yDataPerRow;
var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
if (this.customYUV444) {
tBottom = height / uRowCnt;
tRight = width / uDataPerRow;
} else {
tBottom = (height / 2) / uRowCnt;
tRight = (width / 2) / uDataPerRow;
};
var uTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, uTexturePosValues, gl.DYNAMIC_DRAW);
if (this.customYUV444) {
tBottom = height / vRowCnt;
tRight = width / vDataPerRow;
} else {
tBottom = (height / 2) / vRowCnt;
tRight = (width / 2) / vDataPerRow;
};
var vTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);
gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vTexturePosValues, gl.DYNAMIC_DRAW);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, yDataPerRow, yRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, uDataPerRow, uRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uData);
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, vDataPerRow, vRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, vData);
// draw image
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
Is there a way to just update a specific viewport without having the entire context to be cleaned?
Thanks
You need to turn on the scissor test and set the scissor rectangle
gl.enable(gl.SCISSOR_TEST);
gl.scissor(x, y, width, height);
Example:
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createCylinderBufferInfo(gl, 1, 1, 24, 1);
const scenes = [
{
viewport: [0, 0, 150, 150],
xRot: 0,
zRot: 0,
bg: [1, 0, 0, 1],
color: [0, 1, 1, 1],
},
{
viewport: [150, 0, 150, 50],
xRot: Math.PI * .5,
zRot: 0,
bg: [0, 1, 0, 1],
color: [1, 0, 1, 1],
},
{
viewport: [150, 50, 150, 100],
xRot: 0,
zRot: Math.PI * 0.25,
bg: [0, 0, 1, 1],
color: [1, 1, 0, 1],
},
];
function render(time) {
time *= 0.001;
gl.enable(gl.SCISSOR_TEST);
scenes.forEach((scene, ndx) => {
gl.viewport(...scene.viewport);
gl.scissor(...scene.viewport);
gl.clearColor(...scene.bg);
gl.clear(gl.COLOR_BUFFER_BIT);
const fov = Math.PI * 0.25;
const aspect = scene.viewport[2] / scene.viewport[3];
const near = 0.1;
const far = 10;
const matrix = m4.perspective(fov, aspect, near, far);
m4.translate(matrix, [Math.sin(time + ndx), 0, -4], matrix);
m4.rotateX(matrix, scene.xRot, matrix);
m4.rotateZ(matrix, scene.zRot, matrix);
m4.rotateZ(matrix, time, matrix);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
color: scene.color,
matrix: matrix,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
});
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
and if you are not rendering every viewport every frame then you'll need to pass in preserveDrawingBuffer: true when creating the webgl context.
Example that only updates one viewport per frame
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl', {
preserveDrawingBuffer: true,
});
const vs = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
gl_Position = matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.primitives.createCylinderBufferInfo(gl, 1, 1, 24, 1);
const scenes = [
{
viewport: [0, 0, 150, 150],
xRot: 0,
zRot: 0,
bg: [1, 0, 0, 1],
color: [0, 1, 1, 1],
},
{
viewport: [150, 0, 150, 50],
xRot: Math.PI * .5,
zRot: 0,
bg: [0, 1, 0, 1],
color: [1, 0, 1, 1],
},
{
viewport: [150, 50, 150, 100],
xRot: 0,
zRot: Math.PI * 0.25,
bg: [0, 0, 1, 1],
color: [1, 1, 0, 1],
},
];
let count = 0;
function render(time) {
++count;
time *= 0.001;
gl.enable(gl.SCISSOR_TEST);
const ndx = count % scenes.length;
const scene = scenes[ndx];
gl.viewport(...scene.viewport);
gl.scissor(...scene.viewport);
gl.clearColor(...scene.bg);
gl.clear(gl.COLOR_BUFFER_BIT);
const fov = Math.PI * 0.25;
const aspect = scene.viewport[2] / scene.viewport[3];
const near = 0.1;
const far = 10;
const matrix = m4.perspective(fov, aspect, near, far);
m4.translate(matrix, [Math.sin(time + ndx), 0, -4], matrix);
m4.rotateX(matrix, scene.xRot, matrix);
m4.rotateZ(matrix, scene.zRot, matrix);
m4.rotateZ(matrix, time, matrix);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
color: scene.color,
matrix: matrix,
});
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
I want to do any ANGLE_instanced_arrays and I was read the documentation on MDN and I did not understand anything. Well, I understood that I can make graphic 2D and 3D like : examples but for me these examples are too advanced and it's difficult to understand all the code. Can anyone help me with an example?
This part I understand of the documentation official:
The ANGLE_instanced_arrays extension is part of the WebGL API and allows to draw the same object, or groups of similar objects multiple times, if they share the same vertex data, primitive count and type.
here is what I read
Can you draw without ANGLE_instanced_arrays? The difference between drawing with and without are
you call a different draw function and pass the an extra parameter of how many instances to draw. ext.drawArraysInstancedANGLE or ext.drawElementsInstancedANGLE instead of the normal ext.drawArrays or ext.drawElements
you add one or more attributes to your vertex shader who's values will only change once per instance drawn. In other words if you're drawing a cube the attribute's value will be the same value for every vertex while drawing the first cube, a different value while drawing the 2nd cube, a 3rd value while drawn the 3rd cube. Where as normal attributes change for each vertex these attributes only change once per cube/item.
The most obvious attribute to add is an extra per cube position so that each cube can have a different position added to the vertex positions but you could add another attribute for a pure cube color or add matrix attributes so you can orient each cube completely independently or whatever you want.
For those attributes that only change once per cube you set their vertex divisor to 1 by calling ext.vertexAttribDivisorANGLE. The 1 means "advance the attribute every 1 instance". 0 (the default) means advance the attribute every vertex (every iteration of the vertex shader).
Here's an example drawing a single quad (2 triangles, 6 vertices)
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const program = twgl.createProgram(gl, [vs, fs]);
const positionLocation = gl.getAttribLocation(program, "position");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const colorLocation = gl.getUniformLocation(program, "u_color");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// one face
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
), gl.STATIC_DRAW);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
}
gl.useProgram(program);
gl.uniform4fv(colorLocation, [1, .5, .2, 1]);
gl.uniformMatrix4fv(matrixLocation, false, m4.scaling([.25, .25, .25]));
const offset = 0;
const vertexCount = 6;
gl.drawArrays(gl.TRIANGLES, offset, vertexCount);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
and here's an example drawing 100 quads using ANGLE_instanced_arrays. We've added a planeOffset for an offset for each quad and a planeColor for a color for each quad.
const vs = `
attribute vec4 position;
attribute vec2 planeOffset; // per plane offset
attribute vec4 planeColor; // per plane color
uniform mat4 u_matrix;
varying vec4 v_color;
void main() {
mat4 translation = mat4(
vec4(1, 0, 0, 0),
vec4(0, 1, 0, 0),
vec4(0, 0, 1, 0),
vec4(planeOffset, 0, 1));
gl_Position = u_matrix * translation * position;
v_color = planeColor;
}
`;
const fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
function main() {
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("ANGLE_instanced_arrays");
if (!ext) {
alert("need ANGLE_instanced_arrays");
return;
}
const program = twgl.createProgram(gl, [vs, fs]);
const positionLocation = gl.getAttribLocation(program, "position");
const offsetLocation = gl.getAttribLocation(program, "planeOffset");
const colorLocation = gl.getAttribLocation(program, "planeColor");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// one face
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
), gl.STATIC_DRAW);
// make 100 offsets and 100 colors
const colors = [];
const offsets = [];
const numInstances = 100;
for (let i = 0; i < 100; ++i) {
colors.push(Math.random(), Math.random(), Math.random(), 1);
offsets.push(Math.random() * 20 - 10, Math.random() * 20 - 10);
}
// put those in buffers
const offsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(offsets), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
}
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.enableVertexAttribArray(offsetLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(offsetLocation, size, type, normalize, stride, offset);
ext.vertexAttribDivisorANGLE(offsetLocation, 1);
}
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(colorLocation);
{
const size = 4; // 4 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
ext.vertexAttribDivisorANGLE(colorLocation, 1);
}
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLocation, false, m4.scaling([.1, .1, .1]));
const offset = 0;
const vertexCount = 6;
ext.drawArraysInstancedANGLE(gl.TRIANGLES, offset, vertexCount, numInstances);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>