Cómo dibujar correctamente una línea en Unity

27

Estoy trabajando en un juego que requiere que dibuje algunas líneas desde un solo punto que se dice más formalmente

Dado el punto A con coordenadas x, y, dibujo n líneas donde la i-ésima línea tiene las coordenadas nombradas como xi, yi. Dadas las capacidades de LineRenderer dentro de Unity3D, no pude dibujar más de una línea desde un punto en particular, ya que solo genera polilíneas.

Actualmente estoy usando el método Debug.DrawLine () que representa con éxito la línea. He intentado usar GL.Begin () como se muestra en el ejemplo de Unity pero no puedo ver mis líneas dibujadas.

Mi pregunta es: ¿hay otros métodos para hacer esto? Si no, ¿puede decirme cómo puedo mostrar la línea que se está dibujando con Debug.DrawLine () dentro del modo de reproducción? He visto que podría usar Gizmos.DrawLine () pero no entendí bien su uso.

Christo
fuente

Respuestas:

48

Usando líneas GL:

Recomendaría usar la API GL para dibujar líneas. El grosor de la línea siempre será de 1 px en la pantalla y no hay opción para cambiarlo. Tampoco habrá sombras.

Las llamadas al método GL se ejecutan inmediatamente, por lo que debe asegurarse de llamarlas después de que la cámara ya haya renderizado.

Adjuntar el script a la cámara y usar Camera.OnPostRender () funciona bien para renderizar en la ventana del juego. Para que se muestren en el editor, puede usar MonoBehaviour.OnDrawGizmos () .

Aquí está el código básico para dibujar una línea con la API GL:

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

Aquí hay un guión completo que une todos los puntos dados al punto principal. Hay algunas instrucciones en los comentarios del código para configurarlo correctamente y sobre lo que está sucediendo.

Si tiene problemas para cambiar el color de las líneas de conexión, asegúrese de usar un sombreador en su material de línea que tenga en cuenta el color del vértice, como por ejemplo 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();
    }
}

Nota adicional sobre las sombras: exploré el uso de un sombreador de geometría para crear sombras, pero dado que las llamadas GL se ejecutan de inmediato, no están en la tubería de renderizado normal AutoLight.cgincy Lighting.cgincno recogerán el ShadowCasterpase.


Líneas con sombras y radio

Si necesita cambiar el grosor de la línea y desea tener sombras realistas. Simplemente use una malla de cilindro y escale la altura.

Aquí hay un script que hará un cilindro para conectar cada punto al punto principal. Colóquelo en un objeto de juego vacío y complete los parámetros. Contendrá todos los objetos de conexión adicionales.

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

MLM
fuente
2
Booo, las líneas no tienen sombras. : p
MichaelHouse
Esa es una gran respuesta. Estoy inspeccionando lo que tienes allí para mí. Muchas gracias por el detalle, había hecho algo similar a esto sin ningún éxito. Veré muy bien tu ejemplo para ver dónde me equivoqué allí. ¡Gracias!
Christo
¿Puedo hacer esto sin usar OnPostRender?
Christo
Como necesito hacerlo en una clase personalizada que no se deriva del comportamiento mono, no tengo el método OnPostRender.
Christo
1
No parece importar el color que coloque en GL.Color (), o si lo llamo en absoluto: el color de las líneas sigue siendo el color del material. Actualmente, mi material de línea está usando shader Unlit / Color.
Erhannis
5

Líneas con sombras y radio a través del cubo

Saliendo de la respuesta de @ MadLittleMod , aquí hay otra versión que usa líneas basadas en Cubo ( tris: 12 ) en lugar de líneas basadas en Cilindro ( 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);
        }
    }
}
Purga
fuente