片段着色器生成交互式网格

时间:2018-05-10 06:35:03

标签: javascript webgl

我正在尝试实现自己的基于WebGL的3D图形绘图应用程序。

正如我可能通过处理大量数据点,我希望我可以创建一个交互式网格,以帮助可视化数字的不同数量级与滚动支持(用于缩放)。

这是我的网格立方体的片段着色器。

precision highp float;
varying vec3 pos;
uniform float u_size;
uniform float u_scale;

float my_fmod(float inp_val, float inp_m) {
    float m = inp_m;
    float val = inp_val + 201.0 * m;
    return abs(val - float(int(val/m)) * m);
}

float gridline(float nPos0, float nPos1, float fac, float very_small_number) {
    if ( my_fmod(nPos0, 1./fac) < very_small_number || my_fmod(nPos1, 1./fac) < very_small_number) return 1.0;
    return 0.0;
}

void main() {

    float very_small_number = 0.015 / u_scale;
    float size = u_size;
    vec3 nPos = pos / u_scale;

    vec3 color = vec3(0.,0.,0.5);
    float sqrt5 = 2.236068;

    gl_FragColor = vec4(0.,0.,0.,1.);   

    float n = 5.;
    float sLine = 1.;
    float t1 = 1.;
    float t2 = 1.;

    if (pos.x == -size || pos.x == size) {
        if (gridline(nPos.y, nPos.z, n, very_small_number) > 0.5) gl_FragColor.xyz += color * t1;
        if (gridline(nPos.y*n, nPos.z*n, n, very_small_number) > 0.5) gl_FragColor.xyz += color * t2;
    } else if (pos.y == -size || pos.y == size) {
        if (gridline(nPos.x, nPos.z, n, very_small_number) > 0.5) gl_FragColor.xyz += color * t1;
        if (gridline(nPos.x*n, nPos.z*n, n, very_small_number) > 0.5) gl_FragColor.xyz += color * t2;
    } else if (pos.z == -size || pos.z == size) {
        if (gridline(nPos.x, nPos.y, n, very_small_number) > 0.5) gl_FragColor.xyz += color * t1;
        if (gridline(nPos.x*n, nPos.y*n, n, very_small_number) > 0.5) gl_FragColor.xyz += color * t2;
    }
}

如您所见,我画了两组网格。它们代表两种不同的数量级。此时,它可以完美地缩放,但我想添加一个允许在不同的数量级之间淡入淡出的特征,并且只显示一定比例的网格。

这是App的原始外观,

enter image description here

看起来不错。当我放大时,

enter image description here

网格看起来更大。但我希望我的着色器可以在小网格内绘制新的网格,而不是绘制不再可见的大网格。

当我缩小时,

enter image description here

显示的网格太多,会影响用户体验。

那么,我如何实现不同数量级之间的转换并在转换之间应用渐变?抱歉我的英语不好。任何帮助表示赞赏。祝你有愉快的一天。

编辑1:

以下是试验的完整代码。

https://github.com/Jonathan-D-Ip/WebGLPlottingApp/blob/master/Display.html

1 个答案:

答案 0 :(得分:2)

如果是我,我不会使用片段着色器作为网格我会使用线条。我在一个尺寸上绘制一个网格,如果我接近过渡并需要一个新网格,我会在另一个尺度上绘制另一个网格。两个网格都有alpha设置,所以我可以淡出它们。

这是一个例子,使用鼠标滚轮或等效物进行缩放。也许您可以根据您的首选解决方案调整这些想法。重要的部分可能是这部分

  const gridLevel = Math.log10(zoom * zoomAdjust);
  const gridFract = euclideanModulo(gridLevel, 1);
  const gridZoom = Math.pow(10, Math.floor(gridLevel));

  const alpha1 = Math.max((1 - gridFract) * 1);
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
  drawGrid(viewProjection, gridZoom, [0, alpha1, 0, alpha1]);
  const alpha2 = Math.max(gridFract * 10) - 1;
  if (alpha2 > 0) {
    drawGrid(viewProjection, gridZoom * 10, [0, alpha2, 0, alpha2],);
  }

zoom从0.0001变为10000并表示距目标的距离。该代码使用Math.log10来查找获得该缩放级别所需的功率。换句话说,如果缩放为100,那么gridLevel = 2.如果zoom为1000,那么gridLevel = 3.从中我们可以获得{{1}中10的幂之间的小数当我们在缩放级别之间移动时,它总是在0到1的范围内。

gridFract告诉我们绘制其中一个网格的比例(我们只删除gridZoom的小数部分)然后将10增加到该幂。 gridLevel是下一个最大的网格尺寸。

gridZoom * 10是网格的alpha。 alpha1是第二个网格的alpha。

&#13;
&#13;
alpha2
&#13;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl", {alpha: false});
const zoomElem = document.querySelector("#zoom");
const zoomAdjust = 1;  // change to adjust when things start/end. Try 5 or .5 for example

let zoom = 1;

const gridVS = `
attribute vec4 position;
uniform mat4 matrix;
void main() {
  gl_Position = matrix * position;
}
`;
const gridFS = `
precision mediump float;
uniform vec4 color;
void main() {
  gl_FragColor = color;
}
`;
const gridProgramInfo = twgl.createProgramInfo(gl, [gridVS, gridFS]);

const gridPlaneLines = [];
const numLines = 100;
for (let i = 0; i <= 100; ++i) {
  gridPlaneLines.push(0, i, 100, i);
  gridPlaneLines.push(i, 0, i, 100);
}
const gridPlaneBufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: { numComponents: 2, data: gridPlaneLines },
});

function drawGrid(viewProjection, scale, color) {
  gl.useProgram(gridProgramInfo.program);
  twgl.setBuffersAndAttributes(gl, gridProgramInfo, gridPlaneBufferInfo);

  const scaling = [scale, scale, scale];
  // draw Z plane
  {
    let matrix = m4.scale(viewProjection, scaling);
    matrix = m4.rotateY(matrix, Math.PI);
    twgl.setUniforms(gridProgramInfo, {
      matrix,
      color,
    });
    twgl.drawBufferInfo(gl, gridPlaneBufferInfo, gl.LINES);
  }
  // draw X plane
  {
    let matrix = m4.scale(viewProjection, scaling);
    matrix = m4.rotateY(matrix, Math.PI * .5);
    twgl.setUniforms(gridProgramInfo, {
      matrix,
      color,
    });
    twgl.drawBufferInfo(gl, gridPlaneBufferInfo, gl.LINES);
  }
  // draw Y plane
  {
    let matrix = m4.scale(viewProjection, scaling);
    matrix = m4.rotateY(matrix, Math.PI);
    matrix = m4.rotateX(matrix, Math.PI * .5);
    twgl.setUniforms(gridProgramInfo, {
      matrix,
      color,
    });
    twgl.drawBufferInfo(gl, gridPlaneBufferInfo, gl.LINES);
  }
}

function render() {
  twgl.resizeCanvasToDisplaySize(gl.canvas, window.devicePixelRatio);
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);

  zoomElem.textContent = zoom.toFixed(5);

  const fov = degToRad(60);
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = zoom / 100;
  const zFar = zoom * 100;
  const projection = m4.perspective(fov, aspect, zNear, zFar);

  const eye = [zoom * -10, zoom * 5, zoom * -10];
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);

  const viewProjection = m4.multiply(projection, view);

  const gridLevel = Math.log10(zoom * zoomAdjust);
  const gridFract = euclideanModulo(gridLevel, 1);
  const gridZoom = Math.pow(10, Math.floor(gridLevel));

  const alpha1 = Math.max((1 - gridFract) * 1);
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
  drawGrid(viewProjection, gridZoom, [0, alpha1, 0, alpha1]);
  const alpha2 = Math.max(gridFract * 10) - 1;
  if (alpha2 > 0) {
    drawGrid(viewProjection, gridZoom * 10, [0, alpha2, 0, alpha2],);
  }
}
render();

function euclideanModulo(n, m) {
    return ((n % m) + m) % m;
};

function degToRad(deg) {
  return deg * Math.PI / 180;
}

window.addEventListener('wheel', (e) => {
  e.preventDefault();
  const amount = e.deltaY;
  if (e.deltaY < 0) {
    zoom *= 1 - clamp(e.deltaY / -500, 0, 1);
  } else {
    zoom *= 1 + clamp(e.deltaY / 500, 0, 1);
  }
  zoom = clamp(zoom, 0.0001, 10000);
  render();
});
window.addEventListener('resize', render);

function clamp(v, min, max) {
  return Math.max(min, Math.min(max, v));
}
&#13;
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
#ui { position: absolute; left: 1em; top: 1em; padding: 1em; color: white; }
&#13;
&#13;
&#13;