Billboarded Cutaway Visualization for Subsurface Urban Sewer Networks

Flood Simulation embedded in Unity3D

A peek into some of my code files, the whole unity project will be added later.

SewerManager.cs
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

using UnityEngine;
using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;

public class SewerManager: MonoBehaviour {

  public Material terrainMaterial;
  public Material groundZeroMaterial;
  public Material manholeMaterial;
  public Material shaftMaterial;
  public Material shaftInsideMaterial;
  public Material shaftPipeCancelMaterial;
  public Material shaftWaterMaterial;
  public Material buildingsMaterial;
  public Texture DTglobalTex;

  //for more details look into the method refineNodes
  private static int removedNodes = 0;
  private static int addedNodes = 0;
  private static int pipeNodesCounter = 0;

  private static int maxThreads = 20;

  //Timestep for simulation data
  public static int currenTimestep = 0;

  private int UpdateCounterPipeNodeID = 0;
  private bool computeDTtex = true;
  private data[] dataForPipeNodeBuffer;
  private GameObject mainCam;
  private Texture2D DTglobalTex_dt;
  private ComputeBuffer pipeNodesVec3ReadBuffer;
  private ComputeBuffer RWpipeNodesUVBuffer;
  private SurroundingsCommands surroundingsCommands;
  private Transform initialCamPos;

  struct data {
    public Vector3 pos;
    public int pipeID;
    public int nodeID;
  };

  struct uvData {
    public Vector2 uvPos;
    public int pipeID;
    public int nodeID;
  };

  //Awake will be called first
  void Awake() {
    //while parsing, all data will be added to the static class SewerSystem
    string sewerDataPath = Application.dataPath + "/Worringen Visdom Exports/sewer network/21_global/Sewer Network";

    //get the number of lines from the xml description document
    int nOflinkLines = getNumOfLines(sewerDataPath + "/dataDescription.xml", "links");
    int nOfnodeLines = getNumOfLines(sewerDataPath + "/dataDescription.xml", "nodes");

    //pass number of lines and read binary files
    parseShafts(sewerDataPath + "/nodes/lines.bin", nOfnodeLines);
    parseLinks(sewerDataPath + "/links/lines.bin", nOflinkLines);

    //needs to be called after parsing shafts and pipes and sets references between pipes and shafts
    parseNodeIndicesPerLink(sewerDataPath + "/nodeIndicesPerLink/data.bin", nOflinkLines);

    //pipe radius will be broadcastet to connected pipes as well
    parselinkRadii(sewerDataPath + "/linkRadii/data.bin", nOflinkLines);
    float parseStartTime = Time.realtimeSinceStartup;

    // Parsing simulation data is threaded since this would take pretty long otherwise
    /************ NODE DATA ************/

    List < ParseJob > parseJobs = new List < ParseJob > ();

    //create a job for each timestep
    for (int timestep = 0; timestep <= SewerSystem.maxSimulationTimeStep; timestep += 10) {
      ParseJob myJob = new ParseJob(nOfnodeLines, timestep, "Node");
      parseJobs.Add(myJob);
    }

    ParseJobManager parseJobManager = new ParseJobManager(maxThreads, parseJobs);
    while (!parseJobManager.update()) {
      /*wait until all jobs are finished*/
    }

    //assign data from parse jobs
    foreach(ParseJob job in parseJobs) {
      int timestep = job.timestep / 10;
      for (int i = 0; i < job.numLines; i++) {
        SewerSystem.getShaftbyIndex(i).setDepthForTimestep(job.depthDataOut[i], timestep);
        SewerSystem.getShaftbyIndex(i).setDepthColorForTimestep(job.depthColorDataOut[i], timestep);
        SewerSystem.getShaftbyIndex(i).setDischargeColorForTimestep(job.dischargeColorDataOut[i], timestep);
      }
    }

    /************ LINK DATA COLORS ************/

    parseJobs.Clear();

    //create a job for each timestep
    for (int timestep = 0; timestep <= SewerSystem.maxSimulationTimeStep; timestep += 10) {
      ParseJob myJob = new ParseJob(nOflinkLines, timestep, "Link");
      parseJobs.Add(myJob);
    }

    parseJobManager = new ParseJobManager(maxThreads, parseJobs);
    while (!parseJobManager.update()) {
      /*wait until all jobs are finished*/
    }

    //assign data from parse jobs
    foreach(ParseJob job in parseJobs) {
      int timestep = job.timestep / 10;
      for (int i = 0; i < job.numLines; i++) {
        SewerSystem.getPipebyIndex(i).setDepthForTimestep(job.depthDataOut[i], timestep);
        SewerSystem.getPipebyIndex(i).setDepthColorForTimestep(job.depthColorDataOut[i], timestep);
        SewerSystem.getPipebyIndex(i).setDischargeColorForTimestep(job.dischargeColorDataOut[i], timestep);
      }
    }

    parseJobs.Clear();

    Debug.Log("ParseTime: " + (Time.realtimeSinceStartup - parseStartTime));

    foreach(Pipe pipe in SewerSystem.getPipes()) {
      List < Vector3 > refinedPipeNodes = refineNodes(pipe.getNodes());
      pipe.addAllNodes(refinedPipeNodes);
    }

    Debug.Log("removed PipeNodes: " + removedNodes);
    Debug.Log("added PipeNodes: " + addedNodes);
    Debug.Log("PipeNodes: " + pipeNodesCounter);

    SewerSystem.intializeAfterParsing();
  }

  //Start will be called after Awake or when the GameObject, 
  //this script is attached to, is activated
  void Start() {
    //keyboard input commands for surroundings
    GameObject surroundings = GameObject.FindGameObjectWithTag("surroundings");
    surroundingsCommands = surroundings.GetComponent < SurroundingsCommands > ();

    mainCam = Camera.main.gameObject;
    initialCamPos = mainCam.transform;

    setRenderQueues();

    Texture2D textureDT = LoadPNG(Application.dataPath + "/../AfterDT.png");

    // in case there is no .png file with the distance to pipe information stored it has to be computed and stored
    if (textureDT == null) {
      // 12 Bytes for float3, 8 for float2 .... plus 4 byte per int (two integers)
      computePipeNodesForBuffer();
      pipeNodesVec3ReadBuffer = new ComputeBuffer(SewerSystem.getNumberOfPipeNodes(), sizeof(float) * 3 + sizeof(int) * 2);
      pipeNodesVec3ReadBuffer.SetData(dataForPipeNodeBuffer);
      Shader.SetGlobalBuffer("pipeNodesVec3ReadBuffer", pipeNodesVec3ReadBuffer);

      RWpipeNodesUVBuffer = new ComputeBuffer(1, sizeof(float) * 2 + sizeof(int) * 2);
      Shader.SetGlobalBuffer("RWpipeNodesUVBuffer", RWpipeNodesUVBuffer);
      Graphics.SetRandomWriteTarget(1, RWpipeNodesUVBuffer, true);

      Shader.SetGlobalInt("scanTexCoords", 1); //set to true
      Shader.SetGlobalTexture("DTglobalTex", DTglobalTex);

      surroundingsCommands.setOnlyTerrainActive();
      SewerSystem.setActive(false);

    } else {
      computeDTtex = false;
      Shader.SetGlobalTexture("DTglobalTex", textureDT);
    }

    GameObject[] groundZero = GameObject.FindGameObjectsWithTag("terrainzero");
    foreach(GameObject go in groundZero) {
      go.AddComponent < FlattenTerrain > ();
      go.BroadcastMessage("flatten");
    }

  }

  public Texture2D LoadPNG(string filePath) {
    Texture2D tex = null;
    byte[] fileData;

    if (File.Exists(filePath)) {
      fileData = File.ReadAllBytes(filePath);
      tex = new Texture2D(2, 2);
      //will auto-resize the texture dimensions.
      tex.LoadImage(fileData);
    }
    return tex;
  }

  private void distanceTransform() {
    DTglobalTex_dt = DistanceTransform.transform(DTglobalTex as Texture2D);
    byte[] bytes = DTglobalTex_dt.EncodeToPNG();
    File.WriteAllBytes(Application.dataPath + "/../AfterDT.png", bytes);
    Shader.SetGlobalTexture("DTglobalTex", DTglobalTex_dt);
  }

  private void computePipeNodesForBuffer() {
    List < Pipe > allPipes = SewerSystem.getPipes();
    List < data > nodesData = new List < data > ();

    foreach(Pipe pipe in allPipes) {
      Vector3[] nodes = pipe.getNodes().ToArray();
      for (int i = 0; i < (nodes.Length); i++) {
        data data = new data();
        data.pos = nodes[i];
        data.pipeID = pipe.id;
        data.nodeID = i;
        nodesData.Add(data);
      }
    }
    dataForPipeNodeBuffer = nodesData.ToArray();
  }

  private void setRenderQueues() {
    GameObject[] terrain = GameObject.FindGameObjectsWithTag("terrain");
    GameObject[] terrainZero = GameObject.FindGameObjectsWithTag("terrainzero");
    GameObject[] buildings = GameObject.FindGameObjectsWithTag("buildings");
    GameObject[] trees = GameObject.FindGameObjectsWithTag("tree");

    GameObject water = GameObject.FindGameObjectWithTag("water");
    water.GetComponent < Renderer > ().sharedMaterial.renderQueue = 3001;

    foreach(GameObject go in terrainZero) {
      groundZeroMaterial.renderQueue = 2005;
      go.GetComponent < Renderer > ().sharedMaterial = groundZeroMaterial;
      go.GetComponent < Renderer > ().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    }

    foreach(GameObject go in terrain) {
      terrainMaterial.renderQueue = 3000;
      go.GetComponent < Renderer > ().sharedMaterial = terrainMaterial;
      go.GetComponent < Renderer > ().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    }

    shaftMaterial.renderQueue = 2013;
    shaftInsideMaterial.renderQueue = 2013;
    manholeMaterial.renderQueue = 2014;
    shaftPipeCancelMaterial.renderQueue = 2016;
    shaftWaterMaterial.renderQueue = 2015;

    foreach(GameObject go in buildings) {
      go.GetComponent < Renderer > ().sharedMaterial = buildingsMaterial;
      go.GetComponent < Renderer > ().sharedMaterial.renderQueue = 2018;
      go.GetComponent < Renderer > ().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    }

    foreach(GameObject go in trees) {
      go.GetComponent < Renderer > ().sharedMaterial.renderQueue = 2020;
    }
  }

  // Update is called once per frame
  void Update() {
    if (computeDTtex) {
      if (UpdateCounterPipeNodeID < dataForPipeNodeBuffer.Length) {
        // SETUP FOR SNAPSHOT
        Shader.SetGlobalInt("UpdateCounterPipeNodeID", UpdateCounterPipeNodeID);
        Vector3 vec = dataForPipeNodeBuffer[UpdateCounterPipeNodeID++].pos;
        vec.y = 40;
        mainCam.transform.position = vec;
        mainCam.transform.LookAt(vec + Vector3.down);

        // READ
        uvData[] uvDatas = new uvData[1];
        RWpipeNodesUVBuffer.GetData(uvDatas);
        uvData uv = uvDatas[0];

        // SET
        if (uv.pipeID != 0) {
          //Debug.Log("looking for pipeID: " + uv.pipeID);
          Pipe pipe = SewerSystem.getPipeByID(uv.pipeID);
          if (pipe != null) pipe.setUV(uv.uvPos, uv.nodeID);
        }

      }
      else if (UpdateCounterPipeNodeID == dataForPipeNodeBuffer.Length) {
        Shader.SetGlobalInt("scanTexCoords", 0); //set to false
        distanceTransform();
        surroundingsCommands.setAllActive();
        SewerSystem.setActive(true);
        UpdateCounterPipeNodeID++;
        computeDTtex = false;
        mainCam.transform.position = initialCamPos.position;
      }
    }
  }

  // Fixed timestep of 0.05 seconds
  void FixedUpdate() {
    currenTimestep++;
    if (currenTimestep >= SewerSystem.simulationSteps) {
      currenTimestep = 0;
    }
    foreach(Shaft shaft in SewerSystem.getShafts()) {
      // used to update water levels in shaft
      shaft.setTimeStep(currenTimestep);
    }
  }

  // returns how many lines the binary files have
  // which is defined in xml
  static int getNumOfLines(string path, string dataType) {
    // Create an XML reader for this file.
    using(XmlReader reader = XmlReader.Create(path)) {
      bool match = false;
      while (reader.Read()) {
        // Only detect start elements.
        if (reader.IsStartElement()) {
          if (reader.Name == "name") {
            // check if this is the element we are looking for
            if (reader.ReadInnerXml() == dataType) {
              match = true;
            }
          }

          // we found a match
          if (reader.Name == "numLines" && match) {
            return Int32.Parse(reader.ReadInnerXml());
          }
        }
      }
      //in case nothing was found -1 will skip the parsing algorithm
      Debug.LogError("Failed to read the desired string in XML, the file may be missing.");
      return - 1;
    }
  }

  void parseShafts(string path, int numLines) {

    // Read the data.
    using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) {

      //Parse for each single Line, always 2 positions
      for (int index = 0; index < numLines; index++) {
        // The first element is the id of the line
        int id = bReader.ReadInt32();
        // The second element is the number of positions in the line, shoudl be 2 here
        int numPositions = bReader.ReadInt32();
        if (numPositions != 2) Debug.LogError("SHAFT numPositions != 2 ERROR");

        //first position is the lower one
        Shaft shaft = new Shaft(id, this.gameObject, shaftMaterial, shaftInsideMaterial, manholeMaterial, shaftWaterMaterial, shaftPipeCancelMaterial);
        Transform dummyTransform = new GameObject().transform;

        // Three doubles per position (x,y,z) times number of positions to read
        float x = (float) bReader.ReadDouble();
        float y = (float) bReader.ReadDouble();
        float z = (float) bReader.ReadDouble();

        //for some reason y needs to be inverted to make it fit properly in unity
        y *= -1;

        //and some rotations are needed to make it fit to the terrain

        dummyTransform.position = new Vector3(x, y, z);
        dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), -90f);
        dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(0f, 1f, 0f), 180f);

        shaft.setLower(dummyTransform.position);

        // Three doubles per position (x,y,z) times number of positions to read
        x = (float) bReader.ReadDouble();
        y = (float) bReader.ReadDouble();
        z = (float) bReader.ReadDouble();

        //for some reason y needs to be inverted to make it fit properly in unity
        y *= -1;

        //and some rotations are needed to make it fit to the terrain
        dummyTransform.position = new Vector3(x, y, z);
        dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), -90f);
        dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(0f, 1f, 0f), 180f);

        shaft.setUpper(dummyTransform.position);

        Destroy(dummyTransform.gameObject);

        SewerSystem.addShaft(shaft);

      }
    }
  }

  void parseLinks(string path, int numLines) {

    // Read the data.
    using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) {

      //Parse for each single Line, there might be >= 2 positions per line
      for (int i = 0; i < numLines; i++) {
        // The first element is the id of the line
        int id = bReader.ReadInt32();
        // The second element is the number of positions in the line
        int numPositions = bReader.ReadInt32();

        Pipe pipe = new Pipe(id, this.gameObject);
        List < Vector3 > pipeNodes = new List < Vector3 > ();

        // Three doubles per position (x,y,z) times number of positions to read
        for (int j = 0; j < numPositions; j++) {

          float x = (float) bReader.ReadDouble();
          float y = (float) bReader.ReadDouble();
          float z = (float) bReader.ReadDouble();

          //for some reason y needs to be inverted to make it fit properly in unity
          y *= -1;

          //and some rotations are needed to make it fit to the terrain
          Transform dummyTransform = new GameObject().transform;
          dummyTransform.position = new Vector3(x, y, z);
          dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(1f, 0f, 0f), -90f);
          dummyTransform.RotateAround(new Vector3(0f, 0f, 0f), new Vector3(0f, 1f, 0f), 180f);

          //finally pass the node positions to pipe class
          pipeNodes.Add(dummyTransform.position);
          Destroy(dummyTransform.gameObject);
        }

        //remove or add points without interfering with semantic structure
        pipe.addAllNodes(pipeNodes);

        // and add the pipe to the sewer system class
        SewerSystem.addPipe(pipe);

      }
    }

  }

  // this method will remove unnecessary nodes 
  // and add those wich are needed for the cutaways of the pipes
  private List < Vector3 > refineNodes(List < Vector3 > nodes) {

    if (nodes.Count > 2) {
      int prev = 0;
      int current = 1;
      int next = 2;

      //remove nodes
      while (next < nodes.Count && prev < current && current < next) {
        Vector3 directionBig = nodes[next] - nodes[prev];
        Vector3 directionSmall = nodes[current] - nodes[prev];
        float dot = Vector3.Dot(directionBig.normalized, directionSmall.normalized);

        if (dot > 0.999f || dot < 0.6f) {
          nodes.RemoveAt(current);
          removedNodes++;
        } else {
          next++;
          current++;
          prev++;
        }
      }

    }

    //add nodes for cut
    if ((nodes[nodes.Count - 1] - nodes[0]).magnitude > 7) {

      if (nodes.Count == 2) {
        Vector3 direction = nodes[1] - nodes[0];

        Vector3 tempLast = nodes[1]; //save last
        nodes.Insert(1, nodes[0] + direction.normalized * direction.magnitude / 4);
        nodes.Insert(2, tempLast - direction.normalized * direction.magnitude / 4);

        addedNodes += 2;

      } else if (nodes.Count == 3) {
        Vector3 firstDirection = (nodes[1] - nodes[0]);
        Vector3 secondDirection = (nodes[2] - nodes[1]);

        nodes.Insert(1, nodes[0] + firstDirection.normalized * firstDirection.magnitude / 2);
        nodes.Insert(3, nodes[2] + secondDirection.normalized * secondDirection.magnitude / 2);

      } else if (nodes.Count >= 4) {
        Vector3 firstDirection = (nodes[1] - nodes[0]);
        if (firstDirection.magnitude > 3) {
          nodes.Insert(1, nodes[0] + firstDirection.normalized * firstDirection.magnitude / 2);
        }

        Vector3 secondDirection = (nodes[nodes.Count - 1] - nodes[nodes.Count - 2]);
        if (secondDirection.magnitude > 3) {
          nodes.Insert(nodes.Count - 1, nodes[nodes.Count - 1] - secondDirection.normalized * secondDirection.magnitude / 2);
        }
      }
    }

    pipeNodesCounter += nodes.Count;

    return nodes;
  }

  void parseNodeIndicesPerLink(string path, int numLines) {

    // Read the data.
    using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) {

      //Parse for each single Line
      // add startShaft and endShaft to its Pipes and add Pipes connected to a shaft to this shaft as well
      for (int i = 0; i < numLines; i++) {

        //indices are in the same order as the pipes where parsed with (hopefully), same for shafts
        Pipe pipe = SewerSystem.getPipebyIndex(i);

        //index of the first shaft the pipe is connected to
        int startIndex = bReader.ReadInt32();
        Shaft shaft = SewerSystem.getShaftbyIndex(startIndex);
        pipe.setStartShaft(shaft);
        shaft.addConnectedPipe(pipe);

        //index of the second shaft the pipe is connected to
        int endIndex = bReader.ReadInt32();
        shaft = SewerSystem.getShaftbyIndex(endIndex);
        pipe.setEndShaft(shaft);
        shaft.addConnectedPipe(pipe);
      }
    }
  }

  void parselinkRadii(string path, int numLines) {
    // Read the data.
    using(BinaryReader bReader = new BinaryReader(File.Open(path, FileMode.Open))) {
      // parse for each single for a pipes radius
      for (int i = 0; i < numLines; i++) {
        Pipe pipe = SewerSystem.getPipebyIndex(i);
        //indices are in the same order as the pipes where parsed with (hopefully)
        pipe.radius = bReader.ReadSingle();
        pipe.broadcastRadiusToShafts();

      }
    }
  }

  void OnDestroy() {
    if (pipeNodesVec3ReadBuffer != null) pipeNodesVec3ReadBuffer.Release();
    pipeNodesVec3ReadBuffer = null;

    if (RWpipeNodesUVBuffer != null) RWpipeNodesUVBuffer.Release();
    RWpipeNodesUVBuffer = null;
  }
}
SewerSystem.cs
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

using System.Collections.Generic;
using UnityEngine;

public static class SewerSystem {

  private static List < Pipe > pipes = new List < Pipe > ();
  private static List < Shaft > shafts = new List < Shaft > ();
  private static int numberOfPipeNodes = 0;
  private static int minNumberOfNodesPerPipe = int.MaxValue;
  public static int maxSimulationTimeStep = 7200;
  public static int simulationSteps = 721;

  public static void setActive(bool active) {
    foreach(Pipe pipe in pipes) {
      pipe.setActive(active);
    }

    foreach(Shaft shaft in shafts) {
      shaft.setActive(active);
    }
  }

  public static int getNumberOfPipeNodes() {
    if (numberOfPipeNodes == 0) {
      foreach(Pipe pipe in pipes) {
        numberOfPipeNodes += pipe.getNodeCount();

        if (pipe.getNodeCount() < minNumberOfNodesPerPipe) minNumberOfNodesPerPipe = pipe.getNodeCount();
      }

    }
    return numberOfPipeNodes;
  }

  public static void addShaft(Shaft shaft) {
    shafts.Add(shaft);
  }

  public static void addPipe(Pipe pipe) {
    pipes.Add(pipe);
  }

  public static List < Pipe > getPipes() {
    return pipes;
  }

  public static List < Shaft > getShafts() {
    return shafts;
  }

  public static Shaft getShaftbyIndex(int index) {
    return shafts[index];
  }

  public static Pipe getPipebyIndex(int index) {
    return pipes[index];
  }

  public static Pipe getPipeByID(int id) {
    foreach(Pipe pipe in pipes) {
      if (pipe.id == id) {
        return pipe;
      }
    }

    return null;
  }

  //sort pipes by height, to ensure the are drawn in this order by geometryshader
  private static void sortPipesByHeight() {
    pipes.Sort(delegate(Pipe a, Pipe b) {
      return (b.position.y).CompareTo(a.position.y);
    });
  }

  public static void intializeAfterParsing() {
    foreach(Pipe pipe in pipes) {
      pipe.initializAfterParsing();
    }

    sortPipesByHeight();

    foreach(Shaft shaft in shafts) {
      shaft.createShaft();
      shaft.createWater();
    }

  }

}
Pipe.cs
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

using UnityEngine;
using System.Collections.Generic;
using System.Collections;

public class Pipe {
  private GameObject pipeGameObject;

  private List < Vector3 > pipeNodes;
  private Vector2[] UVs = null;

  private Shaft startShaft;
  private Shaft endShaft;

  public Vector3 position = Vector3.one;
  public float radius = 0;
  public int id;

  public float[] depthTimeline;
  public Color[] depthColorTimeline;
  public Color[] dischargeColorTimeline;

  public Pipe(int id, GameObject parent) {
    this.id = id;
    pipeNodes = new List < Vector3 > ();

    depthTimeline = new float[SewerSystem.simulationSteps];
    depthColorTimeline = new Color[SewerSystem.simulationSteps];
    dischargeColorTimeline = new Color[SewerSystem.simulationSteps];

    pipeGameObject = new GameObject();
    pipeGameObject.name = "Pipe ID: " + this.id;
    pipeGameObject.transform.SetParent(parent.transform);
    pipeGameObject.tag = "pipe";

  }

  public void setDepthForTimestep(float depth, int timestep) {
    depthTimeline[timestep] = depth;
  }

  public void setDepthColorForTimestep(Color color, int timestep) {
    depthColorTimeline[timestep] = color;
  }

  public void setDischargeColorForTimestep(Color color, int timestep) {
    dischargeColorTimeline[timestep] = color;
  }

  public void broadcastRadiusToShafts() {
    startShaft.increaseWidth(radius);
    endShaft.increaseWidth(radius);
  }

  private void initiateUVs() {
    if (UVs == null) UVs = new Vector2[pipeNodes.Count];
  }

  public void setUV(Vector2 uv, int nodeID) {
    initiateUVs();
    this.UVs[nodeID] = uv;
  }

  public Vector2[] getUVs() {
    return this.UVs;
  }

  public void setActive(bool active) {
    pipeGameObject.SetActive(active);
  }

  public void initializAfterParsing() {
    swapShaftsIfNeeded();
    position.x = (pipeNodes[0].x + pipeNodes[pipeNodes.Count - 1].x) / 2;
    position.y = (pipeNodes[0].y + pipeNodes[pipeNodes.Count - 1].y) / 2;
    position.z = (pipeNodes[0].z + pipeNodes[pipeNodes.Count - 1].z) / 2;
  }

  public void setStartShaft(Shaft shaft) {
    startShaft = shaft;
  }

  public void setEndShaft(Shaft shaft) {
    endShaft = shaft;
  }

  public float getHeightOfPipeEndToConnectedShaft(Shaft shaft) {
    if (shaft.getID() == startShaft.getID()) {
      return pipeNodes[0].y;
    } else if (shaft.getID() == endShaft.getID()) {
      return pipeNodes[pipeNodes.Count - 1].y;
    } else {
      Debug.Log("shaft error: given shaft is neither endshaft or startshaft");
      return - 1000000;
    }
  }

  //Make shure that start shaft is closer to index 0 of pipeNodes
  public void swapShaftsIfNeeded() {
    float distStart = Vector3.Distance(startShaft.getPos(), pipeNodes[0]);
    float distEnd = Vector3.Distance(endShaft.getPos(), pipeNodes[0]);
    if (distStart > distEnd) {
      Shaft temp = startShaft;
      startShaft = endShaft;
      endShaft = temp;
    }
  }

  public List < Vector3 > getNodes() {
    return pipeNodes;
  }

  public void addNode(Vector3 node) {
    pipeNodes.Add(node);
  }

  public void addAllNodes(List < Vector3 > nodes) {
    pipeNodes = nodes;
  }

  public int getNodeCount() {
    return pipeNodes.Count;
  }

}
Shaft.cs
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

using UnityEngine;
using System.Collections.Generic;

public class Shaft {

  static private float wallThickness = 0.1f;
  static private float waterOffset = 0.01f;

  private GameObject shaft;
  private GameObject water;
  private GameObject cylinder;
  private GameObject cylinderInside;
  private GameObject manhole;
  private GameObject pipeCancel;
  private Material shaftMaterial;
  private Material manholeMaterial;
  private Material waterMaterial;
  private Material shaftInsideMaterial;
  private Material pipeCancelMaterial;

  private Vector3 upperPoint;
  private Vector3 lowerPoint;

  private float[] depthTimeline;
  private Color[] depthColorTimeline;
  private Color[] dischargeColorTimeline;

  private List < Pipe > connectedPipes;
  private int id;
  private float width;

  private float waterFillPercent;

  public Shaft(int id, GameObject parent, Material shaftMaterial, Material shaftInsideMaterial, Material manholeMaterial, Material waterMaterial, Material pipeCancelMaterial) {
    this.id = id;
    depthTimeline = new float[SewerSystem.simulationSteps];
    depthColorTimeline = new Color[SewerSystem.simulationSteps];
    dischargeColorTimeline = new Color[SewerSystem.simulationSteps];

    shaft = new GameObject();
    shaft.transform.SetParent(parent.transform);
    shaft.name = "Shaft ID: " + this.id;

    this.shaftMaterial = shaftMaterial;
    this.manholeMaterial = manholeMaterial;
    this.waterMaterial = waterMaterial;
    this.shaftInsideMaterial = shaftInsideMaterial;
    this.pipeCancelMaterial = pipeCancelMaterial;

    this.width = 0.7f;

    upperPoint = new Vector3();
    lowerPoint = new Vector3();
    connectedPipes = new List < Pipe > ();

    cylinder = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    cylinder.transform.SetParent(shaft.transform);
    cylinder.name = "Shaft Cylinder";
    cylinder.tag = "shaft";

    pipeCancel = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    pipeCancel.transform.SetParent(shaft.transform);
    pipeCancel.name = "Shaft Pipe Cancel";
    pipeCancel.tag = "shaft";

    cylinderInside = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    cylinderInside.transform.SetParent(shaft.transform);
    cylinderInside.name = "Shaft Cylinder Inside";
    cylinderInside.tag = "shaft";

    water = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    water.transform.SetParent(shaft.transform);
    water.name = "Water";
    waterFillPercent = Random.Range(0.0f, 1.0f);

    manhole = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
    manhole.transform.SetParent(cylinder.transform);
    manhole.name = "Manhole";
  }

  public int getID() {
    return this.id;
  }

  public void setDepthForTimestep(float depth, int timestep) {
    depthTimeline[timestep] = depth;
  }

  public void setDepthColorForTimestep(Color color, int timestep) {
    depthColorTimeline[timestep] = color;
  }

  public void setDischargeColorForTimestep(Color color, int timestep) {
    dischargeColorTimeline[timestep] = color;
  }

  public void increaseWidth(float radius) {
    if (radius * 2.5f > width) width = radius * 2.5f;
  }

  // called at the end of start when all is set up
  public void createShaft() {
    Vector3 offset = lowerPoint - upperPoint;
    Vector3 scale = new Vector3(width, offset.magnitude / 2.0f, width);
    cylinder.transform.position = upperPoint + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f);
    cylinder.GetComponent < MeshRenderer > ().sharedMaterial = shaftMaterial;
    cylinder.transform.up = offset;
    cylinder.transform.localScale = scale;
    cylinder.isStatic = true;

    pipeCancel.transform.position = upperPoint + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f);
    pipeCancel.GetComponent < MeshRenderer > ().sharedMaterial = pipeCancelMaterial;
    pipeCancel.transform.up = offset;
    pipeCancel.transform.localScale = scale;
    pipeCancel.isStatic = true;

    manhole.transform.position = upperPoint + new Vector3(0, 0.05f, 0);
    manhole.GetComponent < MeshRenderer > ().sharedMaterial = manholeMaterial;
    manhole.transform.localScale = new Vector3(0.9f, 0.02f, 0.9f);
    manhole.isStatic = true;

    float widthModifier = 1 - wallThickness;
    Vector3 upperPointInner = upperPoint + Vector3.down * waterOffset / 2;
    Vector3 lowerPointInner = lowerPoint + Vector3.up * wallThickness / 2;
    offset = lowerPointInner - upperPointInner;
    scale = new Vector3(width * widthModifier, offset.magnitude / 2.0f, width * widthModifier);
    cylinderInside.transform.position = upperPointInner + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f);
    cylinderInside.GetComponent < MeshRenderer > ().sharedMaterial = shaftInsideMaterial;
    cylinderInside.transform.up = offset;
    cylinderInside.transform.localScale = scale;
    cylinderInside.isStatic = true;
  }

  public void createWater() {
    Vector3 waterMax = upperPoint + Vector3.down * (wallThickness / 2 + waterOffset);
    Vector3 waterMin = lowerPoint + Vector3.up * (wallThickness / 2 + waterOffset);
    float widthModifier = 1 - wallThickness - waterOffset * 10;

    //float maxDist = Vector3.Distance(waterMin, waterMax);

    Vector3 offset = (waterMin - waterMax);
    Vector3 scale = new Vector3(width * widthModifier, offset.magnitude / 2.0f, width * widthModifier);
    water.transform.position = waterMax + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f);
    //GameObject cylinder = (GameObject)UnityEngine.Object.Instantiate(cylinderPrefab, position, Quaternion.identity);
    water.GetComponent < MeshRenderer > ().sharedMaterial = waterMaterial;
    water.transform.up = offset;
    water.transform.localScale = scale;
  }

  public void setTimeStep(int timestep) {
    float waterHeight = depthTimeline[timestep];
    float minHeight = 100000000.0f;

    //move this to initialzation
    foreach(Pipe pipe in connectedPipes) {
      float height = pipe.getHeightOfPipeEndToConnectedShaft(this);
      if (height < minHeight) minHeight = height;
    }
    if (minHeight < lowerPoint.y) minHeight = lowerPoint.y;
    waterFillPercent = ((minHeight - lowerPoint.y) + waterHeight) / Vector3.Distance(upperPoint, lowerPoint);
    if (waterFillPercent > 1) waterFillPercent = 1.0f;
    waterMaterial.SetColor("_Color", depthColorTimeline[timestep]);

    rescaleWater();
  }

  public void rescaleWater() {
    Vector3 waterMax = upperPoint + Vector3.down * (wallThickness / 2 + waterOffset);
    Vector3 waterMin = lowerPoint + Vector3.up * (wallThickness / 2 + waterOffset);
    float widthModifier = 1 - wallThickness - waterOffset * 10;

    float maxDist = Vector3.Distance(waterMin, waterMax);
    Vector3 waterLevel = waterMin + Vector3.up * maxDist * waterFillPercent;

    Vector3 offset = (waterMin - waterLevel);
    Vector3 scale = new Vector3(width * widthModifier, offset.magnitude / 2.0f, width * widthModifier);
    water.transform.position = waterLevel + new Vector3(offset.x / 2.0f, offset.y / 2.0f, offset.z / 2.0f);
    water.transform.up = offset;
    water.transform.localScale = scale;
  }

  public void setActive(bool active) {
    shaft.SetActive(active);
  }

  public void addConnectedPipe(Pipe pipe) {
    if (!connectedPipes.Contains(pipe)) connectedPipes.Add(pipe);
  }

  public void setLower(Vector3 lower) {
    lowerPoint = lower + Vector3.down;
  }

  //setUpper must be called after setLower for each shaft respectively
  public void setUpper(Vector3 upper) {
    if (upper.y < lowerPoint.y && lowerPoint.y != 0) {
      upperPoint = lowerPoint;
      lowerPoint = upper;
    } else {
      upperPoint = upper;
    }
  }

  public Vector3 getPos() {
    Vector3 position = new Vector3(0f, 0f, 0f);
    position.x = (lowerPoint.x + upperPoint.x) / 2;
    position.y = (lowerPoint.y + upperPoint.y) / 2;
    position.z = (lowerPoint.z + upperPoint.z) / 2;
    return position;
  }

}
DistanceTransform.cs
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public static class DistanceTransform {

  public static Texture2D copyTextureWhite(Texture2D tex_in) {
    Texture2D tex_out = new Texture2D(tex_in.width, tex_in.height);
    Color32[] setColorArray = tex_in.GetPixels32();
    Color32 white = new Color32(255, 255, 255, 255);

    for (int i = 0; i < setColorArray.Length; i++) {
      Color col = setColorArray[i];
      if (col.r > 0.5) setColorArray[i] = white;
      else setColorArray[i] = white;
    }

    tex_out.SetPixels32(setColorArray);
    tex_out.Apply();
    return tex_out;
  }

  public static void DrawPipes(this Texture2D texture) {
    int counter = 0;

    float height = texture.height;
    float width = texture.height;

    foreach(Pipe pipe in SewerSystem.getPipes()) {
      Vector2[] pipeUvNodes = pipe.getUVs();
      if (pipeUvNodes != null) {
        counter++;
        for (int j = 1; j < pipeUvNodes.Length; j++) {
          Vector2 v = new Vector2(pipeUvNodes[j - 1].x * height, pipeUvNodes[j - 1].y * width);
          Vector2 w = new Vector2(pipeUvNodes[j].x * height, pipeUvNodes[j].y * width);
          if (isValidV2(v) && isValidV2(w)) DrawLine(texture, v, w, Color.black);
        }
      }
    }
    Debug.Log("Pipes Drawn: " + counter);
    Debug.Log("Pipes Count: " + SewerSystem.getPipes().Count);
    texture.Apply();
  }

  private static bool isValidV2(Vector2 v) {
    if (v.x == 0 || v.y == 0) return false;
    else return true;
  }

  //Source: http://answers.unity3d.com/questions/244417/create-line-on-a-texture.html
  private static Texture2D DrawLine(this Texture2D tex, Vector2 p1, Vector2 p2, Color col) {

    Vector2 t = p1;
    float frac = 1 / Mathf.Sqrt(Mathf.Pow(p2.x - p1.x, 2) + Mathf.Pow(p2.y - p1.y, 2));
    float ctr = 0;

    while ((int) t.x != (int) p2.x || (int) t.y != (int) p2.y) {
      t = Vector2.Lerp(p1, p2, ctr);
      ctr += frac;
      tex.SetPixel((int) t.x, (int) t.y, col);
    }
    //tex.Apply();
    return tex;
  }

  public static Texture2D transform(Texture2D texture) {
    Texture2D tex = copyTextureWhite(texture);

    DrawPipes(tex);

    L1_2PASS(tex);

    rotate90CCW(tex);
    L1_2PASS(tex);

    rotate90CCW(tex);
    rotate90CCW(tex);
    rotate90CCW(tex);

    return tex;

  }

  public static void rotate90CCW(Texture2D texture) {
    Color32[] ret = new Color32[texture.width * texture.height];
    Color32[] matrix = texture.GetPixels32();
    int n = texture.width;

    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < n; ++j) {
        ret[i * n + j] = matrix[(n - j - 1) * n + i];
      }
    }

    texture.SetPixels32(ret);
    texture.Apply();
  }

  private static Color isLastColorBrighter(Color32 thisColor, Color32 lastColor) {
    float toAdd = (10.0f / 255.0f);
    lastColor = (Color32)((Color) lastColor + new Color(toAdd, toAdd, toAdd, 0)); // add 1
    if (thisColor.r < lastColor.r) return thisColor; // r = g = b
    else return lastColor;
  }

  public static void L1_2PASS(Texture2D tex_in) {
    int height = tex_in.height;
    int width = tex_in.height;
    List < Color[] > rows = new List < Color[] > ();

    for (int y = 0; y < height; y++) {
      Color[] cols = tex_in.GetPixels(0, y, width, 1);
      rows.Add(cols);
    }

    List < Color > colorArray = new List < Color > ();
    foreach(Color[] cols in rows) {

      // 1. Pass (forward)
      for (int i = 1; i < cols.Length; i++) {
        cols[i] = isLastColorBrighter(cols[i], cols[i - 1]);
      }

      // 2. Pass (backwards)
      for (int i = cols.Length - 2; i >= 0; i--) {
        cols[i] = isLastColorBrighter(cols[i], cols[i + 1]);
      }

      //Compose
      foreach(Color col in cols) {
        colorArray.Add(col);
      }
    }

    tex_in.SetPixels(colorArray.ToArray());
    tex_in.Apply();
  }

}
MyPipeRenderer.cs
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MyPipeRenderer: MonoBehaviour {

  public Material cutSurfaceMaterial;
  public Material backFaceRepairMaterial;
  public Material proxyInsideMaterial;
  public Material pipeBillboardMaterial;
  public Material pipeShadowMaterial;
  public Material pipeWaterMaterial;

  private float someOtherRadius = 0.3f;
  private float wallThickness = 0.1f;

  private ComputeBuffer proxyTriangleBuffer;
  private ComputeBuffer proxyQuadsBuffer;
  private ComputeBuffer pipeQuadsBuffer;
  private ComputeBuffer pipeWaterLevelBuffer;
  private ComputeBuffer pipeWaterColorBuffer;

  private static bool drawProxy = true;
  private static bool drawStencilPrepare = false;
  private static bool drawBillboards = false;
  private static bool drawPipes = true;

  private int proxyTriangleBufferSize = 0;
  private int proxyQuadsBufferSize = 0;
  private int pipeQuadsBufferSize = 0;
  private int pipeWaterLevelBufferSize = 0;
  private int pipeWaterColorBufferSize = 0;

  private float[] pipeWaterLevel;
  private Color[] pipeWaterColor;

  struct proxyData {
    public Vector3 lastPos;
    public Vector3 pos;
    public Vector3 nextPos;
    public float radius;
    public int pipeID;
  };

  //40 = 4bytes times float3 times 3 plus 4bytes for int
  private int proxyDataByteSize = sizeof(float) * 10 + sizeof(int);

  struct pipeData {
    public Vector3 lastPos;
    public Vector3 pos;
    public Vector3 nextPos;
    public float radius;
    public int pipeID;
    public int id;
    public int pipeNodeCount;
    public int isCut;
  };

  //52 = 4bytes times float3 times 3 plus 4bytes per int times 4
  private int pipeDataByteSize = sizeof(float) * 10 + sizeof(int) * 4;

  // Use this for initialization
  void Start() {
    cutSurfaceMaterial.renderQueue = 2011;
    proxyInsideMaterial.renderQueue = 2011;
    pipeBillboardMaterial.renderQueue = 2011;
    pipeShadowMaterial.renderQueue = 2011;
    pipeWaterMaterial.renderQueue = 2011;
    backFaceRepairMaterial.renderQueue = 2011;

    List < Pipe > pipes = SewerSystem.getPipes();
    List < proxyData > proxyTriangleData = new List < proxyData > ();
    List < proxyData > proxyQuadsData = new List < proxyData > ();
    List < pipeData > pipeQuadsData = new List < pipeData > ();
    pipeWaterLevel = new float[pipes.Count];
    pipeWaterColor = new Color[pipes.Count];

    for (int p = 0; p < pipes.Count; p++) {
      Pipe pipe = pipes[p];
      List < Vector3 > pipeNodes = pipe.getNodes();

      pipeWaterLevel[p] = pipe.depthTimeline[0];
      pipeWaterColor[p] = pipe.depthColorTimeline[0];

      // data for view aligned billboards
      if (pipeNodes.Count >= 2) {

        //first node
        pipeQuadsData.Add(new pipeData() {
          lastPos = pipeNodes[0],
          pos = pipeNodes[0],
          nextPos = pipeNodes[1],
          radius = pipe.radius,
          pipeID = p,
          id = 0,
          pipeNodeCount = pipeNodes.Count,
          isCut = 0
        });

        //nodes in between
        for (int i = 1; i < pipeNodes.Count - 1; i++) {
          int cut = 0;
          if (pipeNodes.Count >= 4) cut = 1;
          pipeQuadsData.Add(new pipeData() {
            lastPos = pipeNodes[i - 1],
            pos = pipeNodes[i],
            nextPos = pipeNodes[i + 1],
            radius = pipe.radius,
            pipeID = p,
            id = i,
            pipeNodeCount = pipeNodes.Count,
            isCut = cut
          });
        }

        //last node
        pipeQuadsData.Add(new pipeData() {
          lastPos = pipeNodes[pipeNodes.Count - 2],
          pos = pipeNodes[pipeNodes.Count - 1],
          nextPos = pipeNodes[pipeNodes.Count - 1],
          radius = pipe.radius,
          pipeID = p,
          id = pipeNodes.Count - 1,
          pipeNodeCount = pipeNodes.Count,
          isCut = 0
        });
      }

      //ProxyGeometry
      if (pipeNodes.Count >= 4) {

        proxyTriangleData.Add(new proxyData() {
          lastPos = pipeNodes[0],
          pos = pipeNodes[1],
          nextPos = pipeNodes[2],
          radius = pipe.radius,
          pipeID = p
        });

        proxyTriangleData.Add(new proxyData() {
          lastPos = pipeNodes[pipeNodes.Count - 1],
          pos = pipeNodes[pipeNodes.Count - 2],
          nextPos = pipeNodes[pipeNodes.Count - 3],
          radius = pipe.radius,
          pipeID = p
        });

        for (int i = 1; i < pipeNodes.Count - 1; i++) {
          proxyQuadsData.Add(new proxyData() {
            lastPos = pipeNodes[i - 1],
            pos = pipeNodes[i],
            nextPos = pipeNodes[i + 1],
            radius = pipe.radius,
            pipeID = p
          });
        }

      }
    }

    proxyTriangleBufferSize = proxyTriangleData.Count;
    proxyQuadsBufferSize = proxyQuadsData.Count;
    pipeQuadsBufferSize = pipeQuadsData.Count;
    pipeWaterLevelBufferSize = pipeWaterLevel.Length;
    pipeWaterColorBufferSize = pipeWaterColor.Length;

    proxyTriangleBuffer = new ComputeBuffer(proxyTriangleBufferSize, proxyDataByteSize);
    proxyTriangleBuffer.SetData(proxyTriangleData.ToArray());

    proxyQuadsBuffer = new ComputeBuffer(proxyQuadsBufferSize, proxyDataByteSize);
    proxyQuadsBuffer.SetData(proxyQuadsData.ToArray());

    pipeQuadsBuffer = new ComputeBuffer(pipeQuadsBufferSize, pipeDataByteSize);
    pipeQuadsBuffer.SetData(pipeQuadsData.ToArray());

    pipeWaterLevelBuffer = new ComputeBuffer(pipeWaterLevelBufferSize, sizeof(float));
    pipeWaterLevelBuffer.SetData(pipeWaterLevel);

    pipeWaterColorBuffer = new ComputeBuffer(pipeWaterColorBufferSize, sizeof(float) * 4);
    pipeWaterColorBuffer.SetData(pipeWaterColor);

    Shader.SetGlobalFloat("wallThickness", wallThickness);

    cutSurfaceMaterial.SetBuffer("proxyTriangleBuffer", proxyTriangleBuffer);
    cutSurfaceMaterial.SetBuffer("proxyQuadsBuffer", proxyQuadsBuffer);

    proxyInsideMaterial.SetBuffer("proxyTriangleBuffer", proxyTriangleBuffer);
    proxyInsideMaterial.SetBuffer("proxyQuadsBuffer", proxyQuadsBuffer);

    pipeBillboardMaterial.SetBuffer("pipeQuadsBuffer", pipeQuadsBuffer);

    pipeShadowMaterial.SetBuffer("pipeQuadsBuffer", pipeQuadsBuffer);

    backFaceRepairMaterial.SetBuffer("proxyTriangleBuffer", proxyTriangleBuffer);
    backFaceRepairMaterial.SetBuffer("pipeQuadsBuffer", pipeQuadsBuffer);

    pipeWaterMaterial.SetBuffer("pipeQuadsBuffer", pipeQuadsBuffer);

  }

  void OnRenderObject() {
    //TODO: instanced mit 3 parameter 
    //BETTER: ONLY USE one of this call instead of doing it for each pipe respectively with more buffers

    //First two indices form a line, and then each new index 
    //connects a new vertex to the existing line strip.

    if (drawPipes) {
      //vertical triangles cut surfaces
      cutSurfaceMaterial.SetPass(0);
      Graphics.DrawProcedural(MeshTopology.Points, proxyTriangleBufferSize);

      //horizontal quadrilaterals cut surfaces
      cutSurfaceMaterial.SetPass(1);
      Graphics.DrawProcedural(MeshTopology.LineStrip, proxyQuadsBufferSize);

      //vertical triangles stencil prepare
      proxyInsideMaterial.SetPass(0);
      Graphics.DrawProcedural(MeshTopology.Points, proxyTriangleBufferSize);

      //horizontal quadrilaterals stencil prepare
      proxyInsideMaterial.SetPass(1);
      Graphics.DrawProcedural(MeshTopology.LineStrip, proxyQuadsBufferSize);

      //view-aligned quadrilaterals inside
      pipeBillboardMaterial.SetPass(0);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //view-aligned quadrilaterals outside prepare EXCLUDE
      pipeBillboardMaterial.SetPass(1);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //view-aligned quadrilaterals outside
      pipeBillboardMaterial.SetPass(2);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //vertical triangles stencil prepare backfaces
      backFaceRepairMaterial.SetPass(0);
      Graphics.DrawProcedural(MeshTopology.Points, proxyTriangleBufferSize);

      //view-aligned quadrilaterals outside at triangle backfaces
      backFaceRepairMaterial.SetPass(1);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //pipe shadow pass
      pipeShadowMaterial.SetPass(0);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //pipe water prepare
      pipeWaterMaterial.SetPass(0);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //pipe water surface
      pipeWaterMaterial.SetPass(1);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);

      //pipe water body
      pipeWaterMaterial.SetPass(2);
      Graphics.DrawProcedural(MeshTopology.LineStrip, pipeQuadsBufferSize);
    }

  }

  void Update() {

    if (Input.GetKey(KeyCode.Q) && Input.GetKey(KeyCode.P)) {
      drawPipes = false;
    }

    if (Input.GetKey(KeyCode.Y) && Input.GetKey(KeyCode.P)) {
      drawPipes = true;
    }

    if (Input.GetKey(KeyCode.X) && Input.GetKey(KeyCode.P)) {
      drawProxy = !drawProxy;
    }

    if (Input.GetKey(KeyCode.X) && Input.GetKey(KeyCode.S)) {
      drawStencilPrepare = !drawStencilPrepare;
    }

    if (Input.GetKey(KeyCode.X) && Input.GetKey(KeyCode.B)) {
      drawBillboards = !drawBillboards;
    }

    if (Input.GetKey(KeyCode.R) && Input.GetKey(KeyCode.KeypadPlus)) {
      someOtherRadius += 0.01f;
    }

    if (Input.GetKey(KeyCode.R) && Input.GetKey(KeyCode.KeypadMinus)) {
      someOtherRadius -= 0.01f;
    }

    if (Input.GetKey(KeyCode.F) && Input.GetKey(KeyCode.KeypadPlus)) {
      wallThickness += 0.001f;
      if (wallThickness < 0) wallThickness = 0;
      if (wallThickness > 1) wallThickness = 1;

      Shader.SetGlobalFloat("wallThickness", wallThickness);
    }

    if (Input.GetKey(KeyCode.F) && Input.GetKey(KeyCode.KeypadMinus)) {
      wallThickness -= 0.001f;
      if (wallThickness < 0.001) wallThickness = 0.001f;
      if (wallThickness > 0.3f) wallThickness = 0.3f;

      Shader.SetGlobalFloat("wallThickness", wallThickness);
    }

  }

  void FixedUpdate() {
    for (int p = 0; p < SewerSystem.getPipes().Count; p++) {
      pipeWaterLevel[p] = SewerSystem.getPipebyIndex(p).depthTimeline[SewerManager.currenTimestep];
      pipeWaterColor[p] = SewerSystem.getPipebyIndex(p).depthColorTimeline[SewerManager.currenTimestep];
    }
    pipeWaterColorBuffer.SetData(pipeWaterColor);
    pipeWaterMaterial.SetBuffer("pipeWaterColorBuffer", pipeWaterColorBuffer);
    pipeWaterLevelBuffer.SetData(pipeWaterLevel);
    pipeWaterMaterial.SetBuffer("pipeWaterLevelBuffer", pipeWaterLevelBuffer);
  }

  void OnDestroy() {
    if (proxyTriangleBuffer != null) proxyTriangleBuffer.Release();
    proxyTriangleBuffer = null;

    if (proxyQuadsBuffer != null) proxyQuadsBuffer.Release();
    proxyQuadsBuffer = null;

    if (pipeQuadsBuffer != null) pipeQuadsBuffer.Release();
    pipeQuadsBuffer = null;

    if (pipeWaterLevelBuffer != null) pipeWaterLevelBuffer.Release();
    pipeWaterLevelBuffer = null;

    if (pipeWaterColorBuffer != null) pipeWaterColorBuffer.Release();
    pipeWaterColorBuffer = null;

  }

}
PipeCutSurface.shader
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

Shader "Custom/PipeCutsurface" {

  Properties {
    _CutColor("Cut Surface Diffuse Color", Color) = (1, 1, 1, 1)
    _SpecColor("Specular Color", Color) = (1, 1, 1, 1)
    _Shininess("Shininess", Float) = 10
  }

  SubShader {

    /* vertical triangles cut surfaces
    */
    Pass {
      Tags {
        "LightMode" = "ForwardBase"
      }
      Name "PipeCut"

      Stencil {
        Ref 6
        Comp Always
        Pass replace
      }

      Cull Back
      //ZTest Always
      //ZWrite off
      //ColorMask 0

      CGPROGRAM#pragma target 5.0

      #pragma vertex vert#pragma geometry geom#pragma fragment frag

      //#pragma multi_compile_fog

      #include "UnityCG.cginc"

      float4 _CutColor;
      float wallThickness;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      uniform float4 _SpecColor;
      uniform float _Shininess;

      struct proxyData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < proxyData > proxyTriangleBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(proxyTriangleBuffer[id].pos, 1.0f);
        o.lastPos = float4(proxyTriangleBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(proxyTriangleBuffer[id].nextPos, 1.0f);
        o.radius = proxyTriangleBuffer[id].radius;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 posWorld: POSITION1;
        float2 uv: TEXCOORD0;
        float3 quadNormal: NORMAL;
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(3)]
      void geom(point input p[1], inout TriangleStream < vertexOutput > OutputStream) {
        float radius = p[0].radius;
        float halfDiagonal = sqrt(pow(radius, 2) + pow(radius, 2));

        vertexOutput vOut;

        float4 v[4];
        float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
        float3 tangent = normalize(p[0].nextPos.xyz - p[0].pos.xyz);

        float3 bisectTangent = normalize(lastTangent + tangent);

        float3 toSide = normalize(cross(bisectTangent, float3(0, 1, 0)));
        float3 up = normalize(cross(toSide, bisectTangent));
        float3 left = normalize(cross(bisectTangent, up));
        float3 right = invert(left);

        v[0] = p[0].pos + float4(up.xyz, 0) * halfDiagonal;
        v[1] = p[0].pos + float4(left.xyz, 0) * halfDiagonal;
        v[2] = p[0].pos + float4(right.xyz, 0) * halfDiagonal;

        vOut.quadNormal = tangent;

        vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
        vOut.posWorld = v[0];
        vOut.uv = float2(0.0f, 0.0f);
        OutputStream.Append(vOut);

        vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
        vOut.posWorld = v[1];
        vOut.uv = float2(0.0f, 1.0f);
        OutputStream.Append(vOut);

        vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
        vOut.posWorld = v[2];
        vOut.uv = float2(1.0f, 0.0f);
        OutputStream.Append(vOut);

      }

      struct fragOutput {
        float4 color: SV_TARGET;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;

        // (0.5f, 0.5f) = center
        float distanceToCenter = sqrt(pow((0.5f - i.uv.x), 2) + pow((0.5f - i.uv.y), 2));

        if (distanceToCenter > 0.5f) {
          discard;
        }
        else if (distanceToCenter < (0.5f - wallThickness)) {
          o.color = float4(0, 0, 0, 1);
          discard;
        }

        else {
          //LIGHT SHADING
          float3 normalDirection = normalize(i.quadNormal);
          float3 viewDirection = normalize(
          _WorldSpaceCameraPos - i.posWorld.xyz);

          // cull is off, so invert normal if camera is on the other side
          if (dot(normalDirection, viewDirection) < 0.0) {
            normalDirection *= -1;
          }

          float3 lightDirection;
          float attenuation;

          if (0.0 == _WorldSpaceLightPos0.w) // directional light?
          {
            attenuation = 1.0; // no attenuation
            lightDirection = normalize(_WorldSpaceLightPos0.xyz);
          }
          else // point or spot light
          {
            float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - i.posWorld.xyz;
            float distance = length(vertexToLightSource);
            attenuation = 1.0 / distance; // linear attenuation 
            lightDirection = normalize(vertexToLightSource);
          }

          float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _CutColor.rgb;

          float3 diffuseReflection = attenuation * _LightColor0.rgb * _CutColor.rgb * max(0.0f, dot(normalDirection, lightDirection));

          float3 specularReflection;
          if (dot(normalDirection, lightDirection) < 0.0)
          // light source on the wrong side?
          {
            specularReflection = float3(0.0, 0.0, 0.0);
            // no specular reflection
          }
          else // light source on the right side
          {
            specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot(
            reflect( - lightDirection, normalDirection), viewDirection)), _Shininess);
          }

          o.color = float4(diffuseReflection + ambientLighting + specularReflection, 1.0);
        }

        //discard;
        return o;

      }

      ENDCG
    }

    /* horizontal quadrilaterals cut surfaces
    */
    Pass {

      Name "PipeBillboards"
      Tags {
        "LightMode" = "ForwardBase"
      }
      Stencil {
        Ref 6
        Comp Always
        Pass replace
      }

      //LOD 100
      //Blend SrcAlpha OneMinusSrcAlpha

      Cull Front
      //ZTest Always
      //ZWrite Off

      //ColorMask 0

      CGPROGRAM#pragma target 5.0#pragma vertex vert#pragma geometry geom#pragma fragment frag

      //#pragma multi_compile_fog

      #include "UnityCG.cginc"

      float4 _CutColor;
      float wallThickness;
      int nodeCount;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      uniform float4 _SpecColor;
      uniform float _Shininess;

      struct proxyData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < proxyData > proxyQuadsBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
        uint pipeID: BLENDINDICES;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(proxyQuadsBuffer[id].pos, 1.0f);
        o.lastPos = float4(proxyQuadsBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(proxyQuadsBuffer[id].nextPos, 1.0f);
        o.radius = proxyQuadsBuffer[id].radius;
        o.pipeID = proxyQuadsBuffer[id].pipeID;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 posWorld: POSITION1;
        float2 uv: TEXCOORD0;
        float3 quadNormal: NORMAL;
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(4)]
      void geom(line input p[2], inout TriangleStream < vertexOutput > OutputStream) {

        // p[0].lastPos -> previous pos to p[0]
        // p[0] -> current pos
        // p[1] -> next pos
        // p[1].nextPos -> after next to p[0]

        // prevent to connect distinct pipes
        if (p[0].pipeID == p[1].pipeID) {

          float radius = p[0].radius;
          vertexOutput vOut;
          float4 v[4];
          float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
          float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);

          float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
          float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);
          float3 nextTangent = normalize(p[1].nextPos.xyz - p[1].pos.xyz);

          float3 bisectLast = normalize(lastTangent + tangent);
          float3 bisectNext = normalize(nextTangent + tangent);

          float3 upvec = float3(0, 1, 0);
          float3 side = normalize(cross(tangent, upvec));
          float3 upWards = normalize(cross(tangent, side));

          float3 upLast = normalize(cross(upWards, bisectLast));
          float3 downLast = invert(upLast);

          v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
          v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

          float3 upNext = normalize(cross(upWards, bisectNext));
          float3 downNext = invert(upNext);

          v[2] = p[1].pos + float4(upNext.xyz, 0) * radius;
          v[3] = p[1].pos + float4(downNext.xyz, 0) * radius;

          float3 bisectUp = normalize(upLast + upNext);
          vOut.quadNormal = normalize(cross(bisectUp, tangent));

          vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
          vOut.posWorld = v[0];
          vOut.uv = float2(0.0f, 0.0f);
          OutputStream.Append(vOut);

          vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
          vOut.posWorld = v[1];
          vOut.uv = float2(0.0f, 1.0f);
          OutputStream.Append(vOut);

          vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
          vOut.posWorld = v[2];
          vOut.uv = float2(1.0f, 0.0f);
          OutputStream.Append(vOut);

          vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
          vOut.posWorld = v[3];
          vOut.uv = float2(1.0f, 1.0f);
          OutputStream.Append(vOut);
        }

      }

      struct fragOutput {
        float4 color: SV_TARGET;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;
        //o.color = float4(0, 0, 0, 1);
        if (i.uv.y < (1 - wallThickness) && i.uv.y > wallThickness) {
          discard;
        }
        else {
          //LIGHT SHADING
          float3 normalDirection = normalize(i.quadNormal);
          float3 viewDirection = normalize(
          _WorldSpaceCameraPos - i.posWorld.xyz);

          float3 lightDirection;
          float attenuation;

          if (0.0 == _WorldSpaceLightPos0.w) // directional light?
          {
            attenuation = 1.0; // no attenuation
            lightDirection = normalize(_WorldSpaceLightPos0.xyz);
          }
          else // point or spot light
          {
            float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - i.posWorld.xyz;
            float distance = length(vertexToLightSource);
            attenuation = 1.0 / distance; // linear attenuation 
            lightDirection = normalize(vertexToLightSource);
          }

          float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _CutColor.rgb;

          float3 diffuseReflection = attenuation * _LightColor0.rgb * _CutColor.rgb * max(0.0f, dot(normalDirection, lightDirection));

          float3 specularReflection;
          if (dot(normalDirection, lightDirection) < 0.0)
          // light source on the wrong side?
          {
            specularReflection = float3(0.0, 0.0, 0.0);
            // no specular reflection
          }
          else // light source on the right side
          {
            specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot(
            reflect( - lightDirection, normalDirection), viewDirection)), _Shininess);
          }

          o.color = float4(diffuseReflection + ambientLighting + specularReflection, 1.0);
        }

        return o;

      }

      ENDCG
    }

  }
  Fallback Off
}
PipeProxyInside.shader
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

Shader "Custom/PipeProxyInside" {

  Properties {
    _InsideColor("Inside Diffuse Color", Color) = (1, 1, 1, 1)
  }

  SubShader {

    /* vertical triangles stencil prepare
    */
    Pass {

      Stencil {
        Ref 5
        Comp Always
        Pass replace
      }

      Cull Back
      //ZTest Always
      //ZWrite off
      //ColorMask 0

      CGPROGRAM#pragma target 5.0

      #pragma vertex vert#pragma geometry geom#pragma fragment frag

      //#pragma multi_compile_fog

      #include "UnityCG.cginc"

      float4 _InsideColor;
      float wallThickness;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      struct proxyData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < proxyData > proxyTriangleBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(proxyTriangleBuffer[id].pos, 1.0f);
        o.lastPos = float4(proxyTriangleBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(proxyTriangleBuffer[id].nextPos, 1.0f);
        o.radius = proxyTriangleBuffer[id].radius;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 posWorld: POSITION1;
        float2 uv: TEXCOORD0;
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(3)]
      void geom(point input p[1], inout TriangleStream < vertexOutput > OutputStream) {
        float radius = p[0].radius;
        float halfDiagonal = sqrt(pow(radius, 2) + pow(radius, 2));

        vertexOutput vOut;
        float4 v[4];
        float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
        float3 tangent = normalize(p[0].nextPos.xyz - p[0].pos.xyz);

        float3 bisectTangent = normalize(lastTangent + tangent);

        float3 toSide = normalize(cross(bisectTangent, float3(0, 1, 0)));
        float3 up = normalize(cross(toSide, bisectTangent));
        float3 left = normalize(cross(bisectTangent, up));
        float3 right = invert(left);

        v[0] = p[0].pos + float4(up.xyz, 0) * halfDiagonal;
        v[1] = p[0].pos + float4(left.xyz, 0) * halfDiagonal;
        v[2] = p[0].pos + float4(right.xyz, 0) * halfDiagonal;

        vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
        vOut.posWorld = v[0];
        vOut.uv = float2(0.0f, 0.0f);
        OutputStream.Append(vOut);

        vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
        vOut.posWorld = v[1];
        vOut.uv = float2(0.0f, 1.0f);
        OutputStream.Append(vOut);

        vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
        vOut.posWorld = v[2];
        vOut.uv = float2(1.0f, 0.0f);
        OutputStream.Append(vOut);

      }

      struct fragOutput {
        float4 color: SV_TARGET;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;

        // (0.5f, 0.5f) = center
        float distanceToCenter = sqrt(pow((0.5f - i.uv.x), 2) + pow((0.5f - i.uv.y), 2));

        if (distanceToCenter > 0.5f) {
          discard;
        }
        else if (distanceToCenter < (0.5f - wallThickness)) {
          o.color = _InsideColor;
        }
        else {
          discard;
        }

        //discard;
        return o;

      }

      ENDCG
    }

    /* horizontal quadrilaterals stencil prepare 
    */
    Pass {

      Stencil {
        Ref 5
        Comp Always
        Pass replace
      }

      //LOD 100
      //Blend SrcAlpha OneMinusSrcAlpha

      Cull Front
      //ZTest Always
      //ZWrite Off

      //ColorMask 0

      CGPROGRAM#pragma target 5.0#pragma vertex vert#pragma geometry geom#pragma fragment frag

      //#pragma multi_compile_fog

      #include "UnityCG.cginc"

      float4 _InsideColor;
      float wallThickness;
      int nodeCount;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      struct proxyData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < proxyData > proxyQuadsBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
        uint pipeID: BLENDINDICES;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(proxyQuadsBuffer[id].pos, 1.0f);
        o.lastPos = float4(proxyQuadsBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(proxyQuadsBuffer[id].nextPos, 1.0f);
        o.radius = proxyQuadsBuffer[id].radius;
        o.pipeID = proxyQuadsBuffer[id].pipeID;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 posWorld: POSITION1;
        float2 uv: TEXCOORD0;
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(4)]
      void geom(line input p[2], inout TriangleStream < vertexOutput > OutputStream) {
        float radius = p[0].radius;
        // p[0].lastPos -> previous pos to p[0]
        // p[0] -> current pos
        // p[1] -> next pos
        // p[1].nextPos -> after next to p[0]

        // prevent to connect distinct pipes
        if (p[0].pipeID == p[1].pipeID) {

          vertexOutput vOut;
          float4 v[4];
          float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
          float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);

          float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
          float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);
          float3 nextTangent = normalize(p[1].nextPos.xyz - p[1].pos.xyz);

          float3 bisectLast = normalize(lastTangent + tangent);
          float3 bisectNext = normalize(nextTangent + tangent);

          float3 upvec = float3(0, 1, 0);
          float3 side = normalize(cross(tangent, upvec));
          float3 upWards = normalize(cross(tangent, side));

          float3 upLast = normalize(cross(upWards, bisectLast));
          float3 downLast = invert(upLast);

          v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
          v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

          float3 upNext = normalize(cross(upWards, bisectNext));
          float3 downNext = invert(upNext);

          v[2] = p[1].pos + float4(upNext.xyz, 0) * radius;
          v[3] = p[1].pos + float4(downNext.xyz, 0) * radius;

          float3 bisectUp = normalize(upLast + upNext);

          vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
          vOut.posWorld = v[0];
          vOut.uv = float2(0.0f, 0.0f);
          OutputStream.Append(vOut);

          vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
          vOut.posWorld = v[1];
          vOut.uv = float2(0.0f, 1.0f);
          OutputStream.Append(vOut);

          vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
          vOut.posWorld = v[2];
          vOut.uv = float2(1.0f, 0.0f);
          OutputStream.Append(vOut);

          vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
          vOut.posWorld = v[3];
          vOut.uv = float2(1.0f, 1.0f);
          OutputStream.Append(vOut);
        }

      }

      struct fragOutput {
        float4 color: SV_TARGET;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;

        if (i.uv.y < (1 - wallThickness) && i.uv.y > wallThickness) {
          o.color = _InsideColor;
        }
        else {
          discard;
        }

        return o;

      }

      ENDCG
    }

  }
  Fallback Off
}
PipeBillboard.shader
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

Shader "Custom/PipeBillboard" {
  Properties {
    _Color("Diffuse Color Outside", Color) = (1, 1, 1, 1)
    _InsideColor("Diffuse Color Inside", Color) = (1, 1, 1, 1)
    _SpecColor("Specular Color", Color) = (1, 1, 1, 1)
    _Shininess("Shininess", Float) = 10
  }
  SubShader {

    //PIPE BILLBOARDS VIEW ALIGNED INSIDE
    Pass {

      Stencil {
        Ref 5
        Comp Equal
        Pass IncrSat
      }

      //LOD 100
      //Blend SrcAlpha OneMinusSrcAlpha
      //Blend DstAlpha OneMinusDstAlpha

      Cull Front
      ZTest Always
      //ZTest Always
      //ColorMask 0

      CGPROGRAM#pragma target 5.0#pragma vertex vert#pragma geometry geom#pragma fragment frag
      //#pragma multi_compile_fwdbase

      //#pragma multi_compile_fog

      #include "AutoLight.cginc"#include "UnityCG.cginc"

      float4 _Color;
      float4 _InsideColor;
      float wallThickness;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      uniform float4 _SpecColor;
      uniform float _Shininess;

      struct pipeData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
        uint id;
        uint pipeNodeCount;
        uint isCut;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < pipeData > pipeQuadsBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
        uint pipeID: BLENDINDICES0;
        uint id: BLENDINDICES1;
        uint pipeNodeCount: BLENDINDICES2;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(pipeQuadsBuffer[id].pos, 1.0f);
        o.lastPos = float4(pipeQuadsBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(pipeQuadsBuffer[id].nextPos, 1.0f);
        o.pipeID = pipeQuadsBuffer[id].pipeID;
        o.radius = pipeQuadsBuffer[id].radius;
        o.id = pipeQuadsBuffer[id].id;
        o.pipeNodeCount = pipeQuadsBuffer[id].pipeNodeCount;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 posWorld: POSITION1;
        float4 screenPos: POSITION2;
        float4 depthCorrectFarScreenPos: POSITION3;
        float3 quadNormal: TEXCOORD2;
        float3 normal: NORMAL;
        float2 uv: TEXCOORD0;
        //SHADOW_COORDS(3)
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(4)]
      void geom(line input p[2], inout TriangleStream < vertexOutput > OutputStream) {
        int id = p[0].id;
        int nodeCount = p[0].pipeNodeCount;
        float radius = p[0].radius - (wallThickness * p[0].radius);
        // p[0].lastPos -> previous pos to p[0]
        // p[0].pos -> current pos
        // p[1].pos -> next pos
        // p[1].nextPos -> after next to p[0]

        //the quad is drawn from the previous (second last) position to the last
        if (id < nodeCount - 1 && p[0].pipeID == p[1].pipeID) {

          //CASE: EXACTLY 2 POINTS
          if (nodeCount == 2) {
            vertexOutput vOut;
            //TRANSFER_SHADOW(vOut);
            float4 v[4];
            float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
            float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);
            float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);

            float3 up = normalize(cross(tangent, posToCamera));
            float3 down = invert(up);

            v[0] = p[0].pos + float4(up, 0) * radius;
            v[1] = p[0].pos + float4(down, 0) * radius;

            float3 upNext = normalize(cross(tangent, nextPosToCamera));
            float3 downNext = invert(upNext);

            v[2] = p[1].pos + float4(upNext, 0) * radius;
            v[3] = p[1].pos + float4(downNext, 0) * radius;

            float3 bisectUp = normalize(up + upNext);
            vOut.quadNormal = normalize(cross(bisectUp, tangent));

            float3 upvec = float3(0, 1, 0);

            vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
            vOut.posWorld = v[0];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] - float4(cross(up, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
            vOut.uv = float2(0.0f, 0.0f);
            OutputStream.Append(vOut);

            vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
            vOut.posWorld = v[1];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] - float4(cross(up, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, tangent));
            vOut.uv = float2(0.0f, 1.0f);
            OutputStream.Append(vOut);

            vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
            vOut.posWorld = v[2];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] - float4(cross(upNext, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
            vOut.uv = float2(1.0f, 0.0f);
            OutputStream.Append(vOut);

            vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
            vOut.posWorld = v[3];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] - float4(cross(upNext, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, tangent));
            vOut.uv = float2(1.0f, 1.0f);
            OutputStream.Append(vOut);

          }

          //CASE: MORE THAN 2 POINTS
          else if (nodeCount > 2) {
            vertexOutput vOut;
            //TRANSFER_SHADOW(vOut);
            float4 v[4];
            float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
            float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);

            float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
            float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);
            float3 nextTangent = normalize(p[1].nextPos.xyz - p[1].pos.xyz);

            float3 bisectLast = normalize(lastTangent + tangent);
            float3 bisectNext = normalize(nextTangent + tangent);

            float3 upvec = float3(0, 1, 0);

            // FIRST POSITION
            if (id == 0) {

              float3 up = normalize(cross(tangent, posToCamera));
              float3 down = invert(up);

              v[0] = p[0].pos + float4(up, 0) * radius;
              v[1] = p[0].pos + float4(down, 0) * radius;

              float3 upNext = normalize(cross(bisectNext, nextPosToCamera));
              float3 downNext = invert(upNext);

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              float3 bisectUp = normalize(up + upNext);
              vOut.quadNormal = normalize(cross(bisectUp, tangent));

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              vOut.posWorld = v[0];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] - float4(cross(up, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(0.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              vOut.posWorld = v[1];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] - float4(cross(up, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(0.0f, 1.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              vOut.posWorld = v[2];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] - float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(1.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              vOut.posWorld = v[3];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] - float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(1.0f, 1.0f);
              OutputStream.Append(vOut);
            }

            // SECOND LAST POSITION (last billboard quad)
            else if (id == nodeCount - 2) {

              float3 upLast = normalize(cross(bisectLast, posToCamera));
              float3 downLast = invert(upLast);

              float3 upNext = normalize(cross(tangent, nextPosToCamera));
              float3 downNext = invert(upNext);

              v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
              v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              float3 bisectUp = normalize(upLast + upNext);
              vOut.quadNormal = normalize(cross(bisectUp, tangent));

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              vOut.posWorld = v[0];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] - float4(cross(upLast, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(0.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              vOut.posWorld = v[1];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] - float4(cross(upLast, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(0.0f, 1.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              vOut.posWorld = v[2];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] - float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(1.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              vOut.posWorld = v[3];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] - float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(1.0f, 1.0f);
              OutputStream.Append(vOut);
            }

            // SECOND POSITION TO THIRD LAST POSITION
            else {

              float3 upLast = normalize(cross(bisectLast, posToCamera));
              float3 downLast = invert(upLast);

              float3 upNext = normalize(cross(bisectNext, nextPosToCamera));
              float3 downNext = invert(upNext);

              float3 bisectUp = normalize(upLast + upNext);
              vOut.quadNormal = normalize(cross(bisectUp, tangent));
              //vOut.quadNormal = float3(0,1,0);

              float3 upNormal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              float3 downNormal = normalize(cross(vOut.quadNormal, tangent));

              v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
              v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              vOut.posWorld = v[0];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] - float4(cross(upLast, tangent), 0) * radius));
              vOut.normal = upNormal;
              vOut.uv = float2(0.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              vOut.posWorld = v[1];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] - float4(cross(upLast, tangent), 0) * radius));
              vOut.normal = downNormal;
              vOut.uv = float2(0.0f, 1.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              vOut.posWorld = v[2];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] - float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = upNormal;
              vOut.uv = float2(1.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              vOut.posWorld = v[3];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectFarScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] - float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = downNormal;
              vOut.uv = float2(1.0f, 1.0f);
              OutputStream.Append(vOut);
            }
          }
        }
      }

      struct fragOutput {
        float4 color: SV_TARGET;
        float depth: SV_DEPTH;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;

        //LIGHT SHADING

        // i.normal is set to the correct normal at the edge of the pipe
        // i.quadNormal is set to the correct normal at the center of the pipe
        // this gets interpolated automatically
        float3 normalDirection = normalize(i.normal * ( - 1.0f) + i.quadNormal);
        float3 viewDirection = normalize(
        _WorldSpaceCameraPos - i.posWorld.xyz);
        float3 lightDirection;
        float attenuation;

        //INSIDE
        float specularFactor = 0.1f;

        if (0.0 == _WorldSpaceLightPos0.w) // directional light?
        {
          attenuation = 1.0; // no attenuation
          lightDirection = normalize(_WorldSpaceLightPos0.xyz);
        }
        else // point or spot light
        {
          float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - i.posWorld.xyz;
          float distance = length(vertexToLightSource);
          attenuation = 1.0 / distance; // linear attenuation 
          lightDirection = normalize(vertexToLightSource);
        }

        float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

        float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb * max(0.0f, dot(normalDirection, lightDirection));

        float3 specularReflection;
        if (dot(normalDirection, lightDirection) < 0.0)
        // light source on the wrong side?
        {
          specularReflection = float3(0.0, 0.0, 0.0);
          // no specular reflection
        }
        else // light source on the right side
        {
          specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot(
          reflect( - lightDirection, normalDirection), viewDirection)), _Shininess);
        }

        o.color = float4(diffuseReflection + ambientLighting + specularReflection * specularFactor, 1.0);
        o.color *= _InsideColor;

        // DEPTH CORRECTION
        float vertPixPos = i.uv.y;
        float alpha = 0.5f;
        float billboardDepth = (i.screenPos.z / i.screenPos.w);
        float nearDepth = (i.depthCorrectFarScreenPos.z / i.depthCorrectFarScreenPos.w);

        // transform   0 -> 0.5 -> 1   to   0.00001 -> 1 -> 0.00001 
        if (vertPixPos <= 0.5f) {
          alpha = vertPixPos * 2;
        }
        else {
          alpha = (1 - vertPixPos) * 2;
        }
        if (alpha <= 0) alpha = 0.00001f;

        //cicular interpolation (unit circle)
        float circularAlpha = sqrt(1 - pow((1 - alpha), 2));
        o.depth = (1 - (circularAlpha)) * billboardDepth + (circularAlpha) * nearDepth;

        return o;

      }

      ENDCG
    }

    //PIPE BILLBOARDS VIEW ALIGNED OUTSIDE PREPARE EXCLUDE
    Pass {

      Stencil {
        Ref 5
        Comp Always
        Pass replace
      }

      Cull Front
      ZWrite off
      //ZTest Always
      ColorMask 0

      CGPROGRAM#pragma target 5.0#pragma vertex vert#pragma geometry geom#pragma fragment frag

      #include "AutoLight.cginc"#include "UnityCG.cginc"

      float4 _Color;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      uniform float4 _SpecColor;
      uniform float _Shininess;

      struct pipeData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
        uint id;
        uint pipeNodeCount;
        uint isCut;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < pipeData > pipeQuadsBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
        uint pipeID: BLENDINDICES0;
        uint id: BLENDINDICES1;
        uint pipeNodeCount: BLENDINDICES2;
        uint isCut: BLENDINDICES3;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(pipeQuadsBuffer[id].pos, 1.0f);
        o.lastPos = float4(pipeQuadsBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(pipeQuadsBuffer[id].nextPos, 1.0f);
        o.pipeID = pipeQuadsBuffer[id].pipeID;
        o.radius = pipeQuadsBuffer[id].radius;
        o.id = pipeQuadsBuffer[id].id;
        o.pipeNodeCount = pipeQuadsBuffer[id].pipeNodeCount;
        o.isCut = pipeQuadsBuffer[id].isCut;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        //SHADOW_COORDS(3)
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(4)]
      void geom(line input p[2], inout TriangleStream < vertexOutput > OutputStream) {
        int id = p[0].id;
        int nodeCount = p[0].pipeNodeCount;
        float radius = p[0].radius;
        // p[0].lastPos -> previous pos to p[0]
        // p[0].pos -> current pos
        // p[1].pos -> next pos
        // p[1].nextPos -> after next to p[0]

        //the quad is drawn from the previous (second last) position to the last
        if (id < nodeCount - 2 && p[0].pipeID == p[1].pipeID) {

          //CASE: MORE THAN 2 POINTS
          if (nodeCount > 2) {
            vertexOutput vOut;
            //TRANSFER_SHADOW(vOut);
            float4 v[4];
            float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
            float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);

            float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
            float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);
            float3 nextTangent = normalize(p[1].nextPos.xyz - p[1].pos.xyz);

            float3 bisectLast = normalize(lastTangent + tangent);
            float3 bisectNext = normalize(nextTangent + tangent);

            float3 upvec = float3(0, 1, 0);

            // SECOND POSITION TO THIRD LAST POSITION
            {

              float3 upLast = normalize(cross(bisectLast, posToCamera));
              float3 downLast = invert(upLast);

              float3 upNext = normalize(cross(bisectNext, nextPosToCamera));
              float3 downNext = invert(upNext);

              float3 bisectUp = normalize(upLast + upNext);

              v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
              v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              //depending on the view position 
              if (dot(upvec, upLast) >= 0.0f) {
                v[1] = p[0].pos;
                v[3] = p[1].pos;
              }
              else {
                v[0] = p[0].pos;
                v[2] = p[1].pos;
              }

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              OutputStream.Append(vOut);
            }
          }
        }
      }

      struct fragOutput {
        float4 color: SV_TARGET;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;

        o.color = float4(1, 0, 0, 1.0);
        return o;

      }

      ENDCG
    }

    //PIPE BILLBOARDS VIEW ALIGNED OUTSIDE
    Pass {

      Stencil {
        Ref 5
        Comp Greater
        Pass DecrSat
      }

      //LOD 100
      //Blend SrcAlpha OneMinusSrcAlpha
      //Blend DstAlpha OneMinusDstAlpha

      Cull Front
      //ZWrite off
      //ZTest Always
      //ColorMask 0

      CGPROGRAM#pragma target 5.0#pragma vertex vert#pragma geometry geom#pragma fragment frag
      //#pragma multi_compile_fwdbase

      //#pragma multi_compile_fog

      #include "AutoLight.cginc"#include "UnityCG.cginc"

      float4 _Color;

      // color of light source (from "Lighting.cginc")
      uniform float4 _LightColor0;

      uniform float4 _SpecColor;
      uniform float _Shininess;

      struct pipeData {
        float3 lastPos;
        float3 pos;
        float3 nextPos;
        float radius;
        uint pipeID;
        uint id;
        uint pipeNodeCount;
        uint isCut;
      };

      //buffer containing the points of the line we want to draw.
      StructuredBuffer < pipeData > pipeQuadsBuffer;

      struct input {
        float4 pos: POSITION0;
        float4 nextPos: POSITION1;
        float4 lastPos: POSITION2;
        float radius: BLENDWEIGHT;
        uint pipeID: BLENDINDICES0;
        uint id: BLENDINDICES1;
        uint pipeNodeCount: BLENDINDICES2;
        uint isCut: BLENDINDICES3;
      };

      //PREPARE DATA FOR GEOMETRY SHADER
      input vert(uint id: SV_VertexID) {
        input o;

        o.pos = float4(pipeQuadsBuffer[id].pos, 1.0f);
        o.lastPos = float4(pipeQuadsBuffer[id].lastPos, 1.0f);
        o.nextPos = float4(pipeQuadsBuffer[id].nextPos, 1.0f);
        o.pipeID = pipeQuadsBuffer[id].pipeID;
        o.radius = pipeQuadsBuffer[id].radius;
        o.id = pipeQuadsBuffer[id].id;
        o.pipeNodeCount = pipeQuadsBuffer[id].pipeNodeCount;
        o.isCut = pipeQuadsBuffer[id].isCut;

        return o;
      }

      // SEMANTICS DONT SUGGEST THIS PARAMETERS ?
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 posWorld: POSITION1;
        float4 screenPos: POSITION2;
        float4 depthCorrectNearScreenPos: POSITION3;
        float3 quadNormal: TEXCOORD2;
        float3 normal: NORMAL;
        float2 uv: TEXCOORD0;
        //SHADOW_COORDS(3)
      };

      float3 invert(float3 vec) {
        return vec * ( - 1.0f);
      }

      //GEOMETRY SHADER
      [maxvertexcount(4)]
      void geom(line input p[2], inout TriangleStream < vertexOutput > OutputStream) {
        int id = p[0].id;
        int nodeCount = p[0].pipeNodeCount;
        float radius = p[0].radius;
        // p[0].lastPos -> previous pos to p[0]
        // p[0].pos -> current pos
        // p[1].pos -> next pos
        // p[1].nextPos -> after next to p[0]

        //the quad is drawn from the previous (second last) position to the last
        if (id < nodeCount - 1 && p[0].pipeID == p[1].pipeID) {

          //CASE: EXACTLY 2 POINTS
          if (nodeCount == 2) {
            vertexOutput vOut;
            //TRANSFER_SHADOW(vOut);
            float4 v[4];
            float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
            float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);
            float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);

            float3 up = normalize(cross(tangent, posToCamera));
            float3 down = invert(up);

            v[0] = p[0].pos + float4(up, 0) * radius;
            v[1] = p[0].pos + float4(down, 0) * radius;

            float3 upNext = normalize(cross(tangent, nextPosToCamera));
            float3 downNext = invert(upNext);

            v[2] = p[1].pos + float4(upNext, 0) * radius;
            v[3] = p[1].pos + float4(downNext, 0) * radius;

            float3 bisectUp = normalize(up + upNext);
            vOut.quadNormal = normalize(cross(bisectUp, tangent));

            float3 upvec = float3(0, 1, 0);

            vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
            vOut.posWorld = v[0];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] + float4(cross(up, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
            vOut.uv = float2(0.0f, 0.0f);
            OutputStream.Append(vOut);

            vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
            vOut.posWorld = v[1];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] + float4(cross(up, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, tangent));
            vOut.uv = float2(0.0f, 1.0f);
            OutputStream.Append(vOut);

            vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
            vOut.posWorld = v[2];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] + float4(cross(upNext, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
            vOut.uv = float2(1.0f, 0.0f);
            OutputStream.Append(vOut);

            vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
            vOut.posWorld = v[3];
            vOut.screenPos = ComputeScreenPos(vOut.pos);
            vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] + float4(cross(upNext, tangent), 0) * radius));
            vOut.normal = normalize(cross(vOut.quadNormal, tangent));
            vOut.uv = float2(1.0f, 1.0f);
            OutputStream.Append(vOut);

          }

          //CASE: MORE THAN 2 POINTS
          else if (nodeCount > 2) {
            vertexOutput vOut;
            //TRANSFER_SHADOW(vOut);
            float4 v[4];
            float3 posToCamera = normalize(_WorldSpaceCameraPos - p[0].pos.xyz);
            float3 nextPosToCamera = normalize(_WorldSpaceCameraPos - p[1].pos.xyz);

            float3 lastTangent = normalize(p[0].pos.xyz - p[0].lastPos.xyz);
            float3 tangent = normalize(p[1].pos.xyz - p[0].pos.xyz);
            float3 nextTangent = normalize(p[1].nextPos.xyz - p[1].pos.xyz);

            float3 bisectLast = normalize(lastTangent + tangent);
            float3 bisectNext = normalize(nextTangent + tangent);

            float3 upvec = float3(0, 1, 0);
            float3 side = normalize(cross(upvec, tangent));

            // FIRST POSITION
            if (id == 0) {

              float3 up = normalize(cross(tangent, posToCamera));
              float3 down = invert(up);

              v[0] = p[0].pos + float4(up, 0) * radius;
              v[1] = p[0].pos + float4(down, 0) * radius;

              float3 upNext = normalize(cross(bisectNext, nextPosToCamera));
              float3 downNext = invert(upNext);

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              float3 bisectUp = normalize(up + upNext);
              vOut.quadNormal = normalize(cross(bisectUp, tangent));

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              vOut.posWorld = v[0];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] + float4(cross(up, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(0.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              vOut.posWorld = v[1];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] + float4(cross(up, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(0.0f, 1.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              vOut.posWorld = v[2];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] + float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(1.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              vOut.posWorld = v[3];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] + float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(1.0f, 1.0f);
              OutputStream.Append(vOut);
            }

            // SECOND LAST POSITION (last billboard quad)
            else if (id == nodeCount - 2) {

              float3 upLast = normalize(cross(bisectLast, posToCamera));
              float3 downLast = invert(upLast);

              float3 upNext = normalize(cross(tangent, nextPosToCamera));
              float3 downNext = invert(upNext);

              v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
              v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              float3 bisectUp = normalize(upLast + upNext);
              vOut.quadNormal = normalize(cross(bisectUp, tangent));

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              vOut.posWorld = v[0];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] + float4(cross(upLast, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(0.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              vOut.posWorld = v[1];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] + float4(cross(upLast, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(0.0f, 1.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              vOut.posWorld = v[2];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] + float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              vOut.uv = float2(1.0f, 0.0f);
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              vOut.posWorld = v[3];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] + float4(cross(upNext, tangent), 0) * radius));
              vOut.normal = normalize(cross(vOut.quadNormal, tangent));
              vOut.uv = float2(1.0f, 1.0f);
              OutputStream.Append(vOut);
            }

            // SECOND POSITION TO THIRD LAST POSITION
            else {

              float3 upLast = normalize(cross(bisectLast, posToCamera));
              float3 downLast = invert(upLast);

              float3 upNext = normalize(cross(bisectNext, nextPosToCamera));
              float3 downNext = invert(upNext);

              float3 bisectUp = normalize(upLast + upNext);
              vOut.quadNormal = normalize(cross(bisectUp, tangent));

              float3 upNormal = normalize(cross(vOut.quadNormal, (tangent * ( - 1))));
              float3 downNormal = normalize(cross(vOut.quadNormal, tangent));

              float2 uv[4];
              float4 depth[4];

              v[0] = p[0].pos + float4(upLast.xyz, 0) * radius;
              v[1] = p[0].pos + float4(downLast.xyz, 0) * radius;

              v[2] = p[1].pos + float4(upNext, 0) * radius;
              v[3] = p[1].pos + float4(downNext, 0) * radius;

              uv[0] = float2(0.0f, 0.0f);
              uv[1] = float2(0.0f, 1.0f);
              uv[2] = float2(1.0f, 0.0f);
              uv[3] = float2(1.0f, 1.0f);

              depth[0] = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[0] + float4(cross(upLast, tangent), 0) * radius));
              depth[1] = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[1] + float4(cross(upLast, tangent), 0) * radius));
              depth[2] = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[2] + float4(cross(upNext, tangent), 0) * radius));
              depth[3] = ComputeScreenPos(mul(UNITY_MATRIX_VP, v[3] + float4(cross(upNext, tangent), 0) * radius));

              vOut.pos = mul(UNITY_MATRIX_VP, v[0]);
              vOut.posWorld = v[0];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = depth[0];
              vOut.normal = upNormal;
              vOut.uv = uv[0];
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[1]);
              vOut.posWorld = v[1];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = depth[1];
              vOut.normal = downNormal;
              vOut.uv = uv[1];
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[2]);
              vOut.posWorld = v[2];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = depth[2];
              vOut.normal = upNormal;
              vOut.uv = uv[2];
              OutputStream.Append(vOut);

              vOut.pos = mul(UNITY_MATRIX_VP, v[3]);
              vOut.posWorld = v[3];
              vOut.screenPos = ComputeScreenPos(vOut.pos);
              vOut.depthCorrectNearScreenPos = depth[3];
              vOut.normal = downNormal;
              vOut.uv = uv[3];
              OutputStream.Append(vOut);
            }
          }
        }
      }

      struct fragOutput {
        float4 color: SV_TARGET;
        float depth: SV_DEPTH;
      };

      fragOutput frag(vertexOutput i) {
        fragOutput o;

        //LIGHT SHADING

        // i.normal is set to the correct normal at the edge of the pipe
        // i.quadNormal is set to the correct normal at the center of the pipe
        // this gets interpolated automatically
        float3 normalDirection = normalize(i.normal + i.quadNormal);
        float3 viewDirection = normalize(
        _WorldSpaceCameraPos - i.posWorld.xyz);
        float3 lightDirection;
        float attenuation;
        float specularFactor = 0.2f;

        if (0.0 == _WorldSpaceLightPos0.w) // directional light?
        {
          attenuation = 1.0; // no attenuation
          lightDirection = normalize(_WorldSpaceLightPos0.xyz);
        }
        else // point or spot light
        {
          float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - i.posWorld.xyz;
          float distance = length(vertexToLightSource);
          attenuation = 1.0 / distance; // linear attenuation 
          lightDirection = normalize(vertexToLightSource);
        }

        float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

        float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb * max(0.0f, dot(normalDirection, lightDirection));

        float3 specularReflection;
        if (dot(normalDirection, lightDirection) < 0.0)
        // light source on the wrong side?
        {
          specularReflection = float3(0.0, 0.0, 0.0);
          // no specular reflection
        }
        else // light source on the right side
        {
          specularReflection = attenuation * _LightColor0.rgb * _SpecColor.rgb * pow(max(0.0, dot(
          reflect( - lightDirection, normalDirection), viewDirection)), _Shininess);
        }

        //float shadowAtten = SHADOW_ATTENUATION(i);
        //o.color = float4(diffuseReflection * shadowAtten + ambientLighting + specularReflection * specularFactor, 1.0);

        o.color = float4(diffuseReflection + ambientLighting + specularReflection * specularFactor, 1.0);

        // DEPTH CORRECTION
        float vertPixPos = i.uv.y;
        float alpha = 0.5f;
        float billboardDepth = (i.screenPos.z / i.screenPos.w);
        float nearDepth = (i.depthCorrectNearScreenPos.z / i.depthCorrectNearScreenPos.w);

        // transform   0 -> 0.5 -> 1   to   0.00001 -> 1 -> 0.00001 
        if (vertPixPos <= 0.5f) {
          alpha = vertPixPos * 2;
        }
        else {
          alpha = (1 - vertPixPos) * 2;
        }
        if (alpha <= 0) alpha = 0.00001f;

        //cicular interpolation (unit circle)
        float circularAlpha = sqrt(1 - pow((1 - alpha), 2));
        o.depth = (1 - (circularAlpha)) * billboardDepth + (circularAlpha) * nearDepth;

        return o;

      }

      ENDCG
    }

  }
  FallBack Off

}
Terrain.shader
/* Copyright (C) Manuel Matusich
* Unauthorized copying of this file, via any medium is strictly prohibited
* Written by Manuel Matusich , Juli 2017
*/

Shader "Custom/Terrain" {
  Properties {
    _MainTex("Texture For Diffuse Material Color", 2D) = "white" {}
    _TexHeight("TextureHeight", Float) = 2048
    _Color("Diffuse Material Color", Color) = (1, 1, 1, 1)
    _CutColor("Cut Color", Color) = (0, 1, 0, 0)
  }
  SubShader {
    Pass {
      Tags {
        "LightMode" = "ForwardBase"
      }
      // pass for ambient light and first light source
      //Blend SrcAlpha OneMinusSrcAlpha
      //ColorMask 0
      CGPROGRAM#pragma target 5.0#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdadd_fullshadows#include "UnityCG.cginc"#include "AutoLight.cginc"
      uniform float4 _LightColor0;
      // color of light source (from "Lighting.cginc")

      // User-specified properties
      uniform sampler2D _MainTex;
      uniform float4 _Color;
      uniform float4 _CutColor;
      uniform float _TexHeight;

      struct data {
        float3 pos;
        int pipeID;
        int nodeID;
      };

      struct uvData {
        float2 uvPos;
        int pipeID;
        int nodeID;
      };

      uniform sampler2D DTglobalTex;
      uniform int scanTexCoords; // 0 or 1 (bool)

      uniform int UpdateCounterPipeNodeID;
      uniform float terrainCutModifierSize;
      uniform float terrainCutModifierWidth;
      StructuredBuffer < data > pipeNodesVec3ReadBuffer;
      RWStructuredBuffer < uvData > RWpipeNodesUVBuffer: register(u[1]);

      struct vertexInput {
        float4 vertex: POSITION;
        float3 normal: NORMAL;
        float4 texcoord: TEXCOORD0;
      };
      struct vertexOutput {
        float4 pos: SV_POSITION;
        float4 tex: TEXCOORD0;
        float4 posWorld: TEXCOORD1;
        float3 normalDir: TEXCOORD2;
        SHADOW_COORDS(3)
      };

      vertexOutput vert(vertexInput v) {
        vertexOutput o;

        float4x4 modelMatrix = unity_ObjectToWorld;
        float4x4 modelMatrixInverse = unity_WorldToObject;

        o.posWorld = mul(modelMatrix, v.vertex);
        o.normalDir = normalize(
        mul(float4(v.normal, 0.0), modelMatrixInverse).xyz);
        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
        o.tex = v.texcoord;
        TRANSFER_SHADOW(o);

        return o;
      }

      struct fragOutput {
        float4 color: SV_TARGET;
      };

      fragOutput frag(vertexOutput input) {
        fragOutput o;

        float3 normalDirection = normalize(input.normalDir);

        float3 viewDirection = normalize(
        _WorldSpaceCameraPos - input.posWorld.xyz);
        float3 lightDirection;
        float attenuation;

        if (0.0 == _WorldSpaceLightPos0.w) // directional light?
        {
          attenuation = 1.0; // no attenuation
          lightDirection = normalize(_WorldSpaceLightPos0.xyz);
        }
        else // point or spot light
        {
          float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
          float distance = length(vertexToLightSource);
          attenuation = 1.0 / distance; // linear attenuation 
          lightDirection = normalize(vertexToLightSource);
        }

        float3 ambientLighting = UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

        float3 diffuseReflection = attenuation * _LightColor0.rgb * _Color.rgb * max(0.0, dot(normalDirection, lightDirection));

        float shadowAtten = SHADOW_ATTENUATION(input);

        o.color = float4(ambientLighting + diffuseReflection * tex2D(_MainTex, input.tex.xy) * shadowAtten, 1.0);

        if (scanTexCoords == 1) {
          float3 node = pipeNodesVec3ReadBuffer[UpdateCounterPipeNodeID].pos;
          float dist = distance(float2(node.x, node.z), input.posWorld.xz);
          if (dist < 0.1f) {
            o.color = float4(1, 0, 0, 1);
            RWpipeNodesUVBuffer[0].uvPos = input.tex.xy;
            RWpipeNodesUVBuffer[0].pipeID = pipeNodesVec3ReadBuffer[UpdateCounterPipeNodeID].pipeID;
            RWpipeNodesUVBuffer[0].nodeID = pipeNodesVec3ReadBuffer[UpdateCounterPipeNodeID].nodeID;
          }
        }

        if (scanTexCoords != 1) {

          float dist2pipe = tex2Dlod(DTglobalTex, float4(input.tex.xy, 0, 0)).y;
          float dist2cam = distance(_WorldSpaceCameraPos, input.posWorld.xyz);
          //float threshold = terrainCutModifierWidth * 25.0f * terrainCutModifierSize - terrainCutModifierWidth * dist2cam / terrainCutModifierSize;

          float threshold = (terrainCutModifierWidth) * ((pow(terrainCutModifierSize, 2) - dist2cam)) / (terrainCutModifierSize);
          int iterations = 12;
          float epsilon = sqrt(2) / _TexHeight / iterations;
          float2 viewDirection2D = viewDirection.xz * epsilon;

          if (dist2pipe < threshold) {
            float2 coords = float2(input.tex.x, input.tex.y);
            bool solid = false;
            for (int i = 0; i < iterations; i++) {
              float2 lookUpUV = coords + viewDirection2D * i;
              if (tex2Dlod(DTglobalTex, float4(lookUpUV.xy, 0, 0)).y > threshold) {
                solid = true;
                o.color = float4((ambientLighting * 2 + diffuseReflection / 2) * _CutColor.rgb, 1.0);
              }
            }
            if (!solid) discard;
          }
        }

        return o;
      }

      ENDCG
    }

  }
  Fallback "Diffuse"
}