Jak poprawnie narysować linię w Unity


27

Pracuję nad grą, która wymaga ode mnie narysowania kilku linii z jednego punktu, co jest bardziej formalnie powiedziane

Biorąc pod uwagę punkt A o współrzędnych x, y Rysuję n linii, w których i-ta linia ma współrzędne nazwane jako xi, yi. Biorąc pod uwagę możliwości LineRenderer w Unity3D, nie byłem w stanie narysować więcej niż jednej linii z określonego punktu, ponieważ renderuje on tylko polilinie.

Obecnie używam metody Debug.DrawLine (), która z powodzeniem renderuje linię. Próbowałem użyć GL.Begin (), jak pokazano w przykładzie Unity, ale nie widzę, że moje linie są rysowane.

Moje pytanie brzmi: czy są na to inne metody? Jeśli nie, czy możesz mi powiedzieć, jak mogę pokazać linię rysowaną za pomocą Debug.DrawLine () w trybie odtwarzania? Widziałem, że mogę użyć Gizmos.DrawLine (), ale nie do końca rozumiałem, jak to działa.

Odpowiedzi:


48

Korzystanie z linii GL:

Polecam używanie GL API do rysowania linii. Grubość linii zawsze będzie wynosić 1px na ekranie i nie ma możliwości jej zmiany. Nie będzie też cieni.

Wywołania metody GL są wykonywane natychmiast, więc musisz je wywoływać po renderowaniu kamery.

Dołączenie skryptu do kamery i użycie Camera.OnPostRender () działa dobrze do renderowania w oknie gry. Aby je wyświetlić w edytorze, możesz użyć MonoBehaviour.OnDrawGizmos () .

Oto kod barebone do narysowania linii za pomocą 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();
}

Oto pełny skrypt, który dołącza wszystkie podane punkty do punktu głównego. W komentarzach do kodu znajduje się kilka instrukcji, jak go poprawnie skonfigurować i co się dzieje.

Jeśli masz problemy ze zmianą koloru linii łączących, użyj shadera na materiale linii, który bierze pod uwagę kolor wierzchołka, np 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();
    }
}

Dodatkowa uwaga na temat cieni: eksplorowałem za pomocą modułu cieniującego geometrię, aby tworzyć cienie, ale ponieważ wywołania GL są uruchamiane natychmiast, nie są one w normalnym potoku renderowania AutoLight.cginci Lighting.cgincnie odbierają podania ShadowCaster.


Linie z cieniami i promieniem

Jeśli chcesz zmienić grubość linii i chcesz mieć realistyczne cienie. Wystarczy użyć siatki cylindrycznej i wyskalować wysokość.

Oto skrypt, który utworzy cylinder łączący każdy punkt z punktem głównym. Umieść go na pustym obiekcie gry i podaj parametry. Pomieści wszystkie dodatkowe połączone obiekty.

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<MeshFilter>();
            ringMesh.mesh = this.cylinderMesh;

            MeshRenderer ringRenderer = ringOffsetCylinderMeshObject.AddComponent<MeshRenderer>();
            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);
        }
    }
}


2
Booo, linie nie mają cieni. : p
MichaelHouse

To świetna odpowiedź. Sprawdzam, co tam dla mnie masz. Wielkie dzięki za szczegóły, zrobiłem coś podobnego do tego bez żadnego sukcesu. Przyjrzę się twojemu przykładowi bardzo dobrze, aby zobaczyć, gdzie popełniłem błąd.
Christo

Czy mogę to zrobić bez korzystania z OnPostRender?
Christo

Ponieważ muszę to zrobić w klasie niestandardowej, która nie pochodzi z zachowania mono, dlatego nie mam metody OnPostRender.
Christo

1
Wydaje się, że nie ma znaczenia, jaki kolor wstawię w GL.Color (), czy w ogóle go nazywam - kolor linii pozostaje kolorem materiału. Mój materiał liniowy używa obecnie modułu cieniującego Unlit / Color.
Erhannis,

5

Linie z Cieniami i Promieniami przez Kostkę

Wychodząc z odpowiedzi @ MadLittleMod , tutaj inna wersja wykorzystująca linie oparte na modułach ( tris: 12 ) zamiast linii opartych na walcach ( tris: 80 ):

using UnityEngine;
using System.Collections;

public class ConnectPointsWithCubeMesh : 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 Cube mesh
    // We will account for the cube pivot/origin being in the middle.
    public Mesh cubeMesh;


    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 cubemesh pivot/origin being in the middle
            GameObject ringOffsetCubeMeshObject = new GameObject();
            ringOffsetCubeMeshObject.transform.parent = this.ringGameObjects[i].transform;

            // Offset the cube so that the pivot/origin is at the bottom in relation to the outer ring     gameobject.
            ringOffsetCubeMeshObject.transform.localPosition = new Vector3(0f, 1f, 0f);
            // Set the radius
            ringOffsetCubeMeshObject.transform.localScale = new Vector3(radius, 1f, radius);

            // Create the the Mesh and renderer to show the connecting ring
            MeshFilter ringMesh = ringOffsetCubeMeshObject.AddComponent<MeshFilter>();
            ringMesh.mesh = this.cubeMesh;

            MeshRenderer ringRenderer = ringOffsetCubeMeshObject.AddComponent<MeshRenderer>();
            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;

            this.ringGameObjects[i].transform.position = 0.5f * (this.points[i].transform.position + this.mainPoint.transform.position);
            var delta = this.points[i].transform.position - this.mainPoint.transform.position;
            this.ringGameObjects[i].transform.position += delta;

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

            // Make the cube look at the main point.
            // Since the cube 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);
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.