Tuesday, October 23, 2018

c# - How to correctly draw a line in Unity



I'm working on a game which requires me to draw a few lines from a single point that is more formally said


Given point A with coordinates x,y I draw n lines where the i-th line has the coordinates named as xi,yi. Given the LineRenderer capabilities inside Unity3D I was unable to draw more than one line from a particular point with it since it renders only polylines.


I'm currently using the Debug.DrawLine() method which successfully renders the line. I have tried using GL.Begin() as it is shown in the Unity example but I cannot see my lines being drawn.


My question is : are there any other methods for doing this? If not, can you tell me how can I show the line that is being drawn with Debug.DrawLine() inside play mode? I've saw that I could use Gizmos.DrawLine() but I didn't quite understand it's usage.



Answer




I would recommend using the the GL API for drawing lines. The line thickness will always be 1px on screen and there is no option to change it. There will also be no shadows.


The GL method calls are executed immediately so you need to make sure to call them after the camera has already rendered.


Attaching the script to the camera and using Camera.OnPostRender() works good for rendering in the game window. To get them to show in the editor, you can use MonoBehaviour.OnDrawGizmos().


Here is the barebones code to draw a line with the GL API:



public Material lineMat = new Material("Shader \"Lines/Colored Blended\" {" + "SubShader { Pass { " + "    Blend SrcAlpha OneMinusSrcAlpha " + "    ZWrite Off Cull Off Fog { Mode Off } " + "    BindChannels {" + "      Bind \"vertex\", vertex Bind \"color\", color }" + "} } }");

void OnPostRender() {
GL.Begin(GL.LINES);
lineMat.SetPass(0);
GL.Color(new Color(0f, 0f, 0f, 1f));
GL.Vertex3(0f, 0f, 0f);
GL.Vertex3(1f, 1f, 1f);
GL.End();
}


Here is a full script that attaches all of the given points to the main point. There are some instructions in the comments of the code to get it set up right and about what is going on.


If you are having problems changing the color of the connecting lines, make sure to use a shader on your line material that takes into account the vertex color such as Unlit/Color.


using UnityEngine;
using System.Collections;

// Put this script on a Camera
public class DrawLines : MonoBehaviour {

// Fill/drag these in from the editor


// Choose the Unlit/Color shader in the Material Settings
// You can change that color, to change the color of the connecting lines
public Material lineMat;

public GameObject mainPoint;
public GameObject[] points;

// Connect all of the `points` to the `mainPoint`
void DrawConnectingLines() {

if(mainPoint && points.Length > 0) {
// Loop through each point to connect to the mainPoint
foreach(GameObject point in points) {
Vector3 mainPointPos = mainPoint.transform.position;
Vector3 pointPos = point.transform.position;

GL.Begin(GL.LINES);
lineMat.SetPass(0);
GL.Color(new Color(lineMat.color.r, lineMat.color.g, lineMat.color.b, lineMat.color.a));
GL.Vertex3(mainPointPos.x, mainPointPos.y, mainPointPos.z);

GL.Vertex3(pointPos.x, pointPos.y, pointPos.z);
GL.End();
}
}
}

// To show the lines in the game window whne it is running
void OnPostRender() {
DrawConnectingLines();
}


// To show the lines in the editor
void OnDrawGizmos() {
DrawConnectingLines();
}
}


Further note on shadows: I explored using a geometry shader to make shadows but since the GL calls run immediately, they are not in the normal rendering pipeline and AutoLight.cginc and Lighting.cginc won't pick up the ShadowCaster pass.






If you need to change the line thickness and want to have realistic shadows. Just use a cylinder mesh and scale the height.


Here is a script that will make a cylinder to connect each point to the main point. Place it on a empty game object and fill in the parameters. It will hold all of the extra connecting objects.


using UnityEngine;
using System.Collections;

public class ConnectPointsWithCylinderMesh : MonoBehaviour {

// Material used for the connecting lines
public Material lineMat;


public float radius = 0.05f;

// Connect all of the `points` to the `mainPoint`
public GameObject mainPoint;
public GameObject[] points;

// Fill in this with the default Unity Cylinder mesh
// We will account for the cylinder pivot/origin being in the middle.
public Mesh cylinderMesh;



GameObject[] ringGameObjects;

// Use this for initialization
void Start () {
this.ringGameObjects = new GameObject[points.Length];
//this.connectingRings = new ProceduralRing[points.Length];
for(int i = 0; i < points.Length; i++) {
// Make a gameobject that we will put the ring on

// And then put it as a child on the gameobject that has this Command and Control script
this.ringGameObjects[i] = new GameObject();
this.ringGameObjects[i].name = "Connecting ring #" + i;
this.ringGameObjects[i].transform.parent = this.gameObject.transform;

// We make a offset gameobject to counteract the default cylindermesh pivot/origin being in the middle
GameObject ringOffsetCylinderMeshObject = new GameObject();
ringOffsetCylinderMeshObject.transform.parent = this.ringGameObjects[i].transform;

// Offset the cylinder so that the pivot/origin is at the bottom in relation to the outer ring gameobject.

ringOffsetCylinderMeshObject.transform.localPosition = new Vector3(0f, 1f, 0f);
// Set the radius
ringOffsetCylinderMeshObject.transform.localScale = new Vector3(radius, 1f, radius);

// Create the the Mesh and renderer to show the connecting ring
MeshFilter ringMesh = ringOffsetCylinderMeshObject.AddComponent();
ringMesh.mesh = this.cylinderMesh;

MeshRenderer ringRenderer = ringOffsetCylinderMeshObject.AddComponent();
ringRenderer.material = lineMat;


}
}

// Update is called once per frame
void Update () {
for(int i = 0; i < points.Length; i++) {
// Move the ring to the point
this.ringGameObjects[i].transform.position = this.points[i].transform.position;


// Match the scale to the distance
float cylinderDistance = 0.5f*Vector3.Distance(this.points[i].transform.position, this.mainPoint.transform.position);
this.ringGameObjects[i].transform.localScale = new Vector3(this.ringGameObjects[i].transform.localScale.x, cylinderDistance, this.ringGameObjects[i].transform.localScale.z);

// Make the cylinder look at the main point.
// Since the cylinder is pointing up(y) and the forward is z, we need to offset by 90 degrees.
this.ringGameObjects[i].transform.LookAt(this.mainPoint.transform, Vector3.up);
this.ringGameObjects[i].transform.rotation *= Quaternion.Euler(90, 0, 0);
}
}

}


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...