Skip to content

Instantly share code, notes, and snippets.

@andrew-raphael-lukasik
Last active September 28, 2023 18:47
Show Gist options
  • Save andrew-raphael-lukasik/1202f94b853795bb6f78e2fabc824011 to your computer and use it in GitHub Desktop.
Save andrew-raphael-lukasik/1202f94b853795bb6f78e2fabc824011 to your computer and use it in GitHub Desktop.
`GLDraw.cs` tiny open-source rendering system to draw debug shapes with. Target camera is *not* restricted to `SceneView` and unlike `Gizmos` this will work in !DEBUG builds.

Rendering system to draw debug shapes with.

Target camera is not restricted to SceneView and unlike Gizmos this will work in !DEBUG builds.

EditorWindowThatDrawsADebugTriangle.cs shows how to draw lines and triangles from an editor window

// src* https://gist.github.com/andrew-raphael-lukasik/1202f94b853795bb6f78e2fabc824011
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;

public class EditorWindowThatDrawsADebugTriangle : EditorWindow
{
    Vector3Field _fieldA, _fieldB, _fieldC;
    Vector3[] _positions = new Vector3[3]; Color32[] _colors = new Color32[3];
    string _uniqueDrawCallId;
    void OnEnable ()
    {
        _fieldA = new Vector3Field("Point A"){ value = new Vector3(-100,-100,-100) };
        _positions[0] = _fieldA.value;// initial value
        _colors[0] = new Color32(255,0,0,255);// initial value
        _fieldA.RegisterValueChangedCallback( (ctx) => _positions[0] = ctx.newValue );// value change
        rootVisualElement.Add(_fieldA);

        _fieldB = new Vector3Field("Point B"){ value = new Vector3(300,300,100) };
        _positions[1] = _fieldB.value;// initial value
        _colors[1] = new Color32(0,255,0,255);// initial value
        _fieldB.RegisterValueChangedCallback( (ctx) => _positions[1] = ctx.newValue );// value change
        rootVisualElement.Add(_fieldB);

        _fieldC = new Vector3Field("Point C"){ value = new Vector3(-300,300,300) };
        _positions[2] = _fieldC.value;// initial value
        _colors[2] = new Color32(0,0,255,255);// initial value
        _fieldC.RegisterValueChangedCallback( (ctx) => _positions[2] = ctx.newValue );// value change
        rootVisualElement.Add(_fieldC);

        _uniqueDrawCallId = $"{GetType().FullName} {GetInstanceID()}";
        GLDraw.AddBuffer( _uniqueDrawCallId , GLDraw.EMode.TRIANGLES , _positions , _colors );

        // draw 3 lines that will disappear in 10 seconds
        GLDraw.Line( _fieldA.value , _fieldB.value , 10f );
        GLDraw.Line( _fieldB.value , _fieldC.value , 10f );
        GLDraw.Line( _fieldC.value , _fieldA.value , 10f );
    }
    void OnDisable () => GLDraw.RemoveBuffer(_uniqueDrawCallId);
    
    [MenuItem("Tools/"+nameof(EditorWindowThatDrawsADebugTriangle))]
    static void CreateWindow () => GetWindow<EditorWindowThatDrawsADebugTriangle>().titleContent = new GUIContent("(= ФェФ=)");
}
// src* https://gist.github.com/andrew-raphael-lukasik/1202f94b853795bb6f78e2fabc824011
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
public static class GLDraw
{
public static System.Func<Camera,bool> cameraSelector = (camera) => camera.name=="SceneCamera";// Scene view only
static Dictionary<string,FDrawCall> externalBuffers = new Dictionary<string,FDrawCall>();
static List<FLine> lineBuffer = new List<FLine>();
static List<FDrawCallTemp> generalBuffer = new List<FDrawCallTemp>();
static List<FDrawCallTempSingleColor> generalBufferSingleColor = new List<FDrawCallTempSingleColor>();
static Material material;
static long systemTick, ticksPerSecond;
static GLDraw ()
{
ticksPerSecond = System.TimeSpan.TicksPerSecond;
material = new Material(Shader.Find("Sprites/Default"));
RenderPipelineManager.endFrameRendering += EndCameraRendering;
}
static void EndCameraRendering ( ScriptableRenderContext context , Camera[] cameras )
{
int totalDrawCalls = externalBuffers.Count + lineBuffer.Count + generalBuffer.Count + generalBufferSingleColor.Count;
if( totalDrawCalls==0 ) return;// nothing to draw
material.SetPass(0);
GL.PushMatrix();
foreach( Camera camera in cameras )
{
if( !cameraSelector(camera) ) continue;
// draw from external buffers:
foreach( var kv in externalBuffers )
{
string id = kv.Key;
FDrawCall drawCall = kv.Value;
GL.Begin( (int) drawCall.mode );
{
int numVertices = Mathf.Min( drawCall.vertices.Length , drawCall.colors.Length );
for( int i=0 ; i<numVertices ; i++ )
{
GL.Color( drawCall.colors[i] );
GL.Vertex( drawCall.vertices[i] );
}
}
GL.End();
}
// update system time:
systemTick = System.DateTime.Now.Ticks;
// draw form lines-only buffer:
int lineBufferLength = lineBuffer.Count;
if( lineBufferLength!=0 )
{
GL.Begin( GL.LINES );
{
for( int i=lineBufferLength-1 ; i!=-1 ; i-- )
{
FLine line = lineBuffer[i];
GL.Color( line.startColor );
GL.Vertex( line.start );
GL.Color( line.endColor );
GL.Vertex( line.end );
if( systemTick > line.expiration )
lineBuffer.RemoveAt(i);
}
}
GL.End();
}
// draw from a general-use buffer:
int generalBufferLength = generalBuffer.Count;
if( generalBufferLength!=0 )
{
for( int idc=generalBufferLength-1 ; idc!=-1 ; idc-- )
{
FDrawCallTemp drawcall = generalBuffer[idc];
GL.Begin( (int) drawcall.mode );
{
var vertexBuffer = drawcall.vertices;
var colorBuffer = drawcall.colors;
int numVertices = vertexBuffer.Length;
for( int iv=numVertices-1 ; iv!=-1 ; iv-- )
{
GL.Color( colorBuffer[iv] );
GL.Vertex( vertexBuffer[iv] );
}
}
GL.End();
if( systemTick > drawcall.expiration )
generalBuffer.RemoveAt(idc);
}
}
// draw from a general-use single color buffer:
int generalBufferSingleColorLength = generalBufferSingleColor.Count;
{
for( int idc=generalBufferSingleColorLength-1 ; idc!=-1 ; idc-- )
{
FDrawCallTempSingleColor drawcall = generalBufferSingleColor[idc];
GL.Begin( (int) drawcall.mode );
GL.Color( drawcall.color );
{
var vertexBuffer = drawcall.vertices;
int numVertices = vertexBuffer.Length;
for( int iv=numVertices-1 ; iv!=-1 ; iv-- )
GL.Vertex( vertexBuffer[iv] );
}
GL.End();
if( systemTick > drawcall.expiration )
generalBufferSingleColor.RemoveAt(idc);
}
}
}
GL.PopMatrix();
}
public static void Line ( Vector3 start , Vector3 end )
=> Line( start:start , end:end , color:Color.white , duration:0 );
public static void Line ( Vector3 start , Vector3 end , Color32 color )
=> Line( start:start , startColor:color , end:end , endColor:color , duration:0 );
public static void Line ( Vector3 start , Vector3 end , float duration )
=> Line( start:start , end:end , color:Color.white , duration:duration );
public static void Line ( Vector3 start , Vector3 end , Color32 color , float duration )
=> Line( start:start , startColor:color , end:end , endColor:color , duration:duration );
public static void Line ( Vector3 start , Color32 startColor , Vector3 end , Color32 endColor , float duration )
{
lineBuffer.Add( new FLine{ start=start , end=end , startColor=startColor , endColor=endColor , expiration=systemTick+(long)(duration*ticksPerSecond) } );
}
public static void Circle ( Vector3 center , Vector3 normal , float r , int numVertices = 32 )
=> Circle( center:center , normal:normal , r:r , color:Color.white , duration:0 , numVertices:numVertices );
public static void Circle ( Vector3 center , Vector3 normal , float r , Color32 color , int numVertices = 32 )
=> Circle( center:center , normal:normal , r:r , color:color , duration:0 , numVertices:numVertices );
public static void Circle ( Vector3 center , Vector3 normal , float r , Color32 color , float duration , int numVertices = 32 )
{
numVertices = Mathf.Max( numVertices , 3 ) + 1;// "+1" because first and last vertex occupy the same spot
var rot = Quaternion.FromToRotation( Vector3.up , normal );
const float tau = Mathf.PI * 2f;
Vector3[] vertices = new Vector3[ numVertices ];
for( int i=0 ; i<numVertices ; i++ )
{
float angle = tau * ( i / ( numVertices - 1f ) );
float cos = Mathf.Cos(angle);
float sin = Mathf.Sin(angle);
vertices[i] = center + rot * new Vector3(cos*r,0,sin*r);
}
generalBufferSingleColor.Add( new FDrawCallTempSingleColor{ mode=GLDraw.EMode.LINE_STRIP , vertices=vertices , color=color , expiration=systemTick+(long)(duration*ticksPerSecond) } );
}
public static void WireSphere ( Vector3 center , float r , Color32 color , int numVertices = 32 )
=> WireSphere( center:center , r:r , color:color , duration:0 , numVertices:numVertices );
public static void WireSphere ( Vector3 center , float r , Color32 color , float duration , int numVertices = 32 )
{
numVertices = Mathf.Max( numVertices , 3 );
const float tau = Mathf.PI * 2f;
Vector3[] vertices = new Vector3[ numVertices * 3 ];
for( int i=0 ; i<numVertices ; i++ )
{
float angle = tau * ( i / ( numVertices - 1f ) );
float cos = Mathf.Cos(angle);
float sin = Mathf.Sin(angle);
vertices[i] = center + new Vector3(cos*r,0,sin*r);
}
var rot2 = Quaternion.Euler(90,0,0);
for( int i=numVertices ; i<numVertices*2 ; i++ )
{
float angle = tau * ( i / ( numVertices - 1f ) );
float cos = Mathf.Cos(angle);
float sin = Mathf.Sin(angle);
vertices[i] = center + rot2 * new Vector3(cos*r,0,sin*r);
}
var rot3 = Quaternion.Euler(0,0,90);
for( int i=numVertices*2 ; i<numVertices*3 ; i++ )
{
float angle = tau * ( i / ( numVertices - 1f ) );
float cos = Mathf.Cos(angle);
float sin = Mathf.Sin(angle);
vertices[i] = center + rot3 * new Vector3(cos*r,0,sin*r);
}
generalBufferSingleColor.Add( new FDrawCallTempSingleColor{ mode=GLDraw.EMode.LINE_STRIP , vertices=vertices , color=color , expiration=systemTick+(long)(duration*ticksPerSecond) } );
}
public static void Call ( EMode mode , Vector3[] vertices )
=> Call( mode:mode , duration:0 , color:Color.white , vertices:vertices );
public static void Call ( EMode mode , Vector3[] vertices , Color32 color )
=> Call( mode:mode , duration:0 , color:color , vertices:vertices );
public static void Call ( EMode mode , Vector3[] vertices , Color32 color , float duration )
{
generalBufferSingleColor.Add( new FDrawCallTempSingleColor{ mode=mode , color=color , vertices=vertices , expiration=systemTick+(long)(duration*ticksPerSecond) } );
}
public static void Call ( EMode mode , Vector3[] vertices , Color32[] colors )
=> Call( mode:mode , duration:0 , colors:colors , vertices:vertices );
public static void Call ( EMode mode , Vector3[] vertices , Color32[] colors , float duration )
{
generalBuffer.Add( new FDrawCallTemp{ mode=mode , colors=colors , vertices=vertices , expiration=systemTick+(long)(duration*ticksPerSecond) } );
}
public static void AddBuffer ( string id , EMode mode , Vector3[] vertexBuffer , Color32[] colorBuffer )
{
if( vertexBuffer==null || vertexBuffer.Length==0 || colorBuffer==null || colorBuffer.Length==0 )
{
Debug.LogError($"invalid draw call {{ \"{id}\" , mode:{mode} , vertexBuffer:{vertexBuffer?.Length} , colorBuffer:{colorBuffer?.Length} }}");
return;
}
externalBuffers.Add( id , new FDrawCall{ mode=mode , vertices=vertexBuffer , colors=colorBuffer } );
}
public static void RemoveBuffer ( string id )
=> externalBuffers.Remove( id );
struct FLine
{
public Vector3 start, end;
public Color32 startColor, endColor;
public long expiration;
}
struct FDrawCall
{
public EMode mode;
public Vector3[] vertices;
public Color32[] colors;
}
struct FDrawCallTemp
{
public EMode mode;
public Vector3[] vertices;
public Color32[] colors;
// public Matrix4x4[] matrix;
public long expiration;
}
struct FDrawCallTempSingleColor
{
public EMode mode;
public Vector3[] vertices;
public Color32 color;
// public Matrix4x4[] matrix;
public long expiration;
}
public enum EMode : byte
{
LINES = GL.LINES,
LINE_STRIP = GL.LINE_STRIP,
TRIANGLES = GL.TRIANGLES,
TRIANGLE_STRIP = GL.TRIANGLE_STRIP,
QUADS = GL.QUADS,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment