//********************************************************************** // Geometric Edge Rendering Using Edge Mesh Utility Functions //********************************************************************** // Contains all basic and utility functions for doing edge detection/rendering using Morgan McGuire and John F. Hughes' technique // See here for the original paper: http://graphics.cs.williams.edu/papers/EdgesNPAR04/index.html // This shader cannot run without the use of a second vertex shader that uses these functions // All content © 2009 DigiPen (USA) Corporation, all rights reserved. //---------------------------------------------------------------------- // Input variable system //---------------------------------------------------------------------- // gl_Vertex.xyzw = v0.xyz, v1.x // gl_Color.xyzw = v1.yz, v2.xy // gl_MultiTexCoord0.xyzw = v2.z, v3.xyz // gl_MultiTexCoord1.xyzw = n0.xyz, n1.x // gl_MultiTexCoord2.xyzw = n1.yz, r, i //---------------------------------------------------------------------- //---------------------------------------------------------------------- // Common variables meanings not explained elsewhere //---------------------------------------------------------------------- // vec4 s0 - Screen-space v0 point (plus perspective z and w) // vec4 s1 - Screen-space v1 point (plus perspective z and w) // vec2 p - Screen-space edge perpendicular // vec2 m0 - Screen-space n0 // vec2 m1 - Screen-space n1 //---------------------------------------------------------------------- // Notes: // gl_Position is always output with the homogeneous w coordinate multiplied back in, to prevent infinity wrap-around uniform float AspectRatio; // Constants const float thetaR = 1.58; // Slightly over 90 degrees, could be made uniform const float thetaV = 1.58; // Slightly over 90 degrees, could be made uniform const float OVERALL_THICKNESS = 0.12; // Relative thickness of output edges const float INVERSE_THREE = 1.0 / 3.0; //--------------------------------------------------------------- // Necessary Prototypes //--------------------------------------------------------------- vec2 AspectCorrector(); //--------------------------------------------------------------- // Geometric Value Generation //--------------------------------------------------------------- vec4 v0Get() { return vec4(gl_Vertex.xyz, 1.0); } vec4 v1Get() { return vec4(gl_Vertex.w, gl_Color.xy, 1.0); } vec4 v2Get() { return vec4(gl_Color.zw, gl_MultiTexCoord0.x, 1.0); } vec4 v3Get() { return vec4(gl_MultiTexCoord0.yzw, 1.0); } void VerticesGet(out vec4 v0, out vec4 v1, out vec4 v2, out vec4 v3) { v0 = vec4(gl_Vertex.xyz, 1.0); v1 = vec4(gl_Vertex.w, gl_Color.xy, 1.0); v2 = vec4(gl_Color.zw, gl_MultiTexCoord0.x, 1.0); v3 = vec4(gl_MultiTexCoord0.yzw, 1.0); } vec3 n0Get() { return vec3(gl_MultiTexCoord1.xyz); } vec3 n1Get() { return vec3(gl_MultiTexCoord1.w, gl_MultiTexCoord2.xy); } void ScreenEdgeValuesGet(in vec4 v0, in vec4 v1, out vec4 s0, out vec4 s1, out vec2 p) { // Object space -> View space -> Clip space s0 = gl_ModelViewProjectionMatrix * v0; s1 = gl_ModelViewProjectionMatrix * v1; // Normalized device space -> Screen-space s0.xy = (s0.xy / s0.w) * AspectCorrector(); s1.xy = (s1.xy / s1.w) * AspectCorrector(); // Normalized perpendicular of the screen-space edge p = normalize(vec2(s0.y - s1.y, s1.x - s0.x)); } vec2 mGet(in vec4 v, in vec3 n, in vec4 s) { // Object space -> View space -> Clip space vec4 mPoint = gl_ModelViewProjectionMatrix * (v + vec4(n.xyz, 0.0)); // Normalized device space -> Screen-space mPoint.xy = (mPoint.xy / mPoint.w) * AspectCorrector(); return normalize(mPoint.xy - s.xy); } //--------------------------------------------------------------- // Miscellaneous //--------------------------------------------------------------- // Shifts the vertex behind the near plane, erasing it during the rasterization step void NoDraw() { gl_Position = gl_ModelViewProjectionMatrix * vec4(0, 0, -1, 0); } bool iEquals(in int test) { return test == int(gl_MultiTexCoord2.w); // return test == i; } // Get triangle-based indicators for generating the end caps in the correct order int jGet() { vec4 v0 = v0Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0, v1Get(), s0, s1, p); vec2 m0 = mGet(v0, n0Get(), s0); return dot(m0, p) > 0.0 ? int(gl_MultiTexCoord2.w) : 2 - int(gl_MultiTexCoord2.w); // return dot(m0, p) > 0.0 ? i : 2 - i; } vec4 CenterPoint(in vec4 a, in vec4 b, in vec4 c) { return (a + b + c) * INVERSE_THREE; } vec2 AspectCorrector() { return vec2(AspectRatio, 1.0); } // Depth-normalized scalar thickness modifier for edge end associated with point float ThicknessGet(in vec4 point) { return -OVERALL_THICKNESS / (gl_ModelViewMatrix * point).z; } //--------------------------------------------------------------- // Draw Points //--------------------------------------------------------------- // v0 void DrawLeftEdgePoint() { gl_Position = gl_ModelViewProjectionMatrix * v0Get(); } // v1 void DrawRightEdgePoint() { gl_Position = gl_ModelViewProjectionMatrix * v1Get(); } // v0 + p (outward) void DrawTopLeftPoint() { vec4 v0 = v0Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0, v1Get(), s0, s1, p); vec2 m0 = mGet(v0, n0Get(), s0); gl_Position = vec4((s0.xy + p * ThicknessGet(v0) * sign(dot(m0, p))) / AspectCorrector() * s0.w, s0.zw); } // v1 + p (outward) void DrawTopRightPoint() { vec4 v1 = v1Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0Get(), v1, s0, s1, p); vec2 m0 = mGet(v0Get(), n0Get(), s0); gl_Position = vec4((s1.xy + p * ThicknessGet(v1) * sign(dot(m0, p))) / AspectCorrector() * s1.w, s1.zw); } // v1 + p (outward relative to m1) void DrawTopRightNormalCorrectedPoint() { vec4 v1 = v1Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0Get(), v1, s0, s1, p); vec2 m1 = mGet(v1, n1Get(), s1); gl_Position = vec4((s1.xy + p * ThicknessGet(v1) * sign(dot(m1, p))) / AspectCorrector() * s1.w, s1.zw); } // v0 - p (inward) void DrawBottomLeftPoint() { vec4 v0 = v0Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0, v1Get(), s0, s1, p); vec2 m0 = mGet(v0, n0Get(), s0); gl_Position = vec4((s0.xy - p * ThicknessGet(v0) * sign(dot(m0, p))) / AspectCorrector() * s0.w, s0.zw); } // v1 - p (inward) void DrawBottomRightPoint() { vec4 v1 = v1Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0Get(), v1, s0, s1, p); vec2 m0 = mGet(v0Get(), n0Get(), s0); gl_Position = vec4((s1.xy - p * ThicknessGet(v1) * sign(dot(m0, p))) / AspectCorrector() * s1.w, s1.zw); } // v0 + m0 (outward) void DrawLeftNormalPoint() { vec4 v0 = v0Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0, v1Get(), s0, s1, p); vec2 m0 = mGet(v0, n0Get(), s0); gl_Position = vec4((s0.xy + m0 * ThicknessGet(v0)) / AspectCorrector() * s0.w, s0.zw); } // v1 + m1 (outward) void DrawRightNormalPoint() { vec4 v1 = v1Get(); vec4 s0, s1; vec2 p; ScreenEdgeValuesGet(v0Get(), v1, s0, s1, p); vec2 m1 = mGet(v1, n1Get(), s1); gl_Position = vec4((s1.xy + m1 * ThicknessGet(v1)) / AspectCorrector() * s1.w, s1.zw); } // n0 void DrawLeftNormalNormalized() { gl_Position = gl_ModelViewProjectionMatrix * (v0Get() + vec4(n0Get(), 0.0)); } // n1 void DrawRightNormalNormalized() { gl_Position = gl_ModelViewProjectionMatrix * (v1Get() + vec4(n1Get(), 0.0)); } //--------------------------------------------------------------- // Edge Creation //--------------------------------------------------------------- // For drawing a line edge, having no specific width or texture void CreateLine() { if (iEquals(0)) { DrawLeftEdgePoint(); } else { DrawRightEdgePoint(); } } // For drawing a full quad, extending inside and out of the mesh void CreateFullQuad() { if (iEquals(0)) { DrawBottomLeftPoint(); } else if (iEquals(1)) { DrawBottomRightPoint(); } else if (iEquals(2)) { DrawTopRightPoint(); } else { DrawTopLeftPoint(); } } // For drawing a half quad, extending only outside of the mesh void CreateHalfQuad() { if (iEquals(0)) { DrawLeftEdgePoint(); } else if (iEquals(1)) { DrawRightEdgePoint(); } else if (iEquals(2)) { DrawTopRightPoint(); } else { DrawTopLeftPoint(); } } //--------------------------------------------------------------- // End Cap Creation //--------------------------------------------------------------- void CreateStartEndCap() { int j = jGet(); // Output triangle, with two sides along p and m0 vectors if (j == 0) { DrawLeftEdgePoint(); } else if (j == 1) { DrawTopLeftPoint(); } else { DrawLeftNormalPoint(); } } void CreateFinishEndCap() { int j = jGet(); // Output triangle, with two sides along p and m1 vectors if (j == 0) { DrawRightEdgePoint(); } else if (j == 1) { DrawTopRightNormalCorrectedPoint(); } else { DrawRightNormalPoint(); } } //--------------------------------------------------------------- // Hex Edge Creation //--------------------------------------------------------------- void CreateLeftHexEdge() { // Output quad verts to make up an "outward" half hexagon if (iEquals(0)) { DrawLeftEdgePoint(); } else if (iEquals(1)) { DrawRightEdgePoint(); } else if (iEquals(2)) { DrawTopLeftPoint(); } else { DrawLeftNormalPoint(); } } void CreateRightHexEdge() { // Output quad verts to make up an "outward" half hexagon if (iEquals(0)) { DrawRightEdgePoint(); } else if (iEquals(1)) { DrawRightNormalPoint(); } else if (iEquals(2)) { DrawTopRightPoint(); } else { DrawTopLeftPoint(); } } //--------------------------------------------------------------- // Plug Edge Creation //--------------------------------------------------------------- void CreateLeftPlug() { if (iEquals(0)) { DrawBottomLeftPoint(); } else if (iEquals(1)) { DrawBottomRightPoint(); } else if (iEquals(2)) { DrawTopLeftPoint(); } else { DrawLeftNormalPoint(); } } void CreateRightPlug() { if (iEquals(0)) { DrawBottomRightPoint(); } else if (iEquals(1)) { DrawRightNormalPoint(); } else if (iEquals(2)) { DrawTopRightPoint(); } else { DrawTopLeftPoint(); } } //--------------------------------------------------------------- // Normal Creation //--------------------------------------------------------------- void DrawVertexNormals() { if (iEquals(0)) { DrawLeftEdgePoint(); } else if (iEquals(1)) { DrawLeftNormalNormalized(); } else if (iEquals(2)) { DrawRightEdgePoint(); } else { DrawRightNormalNormalized(); } } void DrawFaceNormals() { if (iEquals(0)) { gl_Position = gl_ModelViewProjectionMatrix * CenterPoint(v0Get(), v1Get(), v2Get()); } else if (iEquals(1)) { vec4 v0 = v0Get(); vec4 v1 = v1Get(); vec4 v2 = v2Get(); vec3 nA = normalize(cross(v1.xyz - v0.xyz, v2.xyz - v0.xyz)); gl_Position = gl_ModelViewProjectionMatrix * (CenterPoint(v0, v1, v2) + vec4(nA, 0.0)); } else if (iEquals(2)) { vec4 v0 = v0Get(); vec4 v3 = v3Get(); if (v0 == v3) { NoDraw(); } else { gl_Position = gl_ModelViewProjectionMatrix * CenterPoint(v0, v1Get(), v3); } } else { vec4 v0 = v0Get(); vec4 v3 = v3Get(); if (v0 == v3) { NoDraw(); } else { vec4 v1 = v1Get(); vec3 nB = normalize(cross(v3.xyz - v0.xyz, v1.xyz - v0.xyz)); gl_Position = gl_ModelViewProjectionMatrix * (CenterPoint(v0, v1, v3) + vec4(nB, 0.0)); } } } //--------------------------------------------------------------- // Edge Detection //--------------------------------------------------------------- // Returns true if the current edge is drawable bool IsEdge() { vec4 v0, v1, v2, v3; VerticesGet(v0, v1, v2, v3); // Test for boundary and marked edges if (v0 == v3) { return true; } // Two polygon face normals vec3 nA = normalize(cross(v1.xyz - v0.xyz, v2.xyz - v0.xyz)); vec3 nB = normalize(cross(v3.xyz - v0.xyz, v1.xyz - v0.xyz)); // View vector vec3 eye = (gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0)).xyz; // Eye position in world-space vec3 view = normalize(eye - v0.xyz); // Test for contour edges if (dot(nA, view) < 0.0 != dot(nB, view) < 0.0) { return true; } float normalAngle = dot(nA, nB); float facingAngle = dot(normalize(v3.xyz - v2.xyz), nA); // Test for ridge crease and valley crease edges if ((normalAngle < -cos(thetaR) && facingAngle <= 0.0) || (normalAngle < -cos(thetaV) && facingAngle > 0.0)) // Optimized test if thetaR and thetaV are equal: normalAngle < -cos(thetaR) { return true; } // Not a drawable edge return false; } // Returns true if the current edge is drawable, but ignores contour edges bool IsNonContourEdge() { vec4 v0, v1, v2, v3; VerticesGet(v0, v1, v2, v3); // Test for boundary and marked edges if (v0 == v3) { return true; } // Two polygon face normals vec3 nA = normalize(cross(v1.xyz - v0.xyz, v2.xyz - v0.xyz)); vec3 nB = normalize(cross(v3.xyz - v0.xyz, v1.xyz - v0.xyz)); float normalAngle = dot(nA, nB); float facingAngle = dot(normalize(v3.xyz - v2.xyz), nA); // Test for ridge crease and valley crease edges if ((normalAngle < -cos(thetaR) && facingAngle <= 0.0) || (normalAngle < -cos(thetaV) && facingAngle > 0.0)) // Optimized test if thetaR and thetaV are equal: normalAngle < -cos(thetaR) { return true; } // Not a drawable edge return false; } // Returns true if the edge is both drawable and a contour edge bool IsContourEdge() { vec4 v0, v1, v2, v3; VerticesGet(v0, v1, v2, v3); // Two polygon face normals vec3 nA = normalize(cross(v1.xyz - v0.xyz, v2.xyz - v0.xyz)); vec3 nB = normalize(cross(v3.xyz - v0.xyz, v1.xyz - v0.xyz)); // View vector vec3 eye = (gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0)).xyz; // Eye position in world-space vec3 view = normalize(eye - v0.xyz); // Test for contour edges if (dot(nA, view) < 0.0 != dot(nB, view) < 0.0) { return true; } return false; }