惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
P
Privacy & Cybersecurity Law Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
C
Cybersecurity and Infrastructure Security Agency CISA
S
Security Affairs
Latest news
Latest news
Security Latest
Security Latest
N
News and Events Feed by Topic
Spread Privacy
Spread Privacy
P
Proofpoint News Feed
T
The Blog of Author Tim Ferriss
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
The Exploit Database - CXSecurity.com
The Last Watchdog
The Last Watchdog
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
V
Vulnerabilities – Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
The Cloudflare Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
G
GRAHAM CLULEY
博客园_首页
S
Secure Thoughts
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
AWS News Blog
AWS News Blog
腾讯CDC
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Register - Security
The Register - Security
N
News and Events Feed by Topic
A
Arctic Wolf
MongoDB | Blog
MongoDB | Blog
爱范儿
爱范儿
Project Zero
Project Zero
A
About on SuperTechFans
罗磊的独立博客
云风的 BLOG
云风的 BLOG
Know Your Adversary
Know Your Adversary
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
K
Kaspersky official blog
L
LINUX DO - 最新话题
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
F
Fortinet All Blogs

WebGL Fundamentals

WebGL Using 2 or More Textures WebGL Implementing DrawImage WebGL 2D Matrices WebGL Implementing A Matrix Stack WebGL 2D Rotation WebGL 2D Scale WebGL 2D Translation WebGL - Rasterization vs 3D libraries WebGL 3D - Cameras WebGL 3D Geometry - Lathe WebGL 3D - Directional Lighting WebGL 3D - Point Lighting WebGL 3D - Normal Mapping WebGL 3D - Spot Lighting WebGL - Orthographic 3D WebGL 3D Perspective Correct Texture Mapping WebGL 3D Perspective WebGL Textures WebGL and Alpha WebGL - Animation WebGL Anti-Patterns WebGL Attributes WebGL Boilerplate WebGL - Cross Origin Images WebGL Cross Platform Issues WebGL Cubemaps WebGL 3D - Data Textures WebGL - Drawing Multiple Things WebGL Drawing Without Data WebGL Environment Maps (reflections) WebGL Fog WebGL Framebuffers WebGL Fundamentals WebGL GPGPU WebGL How It Works WebGL Image Processing Continued WebGL Image Processing WebGL Indexed Vertices WebGL Optimization - Instanced Drawing WebGL - Less Code, More Fun WebGL Load Obj with Mtl WebGL Load Obj WebGL Matrices vs Math Matrices WebGL Multiple Views, Multiple Canvases WebGL Picking WebGL Planar and Perspective Projection Mapping WebGL Points, Lines, and Triangles WebGL Post Processing WebGL Precision Issues WebGL Pulling Vertices Accessing textures by pixel coordinate in WebGL2 A simple way to show the load on the GPU's vertex and fragment processing? Apply a displacement map and specular map Can anyone explain what this GLSL fragment shader is doing? Can I mute the warning about vertex attrib 0 being disabled? Create image warping effect in WebGL Creating a smudge/liquify effect How to draw Depth Sprites Determine min/max values for the entire image Don't blend a polygon that crosses itself Drawing 2D image with depth map to achieve pseudo-3D effect Drawing a heightmap Drawing layers with different points Drawing Many different models in a single draw call Drawing textured sprites with instanced drawing Efficient particle system in javascript? (WebGL) Emulating palette based graphics in WebGL FPS-like camera movement with basic matrix transformations Get the size of a point for collision checking GLSL shader to support coloring and texturing How can I compute for 500 points which of 1000 line segments is nearest to each point? How can I create a 16bit historgram of 16bit data How can I get all the uniforms and uniformBlocks How can I move the perspective vanishing point from the center of the canvas? How to Achieve Moving Line with Trail Effects How to bind an array of textures to a WebGL shader uniform? How to blend colors across 2 triangles How to combine more text drawing into fewer draw calls How to control the color between vertices How to create a torus How to detect clipped triangles in the framgment shader How to determine the average brightness in a scene? How to draw correctly textured trapezoid polygons How to fade the drawing buffer How to figure out how much GPU work to do without crashing WebGL How to get audio data into a shader How to get code completion for WebGL in Visual Studio Code How to get the 3d coordinates of a mouse click How to get pixelize effect in webgl? How to implement zoom from mouse in 2D WebGL How to import a heightmap in WebGL How to load images in the background with no jank How to make a smudge brush tool How to make WebGL canvas transparent How to optimize rendering a UI How to prevent texture bleeding with a texture atlas How to process particle positions How to read a single component with readPixels How to render large scale images like 32000x32000 How to simulate a 3D texture in WebGL
WebGL Tips
WebGLFundame · 2025-02-26 · via WebGL Fundamentals

This article is a collection of issues you might run into using WebGL that seemed too small to have their own article.


Taking A Screenshot of the Canvas

In the browser there are effectively 2 functions that will take a screenshot. The old one canvas.toDataURL and the new better one canvas.toBlob

So you'd think it would be easy to take a screenshot by just adding some code like

<canvas id="c"></canvas>
+<button id="screenshot" type="button">Save...</button>
const elem = document.querySelector('#screenshot');
elem.addEventListener('click', () => {
  canvas.toBlob((blob) => {
    saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
  });
});

const saveBlob = (function() {
  const a = document.createElement('a');
  document.body.appendChild(a);
  a.style.display = 'none';
  return function saveData(blob, fileName) {
     const url = window.URL.createObjectURL(blob);
     a.href = url;
     a.download = fileName;
     a.click();
  };
}());

Here's the example from the article on animation with the code above added and some CSS to place the button

When I tried it I got this screenshot

Yes, it's just a blank image.

It's possible it worked for you depending on your browser/OS but in general it's not likely to work.

The issue is that for performance and compatibility reasons, by default the browser will clear a WebGL canvas's drawing buffer after you've drawn to it.

There are 3 solutions.

  1. call your rendering code just before capturing

    The code we used as a drawScene function. It would be best to make that code not change any state and then we could call it to render for capturing.

    elem.addEventListener('click', () => {
    +  drawScene();
      canvas.toBlob((blob) => {
        saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
      });
    });
    
  2. call the capturing code in our render loop

    In this case we'd just set a flag that we want to capture and then in the rendering loop actually do the capture

    let needCapture = false;
    elem.addEventListener('click', () => {
       needCapture = true;
    });
    

    and then in our render loop, which is current implemented in drawScene, somewhere after everything has been drawn

    function drawScene(time) {
      ...
    
    +  if (needCapture) {
    +    needCapture = false;
    +    canvas.toBlob((blob) => {
    +      saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
    +    });
    +  }
    
      ...
    }
    
  3. Set preserveDrawingBuffer: true when creating the WebGL context

     const gl = someCanvas.getContext('webgl', {preserveDrawingBuffer: true});
    

    This makes webgl not clear the canvas after compositing the canvas with the rest of the page but prevents certain possible optimizations.

I'd pick #1 above. For this particular example first I'd separate the parts of the code that update state from the parts that draw.

  var then = 0;

-  requestAnimationFrame(drawScene);
+  requestAnimationFrame(renderLoop);

+  function renderLoop(now) {
+    // Convert to seconds
+    now *= 0.001;
+    // Subtract the previous time from the current time
+    var deltaTime = now - then;
+    // Remember the current time for the next frame.
+    then = now;
+
+    // Every frame increase the rotation a little.
+    rotation[1] += rotationSpeed * deltaTime;
+
+    drawScene();
+
+    // Call renderLoop again next frame
+    requestAnimationFrame(renderLoop);
+  }

  // Draw the scene.
+  function drawScene() {
- function drawScene(now) {
-    // Convert to seconds
-    now *= 0.001;
-    // Subtract the previous time from the current time
-    var deltaTime = now - then;
-    // Remember the current time for the next frame.
-    then = now;
-
-    // Every frame increase the rotation a little.
-    rotation[1] += rotationSpeed * deltaTime;

    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    ...

-    // Call drawScene again next frame
-    requestAnimationFrame(drawScene);
  }

and now we can just call drawScene before capturing

elem.addEventListener('click', () => {
+  drawScene();
  canvas.toBlob((blob) => {
    saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
  });
});

And now it should work.

If you actually check the captured image you'll see the background is transparent. See this article for a few details.


Preventing the canvas being cleared

Let's say you wanted to let the user paint with an animated object. You need to pass in preserveDrawingBuffer: true when you create the webgl context. This prevents the browser from clearing the canvas.

Taking the last example from the article on animation

var canvas = document.querySelector("#canvas");
-var gl = canvas.getContext("webgl");
+var gl = canvas.getContext("webgl", {preserveDrawingBuffer: true});

and change the call to gl.clear so it only clears the depth buffer

-// Clear the canvas AND the depth buffer.
-gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+// Clear the depth buffer.
+gl.clear(gl.DEPTH_BUFFER_BIT);

Note that if you were serious about making a drawing program this would not be a solution as the browser will still clear the canvas anytime we change its resolution. We're changing is resolution based on its display size. Its display size changes when the window changes size. That can include when the user downloads a file, even in another tab, and the browser adds a status bar. It also includes when the user turns their phone and the browser switches from portrait to landscape.

If you really wanted to make a drawing program you'd render to a texture.


Getting Keyboard Input

If you're making a full page / full screen webgl app then you can do whatever you want but often you'd like some canvas to just be a part of a larger page and you'd like it so if the user clicks on the canvas the canvas gets keyboard input. A canvas can't normally get keyboard input though. To fix that set the tabindex of the canvas to 0 or more. Eg.

<canvas tabindex="0"></canvas>

This ends up causing a new issue though. Anything that has a tabindex set will get highlighted when it has the focus. To fix that set its focus CSS outline to none

canvas:focus {
  outline:none;
}

To demonstrate here are 3 canvases

<canvas id="c1"></canvas>
<canvas id="c2" tabindex="0"></canvas>
<canvas id="c3" tabindex="1"></canvas>

and some css just for the last canvas

#c3:focus {
    outline: none;
}

Let's attach the same event listeners to all of them

document.querySelectorAll('canvas').forEach((canvas) => {
  const ctx = canvas.getContext('2d');

  function draw(str) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(str, canvas.width / 2, canvas.height / 2);
  }
  draw(canvas.id);

  canvas.addEventListener('focus', () => {
    draw('has focus press a key');
  });

  canvas.addEventListener('blur', () => {
    draw('lost focus');
  });

  canvas.addEventListener('keydown', (e) => {
    draw(`keyCode: ${e.keyCode}`);
  });
});

Notice you can't get the first canvas to accept keyboard input. The second canvas you can but it gets highlighted. The 3rd canvas has both solutions applied.


Making your background a WebGL animation

A common question is how to make a WebGL animation be the background of a webpage.

There are 2 obvious ways.

  • Set the canvas CSS position to fixed as in
#canvas {
 position: fixed;
 left: 0;
 top: 0;
 z-index: -1;
 ...
}

and set z-index to -1.

A small disadvantage to this solution is your JavaScript must integrate with the page and if you have a complex page then you need to make sure none of the JavaScript in your webgl code conflicts with the JavaScript doing other things in the page.

  • Use an iframe

This is the solution used on the front page of this site.

In your webpage just insert an iframe, for example

<iframe id="background" src="background.html"></iframe>
<div>
  Your content goes here.
</div>

Then style the iframe to fill the window and be in the background which is basically the same code as we used above for the canvas except we also need to set border to none since iframes have a border by default.

#background {
    position: fixed;
    width: 100vw;
    height: 100vh;
    left: 0;
    top: 0;
    z-index: -1;
    border: none;
    pointer-events: none;
}