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

推荐订阅源

T
Tenable Blog
博客园_首页
Vercel News
Vercel News
WordPress大学
WordPress大学
美团技术团队
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Y
Y Combinator Blog
博客园 - 【当耐特】
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
腾讯CDC
M
MIT News - Artificial intelligence
爱范儿
爱范儿
Recent Announcements
Recent Announcements
雷峰网
雷峰网
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
Jina AI
Jina AI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
Help Net Security
Help Net Security
N
News and Events Feed by Topic
博客园 - Franky
P
Proofpoint News Feed
L
LINUX DO - 热门话题
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
D
Docker
Google DeepMind News
Google DeepMind News
有赞技术团队
有赞技术团队
IT之家
IT之家
Security Latest
Security Latest
L
LangChain Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks

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 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 How to support both WebGL and WebGL2
WebGL Anti-Patterns
WebGLFundame · 2025-02-26 · via WebGL Fundamentals

This is a list of anti patterns for WebGL. Anti patterns are things you should avoid doing

  1. Putting viewportWidth and viewportHeight on the WebGLRenderingContext

    Some code adds properties for their viewport width and height and sticks them on the WebGLRenderingContext something like this

    gl = canvas.getContext("webgl");
    gl.viewportWidth = canvas.width;    // BAD!!!
    gl.viewportHeight = canvas.height;  // BAD!!!
    

    Then later they might do something like this

    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    

    Why it's Bad:

    It's objectively bad because you now have 2 properties that need to be updated anytime you change the size of the canvas. For example if you change the size of the canvas when the user resizes the window gl.viewportWidth & gl.viewportHeight will be wrong unless you set them again.

    It's subjectively bad because any new WebGL programmer will glance at your code and likely think gl.viewportWidth and gl.viewportHeight are part of the WebGL spec, confusing them for months.

    What to do instead:

    Why make more work for yourself? The WebGL context has its canvas available and that has a size.

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    

    The context also has its width and height directly on it.

    // When you need to set the viewport to match the size of the canvas's
    // drawingBuffer this will always be correct
    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
    

    Even better it will handle extreme cases whereas using gl.canvas.width and gl.canvas.height will not. As for why see here.

  2. Using canvas.width and canvas.height for aspect ratio

    Often code uses canvas.width and canvas.height for aspect ratio like this

    const aspect = canvas.width / canvas.height;
    perspective(fieldOfView, aspect, zNear, zFar);
    

    Why it's Bad:

    The width and height of the canvas have nothing to do with the size the canvas is displayed. CSS controls the size the canvas is displayed.

    What to do instead:

    Use canvas.clientWidth and canvas.clientHeight. Those values tell you what size your canvas is actually being displayed on the screen. Using those values you'll always get the correct aspect ratio regardless of your CSS settings.

    const aspect = canvas.clientWidth / canvas.clientHeight;
    perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
    

    Here are examples of a canvas who's drawingbuffers are the same size (width="400" height="300") but using CSS we've told the browser to display the canvas a different size. Notice the samples both display the 'F' in the correct aspect ratio.

    If we had used canvas.width and canvas.height that would not be true.

  3. Using window.innerWidth and window.innerHeight to compute anything

    Many WebGL programs use window.innerWidth and window.innerHeight in many places. For example:

    canvas.width = window.innerWidth;                    // BAD!!
    canvas.height = window.innerHeight;                  // BAD!!
    

    Why it's Bad:

    It's not portable. Yes, it can work for WebGL pages where you want to make the canvas fill the screen. The problem comes when you don't. Maybe you decide to make an article like these tutorials where your canvas is just some small diagram in a larger page. Or maybe you need some property editor on the side or a score for a game. Sure you can fix your code to handle those cases but why not just write it so it works in those cases in the first place? Then you won't have to go change any code when you copy it to a new project or use an old project in a new way.

    What to do instead:

    Instead of fighting the Web platform, use the Web platform as it was designed to be used. Use CSS and clientWidth and clientHeight.

    var width = gl.canvas.clientWidth;
    var height = gl.canvas.clientHeight;
    
    gl.canvas.width = width;
    gl.canvas.height = height;
    

    Here are 9 cases. They all use exactly the same code. Notice that none of them reference window.innerWidth nor window.innerHeight.

    A page with nothing but a canvas using CSS to make it fullscreen

    A page with a canvas set to using 70% width so there is room for editor controls

    A page with a canvas embedded in a paragraph

    A page with a canvas embedded in a paragraph using box-sizing: border-box;

    box-sizing: border-box; makes borders and padding take space from the element they're defined on rather than outside it. In other words, in normal box-sizing mode a 400x300 pixel element with 15 pixel border has a 400x300 pixel content space surrounded by a 15 pixel border making its total size 430x330 pixels. In box-sizing: border-box mode the border goes on the inside so that same element would stay 400x300 pixels, the content would end up being 370x270. This is yet another reason why using clientWidth and clientHeight is so important. If you set the border to say 1em you'd have no way of knowing what size your canvas will turn out. It would be different with different fonts on different machines or different browsers.

    A page with nothing but a container using CSS to make it fullscreen into which the code will insert a canvas

    A page with a container set to using 70% width so there is room for editor controls into which the code will insert a canvas

    A page with a container embedded in a paragraph into which the code will insert a canvas

    A page with a container embedded in a paragraph using box-sizing: border-box; into which the code will insert a canvas

    A page with no elements with CSS setup to make it fullscreen into which the code will insert a canvas

    Again, the point is, if you embrace the web and write your code using the techniques above you won't have to change any code when you run into different use cases.

  4. Using the 'resize' event to change the size of your canvas.

    Some apps check for the window 'resize' event like this to resize their canvas.

    window.addEventListener('resize', resizeTheCanvas);
    

    or this

    window.onresize = resizeTheCanvas;
    

    Why it's Bad:

    It's not bad per se, rather, for most WebGL programs it fits less use cases. Specifically 'resize' only works when the window is resized. It doesn't work if the canvas is resized for some other reason. For example let's say you're making a 3D editor. You have your canvas on the left and your settings on the right. You've made it so there's a draggable bar separating the 2 parts and you can drag that bar to make the settings area larger or smaller. In this case you won't get any 'resize' events. Similarly if you've got a page where other content gets added or removed and the canvas changes size as the browser re-lays out the page you won't get a resize event.

    What to do instead:

    Like many of the solutions to anti-patterns above there's a way to write your code so it just works for most cases. For WebGL apps that constantly draw every frame the solution is to check if you need to resize every time you draw like this

    function resizeCanvasToDisplaySize() {
      var width = gl.canvas.clientWidth;
      var height = gl.canvas.clientHeight;
      if (gl.canvas.width != width ||
          gl.canvas.height != height) {
         gl.canvas.width = width;
         gl.canvas.height = height;
      }
    }
    
    function render() {
       resizeCanvasToDisplaySize();
       drawStuff();
       requestAnimationFrame(render);
    }
    render();
    

    Now in any of those cases your canvas will scale to the right size. No need to change any code for different cases. For example using the same code from #3 above here's an editor with a sizable editing area.

    There would be no resize events for this case nor any other where the canvas gets resized based on the size of other dynamic elements on the page.

    For WebGL apps that don't re-draw every frame the code above is still correct, you'll just need to trigger a re-draw in every case where the canvas can possibly get resized. One easy way to use a ResizeObserver

    const resizeObserver = new ResizeObserver(render);
    resizeObserver.observe(gl.canvas, {box: 'content-box'});
    
  5. Adding properties to WebGLObjects

    WebGLObjects are the various types of resources in WebGL like a WebGLBuffer or WebGLTexture. Some apps add properties to those objects. For example code like this:

    var buffer = gl.createBuffer();
    buffer.itemSize = 3;        // BAD!!
    buffer.numComponents = 75;  // BAD!!
    
    var program = gl.createProgram();
    ...
    program.u_matrixLoc = gl.getUniformLocation(program, "u_matrix");  // BAD!!
    

    Why it's Bad:

    The reason this is bad is that WebGL can "lose the context". This can happen for any reason but the most common reason is if the browser decides too many GPU resources are being used it might intentionally lose the context on some WebGLRenderingContexts to free up space. WebGL programs that want to always work have to handle this. Google Maps handles this for example.

    The problem with the code above is that when the context is lost the WebGL creation functions like gl.createBuffer() above will return null. That effectively makes the code this

    var buffer = null;
    buffer.itemSize = 3;        // ERROR!
    buffer.numComponents = 75;  // ERROR!
    

    That will likely kill your app with an error like

    TypeError: Cannot set property 'itemSize' of null
    

    While many apps don't care if they die when the context is lost it seems like a bad idea to write code that will have to be fixed later if the developers ever decide to update their app to handle context lost events.

    What to do instead:

    If you want to keep WebGLObjects and some info about them together one way would be to use JavaScript objects. For example:

    var bufferInfo = {
      id: gl.createBuffer(),
      itemSize: 3,
      numComponents: 75,
    };
    
    var programInfo = {
      id: program,
      u_matrixLoc: gl.getUniformLocation(program, "u_matrix"),
    };
    

    Personally I'd suggest using a few simple helpers that make writing WebGL much simpler.

Those are a few of what I consider WebGL Anti-Patterns in code I've seen around the net. Hopefully I've made the case why to avoid them and given solutions that are easy and useful.

What is drawingBufferWidth and drawingBufferHeight?

GPUs have a limit on how big a rectangle of pixels (texture, renderbuffer) they can support. Often this size is the next power of 2 larger than whatever a common monitor resolution was at the time the GPU was made. For example if the GPU was designed to support 1280x1024 screens it might have a size limit of 2048. If it was designed for 2560x1600 screens it might have a limit of 4096.

That seems reasonable but what happens if you have multiple monitors? Let's say I have a GPU with a limit of 2048 but I have two 1920x1080 monitors. The user opens a browser window with a WebGL page, they then stretch that window across both monitors. Your code tries to set the canvas.width to canvas.clientWidth which in this case is 3840. What should happen?

Off the top of my head there are only 3 options

  1. Throw an exception.

    That seems bad. Most web apps won't be checking for it and the app will crash. If the app had user data in it the user just lost their data

  2. Limit the size of the canvas to the GPUs limit

    The problem with this solution is it will also likely lead to a crash or possibly a messed up webpage because the code expects the canvas to be the size they requested and they expect other parts of the UI and elements on the page to be in the proper places.

  3. Let the canvas be the size the user requested but make its drawingbuffer the limit

    This is the solution WebGL uses. If your code is written correctly the only thing the user might notice is the image in the canvas is being scaled slightly. Otherwise it just works. In the worst case most WebGL programs that don't do the right thing will just have a slightly off display but if the user sizes the window back down things will return to normal.

Most people don't have multiple monitors so this issue rarely comes up. Or at least it used to. Chrome and Safari, at least as of January 2015, had a hard coded limit on canvas size of 4096. Apple's 5k iMac is past that limit. Lots of WebGL apps were having strange displays because of this. Similarly many people have started using WebGL with multiple monitors for installation work and have been hitting this limit.

So, if you want to handle these cases use gl.drawingBufferWidth and gl.drawingBufferHeight as shown in #1 above. For most apps if you follow the best practices above things will just work. Be aware though if you are doing calculations that need to know the actual size of the drawingbuffer you need to take that into account. Examples off the top of my head, [picking](webgl-picking.html), in other words converting from mouse coordinates into canvas pixel coordinates. Another would be any kind of post processing effects that want to know the actual size of the drawingbuffer.