代码之家  ›  专栏  ›  技术社区  ›  duhaime

三js:将RawShaderMaterial与线段一起使用

  •  0
  • duhaime  · 技术社区  · 7 年前

    我正在做一个三。渲染点和线段的js场景。 The scene 如果对线条使用LineBasicMaterial材质,则渲染效果良好:

    /**
    * 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>

    然而,我一直在努力使用rawshadermaterial作为线条来实现相同的场景。以下是我尝试为原始着色器材质设置线条的方法之一:

    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);
    }
    

    然而,这不会产生任何效果。有人知道如何使用上面定义的原始着色器材质渲染上面的线吗?任何提示或建议都会非常有用!

    0 回复  |  直到 7 年前
        1
  •  2
  •   Mugen87    7 年前

    我认为这是因为你正在使用 gl_PointCoord 在片段着色器中,虽然渲染的不是点而是线。如果删除以下两行代码,则会呈现您的行:

    vec2 coord = gl_PointCoord - vec2(0.5);
    if (length(coord) > 0.5) discard;
    

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

    也许最好对两个基本体使用不同的着色器程序。