From 3074be7ad04c9c06a0d89e9581fc5e5859f740fd Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Wed, 16 Nov 2016 19:27:16 -0800
Subject: [PATCH] Glow line replacement without geometry shader

---
 .../hmd/DebugHmdDisplayPlugin.cpp             |   4 +-
 .../display-plugins/hmd/HmdDisplayPlugin.cpp  |  63 ++++++++---
 .../display-plugins/hmd/HmdDisplayPlugin.h    |   5 +-
 libraries/render-utils/src/GeometryCache.cpp  |  63 +++--------
 libraries/render-utils/src/GeometryCache.h    |   1 +
 libraries/render-utils/src/glowLine.slf       |  28 +++--
 libraries/render-utils/src/glowLine.slg       | 102 ------------------
 libraries/render-utils/src/glowLine.slv       |  42 +++++++-
 8 files changed, 121 insertions(+), 187 deletions(-)
 delete mode 100644 libraries/render-utils/src/glowLine.slg

diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp
index fa267e2c68..5a3e5afc86 100644
--- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp
@@ -39,11 +39,11 @@ bool DebugHmdDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
         _uiModelTransform = DependencyManager::get<CompositorHelper>()->getModelTransform();
         _frameInfos[frameIndex] = _currentRenderFrameInfo;
         
-        _handPoses[0] = glm::translate(mat4(), vec3(-0.3f, 0.0f, 0.0f));
+        _handPoses[0] = glm::translate(mat4(), vec3(0.3f * cosf(secTimestampNow() * 3.0f), -0.3f * sinf(secTimestampNow() * 5.0f), 0.0f));
         _handLasers[0].color = vec4(1, 0, 0, 1);
         _handLasers[0].mode = HandLaserMode::Overlay;
 
-        _handPoses[1] = glm::translate(mat4(), vec3(0.3f, 0.0f, 0.0f));
+        _handPoses[1] = glm::translate(mat4(), vec3(0.3f * sinf(secTimestampNow() * 3.0f), -0.3f * cosf(secTimestampNow() * 5.0f), 0.0f));
         _handLasers[1].color = vec4(0, 1, 1, 1);
         _handLasers[1].mode = HandLaserMode::Overlay;
     });
diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp
index c5d7ac5690..4fa1c30815 100644
--- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp
+++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp
@@ -23,7 +23,6 @@
 #include <CursorManager.h>
 #include <gl/GLWidget.h>
 #include <shared/NsightHelpers.h>
-#include <GeometryCache.h>
 #include <gpu/Context.h>
 #include <gpu/StandardShaderLib.h>
 #include <gpu/gl/GLBackend.h>
@@ -32,6 +31,9 @@
 
 #include "../Logging.h"
 #include "../CompositorHelper.h"
+#include <../render-utils/shaders/render-utils/glowLine_vert.h>
+#include <../render-utils/shaders/render-utils/glowLine_frag.h>
+
 
 static const QString MONO_PREVIEW = "Mono Preview";
 static const QString DISABLE_PREVIEW = "Disable Preview";
@@ -47,6 +49,14 @@ static const size_t NUMBER_OF_HANDS = 2;
 //#define LIVE_SHADER_RELOAD 1
 extern glm::vec3 getPoint(float yaw, float pitch);
 
+struct HandLaserData {
+    vec4 p1;
+    vec4 p2;
+    vec4 color;
+};
+
+static const uint32_t HAND_LASER_UNIFORM_SLOT = 1;
+
 static QString readFile(const QString& filename) {
     QFile file(filename);
     file.open(QFile::Text | QFile::ReadOnly);
@@ -112,11 +122,25 @@ void HmdDisplayPlugin::internalDeactivate() {
 void HmdDisplayPlugin::customizeContext() {
     Parent::customizeContext();
     _overlayRenderer.build();
-    auto geometryCache = DependencyManager::get<GeometryCache>();
-    for (size_t i = 0; i < _geometryIds.size(); ++i) {
-        _geometryIds[i] = geometryCache->allocateID();
-    }
-    _extraLaserID = geometryCache->allocateID();
+
+    {
+        auto state = std::make_shared<gpu::State>();
+        auto VS = gpu::Shader::createVertex(std::string(glowLine_vert));
+        auto PS = gpu::Shader::createPixel(std::string(glowLine_frag));
+        auto program = gpu::Shader::createProgram(VS, PS);
+        state->setCullMode(gpu::State::CULL_NONE);
+        state->setDepthTest(true, false, gpu::LESS_EQUAL);
+        state->setBlendFunction(true,
+            gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
+            gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
+        gpu::Shader::BindingSet slotBindings;
+        slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), HAND_LASER_UNIFORM_SLOT));
+        gpu::Shader::makeProgram(*program, slotBindings);
+        _glowLinePipeline = gpu::Pipeline::create(program, state);
+        _handLaserUniforms = std::array<gpu::BufferPointer, 2>{ { std::make_shared<gpu::Buffer>(), std::make_shared<gpu::Buffer>() } };
+        _extraLaserUniforms = std::make_shared<gpu::Buffer>();
+    };
+
 }
 
 void HmdDisplayPlugin::uncustomizeContext() {
@@ -131,12 +155,10 @@ void HmdDisplayPlugin::uncustomizeContext() {
     });
     _overlayRenderer = OverlayRenderer();
     _previewTexture.reset();
-
-    auto geometryCache = DependencyManager::get<GeometryCache>();
-    for (size_t i = 0; i < _geometryIds.size(); ++i) {
-        geometryCache->releaseID(_geometryIds[i]);
-    }
-    geometryCache->releaseID(_extraLaserID);
+    _handLaserUniforms[0].reset();
+    _handLaserUniforms[1].reset();
+    _extraLaserUniforms.reset();
+    _glowLinePipeline.reset();
     Parent::uncustomizeContext();
 }
 
@@ -683,11 +705,15 @@ void HmdDisplayPlugin::compositeExtra() {
         return;
     }
 
-    auto geometryCache = DependencyManager::get<GeometryCache>();
     render([&](gpu::Batch& batch) {
         batch.setFramebuffer(_compositeFramebuffer);
+        batch.setModelTransform(Transform());
         batch.setViewportTransform(ivec4(uvec2(0), _renderTargetSize));
         batch.setViewTransform(_currentPresentFrameInfo.presentPose, false);
+        // Compile the shaders
+        batch.setPipeline(_glowLinePipeline);
+
+
         bilateral::for_each_side([&](bilateral::Side side){
             auto index = bilateral::index(side);
             if (_presentHandPoses[index] == IDENTITY_MATRIX) {
@@ -696,13 +722,20 @@ void HmdDisplayPlugin::compositeExtra() {
             const auto& laser = _presentHandLasers[index];
             if (laser.valid()) {
                 const auto& points = _presentHandLaserPoints[index];
-                geometryCache->renderGlowLine(batch, points.first, points.second, laser.color, _geometryIds[index]);
+                _handLaserUniforms[index]->resize(sizeof(HandLaserData));
+                _handLaserUniforms[index]->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _handLasers[index].color });
+                batch.setUniformBuffer(HAND_LASER_UNIFORM_SLOT, _handLaserUniforms[index]);
+                qDebug() << "Render line " << index;
+                batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
             }
         });
 
         if (_presentExtraLaser.valid()) {
             const auto& points = _presentExtraLaserPoints;
-            geometryCache->renderGlowLine(batch, points.first, points.second, _presentExtraLaser.color, _extraLaserID);
+            _extraLaserUniforms->resize(sizeof(HandLaserData));
+            _extraLaserUniforms->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _presentExtraLaser.color });
+            batch.setUniformBuffer(HAND_LASER_UNIFORM_SLOT, _extraLaserUniforms);
+            batch.draw(gpu::TRIANGLE_STRIP, 4, 0);
         }
     });
 }
diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
index 435f547899..aaa6e347e0 100644
--- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
+++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h
@@ -80,8 +80,6 @@ protected:
 
     Transform _presentUiModelTransform;
     std::array<HandLaserInfo, 2> _presentHandLasers;
-    std::array<int, 2> _geometryIds;
-    int _extraLaserID;
     std::array<mat4, 2> _presentHandPoses;
     std::array<std::pair<vec3, vec3>, 2> _presentHandLaserPoints;
 
@@ -120,6 +118,9 @@ private:
     bool _disablePreviewItemAdded { false };
     bool _monoPreview { true };
     bool _clearPreviewFlag { false };
+    std::array<gpu::BufferPointer, 2> _handLaserUniforms;
+    gpu::BufferPointer _extraLaserUniforms;
+    gpu::PipelinePointer _glowLinePipeline;
     gpu::TexturePointer _previewTexture;
     glm::vec2 _lastWindowSize;
 
diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp
index 3f5dc26db2..a19f1844f0 100644
--- a/libraries/render-utils/src/GeometryCache.cpp
+++ b/libraries/render-utils/src/GeometryCache.cpp
@@ -38,7 +38,6 @@
 #include "simple_opaque_web_browser_frag.h"
 #include "simple_transparent_web_browser_frag.h"
 #include "glowLine_vert.h"
-#include "glowLine_geom.h"
 #include "glowLine_frag.h"
 
 #include "grid_frag.h"
@@ -1405,6 +1404,7 @@ GeometryCache::BatchItemDetails::~BatchItemDetails() {
 
 void GeometryCache::BatchItemDetails::clear() {
     isCreated = false;
+    uniformBuffer.reset();
     verticesBuffer.reset();
     colorBuffer.reset();
     streamFormat.reset();
@@ -1593,8 +1593,6 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
     glowIntensity = 0.0f;
 #endif
 
-    glowIntensity = 0.0f;
-
     if (glowIntensity <= 0) {
         bindSimpleProgram(batch, false, false, false, true, false);
         renderLine(batch, p1, p2, color, id);
@@ -1602,20 +1600,20 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
     }
 
     // Compile the shaders
+    static const uint32_t LINE_DATA_SLOT = 1;
     static std::once_flag once;
     std::call_once(once, [&] {
         auto state = std::make_shared<gpu::State>();
         auto VS = gpu::Shader::createVertex(std::string(glowLine_vert));
-        auto GS = gpu::Shader::createGeometry(std::string(glowLine_geom));
         auto PS = gpu::Shader::createPixel(std::string(glowLine_frag));
-        auto program = gpu::Shader::createProgram(VS, GS, PS);
+        auto program = gpu::Shader::createProgram(VS, PS);
         state->setCullMode(gpu::State::CULL_NONE);
         state->setDepthTest(true, false, gpu::LESS_EQUAL);
         state->setBlendFunction(true, 
             gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
             gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
         gpu::Shader::BindingSet slotBindings;
-        slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
+        slotBindings.insert(gpu::Shader::Binding(std::string("lineData"), LINE_DATA_SLOT));
         gpu::Shader::makeProgram(*program, slotBindings);
         _glowLinePipeline = gpu::Pipeline::create(program, state);
     });
@@ -1626,11 +1624,6 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
     bool registered = (id != UNKNOWN_ID);
     BatchItemDetails& details = _registeredLine3DVBOs[id];
 
-    int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
-        ((int(color.y * 255.0f) & 0xFF) << 8) |
-        ((int(color.z * 255.0f) & 0xFF) << 16) |
-        ((int(color.w * 255.0f) & 0xFF) << 24);
-
     // if this is a registered quad, and we have buffers, then check to see if the geometry changed and rebuild if needed
     if (registered && details.isCreated) {
         Vec3Pair& lastKey = _lastRegisteredLine3D[id];
@@ -1640,47 +1633,25 @@ void GeometryCache::renderGlowLine(gpu::Batch& batch, const glm::vec3& p1, const
         }
     }
 
-    const int FLOATS_PER_VERTEX = 3 + 3; // vertices + normals
-    const int NUM_POS_COORDS = 3;
-    const int VERTEX_NORMAL_OFFSET = NUM_POS_COORDS * sizeof(float);
-    const int vertices = 2;
+    const int NUM_VERTICES = 4;
     if (!details.isCreated) {
         details.isCreated = true;
-        details.vertices = vertices;
-        details.vertexSize = FLOATS_PER_VERTEX;
+        details.uniformBuffer = std::make_shared<gpu::Buffer>();
 
-        auto verticesBuffer = std::make_shared<gpu::Buffer>();
-        auto colorBuffer = std::make_shared<gpu::Buffer>();
-        auto streamFormat = std::make_shared<gpu::Stream::Format>();
-        auto stream = std::make_shared<gpu::BufferStream>();
+        struct LineData {
+            vec4 p1;
+            vec4 p2;
+            vec4 color;
+        };
 
-        details.verticesBuffer = verticesBuffer;
-        details.colorBuffer = colorBuffer;
-        details.streamFormat = streamFormat;
-        details.stream = stream;
-
-        details.streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
-        details.streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_NORMAL_OFFSET);
-        details.streamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
-
-        details.stream->addBuffer(details.verticesBuffer, 0, details.streamFormat->getChannels().at(0)._stride);
-        details.stream->addBuffer(details.colorBuffer, 0, details.streamFormat->getChannels().at(1)._stride);
-
-        const glm::vec3 NORMAL(1.0f, 0.0f, 0.0f);
-        float vertexBuffer[vertices * FLOATS_PER_VERTEX] = {
-            p1.x, p1.y, p1.z, NORMAL.x, NORMAL.y, NORMAL.z,
-            p2.x, p2.y, p2.z, NORMAL.x, NORMAL.y, NORMAL.z };
-
-        const int NUM_COLOR_SCALARS = 2;
-        int colors[NUM_COLOR_SCALARS] = { compactColor, compactColor };
-        details.verticesBuffer->append(sizeof(vertexBuffer), (gpu::Byte*) vertexBuffer);
-        details.colorBuffer->append(sizeof(colors), (gpu::Byte*) colors);
+        LineData lineData { vec4(p1, 1.0f), vec4(p2, 1.0f), color };
+        details.uniformBuffer->resize(sizeof(LineData));
+        details.uniformBuffer->setSubData(0, lineData);
     }
 
-    // this is what it takes to render a quad
-    batch.setInputFormat(details.streamFormat);
-    batch.setInputStream(0, *details.stream);
-    batch.draw(gpu::LINES, 2, 0);
+    // The shader requires no vertices, only uniforms.
+    batch.setUniformBuffer(LINE_DATA_SLOT, details.uniformBuffer);
+    batch.draw(gpu::TRIANGLE_STRIP, NUM_VERTICES, 0);
 }
 
 void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h
index 6e6ac89a8f..84dfd8ccc3 100644
--- a/libraries/render-utils/src/GeometryCache.h
+++ b/libraries/render-utils/src/GeometryCache.h
@@ -369,6 +369,7 @@ private:
         static int population;
         gpu::BufferPointer verticesBuffer;
         gpu::BufferPointer colorBuffer;
+        gpu::BufferPointer uniformBuffer;
         gpu::Stream::FormatPointer streamFormat;
         gpu::BufferStreamPointer stream;
 
diff --git a/libraries/render-utils/src/glowLine.slf b/libraries/render-utils/src/glowLine.slf
index edebc99c81..c0af97930a 100644
--- a/libraries/render-utils/src/glowLine.slf
+++ b/libraries/render-utils/src/glowLine.slf
@@ -10,26 +10,24 @@
 //
 
 layout(location = 0) in vec4 inColor;
-layout(location = 1) in vec3 inLineDistance;
 
 out vec4 _fragColor;
 
 void main(void) {
-    vec2 d = inLineDistance.xy;
-    d.y = abs(d.y);
-    d.x = abs(d.x);
-    if (d.x > 1.0) {
-        d.x = (d.x - 1.0) / 0.02;
-    } else { 
-        d.x = 0.0; 
-    }
-    float alpha = 1.0 - length(d);
-    if (alpha <= 0.0) {
-        discard;
-    }
-    alpha = pow(alpha, 10.0);
-    if (alpha < 0.05) {
+    // The incoming value actually ranges from -1 to 1, so modify it 
+    // so that it goes from 0 -> 1 -> 0 with the solid alpha being at 
+    // the center of the line
+    float alpha = 1.0 - abs(inColor.a);
+    
+    // Convert from a linear alpha curve to a sharp peaked one
+	alpha = pow(alpha, 10); 
+	
+	// Drop everything where the curve falls off to nearly nothing
+    if (alpha <= 0.05) {
         discard;
     }
+    
+    // Emit the color
     _fragColor = vec4(inColor.rgb, alpha);
+    return;
 }
diff --git a/libraries/render-utils/src/glowLine.slg b/libraries/render-utils/src/glowLine.slg
deleted file mode 100644
index 9af8eaa4d0..0000000000
--- a/libraries/render-utils/src/glowLine.slg
+++ /dev/null
@@ -1,102 +0,0 @@
-<@include gpu/Config.slh@>
-<$VERSION_HEADER$>
-//  Generated on <$_SCRIBE_DATE$>
-//
-//  Created by Bradley Austin Davis on 2016/07/05
-//  Copyright 2013-2016 High Fidelity, Inc.
-//
-//  Distributed under the Apache License, Version 2.0.
-//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-//
-#extension GL_EXT_geometry_shader4 : enable
-
-<@include gpu/Transform.slh@>
-<$declareStandardCameraTransform()$>
-
-layout(location = 0) in vec4 inColor[];
-
-layout(location = 0) out vec4 outColor;
-layout(location = 1) out vec3 outLineDistance;
-
-layout(lines) in;
-layout(triangle_strip, max_vertices = 24) out;
-
-vec3 ndcToEyeSpace(in vec4 v) {
-    TransformCamera cam = getTransformCamera();
-    vec4 u = cam._projectionInverse * v;
-    return u.xyz / u.w;
-}
-
-vec2 toScreenSpace(in vec4 v)
-{
-    TransformCamera cam = getTransformCamera();
-    vec4 u = cam._projection * cam._view * v;
-    return u.xy / u.w;
-}
-
-vec3[2] getOrthogonals(in vec3 n, float scale) {
-    float yDot = abs(dot(n, vec3(0, 1, 0)));
-
-    vec3 result[2];
-    if (yDot < 0.9) {
-        result[0] = normalize(cross(n, vec3(0, 1, 0)));
-    } else {
-        result[0] = normalize(cross(n, vec3(1, 0, 0)));
-    }
-    // The cross of result[0] and n is orthogonal to both, which are orthogonal to each other
-    result[1] = cross(result[0], n);
-    result[0] *= scale;
-    result[1] *= scale;
-    return result;
-}
-
-
-vec2 orthogonal(vec2 v) {
-    vec2 result = v.yx;
-    result.y *= -1.0;
-    return result;
-}
-
-void main() {
-    vec2 endpoints[2];
-    vec3 eyeSpace[2];
-    TransformCamera cam = getTransformCamera();
-    for (int i = 0; i < 2; ++i) {
-        eyeSpace[i] = ndcToEyeSpace(gl_PositionIn[i]);
-        endpoints[i] = gl_PositionIn[i].xy / gl_PositionIn[i].w;
-    }
-    vec2 lineNormal = normalize(endpoints[1] - endpoints[0]);
-    vec2 lineOrthogonal = orthogonal(lineNormal);
-    lineNormal *= 0.02;
-    lineOrthogonal *= 0.02;
-
-    gl_Position = gl_PositionIn[0];
-    gl_Position.xy -= lineOrthogonal;
-    outColor = inColor[0];
-    outLineDistance = vec3(-1.02, -1, gl_Position.z);
-    EmitVertex();
-
-    gl_Position = gl_PositionIn[0];
-    gl_Position.xy += lineOrthogonal;
-    outColor = inColor[0];
-    outLineDistance = vec3(-1.02, 1, gl_Position.z);
-    EmitVertex();
-
-    gl_Position = gl_PositionIn[1];
-    gl_Position.xy -= lineOrthogonal;
-    outColor = inColor[1];
-    outLineDistance = vec3(1.02, -1, gl_Position.z);
-    EmitVertex();
-
-    gl_Position = gl_PositionIn[1];
-    gl_Position.xy += lineOrthogonal;
-    outColor = inColor[1];
-    outLineDistance = vec3(1.02, 1, gl_Position.z);
-    EmitVertex();
-
-    EndPrimitive();
-}
-
-
-
-
diff --git a/libraries/render-utils/src/glowLine.slv b/libraries/render-utils/src/glowLine.slv
index aa126fe31a..e856edc787 100644
--- a/libraries/render-utils/src/glowLine.slv
+++ b/libraries/render-utils/src/glowLine.slv
@@ -9,18 +9,50 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-<@include gpu/Inputs.slh@>
-<@include gpu/Color.slh@>
 <@include gpu/Transform.slh@>
 <$declareStandardTransform()$>
 
+layout(std140) uniform lineData {
+    vec4 p1;
+    vec4 p2;
+    vec4 color;
+};
+
 layout(location = 0) out vec4 _color;
 
 void main(void) {
-    _color = inColor;
+    _color = color;
 
-    // standard transform
     TransformCamera cam = getTransformCamera();
     TransformObject obj = getTransformObject();
-    <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$>
+
+    vec4 p1eye, p2eye;
+    <$transformModelToEyePos(cam, obj, p1, p1eye)$> 
+    <$transformModelToEyePos(cam, obj, p2, p2eye)$>
+    p1eye /= p1eye.w;
+    p2eye /= p2eye.w; 
+
+    // Find the line direction
+    vec3 v1 = normalize(p1eye.xyz - p2eye.xyz);
+    // Find the vector from the eye to one of the points
+    vec3 v2 = normalize(p1eye.xyz);
+    // The orthogonal vector is the cross product of these two
+    vec3 orthogonal = cross(v1, v2) * 0.02;
+    
+    // Deteremine which end to emit based on the vertex id (even / odd)
+    vec4 eye = (0 == gl_VertexID % 2) ? p1eye : p2eye;
+
+    // Add or subtract the orthogonal vector based on a different vertex ID 
+    // calculation
+    if (gl_VertexID < 2) {
+        // Use the alpha channel to store the distance from the center in 'quad space'
+        _color.a = -1.0;
+        eye.xyz -= orthogonal;
+    } else {
+        _color.a = 1.0;
+        eye.xyz += orthogonal;
+    }
+
+    // Finally, put the eyespace vertex into clip space
+    <$transformEyeToClipPos(cam, eye, gl_Position)$>
 }
\ No newline at end of file