Skip to content

Instantly share code, notes, and snippets.

@thejasonfisher
Forked from vicrucann/bezier.frag
Created February 24, 2024 23:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thejasonfisher/5b9aa3fa347c9fd13afcb2119e7d7cec to your computer and use it in GitHub Desktop.
Save thejasonfisher/5b9aa3fa347c9fd13afcb2119e7d7cec to your computer and use it in GitHub Desktop.
GLSL shader that allows to draw smooth and thick Bezier lines in 3D; added fog effect; using OpenSceneGraph for visualization
#version 330
in VertexData{
vec4 mColor;
} VertexIn;
void main(void)
{
gl_FragColor = VertexIn.mColor;
}
#version 330
uniform float Thickness;
uniform vec2 Viewport;
uniform float MiterLimit;
uniform int Segments;
uniform vec4 CameraEye;
const int SegmentsMax = 30; // max_vertices = (SegmentsMax+1)*4;
const int SegmentsMin = 3; // min number of segments per curve
layout(lines_adjacency) in;
layout(triangle_strip, max_vertices = 124) out;
in VertexData{
vec4 mColor;
vec4 mVertex;
} VertexIn[4];
out VertexData{
vec4 mColor;
} VertexOut;
vec2 toScreenSpace(vec4 vertex)
{
return vec2( vertex.xy / vertex.w ) * Viewport;
}
float toZValue(vec4 vertex)
{
return (vertex.z/vertex.w);
}
vec4 toBezier(float delta, int i, vec4 P0, vec4 P1, vec4 P2, vec4 P3)
{
float t = delta * float(i);
float t2 = t * t;
float one_minus_t = 1.0 - t;
float one_minus_t2 = one_minus_t * one_minus_t;
return (P0 * one_minus_t2 * one_minus_t + P1 * 3.0 * t * one_minus_t2 + P2 * 3.0 * t2 * one_minus_t + P3 * t2 * t);
}
float getFogFactor(float d)
{
const float mx = 30.0;
const float mn = 10.0;
return (mx - d) / (mx - mn);
}
void drawSegment(vec2 points[4], vec4 colors[4], float zValues[4])
{
vec2 p0 = points[0];
vec2 p1 = points[1];
vec2 p2 = points[2];
vec2 p3 = points[3];
/* perform naive culling */
vec2 area = Viewport * 4;
if( p1.x < -area.x || p1.x > area.x ) return;
if( p1.y < -area.y || p1.y > area.y ) return;
if( p2.x < -area.x || p2.x > area.x ) return;
if( p2.y < -area.y || p2.y > area.y ) return;
/* determine the direction of each of the 3 segments (previous, current, next) */
vec2 v0 = normalize( p1 - p0 );
vec2 v1 = normalize( p2 - p1 );
vec2 v2 = normalize( p3 - p2 );
/* determine the normal of each of the 3 segments (previous, current, next) */
vec2 n0 = vec2( -v0.y, v0.x );
vec2 n1 = vec2( -v1.y, v1.x );
vec2 n2 = vec2( -v2.y, v2.x );
/* determine miter lines by averaging the normals of the 2 segments */
vec2 miter_a = normalize( n0 + n1 ); // miter at start of current segment
vec2 miter_b = normalize( n1 + n2 ); // miter at end of current segment
/* determine the length of the miter by projecting it onto normal and then inverse it */
float an1 = dot(miter_a, n1);
float bn1 = dot(miter_b, n2);
if (an1==0) an1 = 1;
if (bn1==0) bn1 = 1;
float length_a = Thickness / an1;
float length_b = Thickness / bn1;
/* prevent excessively long miters at sharp corners */
if( dot( v0, v1 ) < -MiterLimit ) {
miter_a = n1;
length_a = Thickness;
/* close the gap */
if( dot( v0, n1 ) > 0 ) {
VertexOut.mColor = colors[1];
gl_Position = vec4( ( p1 + Thickness * n0 ) / Viewport, zValues[1], 1.0 );
EmitVertex();
VertexOut.mColor = colors[1];
gl_Position = vec4( ( p1 + Thickness * n1 ) / Viewport, zValues[1], 1.0 );
EmitVertex();
VertexOut.mColor = colors[1];
gl_Position = vec4( p1 / Viewport, 0.0, 1.0 );
EmitVertex();
EndPrimitive();
}
else {
VertexOut.mColor = colors[1];
gl_Position = vec4( ( p1 - Thickness * n1 ) / Viewport, zValues[1], 1.0 );
EmitVertex();
VertexOut.mColor = colors[1];
gl_Position = vec4( ( p1 - Thickness * n0 ) / Viewport, zValues[1], 1.0 );
EmitVertex();
VertexOut.mColor = colors[1];
gl_Position = vec4( p1 / Viewport, zValues[1], 1.0 );
EmitVertex();
EndPrimitive();
}
}
if( dot( v1, v2 ) < -MiterLimit ) {
miter_b = n1;
length_b = Thickness;
}
// generate the triangle strip
VertexOut.mColor = colors[1];
gl_Position = vec4( ( p1 + length_a * miter_a ) / Viewport, zValues[1], 1.0 );
EmitVertex();
VertexOut.mColor = colors[1];
gl_Position = vec4( ( p1 - length_a * miter_a ) / Viewport, zValues[1], 1.0 );
EmitVertex();
VertexOut.mColor = colors[2];
gl_Position = vec4( ( p2 + length_b * miter_b ) / Viewport, zValues[2], 1.0 );
EmitVertex();
VertexOut.mColor = colors[2];
gl_Position = vec4( ( p2 - length_b * miter_b ) / Viewport, zValues[2], 1.0 );
EmitVertex();
EndPrimitive();
}
void main(void)
{
/* cut segments number if larger or smaller than allowed */
int nSegments = (Segments > SegmentsMax)? SegmentsMax : Segments;
nSegments = (nSegments < SegmentsMin)? SegmentsMin: nSegments;
// 4 control points
vec4 B[4];
B[0] = gl_in[0].gl_Position;
B[1] = gl_in[1].gl_Position;
B[2] = gl_in[2].gl_Position;
B[3] = gl_in[3].gl_Position;
// vertex format
vec4 V[4];
V[0] = VertexIn[0].mVertex;
V[1] = VertexIn[1].mVertex;
V[2] = VertexIn[2].mVertex;
V[3] = VertexIn[3].mVertex;
// 4 attached colors
vec4 C[4];
C[0] = VertexIn[0].mColor;
C[1] = VertexIn[1].mColor;
C[2] = VertexIn[2].mColor;
C[3] = VertexIn[3].mColor;
/* use the points to build a bezier line */
float delta = 1.0 / float(nSegments);
vec4 Points[4]; // segments of curve in 3d
vec4 colors[4]; // interpolated colors
float zValues[4];
int j = 0; // bezier segment index for color interpolation
for (int i=0; i<=nSegments; ++i){
/* first point */
if (i==0){
Points[1] = toBezier(delta, i, B[0], B[1], B[2], B[3]);
Points[2] = toBezier(delta, i+1, B[0], B[1], B[2], B[3]);
Points[3] = toBezier(delta, i+2, B[0], B[1], B[2], B[3]);
vec4 dir = normalize(Points[2] - Points[1]);
Points[0] = Points[1] + dir * 0.01;
}
else if (i < nSegments-1){
Points[0] = Points[1];
Points[1] = Points[2];
Points[2] = Points[3];
Points[3] = toBezier(delta, i+2, B[0], B[1], B[2], B[3]);
}
/* last point */
else {
Points[0] = Points[1];
Points[1] = Points[2];
Points[2] = Points[3];
vec4 dir = normalize(Points[2] - Points[1]);
Points[3] = Points[2] + dir * 0.01;
}
/* color interpolation: define which bezier segment the point belongs to and then interpolate
between the two colors of that segment */
if (i==0) colors[1] = C[0];
else colors[1] = colors[2];
/* fraction p{i} is located between fraction p{j} and p{j+1} */
float pi = float(i+1) / float(nSegments);
if (pi >= float(j+1)/3.f) j++;
float pj = float(j)/3.f; // 4 bezier points means 3 segments between which points are plotted
float pj1 = float(j+1)/3.f;
float a = (pi-pj) / (pj1-pj);
colors[2] = mix(C[j], C[j+1], a);
{
/* fog effect: more transparency in color with greater distance; remove the block if not needed */
float d1 = distance(CameraEye, V[1]);
float d2 = distance(CameraEye, V[2]);
float alpha1 = getFogFactor(d1);
float alpha2 = getFogFactor(d2);
colors[1] = vec4(colors[1].rgb, alpha1);
colors[2] = vec4(colors[2].rgb, alpha2);
}
vec2 points[4]; // segments of curve in 2d
points[0] = toScreenSpace(Points[0]);
points[1] = toScreenSpace(Points[1]);
points[2] = toScreenSpace(Points[2]);
points[3] = toScreenSpace(Points[3]);
zValues[0] = toZValue(Points[0]);
zValues[1] = toZValue(Points[1]);
zValues[2] = toZValue(Points[2]);
zValues[3] = toZValue(Points[3]);
drawSegment(points, colors, zValues);
}
}
#version 330
uniform mat4 ModelViewProjectionMatrix;
layout(location = 0) in vec4 Vertex;
layout(location = 1) in vec4 Color;
out VertexData{
vec4 mColor;
vec4 mVertex;
} VertexOut;
void main(void)
{
VertexOut.mColor = Color;
VertexOut.mVertex = Vertex;
gl_Position = ModelViewProjectionMatrix * Vertex;
}
#ifdef _WIN32
#include <Windows.h>
#endif // _WIN32
#include <osg/Camera>
#include <osg/Drawable>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Node>
#include <osg/NodeVisitor>
#include <osg/Object>
#include <osg/PrimitiveSet>
#include <osg/Program>
#include <osg/Shader>
#include <osg/StateSet>
#include <osg/Transform>
#include <osg/Uniform>
#include <osgViewer/Viewer>
#include <osg/LineWidth>
#include <osg/Point>
#include <osg/Viewport>
#include <osg/BlendFunc>
#include <osgDB/ReadFile>
#include <osg/Depth>
#include <osgGA/TrackballManipulator>
struct ModelViewProjectionMatrixCallback: public osg::Uniform::Callback
{
ModelViewProjectionMatrixCallback(osg::Camera* camera) :
_camera(camera) {
}
virtual void operator()(osg::Uniform* uniform, osg::NodeVisitor* nv) {
osg::Matrixd viewMatrix = _camera->getViewMatrix();
osg::Matrixd modelMatrix = osg::computeLocalToWorld(nv->getNodePath());
osg::Matrixd modelViewProjectionMatrix = modelMatrix * viewMatrix * _camera->getProjectionMatrix();
uniform->set(modelViewProjectionMatrix);
}
osg::Camera* _camera;
};
struct ViewportCallback: public osg::Uniform::Callback
{
ViewportCallback(osg::Camera* camera) :
_camera(camera) {
}
virtual void operator()(osg::Uniform* uniform, osg::NodeVisitor* /*nv*/) {
const osg::Viewport* viewport = _camera->getViewport();
osg::Vec2f viewportVector = osg::Vec2f(viewport->width(), viewport->height());
uniform->set(viewportVector);
}
osg::Camera* _camera;
};
struct CameraEyeCallback: public osg::Uniform::Callback
{
CameraEyeCallback(osg::Camera* camera) :
_camera(camera) {
}
virtual void operator()(osg::Uniform* uniform, osg::NodeVisitor* /*nv*/) {
osg::Vec3f eye, center, up;
_camera->getViewMatrixAsLookAt(eye, center, up);
osg::Vec4f eye_vec = osg::Vec4f(eye.x(), eye.y(), eye.z(), 1);
uniform->set(eye_vec);
}
osg::Camera* _camera;
};
const int OSG_WIDTH = 1280;
const int OSG_HEIGHT = 960;
osg::Vec3Array* createDataPoints()
{
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
vertices->push_back(osg::Vec3f(0,0,0));
vertices->push_back(osg::Vec3f(0,0,1));
vertices->push_back(osg::Vec3f(1,0,1));
vertices->push_back(osg::Vec3f(1,0,0));
vertices->push_back(osg::Vec3f(1,0,0));
vertices->push_back(osg::Vec3f(1,0,-1));
vertices->push_back(osg::Vec3f(2,0,-1));
vertices->push_back(osg::Vec3f(2,0,0));
vertices->push_back(osg::Vec3f(2,0,0));
vertices->push_back(osg::Vec3f(2,3,0));
vertices->push_back(osg::Vec3f(2,3,1));
vertices->push_back(osg::Vec3f(2,3,2));
return vertices.release();
}
osg::Node* createBezierScene(osg::Camera* camera)
{
osg::Vec3Array* vertices = createDataPoints();
osg::Vec4Array* colors = new osg::Vec4Array;
colors->setName("Color");
colors->push_back(osg::Vec4f(0.1, 0.9, 0.1, 1));
colors->push_back(osg::Vec4f(0.2, 0.1, 0.9, 1));
colors->push_back(osg::Vec4f(0.7, 0.9, 0.1, 1));
colors->push_back(osg::Vec4f(0.9, 0.2, 0.9, 1));
colors->push_back(osg::Vec4f(0.9, 0.2, 0.9, 1));
colors->push_back(osg::Vec4f(0.9, 0.9, 0.1, 1));
colors->push_back(osg::Vec4f(0.7, 0.1, 0.9, 1));
colors->push_back(osg::Vec4f(0.2, 0.9, 0.1, 1));
colors->push_back(osg::Vec4f(0.0, 0.0, 0.0, 1));
colors->push_back(osg::Vec4f(0.25, 0.25, 0.25, 1));
colors->push_back(osg::Vec4f(0.5, 0.5, 0.5, 1));
colors->push_back(osg::Vec4f(1.0, 1.0, 1.0, 1));
// create geometry
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
geom->addPrimitiveSet(new osg::DrawArrays(GL_LINES_ADJACENCY_EXT, 0, vertices->size()));
geom->setUseDisplayList(false);
// defaults
geom->setVertexArray(vertices);
geom->setColorArray(colors, osg::Array::BIND_OVERALL);
// set attributes
geom->setVertexAttribArray(0, vertices, osg::Array::BIND_PER_VERTEX);
geom->setVertexAttribArray(1, colors, osg::Array::BIND_PER_VERTEX);
// create shader
osg::ref_ptr<osg::Program> program = new osg::Program;
program->setName("shader");
osg::ref_ptr<osg::Shader> vertShader = new osg::Shader(osg::Shader::VERTEX);
if (!vertShader->loadShaderSourceFromFile("Shaders/bezier.vert"))
std::cerr << "Could not read VERTEX shader from file" << std::endl;
program->addShader(vertShader);
osg::ref_ptr<osg::Shader> geomShader = new osg::Shader(osg::Shader::GEOMETRY);
if (!geomShader->loadShaderSourceFromFile("Shaders/bezier.geom"))
std::cerr << "Could not read GEOMETRY shader from file" << std::endl;
program->addShader(geomShader);
osg::ref_ptr<osg::Shader> fragShader = new osg::Shader(osg::Shader::FRAGMENT);
if (!fragShader->loadShaderSourceFromFile("Shaders/bezier.frag"))
std::cerr << "Could not read FRAGMENT shader from file" << std::endl;
program->addShader(fragShader);
// program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragSource));
// geode
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(geom.get());
osg::StateSet* state = geode->getOrCreateStateSet();
state->setAttributeAndModes(program.get(), osg::StateAttribute::ON);
// add uniforms
osg::Uniform* modelViewProjectionMatrix = new osg::Uniform(osg::Uniform::FLOAT_MAT4, "ModelViewProjectionMatrix");
modelViewProjectionMatrix->setUpdateCallback(new ModelViewProjectionMatrixCallback(camera));
state->addUniform(modelViewProjectionMatrix);
osg::Uniform* viewportVector = new osg::Uniform(osg::Uniform::FLOAT_VEC2, "Viewport");
viewportVector->setUpdateCallback(new ViewportCallback(camera));
state->addUniform(viewportVector);
osg::Uniform* cameraEye = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "CameraEye");
cameraEye->setUpdateCallback(new CameraEyeCallback(camera));
state->addUniform(cameraEye);
float Thickness = 17.0;
float miterLimit = 0.75;
int segments = 30;
state->addUniform(new osg::Uniform("Thickness", Thickness));
state->addUniform(new osg::Uniform("MiterLimit", miterLimit));
state->addUniform(new osg::Uniform("Segments", segments));
// state settings
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setMode(GL_LINE_SMOOTH, osg::StateAttribute::ON);
osg::LineWidth* lw = new osg::LineWidth;
lw->setWidth(10.f);
state->setAttribute(lw, osg::StateAttribute::ON);
osg::BlendFunc* blendfunc = new osg::BlendFunc();
state->setAttributeAndModes(blendfunc, osg::StateAttribute::ON);
return geode.release();
}
int main(int, char**)
{
#ifdef _WIN32
::SetProcessDPIAware(); // to avoid high DPI issues
#endif // _WIN32
osgViewer::Viewer viewer;
osg::DisplaySettings::instance()->setNumMultiSamples(4); // smooth shadered lines
osg::ref_ptr<osg::Group> root = new osg::Group();
root->addChild(createBezierScene(viewer.getCamera()));
viewer.setSceneData(root.get());
viewer.setUpViewInWindow(100,100,OSG_WIDTH, OSG_HEIGHT);
/* depth settings */
osg::StateSet* state = root->getOrCreateStateSet();
state->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);
osg::ref_ptr<osg::Depth> depth = new osg::Depth(osg::Depth::LEQUAL);
state->setAttributeAndModes(depth, osg::StateAttribute::ON);
osg::Camera* camera = viewer.getCamera();
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
viewer.setCameraManipulator( new osgGA::TrackballManipulator);
viewer.realize();
while (!viewer.done()){
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
viewer.frame();
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment