如何在网页中同时传播数百个带有动画和交互式 webgl 上下文的小画布?

Posted

技术标签:

【中文标题】如何在网页中同时传播数百个带有动画和交互式 webgl 上下文的小画布?【英文标题】:How to have hundred of simultaneous small canvas with animated and interactive webgl context spread in a webpage? 【发布时间】:2014-04-06 05:29:46 【问题描述】:

我有一个网页,其中有大量画布分布在一个文档中。

<canvas type="myCanvas"   config="something different for each" ></canvas>

他们必须渲染一些足够简单的东西,以便在性能和内存方面可以接受,但是同时 webgl 上下文的数量被浏览器限制为十几个。

另一种方法是保留单个上下文并按顺序更新画布,类似于拥有单个 d3d11 设备和多个 dxgiswapchain。

问题:有没有一种有效的方法(没有 gpu 到 cpu 内存传输)将帧缓冲区从由代码创建的唯一的屏幕外启用 webgl 的画布多次复制到页面中的可见画布,如果可能,做一个事先测试不刷新窗口外的?

【问题讨论】:

我的半分钱:重构以保持在 12 个同时曲面限制内。 【参考方案1】:

我的建议:

使用单个画布 改为更新该画布中的区域 除了getImageData / createImageDataputImageData 之外,Canvas 的大部分方法(如果可用)都使用 GPU。

除了位图之外,每个画布都有开销,因此使用单个画布可以减少这种开销,还可以让浏览器更容易解析和更新 DOM。

您应该能够在该画布上绘制区域,就好像它们在单独的画布/图像中一样。

【讨论】:

是的,自从我提出这个问题以来,我已经通过在页面顶部强制使用透明的全屏画布并在跟踪画布的位置上渲染来实现类似的解决方法。它仍然存在问题,chrome vs firefox,在某些矩形的边界上小于像素对齐和滚动时的一帧延迟。我可能能够解决这个问题,毕竟这是我使用 javascript+webgl 的第一天。我只是觉得这是一个系统上的大量手动覆盖,首先为您处理页面布局。【参考方案2】:

不确定您要做什么,也许这就是您的建议,但是,

    您可以渲染到一个基于 WebGL 的画布,然后使用 drawImage 复制到多个基于 2D 的画布。实际上类似于

    <canvas id="c1"></canvas>
    <canvas id="c2"></canvas>
    <canvas id="c3"></canvas>
    

    然后

    var webGLOffScreenCanvas = document.createElement("canvas");
    var gl = webglOffScreenCanvas = webGLOffScreenCanvas.getContext("experimental-webgl");
    
    var c1 = document.getElementById("c1");
    var c2 = document.getElementById("c2");
    var c3 = document.getElementById("c3");
    
    var ctx1 = c1.getContext("2d");
    var ctx2 = c2.getContext("2d");
    var ctx3 = c3.getContext("2d");
    
    drawSomeWebGLSceneForC1(gl);
    ctx1.drawImage(webGLOffScreenCanvas, 0, 0);
    drawSomeWebGLSceneForC2(gl);
    ctx2.drawImage(webGLOffScreenCanvas, 0, 0);
    drawSomeWebGLSceneForC3(gl);
    ctx3.drawImage(webGLOffScreenCanvas, 0, 0);
    

    AFAIK 这将是一个基于 GPU 的渲染(将 WebGL 的画布作为纹理绘制到另一个画布的帧缓冲区纹理中,至少 Chrome 是这样,尽管我不是 100% 确定这一点。它很容易测试。这是一个 sn- p

// make relatively large canvases to show possible readback
var width = 2048;
var height = 2048;
var numCanvases = 3;

var webGLOffScreenCanvas = makeCanvas();
var gl = webglOffScreenCanvas = webGLOffScreenCanvas.getContext("experimental-webgl");

var contexts = [];
for (var ii = 0; ii < numCanvases; ++ii) 
    var c = makeCanvas();
    c.style.width = "64px";
    c.style.height = "64px";
    document.body.appendChild(c);
    contexts.push(c.getContext("2d"));


function makeCanvas() 
    var c = document.createElement("canvas"); 
    c.width = width;
    c.height = height;
    return c;


function drawSomeWebGLScene(gl) 
    gl.clearColor(Math.random(), Math.random(), Math.random(), 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

     
function render() 
    contexts.forEach(function(ctx) 
      drawSomeWebGLScene(gl);
      ctx.drawImage(webGLOffScreenCanvas, 0, 0);
    );
    requestAnimationFrame(render);

render();
canvas  border: 1px solid black; margin: 0.5em; 

查看 about:tracing 它并没有出现一眼就读回任何数据。

    制作一个与浏览器窗口(视口)大小相同的画布并将其置于背景中,在要绘制内容的地方使用占位符 div,查找这些 div,获取它们的位置并使用 gl.scissor 和 @987654326 进行绘制@ 设置为匹配该位置

"use strict";
twgl.setDefaults(attribPrefix: "a_");
var m4 = twgl.m4;
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),
  twgl.primitives.createCylinderBufferInfo(gl, 1, 2, 24, 2),
  twgl.primitives.createTorusBufferInfo(gl, 1, 0.4, 24, 12),
];

function rand(min, max) 
  return min + Math.random() * (max - min);


// 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 tex = twgl.createTexture(gl, 
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
  255, 255, 255, 255,
  192, 192, 192, 255,
  192, 192, 192, 255,
  255, 255, 255, 255,
],
);

var objects = [];
var numObjects = 100;
var list = document.getElementById("list");
var listItemTemplate = document.getElementById("list-item-template").text;
for (var ii = 0; ii < numObjects; ++ii) 
var listElement = document.createElement("div");
listElement.innerhtml = listItemTemplate;
listElement.className = "list-item";
var viewElement = listElement.querySelector(".view");
var uniforms = 
  u_lightWorldPos: lightWorldPosition,
  u_lightColor: lightColor,
  u_diffuseMult: chroma.hsv(rand(0, 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(),
;
objects.push(
  ySpeed: rand(0.1, 0.3),
  zSpeed: rand(0.1, 0.3),
  uniforms: uniforms,
  viewElement: viewElement,
  programInfo: programInfo,
  bufferInfo: shapes[ii % shapes.length],
);
list.appendChild(listElement);


function render(time) 
  time *= 0.001;
  twgl.resizeCanvasToDisplaySize(gl.canvas);

  gl.enable(gl.DEPTH_TEST);
  gl.disable(gl.SCISSOR_TEST);
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  gl.enable(gl.SCISSOR_TEST);
  gl.clearColor(0.8, 0.8, 0.8, 1);

  var eye = [0, 0, -8];
  var target = [0, 0, 0];
  var up = [0, 1, 0];

  m4.lookAt(eye, target, up, camera);
  m4.inverse(camera, view);

  objects.forEach(function(obj) 
    var viewElement = obj.viewElement;
    // get viewElement's position
    var rect = viewElement.getBoundingClientRect();
    if (rect.bottom < 0 || rect.top  > gl.canvas.clientHeight ||
        rect.right  < 0 || rect.left > gl.canvas.clientWidth) 
      return;  // it's off screen
    

    var width  = rect.right - rect.left;
    var height = rect.bottom - rect.top;
    var left   = rect.left;
    var bottom = gl.canvas.clientHeight - rect.bottom - 1;

    gl.viewport(left, bottom, width, height);
    gl.scissor(left, bottom, width, height);
    gl.clear(gl.COLOR_BUFFER_BIT);

    var projection = m4.perspective(30 * Math.PI / 180, width / height, 0.5, 100);
    m4.multiply(projection, view, viewProjection);

    var uni = obj.uniforms;
    var world = uni.u_world;
    m4.identity(world);
    m4.rotateY(world, time * obj.ySpeed, world);
    m4.rotateZ(world, time * obj.zSpeed, world);
    m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose);
    m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection);

    gl.useProgram(obj.programInfo.program);
    twgl.setBuffersAndAttributes(gl, obj.programInfo, obj.bufferInfo);
    twgl.setUniforms(obj.programInfo, uni);
    twgl.drawBufferInfo(gl, obj.bufferInfo);
  );

  requestAnimationFrame(render);

requestAnimationFrame(render);
* 
  box-sizing: border-box;
  -moz-box-sizing: border-box;

body 
  margin: 0;
  font-family: monospace;

canvas 
  display: block;
  width: 100vw;
  height: 100vh;

#c 
  position: fixed;

#outer 
  width: 100%;
  z-index: 2;
  position: absolute;
  top: 0px;

#content 
  margin: auto;
  padding: 2em;

#b 
  width: 100%;
  text-align: center;

.list-item 
  margin: 1em;
  padding: 1em;
  border: 1px solid #ccc;
  display: inline-block;
  width: 160px;

.list-item .view 
  width: 100px;
  height: 100px;
  float: left;
  margin: 0 1em 1em 0;

.list-item .description 
  font-family: sans-serif;
  font-size: small;
<canvas id="c"></canvas>
<div id="outer">
  <div id="content">
    <div id="b"><a href="http://twgljs.org">twgl.js</a> - item list</div>
    <div id="list"></div>
  </div>
</div>
  <script id="list-item-template" type="notjs">
    <div class="view"></div>
    <div class="description">bla blaaa blaba bla blabla bla</div>
  </script>
  <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>
  <script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
  <script src="https://twgljs.org/3rdparty/chroma.min.js"></script>

【讨论】:

以上是关于如何在网页中同时传播数百个带有动画和交互式 webgl 上下文的小画布?的主要内容,如果未能解决你的问题,请参考以下文章

如何加载包含数百个 UIImage 实例的数组而不会因为内存激增而导致应用程序崩溃?

如何在 Databricks 中迭代以读取存储在数据湖不同子目录中的数百个文件?

Dash:如何更新回调中的数百个元素?

anime.js 实战:实现一个带有描边动画效果的复选框

如果我需要用户可以访问数百个其他用户的数据,我可以使用Realm DB吗?

python 如何:使用Python和Google查找数百个电子邮件地址