Volumetric lines

There is that great volumetric lines algorithm by Sébastien Hillaire:
http://sebastien.hillaire.free.fr/index.php?option=com_content&view=article&id=57&Itemid=74
We have ported it to Unity, and the .unitypackage is available for download there.

Included in the package are a MonoBehavior, a prefab, and four shaders:

  • Additive
  • Alpha blended
  • Additive fast (with minimal fragment shader instructions, no _MainColor, etc.)
  • Alpha blended fast (with minimal fragment shader instructions, no _MainColor, etc.)

Just drag the the prefab in the scene and set a texture like the “Default-Particle”.
Enjoy.

1 Like

Brilliant! This looks gorgeous, with the light-saber effect and all. And best of all, pure shader-based displacement to screen-space!
While testing on Android, I had some weird glitches where some vertices where transformed in the wrong direction.
To make a long story short, turns out OpenGL ES 2 will normalize the mesh’s normals before they get to the vertex shader.

If you’re interested, I changed the shader so that the other point used for calculating line-direction gets calculated by using normalized normal + the current end-points position instead of expecting the correct position of the other end-point through the normal input.

           v2f vert (a2v v)
            {
                v2f o;
                o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);  
                float4 vMVP = mul(UNITY_MATRIX_MVP, v.vertex);
                float4 lol = float4(v.normal.x + v.vertex.x, v.normal.y + v.vertex.y, v.normal.z + v.vertex.z, 1.0);
                float4 otherMVP = mul(UNITY_MATRIX_MVP, lol);
                float2 lineDirProj = _LineWidth *  normalize((vMVP.xy/vMVP.w) - (otherMVP.xy/otherMVP.w));
                if (sign(otherMVP.w) != sign(vMVP.w))
                	lineDirProj = -lineDirProj;
                vMVP.x = vMVP.x + lineDirProj.x * v.texcoord1.x;
                vMVP.y = vMVP.y + lineDirProj.y * v.texcoord1.x;
                vMVP.x = vMVP.x + lineDirProj.y * v.texcoord1.y;
                vMVP.y = vMVP.y - lineDirProj.x * v.texcoord1.y;
				
                o.pos = vMVP;
                o.color = v.color;
                return o;
            }

I also changed the shader to allow vertex-colors so that the same material can be shared for all lines.

I made a small wrapper for handling multiple lines in my monobehaviour, and in the code that sets up my mesh you can see that the normals for the mesh are now being normalized from start.

	void SetupMesh(params VolLine[] lines)
	{		
		Vector3[] 	vtxPos = new Vector3[lines.Length * 8];
		Vector3[] 	vtxNorm = new Vector3[lines.Length * 8];
		Vector2[] 	vtxUV = new Vector2[lines.Length * 8];
		Vector2[] 	vtxUV2 = new Vector2[lines.Length * 8];
		Color[]		vtxCol = new Color[lines.Length * 8];
		int[] 		vtxInd = new int[lines.Length * 18];
		
		int iCnt = 0;
		int kCnt = 0;
		
		for (int i = 0; i < lines.Length; i++)
		{
			for (int j = 0; j < 8; j++)
			{
				Vector3 p1 = lines[i].p1;
				Vector3 p2 = lines[i].p2;
				Color	c1 = lines[i].c1;
				Color	c2 = lines[i].c1;   //c2;
				
				vtxPos[iCnt + j]	= j < 4 ? p1 : p2;
				vtxNorm[iCnt + j] 	= j < 4 ? (p2 - p1).normalized : (p1 - p2).normalized;  
				vtxUV[iCnt + j] 	= m_vline_texCoords[j];
				vtxUV2[iCnt + j] 	= m_vline_vertexOffsets[j];				
				vtxCol[iCnt + j]	= j < 4 ? c1 : c2;
			}
			for (int k = 0; k < 18; k++)
			{
				vtxInd[kCnt + k] = m_vline_indices[k] + iCnt;
			}
			
			iCnt += 8;
			kCnt += 18;
		}
		
		// Need to set vertices before assigning new Mesh to the MeshFilter's mesh property
		Mesh mesh = new Mesh();
		mesh.vertices = vtxPos;
		mesh.normals = vtxNorm;
		mesh.colors = vtxCol;
		mesh.uv = vtxUV;
		mesh.uv2 = vtxUV2;
		mesh.SetIndices(vtxInd, MeshTopology.Triangles, 0);
		GetComponent<MeshFilter>().sharedMesh = mesh;	
	}

Thank you for the excellent code-base, this will be AWESOME to use for retro games (+special effects)!

Best regards

Anders

HI Guys

Thanks for sharing your great work on this.
When using this method the view of the line along its direction is distorted. Sebastien has solved this issue here.
Do you have plans to update the Unity demo using this method?

Jared

Hi,

first of all, big thank you @Mistale for your improvement/Android-fix.
I will do some tests by myself and then update the code based on your suggestions.
I’d like to support multiple line segments (just like LineRenderer). Have you done some research in that direction? If not, I will keep you updated with my progress on that matter.

@Lumen Digital: We’re using the algorithm for mobile games. The quality-improved algorithm by Sebastien Hillaire apparently requires geometry shaders, therefore we’re not planning to implement this algorithm in the near future, since we’re overloaded with work right now. Might be that I happen to create a Unity port nevertheless, but I can’t promise anything.

An alternative fix for mobile devices would be the following #pragma statement in the shader:

#pragma glsl_no_auto_normalization

What do you think is better?
The original code + the #pragma statement has fewer vertex shader instructions than the version proposed by Mistale, I think?!

Another thing:
I’ve tried to support multiple line segments, but I’m a little bit stuck right now.
My idea was to pass per vertex: the vertex position, the position of the previous point (via normals), and the position of the next point (via tangents). Here is my current (non-optimal) code:

v2f vert (a2v v)
{
    v2f o;
    o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);  
    
    float4 vMVP = mul(UNITY_MATRIX_MVP, v.vertex);
    
    float4 prev = float4(v.normal.x, v.normal.y, v.normal.z, 1.0);
    float4 prevMVP = mul(UNITY_MATRIX_MVP, prev);
    
    float4 next = float4(v.tangent.x, v.tangent.y, v.tangent.z, 1.0);
    float4 nextMVP = mul(UNITY_MATRIX_MVP, next);
    
    float2 lineDirProjPrev = _LineWidth * normalize((vMVP.xy/vMVP.w) - (prevMVP.xy/prevMVP.w));
//                if (sign(prevMVP.w) != sign(vMVP.w))
//                	lineDirProjPrev = -lineDirProjPrev;
    	
    float2 lineDirProjNext = _LineWidth * normalize((vMVP.xy/vMVP.w) - (nextMVP.xy/nextMVP.w));
//                if (sign(nextMVP.w) != sign(vMVP.w))
//                	lineDirProjNext = -lineDirProjNext;
    	
    if (distance(prev, next) < 1.0)
    {
        vMVP.x = vMVP.x + lineDirProjPrev.x * v.texcoord1.x;
        vMVP.y = vMVP.y + lineDirProjPrev.y * v.texcoord1.x;
        vMVP.x = vMVP.x + lineDirProjPrev.y * v.texcoord1.y;
        vMVP.y = vMVP.y - lineDirProjPrev.x * v.texcoord1.y;
    }
    else 
    {
        vMVP.x = vMVP.x + ((lineDirProjPrev.x * v.texcoord1.x - lineDirProjNext.x * v.texcoord1.x) * .5);
        vMVP.y = vMVP.y + ((lineDirProjPrev.y * v.texcoord1.x - lineDirProjNext.y * v.texcoord1.x) * .5);
        vMVP.x = vMVP.x + ((lineDirProjPrev.y * v.texcoord1.y - lineDirProjNext.y * v.texcoord1.y) * .5);
        vMVP.y = vMVP.y - ((lineDirProjPrev.x * v.texcoord1.y - lineDirProjNext.x * v.texcoord1.y) * .5); 
    }
	
    o.pos = vMVP;
    return o;
}

which produces something like this:

Anyone any ideas?

I’m wondering if a trivial solution is even possible, because Unity’s LineRenderer produces similar glitches (The smaller green line at the right in the screen shot is a Unity LineRenderer)

@j00hi: I’m using the shader as-is for both single-segment and multi-segment lines. Treat every pair of points in the line as a separate line when building the procedural mesh, so that point 1 has a direction-vector to point 2, point 2 has a direction-vector to point 1, point 3 has a direction-vector to point 4 and so on…

I’m not sure that I understood your questions completely. If not, please let me know.

Btw, I’ve refined the shader a bit since my last post, so that it now displays lines correctly in the scene-view without the need for being in perspective mode.

Shader "FrontLine/FrontLineShaderConstWidth" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _LineWidth ("Line Width", Range(0.01, 100)) = 1.0
        _LightSaberFactor ("LightSaberFactor", Range(0.0, 1.0)) = 0.9
    }
    SubShader {
        Tags { "RenderType"="Geometry" "Queue" = "Transparent" }
        LOD 200
 
        Pass {
 
            Cull Off 
            ZWrite Off
            //ZTest LEqual
            Blend One One
            Lighting Off            
 
            CGPROGRAM
            #pragma exclude_renderers d3d11
            #pragma exclude_renderers d3d11_9x
            #pragma vertex vert
            #pragma fragment frag
            #pragma debug
            #pragma glsl_no_auto_normalization
 
            #include "UnityCG.cginc"
 
            sampler2D _MainTex;
            float _LineWidth;
            float _LightSaberFactor;
 
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
                float4 texcoord1 : TEXCOORD1;
 				float4 color : COLOR;
            }; 
 
            struct v2f
            {
                float4 pos : POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
            };
 
            v2f vert (a2v v)
            {
                v2f o;
                o.uv = v.texcoord.xy;
                float4 vMVP = mul(UNITY_MATRIX_MVP, v.vertex);          
                float4 otherMVP = mul(UNITY_MATRIX_MVP, float4(v.vertex + v.normal,1.0));  
				float2 lineDirProj = _LineWidth *  normalize((vMVP.xy/vMVP.w) - (otherMVP.xy/otherMVP.w));
				float4 vMV = mul(UNITY_MATRIX_MV, v.vertex);

                vMV.x = vMV.x + lineDirProj.x * v.texcoord1.x + lineDirProj.y * v.texcoord1.y;
                vMV.y = vMV.y + lineDirProj.y * v.texcoord1.x - lineDirProj.x * v.texcoord1.y;

				o.pos = mul(UNITY_MATRIX_P, vMV);
                o.color = v.color;
                return o;
            }
 
            float4 frag(v2f i) : COLOR
            {
                float4 tx = tex2D (_MainTex, i.uv);
                                
                if (tx.a > _LightSaberFactor)
                {
                	return float4(1.0, 1.0, 1.0, tx.a);
                }
                else 
                {
                	return tx * i.color; //_Color; 
                }
            }
 
            ENDCG
        }
    }
    FallBack "Diffuse"
}

Firstly, thank you both for providing these useful stuff! In case you are still able to monitor this thread, I was wondering about the following case. I have used the shader above to create a curved line, but I get the effect of the picture below, which makes the curve to look more like a spine, instead of a continuous line. Could you suggest of a workaround to reduce this effect. I have tried various methods, from different textures to changing the mesh’s vertices position, as well as different shaders(like this with tangents above) to no avail.

That’s because at the beginning and at the end of each line segment, basically 2 textures are drawn above each other. Additive blending or alpha blending results in such an effect. I suspect, you are just stitching together multiple line segments using the original technique, i.e. your arc consists of multiple volumetric lines. With the original technique, this is the effect you get.

I tried to overcome this limitation by the approach described in post #5. You can try that, but I cannot guarantee that it will work in all cases because I haven’t continued to work on that. However, maybe I will somewhen in the near future.

Hey Mistale, do you remember the reason for including those two pragma statements in the shader?

#pragma exclude_renderers d3d11
#pragma exclude_renderers d3d11_9x

Was there any problem with the d3d renderers?

Hey J00hi, I honestly don’t remember why I excluded those renderers… If the shader works without those lines, go for it! I have a vague memory of trying something that only worked with OpenGL, but I don’t see anything of that nature present in the current shader.

Ok, thanks - then I’ll just leave those #pragma statements out.

It’s been a while but I finally found some spare time to continue work on the volumetric lines. I’ve created an asset which will be available in the Asset Store for free. As an additional feature, I’ve added support for nicer looking line strips (without those “hinge joint”-like looking things between the line segments)

@dot_entity , that should be especially good news for you if you are still looking for a solution to your problem from post #7.

I hope, the asset will be available until the end of this week, it is currently in review. I’ll keep you updated.

@Mistale regarding the asset: I’ve incorporated some of the code changes which you have proposed into the shader code - most importantly, that mul(UNITY_MATRIX_P, vMV) calculation so that it works for both, orthographic and perspective viewports. I gave you credit for your support in the source code by referring to your Unty forum username “Mistale”. I hope you are fine with that?! If you want me to change something, include your mail addres, or something like that, please let me know. I won’t make any money with the asset, it will be free.

@j00hi : That’s perfectly fine by me :wink:

Hi j00hi! I am terribly sorry for being absent, but it turns out that I forgot to activate the ”watch thread" option and after posting I started working on other things and I just missed to check back. To this day though, my concern about these volumetric lines is yet to be fulfilled and your new information comes as a valuable present!

When all started, I just wanted to be able to “draw” lines in Unity (efficiently) and I was not expecting it would turn to be the trickiest task I met so far. I started my attempts by using this solution I found of drawing meshes with Graphics class, in order to save the overhead of creating gameobjects. It worked, but the lines were oriented to the direction they were created only. I learnt that a more efficient way than reconfiguring the mesh in every frame the camera moves (for a billboarding effect), would be to let a shader do it. I then found your post, but I had this little issue with the joints. You’re absolutely correct that the method I used when I posted, was to create multiple prefabs. Of course, this is something I want to avoid and, probably, I was not certain of what I was doing at this time. I am still at the process of learning the basics, I would say, and although I enjoy digging into all the different challenges, I am nevertheless far from efficient with all these game developing requirements.

Due to my mentioned insufficiency, it took my some time to refresh my memory regarding my previous tries with VolumetricLines. I finally remembered that the result in the picture of my previous post would be achieved with the shader Mistale provided. When I tried to paste the code of your #5 post into the Additive shader, there was the compiling complaint of not having tangent struct. I then somehow managed to combine the code in #5 with Mistale’s shader without errors, but then the material would ask for mesh tangents. I finally added a class that calculates the tangents, there where no errors, but despite that the mesh was there (I could see the wireframe), there were no drawn pixels. I thereafter quitted.

Since my long research with the shader method had not yielded very good results, my latest attempt was to create tube-like meshes, with the help of MegaShapes since I had the plug-in already, and just ignore the billboarding attempt. Now, however, I am looking forward for your updated package, in order to test it. I would value your advice here of what would be the most efficient, performance-wise, way to go, in order to create a lot of lines. Tube objects (I am also currently looking of how to combine several independent meshes in one game object with megashapes) or one (maybe a few chunks of a) big mesh that contains the volumetric line components that create the lines and billboard them with your shader? Or would your shader work with the Graphics method as well (I don’t see why not)?

Finally, I thank you so much for your first reply and I am grateful for the upcoming package. Please excuse my lengthy post, but I considered it as a way to balance my former absence. Somehow…:slight_smile:

In terms of performance, I am pretty sure that VolumetricLines rendering is quite the same as using a LineRenderer. It has a little computational overhead to create those volumetric look, of course, but that shouldn’t be really noticeable since it features a very fast shader-based implementation.

What VolumetricLines can achieve, what none of the other techniques can, is to let the lines appear volumetric. See the following screenshot. Depending on which texture you give the material, you can achieve the look of a volumetric laser shot or a solid tube.

On the screenshot, you can also see the new feature in action: the volumetric line-strip. It has some limitations, but for some cases (when the line strip behaves nicely and doesn’t bend too much between the segments) it works quite well.

The other options from the website you refered to are also great, use one of them if you have to draw a huge amount of lines which don’t have to appear volumetric.

Hi j00hi! I was coming back to the forum to make some more clear inquiries than those with the previous post and it seems you were patient enough to read the whole thing, so thanks a lot. It’s the volumetric effect that I desire the most (actually this arc you have in the screenshot, is exactly the geometry I want to use and in the image it looks perfect), so I’ll mind performance later. I have a question about this line strips of yours however (both in this screenshot and that on post #5). Have you edited the VolumetricLineBehavior to create one mesh that has multiple of these 3ple-quad components? By just adding the volumetric line shader on a unity quad (for example), would not give the billboarding effect. Are these vertexOffset uvs that make the difference? Maybe I should simply wait for the new package.

The arc looks quite good from this perspective but there are some little glitches when viewed from steeper angles… you’ll see. :wink: So, it’s not perfect but I believe, it’s the best which can be achieved with the volumetric line rendering algorithm 1 by Sebastien Hillaire. He has proposed another volumetric line rendering algorithm which is computationally more expensive but also looks better:

But I haven’t implemented the second one and I can’t tell if I ever will.

The line strip uses the shader from post #5 without major modifications, actually. What’s new is that I am creating a tailored mesh geometry for the line strip. One line strip has one underlying mesh which has all the data needed for the shader (stored in vertices, normals, tangents, uv, uv2). The source code is included in the package, so you can explore it in detail.

I don’t know when the Unity guys will review the asset. It’s in pending review state since almost one week. If you are in urgent need of the asset, i can send you a .unitypackage beforehand.

Thanks j00hi! You’ve been absolutely helpful and encouraging, while I think the tube-mesh objects method would not work very well and, concerning performance, I would have -sooner or later- to review the shaders method. Thank you for the links, I had visited these places before, I downloaded the assets, but due to my knowledge level, I didn’t know what to do with them and I decided to discreetly look away :). As for the glitches, I think I can imagine what are you talking about and I am sure they won’t bother at all. While I was trying to deal with this overlapping additive effect before, I converted the volLineBehaviour script to generate single quads and despite the effect on the image below, which I found from that time, I was even thinking to use it as the final solution. It was far from ideal though and it was then that I posted here. Anyway, enough with this “nostalgic flashback”, I’m now looking forward to the new package, i could wait for the official release and I could easily work with other things in the meanwhile. On the other hand though, I’m excited to test and experiment with the new goodies, so only in the case it is not a big trouble…ok I’ll pm you my email! Thanks! ε(

Well, I don’t seem to find the way to send you a private message. But it’s ok, I can wait for the public release.

Thanks to j00hi’s good work and generosity to offer me the new package earlier than its public release, I had the chance to try it and to find out how cool it is, with its new features and all! And, of course -as also j00hi reminded me-, deep respect goes to Sébastien Hillaire, the inventor of the algorithm.

It took me first some time to realize that the volumetricLine classes were nicely organized in a namespace and after I figured this out, it took me some more to introduce it in my things (I am using a method of creating arcs and I messed up local and world spaces, however, j00hi’s one, that is to draw a part of sinus function, is remarkably smart). Finally, I wanted to have vertex colors, so I added some more lines of code (both in the behavior and shader scripts) and I was ready to go. Well, the mentioned glitches are there, but they are hardly noticeable. What will might concern me are the instances of the material used that have to be created for the lines not to interfere (thus, one draw call per line object). Is it the case, due to the properties of the shader? However, I have tested it with decades of lines and the performance is significantly good. So thanks again to everyone who contributed!!


(that is 3 lines)

1 Like