Shader wireframe of an object

2019-06-10 00:17发布

问题:

I want to see a wireframe of an object without the diagonals like

Currently, I add lines according to the vertices, the problem is after I have several of those I experience a major performance degradation.

The examples here are either too new for my version of Three or don't work (I commented there about it).

So I want to try to implement a shader instead.

I tried to use this shader: https://stackoverflow.com/a/31610464/4279201 but it breaks the shape to parts and I'm getting WebGL errors.

That's how I use it:

const vertexShader = `
varying vec2 vUv;
void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`

const fragmentShader = `
#version 150 compatibility                                         
flat in float diffuse;
flat in float specular;
flat in vec3  edge_mask;                                           
in vec2 bary;
uniform float mesh_width = 1.0;
uniform vec3 mesh_color = vec3(0.0, 0.0, 0.0);
uniform bool lighting = true;
out vec4 frag_color ;
float edge_factor(){                                               
  vec3 bary3 = vec3(bary.x, bary.y, 1.0 - bary.x - bary.y);
  vec3 d = fwidth(bary3);
  vec3 a3 = smoothstep(vec3(0.0, 0.0, 0.0), d * mesh_width, bary3);
  a3 = vec3(1.0, 1.0, 1.0) - edge_mask + edge_mask * a3;
  return min(min(a3.x, a3.y), a3.z);
}
void main() {                                                      
  float s = (lighting && gl_FrontFacing) ? 1.0 : -1.0;
  vec4  Kdiff = gl_FrontFacing ?
    gl_FrontMaterial.diffuse : gl_BackMaterial.diffuse;
  float sdiffuse = s * diffuse;
  vec4 result = vec4(0.1, 0.1, 0.1, 1.0);
  if (sdiffuse > 0.0) {
    result += sdiffuse * Kdiff +
      specular * gl_FrontMaterial.specular;
  }
  frag_color = (mesh_width != 0.0) ?
    mix(vec4(mesh_color, 1.0), result, edge_factor()) :
    result;
}`     

...

const uniforms = {
  color: {
    value: new THREE.Vector4(0, 0, 1, 1),
    type: 'v4'
  }
}

const material = new THREE.ShaderMaterial({
  fragmentShader: data.fragmentShader,
  vertexShader: data.vertexShader,
  uniforms
})

this._viewer.impl.matman().addMaterial(
  data.name, material, true)

    const fragList = this._viewer.model.getFragmentList()

this.toArray(fragIds).forEach((fragId) => {

  fragList.setMaterial(fragId, material)
})

So to implement this shader, is the right approach would be to basically check the angle between every two vertices, and draw a line if the degree is 90?

How can I have access to all the vertices of the shape from the vertex shader?

And how can I tell the fragment shader to draw a line between two vertices that match the above condition? (also to leave the default shading for everything else as is)

I'm using Autodesk viewer that uses Three.js rev 71.

回答1:

// -- Vertex Shader --
precision mediump float;

// Input from buffers
attribute vec3 aPosition;
attribute vec2 aBaryCoord;

// Value interpolated accross pixels and passed to the fragment shader
varying vec2 vBaryCoord;

// Uniforms
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjMatrix;

void main() {
    vBaryCoord = aBaryCoord;
    gl_Position = uProjMatrix * uViewMatrix * uModelMatrix * vec4(aPosition,1.0);
}
// ---------------------

// -- Fragment Shader --
// This shader doesn't perform any lighting
precision mediump float;

varying vec2 vBaryCoord;

uniform vec3 uMeshColour;

float edgeFactor() {
    vec3 d = fwidth(vBaryCoord);
    vec3 a3 = smoothstep(vec3(0.0,0.0,0.0),d * 1.5,vBaryCoord);
    return min(min(a3.x,a3.y),a3.z);
}

void main() {
    gl_FragColor = vec4(uMeshColour,(1.0 - edgeFactor()) * 0.95);
}
// ---------------------

/*
    This code isn't tested so take it with a grain of salt

    Idea taken from
    http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/
*/