在浏览器上,如何绘制100k系列,每个64-128点? [英] On the browser, how to plot 100k series with 64-128 points each?

查看:61
本文介绍了在浏览器上,如何绘制100k系列,每个64-128点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想绘制约12万个序列的图,每个序列有64个点(向下采样,如果使用实际采样率,则为128-512点,甚至更大)

I want to graph about 120k series, each of them having 64 points (down sampled, 128-512 points if using the real sampling rate, which is even larger)

我试图用dygraph来做,但是如果我使用1000个以上的序列,这似乎会很慢.

I have attempted to do it with dygraph but it seems to be very slow if i use more than 1000 series.

我尝试使用香草WebGL,绘制速度非常快,但是比我的问题是要获得鼠标的点击,而不是确定是哪一个系列-对此有何策略?(我相信它叫做非投影吗?)-因为有100k +系列,所以对于每个系列使用不同的颜色,而不是使用单击坐标的像素颜色来确定系列是不切实际的.还有其他策略吗?

I have attempted to use vanilla WebGL, it drew really fast, but than my problem was getting the mouse's click and than deciding which series it was - any strategies on this? (I believe its called unprojecting?) - since there are 100k+ series, using a different color for each series and than using the click coordinate's pixel's color to determine the series is impractical. Any other strategies?

我当前的设计将图形绘制为包含所有图形的大型PNG地图集,加载速度很快,但是在数据更改时,我不得不在服务器上重新绘制PNG,然后再次显示,也未投影"这里有一个问题-关于如何解决的任何想法?如果可能的话?

My current design draws the graph as a large PNG atlas containing all the graphs, this is fast to load, but on changes on the data i have to redraw the PNG on the server and than show it again, also "unprojecting" is an issue here - any ideas on how to solve it? if possible?

数据已经过采样了,进一步的采样可能会导致我想向最终用户显示的细节丢失.

The data is already quite down sampled, further down sampling will probably result in loss of details i would like to show the end-user.

推荐答案

绘制120k * 64表示2700x2700的每个像素都可以覆盖.换句话说,您可能试图显示太多数据?这也是非常多的事情,并且可能会很慢.

Drawing 120k * 64 things means every single pixel of a 2700x2700 could be covered. In other words you're probably trying to display too much data? It's also a very large number of things and likely to be slow.

在任何情况下,通过WebGL绘图和拾取都是相对容易的.您可以使用所需的任何技术绘制场景.然后,单独地,当用户单击鼠标(或始终在鼠标下方)时,您会再次将整个场景绘制到屏幕外的帧缓冲区中,从而为每个可选择的事物提供不同的颜色.给定默认情况下有32位颜色(红色8位,绿色8位,蓝色8位,alpha 8位)可以计数2 ^ 32-1的事物.当然,使用其他缓冲区格式时,您甚至可以数更多或者使用多个缓冲区,但是存储2 ^ 32个对象的数据可能是更大的限制.

In any case drawing and picking via WebGL is relatively easy. You draw your scene using whatever techniques you want. Then, separately, when the user clicks the mouse (or always under the mouse) you draw the entire scene again to an offscreen framebuffer giving every selectable thing a different color. Given there are 32bits of color by default (8bits red, 8bits green, 8bits blue, 8bits alpha) can count 2^32-1 things. Of course with other buffer formats you could count even higher or draw to multiple buffers but storing the data for 2^32 things is probably the larger limit.

无论如何,这是一个例子.这可以制作1000个多维数据集(因为该示例已经存在,所以只能使用多维数据集).您可以将每个多维数据集视为带有8点的系列"之一,尽管该代码实际上每个多维数据集绘制了24点.(设置 primType = gl.TRIANGLES )来查看多维数据集.它将所有多维数据集放在同一个缓冲区中,以便通过一次draw调用即可绘制所有多维数据集.这比我们用单独的绘制调用绘制每个多维数据集要快得多.

In any case here's an example. This one makes 1000 cubes (just used cubes because this sample already existed). You can consider each cube one of of your "series" with 8 points although the code is actually drawing 24 points per cube. (set primType = gl.TRIANGLES) to see the cubes. It put all the cubes in the same buffer so that a single draw call draws all the cubes. This makes it much faster than if we draw each cube with a separate draw call.

重要的部分是为每个系列制作一个系列ID.在下面的代码中,一个多维数据集的所有点都具有相同的ID.

The important part is making a series ID per series. In the code below all points of one cube have the same ID.

该代码绘制场景两次.一次使用每个多维数据集的颜色,再次使用每个多维数据集的ID进入屏幕外纹理(作为帧缓冲区附件).要知道鼠标下方是哪个多维数据集,我们可以查询鼠标下方的像素,将其颜色转换回ID,并更新该多维数据集的顶点颜色以突出显示它.

The code draws the scene twice. Once with each cube's color, again with each cube's ID into an offscreen texture (as a framebuffer attachment). To know which cube is under the mouse we look up the pixel under the mouse, convert its color back into an ID and update that cube's vertex colors to highlight it.

const gl = document.querySelector('canvas').getContext("webgl");
const m4 = twgl.m4;
const v3 = twgl.v3;
// const primType = gl.TRIANGLES;
const primType = gl.POINTS;

const renderVS = `
attribute vec4 position;
attribute vec4 color;

uniform mat4 u_projection;
uniform mat4 u_modelView;

varying vec4 v_color;

void main() {
  gl_PointSize = 10.0;
  gl_Position = u_projection * u_modelView * position;
  v_color = color;
}
`;

const renderFS = `
precision mediump float;
varying vec4 v_color;
void main() {
  gl_FragColor = v_color;
}
`;

const idVS = `
attribute vec4 position;
attribute vec4 id;

uniform mat4 u_projection;
uniform mat4 u_modelView;

varying vec4 v_id;
void main() {
  gl_PointSize = 10.0;
  gl_Position = u_projection * u_modelView * position;
  v_id = id;  // pass the id to the fragment shader
}
`;

const idFS = `
precision mediump float;
varying vec4 v_id;
void main() {
  gl_FragColor = v_id;
}
`;

// creates shaders, programs, looks up attribute and uniform locations
const renderProgramInfo = twgl.createProgramInfo(gl, [renderVS, renderFS]);
const idProgramInfo = twgl.createProgramInfo(gl, [idVS, idFS]);

// create one set of geometry with a bunch of cubes
// for each cube give it random color (so every vertex
// that cube will have the same color) and give it an id (so
// every vertex for that cube will have the same id)
const numCubes = 1000;
const positions = [];
const normals = [];
const colors = [];
const timeStamps = [];
const ids = [];
// Save the color of each cube so we can restore it after highlighting
const cubeColors = [];
const radius = 25;

// adapted from http://stackoverflow.com/a/26127012/128511
// used to space the cubes around the sphere
function fibonacciSphere(samples, i) {
  const rnd = 1.;
  const offset = 2. / samples;
  const increment = Math.PI * (3. - Math.sqrt(5.));

  //  for i in range(samples):
  const y = ((i * offset) - 1.) + (offset / 2.);
  const r = Math.sqrt(1. - Math.pow(y ,2.));

  const phi = ((i + rnd) % samples) * increment;

  const x = Math.cos(phi) * r;
  const z = Math.sin(phi) * r;

  return [x, y, z];
}

const addCubeVertexData = (function() {
  const CUBE_FACE_INDICES = [
    [3, 7, 5, 1],  // right
    [6, 2, 0, 4],  // left
    [6, 7, 3, 2],  // ??
    [0, 1, 5, 4],  // ??
    [7, 6, 4, 5],  // front
    [2, 3, 1, 0],  // back
  ];

  const cornerVertices = [
    [-1, -1, -1],
    [+1, -1, -1],
    [-1, +1, -1],
    [+1, +1, -1],
    [-1, -1, +1],
    [+1, -1, +1],
    [-1, +1, +1],
    [+1, +1, +1],
  ];

  const faceNormals = [
    [+1, +0, +0],
    [-1, +0, +0],
    [+0, +1, +0],
    [+0, -1, +0],
    [+0, +0, +1],
    [+0, +0, -1],
  ];

  const quadIndices = [0, 1, 2, 0, 2, 3];

  return function addCubeVertexData(id, matrix, color) {
    for (let f = 0; f < 6; ++f) {
      const faceIndices = CUBE_FACE_INDICES[f];
      for (let v = 0; v < 6; ++v) {
        const ndx = faceIndices[quadIndices[v]];
        const position = cornerVertices[ndx];
        const normal = faceNormals[f];

        positions.push(...m4.transformPoint(matrix, position));
        normals.push(...m4.transformDirection(matrix, normal));
        colors.push(color);
        ids.push(id);
        timeStamps.push(-1000);
      }
    }
  };
}());

for (let i = 0; i < numCubes; ++i) {
  const direction = fibonacciSphere(numCubes, i);
  const cubePosition = v3.mulScalar(direction, radius);
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const matrix = m4.lookAt(cubePosition, target, up);
  const color = (Math.random() * 0xFFFFFF | 0) + 0xFF000000;
  cubeColors.push(color);
  addCubeVertexData(i + 1, matrix, color);
}

const colorData = new Uint32Array(colors);
const cubeColorsAsUint32 = new Uint32Array(cubeColors);
const timeStampData = new Float32Array(timeStamps);

// pass color as Uint32. Example 0x0000FFFF; // blue with alpha 0
function setCubeColor(id, color) {
  // we know each cube uses 36 vertices. If each model was different
  // we need to save the offset and number of vertices for each model
  const numVertices = 36;
  const offset = (id - 1) * numVertices;
  colorData.fill(color, offset, offset + numVertices);
}

function setCubeTimestamp(id, timeStamp) {
  const numVertices = 36;
  const offset = (id - 1) * numVertices;
  timeStampData.fill(timeStamp, offset, offset + numVertices);
}

// calls gl.createBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: positions,
  normal: normals,
  color: new Uint8Array(colorData.buffer),
  // the colors are stored as 32bit unsigned ints
  // but we want them as 4 channel 8bit RGBA values
  id: {
    numComponents: 4,
    data: new Uint8Array((new Uint32Array(ids)).buffer),
  },
  timeStamp: {
    numComponents: 1,
    data: timeStampData,
  },
});

const lightDir = v3.normalize([3, 5, 10]);

// creates an RGBA/UNSIGNED_BYTE texture
// and a depth renderbuffer and attaches them
// to a framebuffer.
const fbi = twgl.createFramebufferInfo(gl);

// current mouse position in canvas relative coords
let mousePos = {x: 0, y: 0};
let lastHighlightedCubeId = 0;
let highlightedCubeId = 0;
let frameCount = 0;

function getIdAtPixel(x, y, projection, view, time) {
  // calls gl.bindFramebuffer and gl.viewport
  twgl.bindFramebufferInfo(gl, fbi);

  // no reason to render 100000s of pixels when
  // we're only going to read one
  gl.enable(gl.SCISSOR_TEST);
  gl.scissor(x, y, 1, 1);

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

  drawCubes(idProgramInfo, projection, view, time);

  gl.disable(gl.SCISSOR_TEST);

  const idPixel = new Uint8Array(4);
  gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, idPixel);
  // convert from RGBA back into ID.
  const id = (idPixel[0] <<  0) +
             (idPixel[1] <<  8) +
             (idPixel[2] << 16) +
             (idPixel[3] << 24);
  return id;
}

function drawCubes(programInfo, projection, modelView, time) {
  gl.useProgram(programInfo.program);
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

  // calls gl.uniformXXX
  twgl.setUniforms(programInfo, {
    u_projection: projection,
    u_modelView: modelView,  // drawing at origin so model is identity
  });

  gl.drawArrays(primType, 0, bufferInfo.numElements);
}


function render(time) {
  time *= 0.001;
  ++frameCount;

  if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
    // resizes the texture and depth renderbuffer to
    // match the new size of the canvas.
    twgl.resizeFramebufferInfo(gl, fbi);
  }

  const fov = Math.PI * .35;
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = 0.1;
  const zFar = 1000;
  const projection = m4.perspective(fov, aspect, zNear, zFar);

  const radius = 45;
  const angle = time * .2;
  const eye = [
    Math.cos(angle) * radius,
    0,
    Math.sin(angle) * radius,
  ];
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);

  if (lastHighlightedCubeId > 0) {
    // restore the last highlighted cube's color
    setCubeColor(
      lastHighlightedCubeId,
      cubeColorsAsUint32[lastHighlightedCubeId]);
    lastHighlightedCubeId = -1;
  }

  {
    const x = mousePos.x;
    const y = gl.canvas.height - mousePos.y - 1;
    highlightedCubeId = getIdAtPixel(x, y, projection, view, time);
  }

  if (highlightedCubeId > 0) {
    const color = (frameCount & 0x2) ? 0xFF0000FF : 0xFFFFFFFF;
    setCubeColor(highlightedCubeId, color);
    setCubeTimestamp(highlightedCubeId, time);
    lastHighlightedCubeId = highlightedCubeId;
  }

  highlightedCubeId = Math.random() * numCubes | 0;

  // NOTE: We could use `gl.bufferSubData` and just upload
  // the portion that changed.

  // upload cube color data.
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.color.buffer);
  gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.DYNAMIC_DRAW);
  // upload the timestamp
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.timeStamp.buffer);
  gl.bufferData(gl.ARRAY_BUFFER, timeStampData, gl.DYNAMIC_DRAW);

  // calls gl.bindFramebuffer and gl.viewport
  twgl.bindFramebufferInfo(gl, null);

  gl.enable(gl.DEPTH_TEST);

  drawCubes(renderProgramInfo, projection, view, time);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function getRelativeMousePosition(event, target) {
  target = target || event.target;
  const rect = target.getBoundingClientRect();

  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  }
}

// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
  target = target || event.target;
  const pos = getRelativeMousePosition(event, target);

  pos.x = pos.x * target.width  / target.clientWidth;
  pos.y = pos.y * target.height / target.clientHeight;

  return pos;
}

gl.canvas.addEventListener('mousemove', (event, target) => {
  mousePos = getRelativeMousePosition(event, target);
});

body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }

<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

上面的代码使用与画布大小相同的屏幕外帧缓冲区,但是它使用剪刀测试仅绘制单个像素(鼠标下方的像素).如果没有剪刀测试,它仍然可以运行,只会更慢.

The code above uses an offscreen framebuffer the same size as the canvas but it uses the scissor test to only draw a single pixel (the one under the mouse). It would still run without the scissor test it would just be slower.

我们还可以仅使用一个像素的屏幕外帧缓冲区并使用投影数学来使它正常工作.

We could also make it work using just a single pixel offscreen framebuffer and using projection math so things work out.

const gl = document.querySelector('canvas').getContext("webgl");
const m4 = twgl.m4;
const v3 = twgl.v3;
// const primType = gl.TRIANGLES;
const primType = gl.POINTS;

const renderVS = `
attribute vec4 position;
attribute vec4 color;

uniform mat4 u_projection;
uniform mat4 u_modelView;

varying vec4 v_color;

void main() {
  gl_PointSize = 10.0;
  gl_Position = u_projection * u_modelView * position;
  v_color = color;
}
`;

const renderFS = `
precision mediump float;
varying vec4 v_color;
void main() {
  gl_FragColor = v_color;
}
`;

const idVS = `
attribute vec4 position;
attribute vec4 id;

uniform mat4 u_projection;
uniform mat4 u_modelView;

varying vec4 v_id;
void main() {
  gl_PointSize = 10.0;
  gl_Position = u_projection * u_modelView * position;
  v_id = id;  // pass the id to the fragment shader
}
`;

const idFS = `
precision mediump float;
varying vec4 v_id;
void main() {
  gl_FragColor = v_id;
}
`;

// creates shaders, programs, looks up attribute and uniform locations
const renderProgramInfo = twgl.createProgramInfo(gl, [renderVS, renderFS]);
const idProgramInfo = twgl.createProgramInfo(gl, [idVS, idFS]);

// create one set of geometry with a bunch of cubes
// for each cube give it random color (so every vertex
// that cube will have the same color) and give it an id (so
// every vertex for that cube will have the same id)
const numCubes = 1000;
const positions = [];
const normals = [];
const colors = [];
const timeStamps = [];
const ids = [];
// Save the color of each cube so we can restore it after highlighting
const cubeColors = [];
const radius = 25;

// adapted from http://stackoverflow.com/a/26127012/128511
// used to space the cubes around the sphere
function fibonacciSphere(samples, i) {
  const rnd = 1.;
  const offset = 2. / samples;
  const increment = Math.PI * (3. - Math.sqrt(5.));

  //  for i in range(samples):
  const y = ((i * offset) - 1.) + (offset / 2.);
  const r = Math.sqrt(1. - Math.pow(y ,2.));

  const phi = ((i + rnd) % samples) * increment;

  const x = Math.cos(phi) * r;
  const z = Math.sin(phi) * r;

  return [x, y, z];
}

const addCubeVertexData = (function() {
  const CUBE_FACE_INDICES = [
    [3, 7, 5, 1],  // right
    [6, 2, 0, 4],  // left
    [6, 7, 3, 2],  // ??
    [0, 1, 5, 4],  // ??
    [7, 6, 4, 5],  // front
    [2, 3, 1, 0],  // back
  ];

  const cornerVertices = [
    [-1, -1, -1],
    [+1, -1, -1],
    [-1, +1, -1],
    [+1, +1, -1],
    [-1, -1, +1],
    [+1, -1, +1],
    [-1, +1, +1],
    [+1, +1, +1],
  ];

  const faceNormals = [
    [+1, +0, +0],
    [-1, +0, +0],
    [+0, +1, +0],
    [+0, -1, +0],
    [+0, +0, +1],
    [+0, +0, -1],
  ];

  const quadIndices = [0, 1, 2, 0, 2, 3];

  return function addCubeVertexData(id, matrix, color) {
    for (let f = 0; f < 6; ++f) {
      const faceIndices = CUBE_FACE_INDICES[f];
      for (let v = 0; v < 6; ++v) {
        const ndx = faceIndices[quadIndices[v]];
        const position = cornerVertices[ndx];
        const normal = faceNormals[f];

        positions.push(...m4.transformPoint(matrix, position));
        normals.push(...m4.transformDirection(matrix, normal));
        colors.push(color);
        ids.push(id);
        timeStamps.push(-1000);
      }
    }
  };
}());

for (let i = 0; i < numCubes; ++i) {
  const direction = fibonacciSphere(numCubes, i);
  const cubePosition = v3.mulScalar(direction, radius);
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const matrix = m4.lookAt(cubePosition, target, up);
  const color = (Math.random() * 0xFFFFFF | 0) + 0xFF000000;
  cubeColors.push(color);
  addCubeVertexData(i + 1, matrix, color);
}

const colorData = new Uint32Array(colors);
const cubeColorsAsUint32 = new Uint32Array(cubeColors);
const timeStampData = new Float32Array(timeStamps);

// pass color as Uint32. Example 0x0000FFFF; // blue with alpha 0
function setCubeColor(id, color) {
  // we know each cube uses 36 vertices. If each model was different
  // we need to save the offset and number of vertices for each model
  const numVertices = 36;
  const offset = (id - 1) * numVertices;
  colorData.fill(color, offset, offset + numVertices);
}

function setCubeTimestamp(id, timeStamp) {
  const numVertices = 36;
  const offset = (id - 1) * numVertices;
  timeStampData.fill(timeStamp, offset, offset + numVertices);
}

// calls gl.createBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: positions,
  normal: normals,
  color: new Uint8Array(colorData.buffer),
  // the colors are stored as 32bit unsigned ints
  // but we want them as 4 channel 8bit RGBA values
  id: {
    numComponents: 4,
    data: new Uint8Array((new Uint32Array(ids)).buffer),
  },
  timeStamp: {
    numComponents: 1,
    data: timeStampData,
  },
});

const lightDir = v3.normalize([3, 5, 10]);

// creates an 1x1 pixel RGBA/UNSIGNED_BYTE texture
// and a depth renderbuffer and attaches them
// to a framebuffer.
const fbi = twgl.createFramebufferInfo(gl, [
  { format: gl.RGBA, type: gl.UNSIGNED_BYTE, minMag: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },
  { format: gl.DEPTH_STENCIL, },
], 1, 1);

// current mouse position in canvas relative coords
let mousePos = {x: 0, y: 0};
let lastHighlightedCubeId = 0;
let highlightedCubeId = 0;
let frameCount = 0;

function getIdAtPixel(x, y, projectionInfo, view, time) {
  // calls gl.bindFramebuffer and gl.viewport
  twgl.bindFramebufferInfo(gl, fbi);

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

  drawCubes(idProgramInfo, projectionInfo, {
    totalWidth: gl.canvas.width,
    totalHeight: gl.canvas.height,
    partWidth: 1,
    partHeight: 1,
    partX: x,
    partY: y,
  }, view, time);

  const idPixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, idPixel);
  // convert from RGBA back into ID.
  const id = (idPixel[0] <<  0) +
             (idPixel[1] <<  8) +
             (idPixel[2] << 16) +
             (idPixel[3] << 24);
  return id;
}

function drawCubes(programInfo, projectionInfo, partInfo, modelView, time) {

  const projection = projectionForPart(projectionInfo, partInfo);

  gl.useProgram(programInfo.program);
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

  // calls gl.uniformXXX
  twgl.setUniforms(programInfo, {
    u_projection: projection,
    u_modelView: modelView,  // drawing at origin so model is identity
  });

  gl.drawArrays(primType, 0, bufferInfo.numElements);
}

function projectionForPart(projectionInfo, partInfo) {
  const {fov, zNear, zFar} = projectionInfo;
  const {
    totalWidth,
    totalHeight,
    partX,
    partY,
    partWidth,
    partHeight,
  } = partInfo;
  
  const aspect = totalWidth / totalHeight;
  
  // corners at zNear for total image
  const zNearTotalTop = Math.tan(fov) * 0.5 * zNear;
  const zNearTotalBottom = -zNearTotalTop;
  const zNearTotalLeft = zNearTotalBottom * aspect;
  const zNearTotalRight = zNearTotalTop * aspect;
  
  // width, height at zNear for total image
  const zNearTotalWidth = zNearTotalRight - zNearTotalLeft;
  const zNearTotalHeight = zNearTotalTop - zNearTotalBottom;
  
  const zNearPartLeft = zNearTotalLeft + partX * zNearTotalWidth / totalWidth;   const zNearPartRight = zNearTotalLeft + (partX + partWidth) * zNearTotalWidth / totalWidth;
  const zNearPartBottom = zNearTotalBottom + partY * zNearTotalHeight / totalHeight;
  const zNearPartTop = zNearTotalBottom + (partY + partHeight) * zNearTotalHeight / totalHeight;

  return m4.frustum(zNearPartLeft, zNearPartRight, zNearPartBottom, zNearPartTop, zNear, zFar);
}

function render(time) {
  time *= 0.001;
  ++frameCount;

  twgl.resizeCanvasToDisplaySize(gl.canvas);

  const projectionInfo = {
    fov: Math.PI * .35,
    zNear: 0.1,
    zFar: 1000,
  };

  const radius = 45;
  const angle = time * .2;
  const eye = [
    Math.cos(angle) * radius,
    0,
    Math.sin(angle) * radius,
  ];
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);

  if (lastHighlightedCubeId > 0) {
    // restore the last highlighted cube's color
    setCubeColor(
      lastHighlightedCubeId,
      cubeColorsAsUint32[lastHighlightedCubeId]);
    lastHighlightedCubeId = -1;
  }

  {
    const x = mousePos.x;
    const y = gl.canvas.height - mousePos.y - 1;
    highlightedCubeId = getIdAtPixel(x, y, projectionInfo, view, time);
  }

  if (highlightedCubeId > 0) {
    const color = (frameCount & 0x2) ? 0xFF0000FF : 0xFFFFFFFF;
    setCubeColor(highlightedCubeId, color);
    setCubeTimestamp(highlightedCubeId, time);
    lastHighlightedCubeId = highlightedCubeId;
  }

  highlightedCubeId = Math.random() * numCubes | 0;

  // NOTE: We could use `gl.bufferSubData` and just upload
  // the portion that changed.

  // upload cube color data.
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.color.buffer);
  gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.DYNAMIC_DRAW);
  // upload the timestamp
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.timeStamp.buffer);
  gl.bufferData(gl.ARRAY_BUFFER, timeStampData, gl.DYNAMIC_DRAW);

  // calls gl.bindFramebuffer and gl.viewport
  twgl.bindFramebufferInfo(gl, null);

  gl.enable(gl.DEPTH_TEST);

  drawCubes(renderProgramInfo, projectionInfo, {
    totalWidth: gl.canvas.width,
    totalHeight: gl.canvas.height,
    partWidth: gl.canvas.width,
    partHeight: gl.canvas.height,
    partX: 0,
    partY: 0,
  }, view, time);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function getRelativeMousePosition(event, target) {
  target = target || event.target;
  const rect = target.getBoundingClientRect();

  return {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top,
  }
}

// assumes target or event.target is canvas
function getNoPaddingNoBorderCanvasRelativeMousePosition(event, target) {
  target = target || event.target;
  const pos = getRelativeMousePosition(event, target);

  pos.x = pos.x * target.width  / target.clientWidth;
  pos.y = pos.y * target.height / target.clientHeight;

  return pos;
}

gl.canvas.addEventListener('mousemove', (event, target) => {
  mousePos = getRelativeMousePosition(event, target);
});

body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }

<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

请注意,在WebGL中绘制 POINTS 通常比绘制2个相同大小的 TRIANGLES 要慢.如果我将多维数据集的数量设置为100k,并将primType设置为 TRIANGLES ,它将绘制100k多维数据集.在我的集成GPU上,摘要窗口的运行速度约为10-20fps.当然,有那么多立方体,就不可能选一个.如果将半径设置为250,则至少可以看到拾取仍在进行.

note that drawing POINTS in WebGL is generally slower than drawing 2 TRIANGLES of the same size. If I set the number of cubes to 100k and set primType to TRIANGLES it draws 100k cubes. On my integrated GPU the snippet window it runs at about 10-20fps. Of course with that many cubes it's impossible to pick one. If I set the radius to 250 I can at least see the picking is still working.

这篇关于在浏览器上,如何绘制100k系列,每个64-128点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆