3D模型上的webGL轮廓颜色图

时间:2019-02-24 07:10:08

标签: webgl

我正在开发一种软​​件,该软件可以将3D模型表面的工程数据可视化为彩色图。为此,我正在使用WebGL。目前,我能够在3D模型的表面上显示颜色。 但是现在我需要改善可视化效果,以在颜色之间进行尖锐过渡(在三角形表面上没有颜色插值)。 我不确定如何有效地做到这一点。

平滑轮廓图

清晰的轮廓图

2 个答案:

答案 0 :(得分:0)

一种实现方法是将flat interpolation modifier添加到您的color属性中,如this教程中所述。这样可以防止对颜色值进行插值,因此每个三角形只会以一种颜色(在三个顶点中的第一个顶点中指定)结束。 遗憾的是,我找不到关于它的webgl支持的任何信息,但您最好尝试一下它是否可以工作。

如果它不起作用或者您不希望看到单个三角形,也可以load the color data to a texture并获取片段着色器中每个像素的颜色。不过仍然会根据纹理大小进行一些插值(类似于放大后图像变得模糊)。

答案 1 :(得分:0)

不清楚您要做什么。您没有提供足够的信息来首先了解如何选择/计算颜色。

我只能猜出一些可能符合您描述的解决方案

  1. 采用后代技术的后期处理

    您可以做一个简单的

    gl_FragColor.rgb = floor(gl_FragColor.rgb * numLevels) / numLevels;
    

    或者您可以在某些颜色空间中做到

    // convert to HSV
    vec3 hsv = rgb2hsv(gl_FragColor.rgb);
    
    // quantize hue only
    hsv.x = floor(hsv.x * numLevels) / numLevels;
    
    // concert back to RGB
    gl_FragColor.rgb = hsv2rgb(hsv);
    

    或者您也可以在3D着色器中执行此操作,不必进行后期处理。

    您可以找到rgb2hsv和hsv2rgb here,但是您当然可以使用其他颜色空间。

示例:

const gl = document.querySelector('canvas').getContext('webgl');
const m4 = twgl.m4;
const v3 = twgl.v3;
// used to generate colors
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 1;
ctx.canvas.height = 1;

const vs = `
  attribute vec4 position;
  attribute vec3 normal;

  // note: there is no reason this has to come from an attrbute (per vertex)
  // it could just as easily come from a texture used in the fragment shader
  // for more resolution

  attribute vec4 color;

  uniform mat4 projection;
  uniform mat4 modelView;
  
  varying vec3 v_normal;
  varying vec4 v_color;
  
  void main () {
    gl_Position = projection * modelView * position;
    v_normal = mat3(modelView) * normal;
    v_color = color;
  }
`;
const fs = `
  precision mediump float;
  
  varying vec3 v_normal;
  varying vec4 v_color;
  
  uniform float numLevels;
  uniform vec3 lightDirection;

  vec3 rgb2hsv(vec3 c) {
    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

    float d = q.x - min(q.w, q.y);
    float e = 1.0e-10;
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
  }

  vec3 hsv2rgb(vec3 c) {
    c = vec3(c.x, clamp(c.yz, 0.0, 1.0));
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
  }

  void main() {
    vec3 hsv = rgb2hsv(v_color.rgb);
    
    hsv.x = floor(hsv.x * numLevels) / numLevels;
    
    vec3 rgb = hsv2rgb(hsv);
    
    // fake light
    float light = dot(normalize(v_normal), lightDirection) * .5 + .5;
    
    gl_FragColor = vec4(rgb * light, v_color.a);
    
    // uncomment next line to see without hue quantization
    // gl_FragColor = v_color;
  }
`;
  
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

const radius = 5;
const thickness = 2;
const radialDivisions = 32;
const bodyDivisions = 12;
// creates positions, normals, etc...
const arrays = twgl.primitives.createTorusVertices(
    radius, thickness, radialDivisions, bodyDivisions);

// add colors  for each vertex
const numVerts = arrays.position.length / 3;
const colors = new Uint8Array(numVerts * 4);
for (let i = 0; i < numVerts; ++i) {
  const pos = arrays.position.subarray(i * 3, i * 3 + 3);
  const dist = v3.distance([3, 1, 3 + Math.sin(pos[0])], pos);
  colors.set(hsla(clamp(dist / 10, 0, 1), 1, .5, 1), i * 4);
}
arrays.color = {
  numComponents: 4,
  data: colors,
};

// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each
// array in arrays
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

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

gl.enable(gl.DEPTH_TEST);

const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const halfHeight = 8;
const halfWidth = halfHeight * aspect;
const projection = m4.ortho(
  -halfWidth, halfWidth,
  -halfHeight, halfHeight,
  -2, 2);
const modelView = m4.identity();
m4.rotateX(modelView, Math.PI * .5, modelView);

gl.useProgram(programInfo.program);

// calls gl.bindbuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
// for each attribute
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniforms(programInfo, {
  projection,
  modelView,
  numLevels: 8,
  lightDirection: v3.normalize([1, 2, 3]),
});

// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);

function hsla(h, s, l, a) {
  ctx.fillStyle = `hsla(${h * 360 | 0},${s * 100 | 0}%,${l * 100 | 0}%,${a})`;
  ctx.fillRect(0, 0, 1, 1);
  return ctx.getImageData(0, 0, 1, 1).data;
}

function clamp(v, min, max) {
  return Math.min(max, Math.max(min, v));
}
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>

  1. 在1个通道中渲染,使用查找表

    在这种情况下,您将使用N种颜色制作Nx1纹理。然后在着色器中,您只需计算一个灰度等级(目前尚不清楚如何着色),然后使用它来从纹理中查找颜色

    uniform sampler2D lookupTable;  // Nx1 texture set to nearest filtering
    
    float gray = whateverYourDoingNow();
    vec4 color = texture2D(lookupTable, vec2((gray, 0.5);
    
    // apply lighting to color
    ...
    

示例:

const gl = document.querySelector('canvas').getContext('webgl');
const m4 = twgl.m4;
const v3 = twgl.v3;

const vs = `
  attribute vec4 position;
  attribute vec3 normal;
  
  // note: there is no reason this has to come from an attrbute (per vertex)
  // it could just as easily come from a texture used in the fragment shader
  // for more resolution

  attribute float hotness;  // the data value 0 to 1
  
  uniform mat4 projection;
  uniform mat4 modelView;
  
  varying vec3 v_normal;
  varying float v_hotness;
  
  void main () {
    gl_Position = projection * modelView * position;
    v_normal = mat3(modelView) * normal;
    v_hotness = hotness;
  }
  `;
  const fs = `
  precision mediump float;
  
  varying vec3 v_normal;
  varying float v_hotness;
  
  uniform float numColors;
  uniform sampler2D lookupTable;
  uniform vec3 lightDirection;
  
  void main() {
    vec4 color = texture2D(lookupTable, vec2(v_hotness, 0.5));
    
    // fake light
    float light = dot(normalize(v_normal), lightDirection) * .5 + .5;
    
    gl_FragColor = vec4(color.rgb * light, color.a);
  }
  `;
  
  const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
  
  const radius = 5;
  const thickness = 2;
  const radialDivisions = 32;
  const bodyDivisions = 12;
  // creates positions, normals, etc...
  const arrays = twgl.primitives.createTorusVertices(
      radius, thickness, radialDivisions, bodyDivisions);
      
  // add a hotness value, 0 <-> 1, for each vertex
  const numVerts = arrays.position.length / 3;
  const hotness = [];
  for (let i = 0; i < numVerts; ++i) {
    const pos = arrays.position.subarray(i * 3, i * 3 + 3);
    const dist = v3.distance([3, 1, 3 + Math.sin(pos[0])], pos);
    hotness[i] = clamp(dist / 10, 0, 1);
  }
  arrays.hotness = {
    numComponents: 1,
    data: hotness,
  };

  // calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each
  // array in arrays
  const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
  
  const colors = [
     255,   0,   0, 255,  // red
     255, 150,  30, 255,  // orange
     255, 255,   0, 255,  // yellow
       0, 210,   0, 255,  // green
       0, 255, 255, 255,  // cyan
       0,   0, 255, 255,  // blue
     160,  30, 255, 255,  // purple
     255,   0, 255, 255,  // magenta
  ];
  // calls gl.createTexture, gl.texImage2D, gl.texParameteri
  const lookupTableTexture = twgl.createTexture(gl, {
    src: colors,
    width: colors.length / 4,
    wrap: gl.CLAMP_TO_EDGE,
    minMag: gl.NEAREST,   // comment this line out to see non hard edges
  });
  
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  
  gl.enable(gl.DEPTH_TEST);
  
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const halfHeight = 8;
  const halfWidth = halfHeight * aspect;
  const projection = m4.ortho(
    -halfWidth, halfWidth,
    -halfHeight, halfHeight,
    -2, 2);
  const modelView = m4.identity();
  m4.rotateX(modelView, Math.PI * .5, modelView);
  
  gl.useProgram(programInfo.program);

  // calls gl.bindbuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  // for each attribute
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
  twgl.setUniforms(programInfo, {
    projection,
    modelView,
    lookupTable: lookupTableTexture,
    lightDirection: v3.normalize([1, 2, 3]),
  });
  
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);
  
  
  function clamp(v, min, max) {
    return Math.min(max, Math.max(min, v));
  }
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>