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" }