Three.js:将RawShaderMaterial与LineSegments结合使用

时间:2019-04-20 20:19:12

标签: javascript three.js

我正在一个Three.js场景中渲染点和线段。如果我将LineBasicMaterial材质用作线条,则The scene会很好:

/**
* constructor for the gl manager
**/

function World() {
  this.renderTarget = document.querySelector('#render-target');
  this.scene = this.getScene();
  this.camera = this.getCamera();
  this.renderer = this.getRenderer();
  this.controls = this.getControls();
  this.masterCounts = null; // {id: nMasters}
  this.edges = null; // 2d array where [[master, app, app]]
  this.positions = null; // {id: [x,y]}
  this.z = 0; // flat z dim
  this.loadData();
  this.render();
}

World.prototype.getScene = function() {
  return new THREE.Scene();
}

World.prototype.getContainerSize = function() {
  var elem = this.renderTarget;
  return {
    w: elem.clientWidth,
    h: elem.clientHeight,
  }
}

World.prototype.getCamera = function() {
  var size = this.getContainerSize();
  var camera = new THREE.PerspectiveCamera(75, size.w/size.h, 0.01, 10);
  camera.position.set(0.5, 0.5, -0.67);
  return camera;
}

World.prototype.getRenderer = function() {
  var renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
  var size = this.getContainerSize();
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(size.w, size.h);
  document.querySelector('#render-target').appendChild(renderer.domElement);
  return renderer;
}

World.prototype.getControls = function() {
  var controls = new THREE.TrackballControls(this.camera, this.renderer.domElement);
  controls.zoomSpeed = 0.4;
  controls.panSpeed = 0.4;
  controls.target.set(0.5, 0.5, 1);
  return controls;
}

World.prototype.render = function() {
  requestAnimationFrame(this.render.bind(this));
  this.renderer.render(this.scene, this.camera);
  this.controls.update();
}

World.prototype.getPointScale = function() {
  return window.devicePixelRatio * window.innerHeight * 0.00001;
}

World.prototype.loadData = function() {
  get('https://s3.amazonaws.com/duhaime/blog/visualizations/line-segments-network/node-positions-twopi.json', function(data) {
    this.positions = center(JSON.parse(data));
    get('https://s3.amazonaws.com/duhaime/blog/visualizations/line-segments-network/id-to-aggregate-masters.json', function(data) {
      this.masterCounts = JSON.parse(data);
      get('https://s3.amazonaws.com/duhaime/blog/visualizations/line-segments-network/edges.json', function(data) {
        this.edges = JSON.parse(data);
        this.addPoints();
        this.addEdges();
      }.bind(this));
    }.bind(this));
  }.bind(this));
}

World.prototype.addPoints = function() {

  var geometry  = new THREE.InstancedBufferGeometry(),
      translations = getPointTranslations(this.positions),
      colors = getColors(this.positions, this.masterCounts);

  geometry.addAttribute('position',
    new THREE.BufferAttribute( new Float32Array([0, 0, 0]), true, 3));
  geometry.addAttribute('translation',
    new THREE.InstancedBufferAttribute(translations, 3, true, 1) );
  geometry.addAttribute('target',
    new THREE.InstancedBufferAttribute(translations, 3, true, 1) );
  geometry.addAttribute('color',
    new THREE.InstancedBufferAttribute(colors, 3, true, 1) );

  this.points = new THREE.Points(geometry, this.getShaderMaterial());
  this.points.frustumCulled = false; // prevent mesh click on drag
  this.scene.add(this.points);
}

World.prototype.addEdges = function() {
  var indices = [],
      positions = [],
      idToIndex = {}, // {node id: index in edgePositions}
      ids = Object.keys(this.edges);
  // flatten edges into [[s,t],[s,t]]
  for (var i=0; i<ids.length; i++) {
    var idEdges = this.edges[ids[i]];
    for (var j=0; j<idEdges.length; j++) {
      // here ids[i] is a master node id, idEdges is list of
      // apprentice node ids
      var masterId = ids[i];
      var apprenticeId = idEdges[j];
      if (!(masterId in idToIndex)) {
        idToIndex[masterId] = positions.length;
        positions.push(this.positions[masterId]);
      }
      if (!(apprenticeId in idToIndex)) {
        idToIndex[apprenticeId] = positions.length;
        positions.push(this.positions[apprenticeId]);
      }
      indices = indices.concat([
        idToIndex[masterId],
        idToIndex[apprenticeId]
      ]);
    }
  }

  var geometry = new THREE.BufferGeometry(),
      translations = new Float32Array(3*positions.length),
      iter = 0,
      indices = new Uint16Array(indices);
  for (var i=0; i<positions.length; i++) {
    var e = positions[i];
    translations[iter++] = e[0];
    translations[iter++] = e[1];
    translations[iter++] = this.z;
  }

  var material = new THREE.LineBasicMaterial({
    color: 0xee6559,
    opacity: 0.3,
    transparent: true,
  })

  geometry.addAttribute('position',
    new THREE.BufferAttribute(translations, 3, true, 1));
  geometry.setIndex(new THREE.BufferAttribute(indices, 1, true, 1));

  this.lines = new THREE.LineSegments(geometry, material);
  this.lines.frustumCulled = false; // prevent mesh click on drag
  this.scene.add(this.lines);
}

World.prototype.getShaderMaterial = function() {
  return new THREE.RawShaderMaterial({
    vertexShader: find('#vertex-shader').textContent,
    fragmentShader: find('#fragment-shader').textContent,
    uniforms: {
      transitionPercent: { type: 'f', value: 0.0 },
      pointScale: { type: 'f', value: this.getPointScale(), },
    }
  });
}

/**
* Helpers
**/

function get(url, handleSuccess, handleErr, handleProgress) {
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == XMLHttpRequest.DONE) {
      if (xmlhttp.status === 200) {
        if (handleSuccess) handleSuccess(xmlhttp.responseText)
      } else {
        if (handleErr) handleErr(xmlhttp)
      }
    };
  };
  xmlhttp.onprogress = function(e) {
    if (handleProgress) handleProgress(e);
  };
  xmlhttp.open('GET', url, true);
  xmlhttp.send();
};

function find(querySelector) {
  return document.querySelector(querySelector);
}

window.addEventListener('resize', function() {
  var size = world.getContainerSize();
  world.camera.aspect = size.w / size.h;
  world.camera.updateProjectionMatrix();
  world.renderer.setSize(size.w, size.h);
})

function center(data) {
  var ids = Object.keys(data);
  // find the domains of each axis in the data
  var p = Number.POSITIVE_INFINITY,
      n = Number.NEGATIVE_INFINITY,
      domains = {x: {min: p, max: n}, y: {min: p, max: n}};
  for (var i=0; i<ids.length; i++) {
    vals = data[ids[i]];
    if (vals[0] < domains.x.min) domains.x.min = vals[0];
    if (vals[0] > domains.x.max) domains.x.max = vals[0];
    if (vals[1] < domains.y.min) domains.y.min = vals[1];
    if (vals[1] > domains.y.max) domains.y.max = vals[1];
  }
  // center each axis 0:1
  for (var i=0; i<ids.length; i++) {
    var d = data[ids[i]];
    d[0] = (d[0]-domains.x.min)/(domains.x.max-domains.x.min);
    d[1] = (d[1]-domains.y.min)/(domains.y.max-domains.y.min);
  }
  return data;
}

function getPointTranslations(data) {
  var ids = Object.keys(data);
  var arr = new Float32Array(ids.length*3),
      iter = 0;
  for (var i=0; i<ids.length; i++) {
    arr[iter++] = data[ids[i]][0];
    arr[iter++] = data[ids[i]][1];
    arr[iter++] = world.z;
  }
  return arr;
}

function getColors(data, masterCounts) {
  var ids = Object.keys(data);
  var maxMasters = 0;
  for (var i=0; i<ids.length; i++) {
    if (masterCounts[ids[i]] > maxMasters) maxMasters = masterCounts[ids[i]];
  }
  var colors = [
    '#1f77b4', '#86abd7', '#cbcdd3', '#f8dba8',
    '#eec570', '#eba055', '#ee6559',
  ];
  var arr = new Float32Array(ids.length * 3),
      iter = 0;
  for (var i=0; i<ids.length; i++) {
    var nMasters = Math.min(colors.length, masterCounts[ids[i]] || 0);
    var hex = colors[ Math.floor(colors.length * (nMasters / maxMasters)) ];
    var c = hexToRgb(hex);
    arr[iter++] = c.r / 255;
    arr[iter++] = c.g / 255;
    arr[iter++] = c.b / 255;
  }
  return arr;
}

function componentToHex(c) {
  var hex = c.toString(16);
  return hex.length == 1 ? '0' + hex : hex;
}

function rgbToHex(r, g, b) {
  return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b);
}

function hexToRgb(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16),
  } : {r: 0, g: 0, b: 0};
}

var world = new World();
html,
body {
  width: 100%;
  height: 100%;
  background: linear-gradient(#efefef, #efefef);
}
body {
  margin: 0;
  overflow: hidden;
}
div#select-target {
  padding: 20px 0;
}
select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  font-family: 'Mallory';
  text-indent: 0.01px;
  text-overflow: '';
  border: none;
  padding: 7px 40px 7px 10px;
  background-image: url(down-caret.png);
  background-position: 90% 50%;
  background-size: 15px;
  background-repeat: no-repeat;
  font-size: 1em;
  text-transform: uppercase;
  border: 1px solid #c7c7c7;
  font-family: arial, sans-serif;
}
#render-container {
  text-align: center;
  max-height: 100%;
  max-width: 100%;
  padding-bottom: 20px;
}
#render-target {
  margin: 0 auto;
  width: 700px;
  height: 700px;
}
<div id='render-container'>
  <div id='select-target'></div>
  <div id='render-target'></div>
</div>

<script src='https://s3.amazonaws.com/duhaime/blog/visualizations/line-segments-network/three.min.js'></script>
<script src='https://s3.amazonaws.com/duhaime/blog/visualizations/line-segments-network/trackball-controls.min.js'></script>
<script src='https://s3.amazonaws.com/duhaime/blog/visualizations/line-segments-network/tweenlite.min.js'></script>

<script type='x-shader/x-vertex' id='vertex-shader'>
precision highp float;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform float transitionPercent;
uniform vec3 cameraPosition;
uniform float pointScale;

attribute vec3 position;
attribute vec3 translation;
attribute vec3 target;
attribute vec3 color;

varying vec3 vColor;

float scalePointZ(in vec4 pos, in vec3 cameraPosition) {
  float zDelta = pow(pos[2] - cameraPosition[2], 2.0);
  float delta  = pow(zDelta, 0.5);
  float scaled = pointScale / delta;
  return scaled;
}

void main() {
  vec3 t1 = position + translation;
  vec3 t2 = position + target;
  vec3 pos = mix(t1, t2, clamp(transitionPercent, 0.0, 1.0));
  vec4 mvPos = modelViewMatrix * vec4(pos, 1.0);
  gl_Position = projectionMatrix * mvPos;
  gl_PointSize = 6.0;
  vColor = color;
}
</script>

<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;

varying vec3 vColor;

void main() {
  // make points circular
  vec2 coord = gl_PointCoord - vec2(0.5);
  if (length(coord) > 0.5) discard;

  // set point color
  gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
</script>

但是,我一直在努力使用原始着色器材料制作线条以实现相同的场景。这是我尝试为原始着色器材料设置线条的方法之一:

World.prototype.addEdges = function() {

        var indices = [],
      positions = [],
      idToIndex = {}, // {node id: index in edgePositions}
      ids = Object.keys(this.edges);
  // flatten edges into [[s,t],[s,t]]
  for (var i=0; i<ids.length; i++) {
    var idEdges = this.edges[ids[i]];
    for (var j=0; j<idEdges.length; j++) {
      // here ids[i] is a master node id, idEdges is list of
      // apprentice node ids
      var masterId = ids[i];
      var apprenticeId = idEdges[j];
      if (!(masterId in idToIndex)) {
        idToIndex[masterId] = positions.length;
        positions.push(this.positions[masterId]);
      }
      if (!(apprenticeId in idToIndex)) {
        idToIndex[apprenticeId] = positions.length;
        positions.push(this.positions[apprenticeId]);
      }
      indices = indices.concat([
        idToIndex[masterId],
        idToIndex[apprenticeId]
      ]);
    }
  }

  var geometry = new THREE.BufferGeometry(),
      translations = new Float32Array(3*positions.length),
      iter = 0,
      indices = new Uint16Array(indices);
  for (var i=0; i<positions.length; i++) {
    var e = positions[i];
    translations[iter++] = e[0];
    translations[iter++] = e[1];
    translations[iter++] = this.z;
  }

  var material = this.getShaderMaterial().clone();

  geometry.addAttribute('position',
    new THREE.BufferAttribute(new Float32Array([0, 0, 0]), 3, true, 1));
  geometry.addAttribute('translation',
    new THREE.BufferAttribute(translations, 3, true, 1));
  geometry.addAttribute('target',
    new THREE.BufferAttribute(translations, 3, true, 1));
  geometry.setIndex(new THREE.BufferAttribute(indices, 1, true, 1));

  this.lines = new THREE.LineSegments(geometry, material);
  this.lines.frustumCulled = false; // prevent mesh click on drag
  this.scene.add(this.lines);
}

但是,这什么也没有呈现。有谁知道我如何使用上面定义的原始着色器材料渲染上面的线条?任何指示或建议都将非常有帮助!

1 个答案:

答案 0 :(得分:2)

我认为发生这种情况是因为您在片段着色器中使用了Convolution2D,尽管您不是在渲染点而是在渲染线。如果我删除以下两行代码,则会呈现您的行:

gl_PointCoord

演示:https://jsfiddle.net/Ldynhxkq/

对于两个基元最好使用不同的着色器程序。