Last active
February 24, 2024 23:00
-
-
Save vicrucann/8b1df63e12169d0a5dde5112d81a980d 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#version 330 | |
in VertexData{ | |
vec4 mColor; | |
} VertexIn; | |
void main(void) | |
{ | |
gl_FragColor = VertexIn.mColor; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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