Circle not rendering to canvas - webgl

I'm trying to render a circle using WebGL. I only need to translate the circle by a dynamic pixel value, so I used some translation logic from a tutorial converting pixel space to clipspace and put that in the vertex shader. I'm also using the common TRIANGLE_FAN technique to make the circle.
I currently can't see anything on the canvas; it renders as a white screen and there is no circle anywhere. I only want the circle to have a radius of 1px.
//shaders
const glsl = (x) => x;
const vertex = glsl`
attribute vec2 a_position;
uniform vec2 u_resolution;
uniform vec2 u_translation;
void main() {
//add in the translation
vec2 position = a_position + u_translation;
// convert the circle points 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);
}
`;
const fragment = glsl`
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
function main() {
// Get A WebGL context
var canvas = document.querySelector("#c");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
const opacity = 0.5; //opacity will be dynamic
const color = [0, 0, 0, opacity];
const translation = [50, 50]; //this translation value with be dynamic but using [50,50] for demo purposes
// Use our boilerplate utils to compile the shaders and link into a program
var program = webglUtils.createProgramFromScripts(gl, [vertex, fragment]);
// look up where the vertex data needs to go.
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// look up uniform locations
var resolutionUniformLocation = gl.getUniformLocation(program,"u_resolution");
var translationUniformLocation = gl.getUniformLocation(program, "u_translation");
var colorUniformLocation = gl.getUniformLocation(program, "u_color");
// 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);
const positions = [
0.0, 0.0 //circle center vertex
];
const stops = 100;
for (i = 0; i < stops; i++){
positions.push(Math.cos(i * 2 * Math.PI/stops)); // x coord
positions.push(Math.sin(i * 2 * Math.PI/stops)); // y coord
}
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
//sets canvas width and height to current size of canvas as specified in css
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);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the attribute
gl.enableVertexAttribArray(positionAttributeLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per stop
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
);
// set the resolution
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
//set the translation
gl.uniform2fv(translationUniformLocation, translation);
//set the color
gl.uniform4fv(colorUniformLocation, color);
// draw
var primitiveType = gl.TRIANGLE_FAN;
var offset = 0;
const count = stops + 1; //adding one for center of circle
gl.drawArrays(primitiveType, offset, count);
}
main();
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<canvas id="c"></canvas>

There are 3 problems with the code above
It's calling createProgramFromScripts instead of createProgramFromSources
The shader doesn't use u_translation
Here's the first 2 lines
//add in the translation
vec2 position = a_position + u_translation;
// convert the circle points from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
that second line is using a_position instead of position
The fan is missing the last triangle.
You probably want <= stops in your for loop
I'd strongly encourge you to follow the tutorials there and get comfortable using matrices. They start with shader code like the code above uses but progress into replacing it with matrices. Even for pixels matrices enable many things that will be hard without.

Related

Webgl fragment shader transparency on pixel that has already been colored

I just learned about Webgl a few days ago, and I am still a bit confused about transparency behavior. I am trying to draw some circles inside a triangle using a fragment shader. Everything appears to be working alright until I have two triangles overlapping. My fragment shader determines if the point is in the circle or not, and then sets the color to either (1.,0.,0.,1.) if it is inside, or (1.,0.,0.,0.) if it is not.
The behavior I am expecting is that when the fragment shader sets a pixel to (1.,0.,0.,0.) that has already been colored (1.,0.,0.,1.), it will appear as (1.,0.,0.,1.). However, it appears colored the same color as the background.
I have tried to turn on blend mode with the following, but it still doesn't seem to be working:
gl.enable( gl.BLEND );
gl.blendEquation( gl.FUNC_ADD );
gl.blendFunc( gl.ONE_MINUS_CONSTANT_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
I have attached my code below.
const vertexShader = `
attribute vec4 aPosition;
attribute vec4 aCenter;
varying vec4 pos;
varying vec4 center;
void main() {
center=aCenter;
pos=aPosition;
gl_Position = aPosition;
}
`;
const fragmentShader = `
precision mediump float;
varying vec4 pos;
varying vec4 center;
void main() {
float inside = pow(pos.x - center.x, 2.0) + pow(pos.y - center.y, 2.0);
float val=max(sign(pow(center.z, 2.0)-inside),0.);
vec4 color=vec4(1.,0.,0.,val);
gl_FragColor = color;
}
`;
const DEG_TO_RAD = 0.0174532925;
const TRI_HEIGHT_MOD = 2;
// build simple circle
var points = [];
var centers = [];
function buildCircle(center, r) {
let angles=[0,120,240];
for(let k=0;k<angles.length;k++){
centers.push(center[0]);
centers.push(center[1]);
centers.push(r);
var x=r * TRI_HEIGHT_MOD * Math.cos(angles[k] * DEG_TO_RAD) + center[0];
var y=r * TRI_HEIGHT_MOD * Math.sin(angles[k] * DEG_TO_RAD) + center[1];
points.push(x);
points.push(y);
}
}
buildCircle([-.3,0],.5);
buildCircle([0,0],.5);
function loadShadersFromString(gl,vertexSource,fragmentSource){
// first compile the vertex shader
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader,vertexSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
console.log(gl.getShaderInfoLog(vertexShader));
return null;
}
// now compile the fragment shader
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader,fragmentSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
console.log(gl.getShaderInfoLog(fragmentShader));
return null;
}
// OK, we have a pair of shaders, we need to put them together
// into a "shader program" object
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert("Could not initialise shaders");
}
return shaderProgram;
}
function loadScene() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if(!gl){return;}
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
// gl.drawElements(gl.LINES, given_animal.vertex_indices_buffer.numItems, gl.UNSIGNED_SHORT, 0);
// gl.lineWidth(width);
// setup GLSL program
// var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
var program = loadShadersFromString(gl,vertexShader,fragmentShader);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "aPosition");
var centerLocation = gl.getAttribLocation(program, "aCenter");
// Create a buffer to put positions in
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(points),gl.STATIC_DRAW);
var centerBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(centers),gl.STATIC_DRAW);
drawScene();
function drawScene() {
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 AND the depth buffer.
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Turn on culling. By default backfacing triangles
// will be culled.
gl.enable(gl.CULL_FACE);
// Enable the depth buffer
gl.enable(gl.DEPTH_TEST);
gl.enable( gl.BLEND );
gl.blendEquation( gl.FUNC_ADD );
gl.blendFunc( gl.ONE_MINUS_CONSTANT_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// 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; // 3 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);
gl.enableVertexAttribArray(centerLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, centerBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 3 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(centerLocation, size, type, normalize, stride, offset);
// Draw the geometry.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = points.length/2;
gl.drawArrays(primitiveType,offset,count);
// Call drawScene again next frame
requestAnimationFrame(drawScene);
}
}
loadScene();
body {
margin: 0;
background-color: wheat;
}
#html{
background-color: wheat;
}
canvas {
margin: 0;
position: absolute;
width: 90vw;
height: 90vh;
left: 5vw;
top: 5vh;
display: block;
}
<canvas id="canvas"></canvas>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
Blending can only work if the Depth Test is not enabled:
gl.enable(gl.DEPTH_TEST);
When the depth test is enabled, the fragments may be discarded by the depth test, before they can be blended.

Webgl rotate two triangles using offset in vertex shader without using transformation matrix

The goal of this task is to display two triangles on the canvas using the same vertex data and an offset to display the triangles and have them rotated in the vertex shader. I can get two triangles to display (comment out the window.requestAnimFrame(render, canvas); in my render function) how ever when trying to animate this code only one of the triangles displays, is there something really obvious I'm missing? Code below.
canvas display with requestAnimFrame commented out
canvas display after trying to animate the triangles
var fRotation;
var uOffset;
window.onload = function init()
{
canvas = document.getElementById("gl-canvas");
gl = WebGLUtils.setupWebGL(canvas);
if (!gl) {alter("WebGL is not available.");}
fRotation = 1;
gl.viewport(0, 0, 512, 512);
gl.clearColor(0, 0, 0, 1);
points = [
vec2(-1, 0),
vec2(1, 0),
vec2(0, 1)
];
colors = [
vec3(0, 1, 0),
vec3(1, 0, 0),
vec3(0, 0, 1)
];
var program = initShaders(gl, vBasicShaderCode, fBasicShaderCode);
gl.useProgram(program);
var posBufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBufferId);
gl.bufferData(gl.ARRAY_BUFFER, flatten(points), gl.STATIC_DRAW);
var vPos = gl.getAttribLocation(program, "aPosition");
console.log("position data loaded");
// load the data into GPU
var colBufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colBufferId);
gl.bufferData(gl.ARRAY_BUFFER, flatten(colors), gl.STATIC_DRAW);
// Associate shader variables with data buffer
var vCol = gl.getAttribLocation(program, "aColour");
gl.vertexAttribPointer(vCol, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vCol);
console.log("color data loaded");
render();
function drawtri(){
gl.enableVertexAttribArray(vPos);
gl.bindBuffer(gl.ARRAY_BUFFER, posBufferId);
gl.vertexAttribPointer(vPos, 2, gl.FLOAT, false, 0, 0);
fRotation += 0.1 / 144;
gl.uniform1f(gl.getUniformLocation(program, "fRotation"), fRotation );
gl.drawArrays(gl.TRIANGLES, 0, 3);
}
function render(){
gl.clear(gl.COLOR_BUFFER_BIT);
drawtri();
var uOffset = gl.getUniformLocation(program, "uOffset"); // first need to get the location of the uniform variable
var offset = vec2(0.3, 0.1); // we define 'offset' which is a 2 dimensional vector
gl.uniform2fv(uOffset, offset); // we pass 'offset' to the variable in the Vertex Shader.
drawtri();
window.requestAnimFrame(render, canvas);
}
}
and the vertex shader
var vBasicShaderCode =`
attribute vec2 aPosition;
uniform vec2 uOffset;
attribute vec3 aColour;
uniform float fRotation;
varying vec3 vColour;
void
main()
{
vColour=aColour;
vec2 uPosition = vec2(0.0,0.0);
//translate
uPosition.x = aPosition.x;
uPosition.y = aPosition.y;
vec2 transformedVertexPosition = (aPosition + uOffset );
uPosition.x = (cos(fRotation)*transformedVertexPosition.x)-(sin(fRotation)*transformedVertexPosition.y);
uPosition.y = (cos(fRotation)*transformedVertexPosition.y)+(sin(fRotation)*transformedVertexPosition.x);
//gl_Position = vec4(transformedVertexPosition, 0.0, 1.0);
gl_Position = vec4(uPosition.x, uPosition.y, 0.0, 1.0);
}`;
any help would be greatly appreciated.
You need to set the uOffset for every draw.
The code is effectively doing this
uOffset = 0 // the default value
render
drawTri
uOffset = 0.3, 0.1
drawTri
requestAnimationFrame
render
drawTri // uOffset is still 0.3, 0.1 here. it doesn't magically go back to 0
uOffset = 0.3, 0.1
drawTri
Adding answer to show the code that fixed this issue, thanks to Gman for the advice.
gl.clear(gl.COLOR_BUFFER_BIT);
var uOffset = gl.getUniformLocation(program, "uOffset");
var offset = vec2(0.0, 0.0);
gl.uniform2fv(uOffset, offset);
drawtri();
var offset = vec2(0.3, 0.1);
gl.uniform2fv(uOffset, offset);
drawtri();
window.requestAnimFrame(render, canvas);
} ```

Why is rendering blurred in WebGL?

I'm very new to WebGL. I tried copying and pasting code from a WebGL tutorial https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html to render randomly sized and randomly colored rectangles, but found that the rectangles were very blurry in my browser (Firefox 67.0.4).
I've pasted the screenshot below. Because the image below is much smaller, the blurriness isn't as apparent as when viewed in my browser, but you can still see that it's blurry.
Does anyone know why it's coming out blurry in my browser, and how to fix?
Below I've re-pasted in its entirety the code for the WebGL program:
<canvas id="canvas"></canvas>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the rectangle 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);
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script>
//MAIN JAVASCRIPT CODE FOLLOWS HERE
"use strict";
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
// look up where the vertex data needs to go.
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// look up uniform locations
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
var colorUniformLocation = gl.getUniformLocation(program, "u_color");
// 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);
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);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the attribute
gl.enableVertexAttribArray(positionAttributeLocation);
// Bind the position buffer.
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);
// set the resolution
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
// draw 50 random rectangles in random colors
for (var ii = 0; ii < 50; ++ii) {
// Setup a random rectangle
// This will write to positionBuffer because
// its the last thing we bound on the ARRAY_BUFFER
// bind point
setRectangle(
gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
// Set a random color.
gl.uniform4f(colorUniformLocation, Math.random(), Math.random(), Math.random(), 1);
// Draw the rectangle.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// Fill the buffer with the values that define a rectangle.
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);
}
main();
</script>
<style>
#import url("https://webglfundamentals.org/webgl/resources/webgl-tutorials.css");
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
</style>
The reason is is because you put the style section at the end. Things get executed in order so first the script executes. At that time there is no style so the canvas is the default 300x150. The script draws. Then the <style> section comes and tells the browser to display that 300x150 texture the full size of the window. Move the style section before the script or ideally to the top.
Still the sample only renders one time. If you resize the page it does not re-render so even if you move the <style> above the <script> if the window starts small and you resize the window larger you'll still get blur.
To handle resizing you'd need to render the rectangles again. To draw the same rectangles you'd need to save the positions, sizes, and colors used. It would be up to you to decide if they should stay the same size relative to the window or not, the same aspect or not.
You might find this article useful.
The code below picks 50 random rectangles and colors
// pick 50 random rectangles and their colors
const rectangles = [];
for (let ii = 0; ii < 50; ++ii) {
rectangles.push({
rect: [randomInt(300), randomInt(300), randomInt(300), randomInt(300)],
color: [Math.random(), Math.random(), Math.random(), 1],
});
}
It then draws the previously picked rectangles in a render function
function render() {
...
for (const rectangle of rectangles) {
// This will write to positionBuffer because
// its the last thing we bound on the ARRAY_BUFFER
// bind point
setRectangle(
gl, ...rectangle.rect);
// Set the color.
gl.uniform4f(colorUniformLocation, ...rectangle.color);
// Draw the rectangle.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
finally it calls render when the page resizes
window.addEventListener('resize', render);
"use strict";
function main() {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
// look up where the vertex data needs to go.
var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// look up uniform locations
var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
var colorUniformLocation = gl.getUniformLocation(program, "u_color");
// 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);
// pick 50 random rectangles and their colors
const rectangles = [];
for (let ii = 0; ii < 50; ++ii) {
rectangles.push({
rect: [randomInt(300), randomInt(300), randomInt(300), randomInt(300)],
color: [Math.random(), Math.random(), Math.random(), 1],
});
}
function render() {
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);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the attribute
gl.enableVertexAttribArray(positionAttributeLocation);
// Bind the position buffer.
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);
// set the resolution
gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
for (const rectangle of rectangles) {
// This will write to positionBuffer because
// its the last thing we bound on the ARRAY_BUFFER
// bind point
setRectangle(
gl, ...rectangle.rect);
// Set the color.
gl.uniform4f(colorUniformLocation, ...rectangle.color);
// Draw the rectangle.
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
render();
window.addEventListener('resize', render);
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// Fill the buffer with the values that define a rectangle.
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);
}
main();
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="canvas"></canvas>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the rectangle 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);
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>

WebGL: fade drawing buffer

I've set preserveDrawingBuffer to true.
Doing this results in everything drawn on the buffer to be seen all at once, however,
I was wondering if there is a way to somehow fade the buffer as time goes on so that the old elements drawn disappear over time, and the newest drawn elements appear with a relatively high opacity until they also fade away.
Is there a better way to achieve such an effect?
I've tried to render previous elements again by lowering their opacity until it reaches 0 but it didn't seem like an efficient way of fading as once something is drawn I don't plan on changing it.
Thanks!
It's actually common it just redraw stuff which I went over here
WebGL: smoothly fade lines out of canvas
Redrawing stuff means you can keep some things from not fading out. For example if you're making a space shooting game and you only want explosions and missile trails to fade out but you don't want the spaceships and asteroids to fade out then you need to do it by redrawing everything and manually fading stuff out by drawn them while decreasing their alpha
If you just want everything to fade out then you can use a post processing type effect.
You make 2 textures and attach them to 2 framebuffers. You blend/fade the first framebuffer fadeFb1 into the second one fadeFb2 with a fadeColor using
gl_FragColor = mix(textureColor, fadeColor, mixAmount);
You then draw any new stuff to fadeFb2
Then finally draw fadeFb2 to the canvas so you can see the result.
The next frame you do the same thing except swap which buffer you're drawing to and which one you're fading to.
frame 0: mix(fadeFb1,fadeColor)->fadeFb2, draw->fadeFb2, fadeFB2->canvas
frame 1: mix(fadeFb2,fadeColor)->fadeFb1, draw->fadeFb1, fadeFB1->canvas
frame 2: mix(fadeFb1,fadeColor)->fadeFb2, draw->fadeFb2, fadeFB2->canvas
...
Note you don't clear when you draw since you need the result to be left behind
As for setting up framebuffers there's a tutorial here that might be useful
http://webglfundamentals.org/webgl/lessons/webgl-image-processing-continued.html
Here's an example using twgl since I'm too lazy for straight WebGL
var vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
var fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
var vsQuad = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
var fsFade = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_mixAmount;
uniform vec4 u_fadeColor;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
gl_FragColor = mix(color, u_fadeColor, u_mixAmount);
}
`;
var fsCopy = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texcoord);
}
`;
var $ = document.querySelector.bind(document);
var mixAmount = 0.05;
var mixElem = $("#mix");
var mixValueElem = $("#mixValue");
mixElem.addEventListener('input', function(e) {
setMixAmount(e.target.value / 100);
});
function setMixAmount(value) {
mixAmount = value;
mixValueElem.innerHTML = mixAmount;
}
setMixAmount(mixAmount);
var gl = $("canvas").getContext("webgl");
var m4 = twgl.m4;
var programInfo = twgl.createProgramInfo(gl, [vs, fs]);
var fadeProgramInfo = twgl.createProgramInfo(gl, [vsQuad, fsFade]);
var copyProgramInfo = twgl.createProgramInfo(gl, [vsQuad, fsCopy]);
// Creates a -1 to +1 quad
var quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
// Creates an RGBA/UNSIGNED_BYTE texture and depth buffer framebuffer
var imgFbi = twgl.createFramebufferInfo(gl);
// Creates 2 RGBA texture + depth framebuffers
var fadeAttachments = [
{ format: gl.RGBA, min: gl.NEAREST, max: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
{ format: gl.DEPTH_STENCIL },
];
var fadeFbi1 = twgl.createFramebufferInfo(gl, fadeAttachments);
var fadeFbi2 = twgl.createFramebufferInfo(gl, fadeAttachments);
function drawThing(gl, x, y, rotation, scale, color) {
var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1);
matrix = m4.translate(matrix, [x, y, 0]);
matrix = m4.rotateZ(matrix, rotation);
matrix = m4.scale(matrix, [scale, scale, 1]);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo);
twgl.setUniforms(programInfo, {
u_matrix: matrix,
u_color: color,
});
twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo);
}
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return min + Math.random() * (max - min);
}
function render(time) {
if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
twgl.resizeFramebufferInfo(gl, fadeFbi1, fadeAttachments);
twgl.resizeFramebufferInfo(gl, fadeFbi2, fadeAttachments);
}
// fade by copying from fadeFbi1 into fabeFbi2 using mixAmount.
// fadeFbi2 will contain mix(fadeFb1, u_fadeColor, u_mixAmount)
twgl.bindFramebufferInfo(gl, fadeFbi2);
gl.useProgram(fadeProgramInfo.program);
twgl.setBuffersAndAttributes(gl, fadeProgramInfo, quadBufferInfo);
twgl.setUniforms(fadeProgramInfo, {
u_texture: fadeFbi1.attachments[0],
u_mixAmount: mixAmount,
u_fadeColor: [0, 0, 0, 0],
});
twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo);
// now draw new stuff to fadeFb2. Notice we don't clear!
twgl.bindFramebufferInfo(gl, fadeFbi2);
var x = rand(gl.canvas.width);
var y = rand(gl.canvas.height);
var rotation = rand(Math.PI);
var scale = rand(10, 20);
var color = [rand(1), rand(1), rand(1), 1];
drawThing(gl, x, y, rotation, scale, color);
// now copy fadeFbi2 to the canvas so we can see the result
twgl.bindFramebufferInfo(gl, null);
gl.useProgram(copyProgramInfo.program);
twgl.setBuffersAndAttributes(gl, copyProgramInfo, quadBufferInfo);
twgl.setUniforms(copyProgramInfo, {
u_texture: fadeFbi2.attachments[0],
});
twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo);
// swap the variables so we render to the opposite textures next time
var temp = fadeFbi1;
fadeFbi1 = fadeFbi2;
fadeFbi2 = temp;
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
#ui { position: absolute; top: 0 }
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas></canvas>
<div id="ui">
<span>mix:</span><input id="mix" type="range" min="0" max="100" value="5" /><span id="mixValue"></span>
</div>
The preserveDrawingBuffer flag is useful on a device with limited memory (mobile phones) as it allows those devices to reuse that chunk of memory.
The fading/ghosting effect is done in a different manner: you allocate a texture with the same size as the viewport and do the darkening on this texture instead. Every frame you re-render the contents of this texture to itself while multiplying the color value with a fading factor (say 0.9). Afterwards, on the same texture you render your new elements and finally you render the texture to the viewport (a simple "copy-render").

WebGL: Zooming to and stopping at object in a scene in WebGL

We've created a WebGl application which displays a scene containing multiple objects. The entire scene can be rotated in multiple directions. The application requires the user to be able to zoom up to but NOT thru the object. I know this functionality can be implemented using webgl frameworks such as Three.js and SceneJs. Unfortunately, our application is not leveraging a framework. Is there a way to implement the zoom functionality described here using webgl only? Note: I don't believe object picking will work for us since the user is not required to select any object in the scene. Thanks for your help.
Off the top of my head.
First off you need to know the size of each object in world space. For example if one object is 10 units big and another is 100 units big you probably want to be a different distance from the 100 unit object as the 10 unit object. By world space I also mean if you're scaling the 10 unit object by 9 then in world space it would be 90 units big and again you'd want to get a different distance away then if it was 10 units
You generally compute the size of an object in local space by computing the extents of its vertices. Just go through all the vertices and keep track of the min and max values in x, y, and z. Whether you want to take the biggest value from the object's origin or compute an actual center point is up to you.
So, given the size we can compute how far away you need to be to see the entire object. For the standard perspective matrix you can just work backward. If you know your object is 10 units big then you need to fit 10 units in your frustum. You'd probably actually pick something like 14 units (say size * 1.4) so there's some space around the object.
We know halfFovy, halfSizeToFitOnScreen, we need to compute distance
sohcahtoa
tangent = opposite / adjacent
opposite = halfsizeToFitOnScreen
adjacent = distance
tangent = Math.tan(halfFovY)
Therefore
tangent = sizeToFitOnScreen / distance
tangent * distance = sizeToFitOnScreen
distance = sizeToFitOnScreen / tangent
distance = sizeToFitOnScreen / Math.tan(halfFovY)
So now we know the camera needs to be distance away from the object. There's an entire sphere that's distance away from the object. Where you pick on that sphere is up to you. Assuming you go from where the camera currently is you can compute the direction from the object to the camera
direction = normalize(cameraPos - objectPos)
Now you can compute a point distance away in that direction.
desiredCameraPosition = direction * distance
Now either put the camera there using some lookAt function
matrix = lookAt(desiredCameraPosition, objectPosition, up)
Or lerp between where the camera currently is to it's new desired position
var m4 = twgl.m4;
var v3 = twgl.v3;
twgl.setAttributePrefix("a_");
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var shapes = [
twgl.primitives.createCubeBufferInfo(gl, 2),
twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12),
twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1),
];
function rand(min, max) {
return min + Math.random() * (max - min);
}
function easeInOut(t, start, end) {
var c = end - start;
if ((t /= 0.5) < 1) {
return c / 2 * t * t + start;
} else {
return -c / 2 * ((--t) * (t - 2) - 1) + start;
}
}
// Shared values
var lightWorldPosition = [1, 8, -10];
var lightColor = [1, 1, 1, 1];
var camera = m4.identity();
var view = m4.identity();
var viewProjection = m4.identity();
var targetNdx = 0;
var targetTimer = 0;
var zoomTimer = 0;
var eye = v3.copy([1, 4, -60]);
var target = v3.copy([0, 0, 0]);
var up = [0, 1, 0];
var zoomScale = 1.4;
var zoomDuration = 2;
var targetChangeInterval = 3;
var oldEye;
var oldTarget;
var newEye;
var newTarget;
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([
255,255,255,255,
192,192,192,255,
192,192,192,255,
255,255,255,255]));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
var objects = [];
var drawObjects = [];
var numObjects = 100;
var baseHue = rand(0, 360);
for (var ii = 0; ii < numObjects; ++ii) {
var uniforms = {
u_lightWorldPos: lightWorldPosition,
u_lightColor: lightColor,
u_diffuseMult: chroma.hsv((baseHue + rand(0, 60)) % 360, 0.4, 0.8).gl(),
u_specular: [1, 1, 1, 1],
u_shininess: 50,
u_specularFactor: 1,
u_diffuse: tex,
u_viewInverse: camera,
u_world: m4.identity(),
u_worldInverseTranspose: m4.identity(),
u_worldViewProjection: m4.identity(),
};
drawObjects.push({
programInfo: programInfo,
bufferInfo: shapes[ii % shapes.length],
uniforms: uniforms,
});
objects.push({
translation: [rand(-50, 50), rand(-50, 50), rand(-50, 50)],
scale: rand(1, 5),
size: 2,
xSpeed: rand(0.2, 0.7),
zSpeed: rand(0.2, 0.7),
uniforms: uniforms,
});
}
var then = Date.now() * 0.001;
function render() {
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var time = Date.now() * 0.001;
var elapsed = time - then;
then = time;
var radius = 6;
var fovy = 30 * Math.PI / 180;
var projection = m4.perspective(fovy, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.5, 200);
targetTimer -= elapsed;
if (targetTimer <= 0) {
targetTimer = targetChangeInterval;
zoomTimer = 0;
targetNdx = (targetNdx + 1) % objects.length;
oldEye = v3.copy(eye);
oldTarget = v3.copy(target);
var targetObj = objects[targetNdx];
newTarget = targetObj.translation;
var halfSize = targetObj.size * targetObj.scale * zoomScale * 0.5;
var distance = halfSize / Math.tan(fovy * 0.5);
var direction = v3.normalize(v3.subtract(eye, newTarget));
newEye = v3.add(newTarget, v3.mulScalar(direction, distance));
}
zoomTimer += elapsed;
var lerp = easeInOut(Math.min(1, zoomTimer / zoomDuration), 0, 1);
eye = v3.lerp(oldEye, newEye, lerp);
target = v3.lerp(oldTarget, newTarget, lerp);
m4.lookAt(eye, target, up, camera);
m4.inverse(camera, view);
m4.multiply(projection, view, viewProjection);
objects.forEach(function(obj, ndx) {
var uni = obj.uniforms;
var world = uni.u_world;
m4.identity(world);
m4.translate(world, obj.translation, world);
m4.rotateX(world, time * obj.xSpeed, world);
m4.rotateZ(world, time * obj.zSpeed, world);
m4.scale(world, [obj.scale, obj.scale, obj.scale], world);
m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose);
m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection);
});
twgl.drawObjectList(gl, drawObjects);
requestAnimationFrame(render);
}
render();
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<script src="//twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chroma-js/0.6.3/chroma.min.js"></script>
<canvas id="c"></canvas>
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (u_worldViewProjection * a_position);
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
gl_Position = v_position;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_diffuseMult;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
abs(l),//max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord) * u_diffuseMult;
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
</script>

Resources