Billboard shader using vertex offsets in color or uv2 data

I’m working on a poor man’s terrain replacement project. It creates a mesh from a heightmap, has lod handling, all that.
Here’s some screenshots, if you look at them on another tab and zoom in you can see that the distant trees are very dense, and very distant. I haven’t been able to get this kind of distance and density using Unity’s billboard trees.

http://forum.unity3d.com/threads/188495-Procedural-Terrain-with-Dense-Forests/page3

The way I handle distant trees is, they are just tall grass basically. It’s two bisecting planes. 8 Vertices, 4 polygons. There is no “billboard” rotation to always face the camera. I create patches of trees, and combine them into one mesh. The performance is great, in my opinion. Obviously, there are some “gotchas”, but it works well for my needs. The upside is, I don’t have to manage each individual tree polygon, just the mesh patches. I suspect Unity draws and calculates its billboard trees on an individual basis.

Ok, here is what I need to know.

I want to just use billboard trees, locked to the Y axis.
But the challenge is…each tree quad would be part of a mesh of other tree quad polygons.
The trick as I see it is, each vertex needs to store the vertex offset from the pivot, AND the location of the pivot itself. This would allow you to rotate each vertex around the pivot to face the camera. Why not have each vertex coordinate the actual pivot, and the UV2 data be the offset data?

So, four vertices. A tree polygon, 5 meters wide, 10 meters tall. Pivot is in the center at 0.0 (local to the polygon vertices, NOT the mesh vertices).
This quad is to be positioned at 12.0, 10, 3.5 in world coordinates.

Vert 1 (bottom left, on the ground) - UV2 would be new vector2(-2.5f, 0)
Vert 2 (top left) - UV2 would be new vector2(-2.5.0f, 10.0f)
Vert 3 (top right) - UV2 would be vector2(2.5f, 10.0f)
Vert 4 (bottom right) - UV2 would be vector2 (2.5f, 0.0f)
Each vertex coordiante would actually be the quad pivot coordinate…12.0, 10, 3.5.

Again, I want this to be locked to the Y axis. So, it would only be adjusting the vertex X and Z positions, to avoid the “tilting” or bowing effect when you are nearby and tilt the camera. I could probably do this in C# or something pretty easily, but I don’t know how to do it in a vertex shader.

So, in summary…with the UV2 coordinates for a quad holding offsets of each vertex from the quad pivot, would it be possible for a vertex shader to use this to rotate those to face the camera. Even if the polygons are part of a larger mesh. This way, even a giant mesh of polygons could be used for distant billboards. Maybe this is how the particle system works, I don’t know.

Really hoping for some advice or direction on this.

Thanks,
JC

Hope this helps

Shader "Crankshaft/Vegetation/Billboard Tree Tinted" {
Properties {
    _Color ("Tint Color", Color) = (1,1,1,1)
    _MainTex ("Texture", 2D) = "white" { }
	_Speed ("Wave Speed", Float) = 0.12
}


SubShader {
	Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="Opaque"}
	//Cull Back Lighting Off ZWrite Off Fog { Mode Off }
	//Blend One One
    Pass {
		AlphaTest Greater 0.25

CGPROGRAM 
#pragma vertex vert 
#pragma fragment frag

#include "UnityCG.cginc"
//#include "../CrankshaftShared.cginc" 

float4 _Color;
sampler2D _MainTex;
float _Speed;

struct appdata {
    float4 vertex : POSITION;
    float4 texcoord : TEXCOORD0;//texture - uv1
	float4 texcoord1 : TEXCOORD1;//billboard offset - uv2
	float4 color : COLOR;
};

struct v2f {
    float4  pos : SV_POSITION;
    float2  uv : TEXCOORD0;
	float4 color : COLOR;
};

v2f vert (appdata v)
{
    v2f o;
	//vertical billboarding
	float3 eyeVector = ObjSpaceViewDir( v.vertex );
    
	float3 upVector = float3(0,1,0);
	float3 sideVector = normalize(cross(eyeVector,upVector));

	float3 finalposition = v.vertex;
	finalposition += (v.texcoord1.x) * sideVector;
	finalposition += (v.texcoord1.y) * upVector;
				
        //waves
	
	float4 pos = float4(finalposition, 1);
				
	o.pos = mul( UNITY_MATRIX_MVP, pos );

    o.uv = v.texcoord.xy;
	o.color = v.color;
    o.color.a = 1;//fragment shader need one, we use alpha in vertex shader to waving

    return o;
}

half4 frag (v2f i) : COLOR
{
	return tex2D(_MainTex, i.uv) *  _Color;
}
ENDCG

    }
}


Fallback "VertexLit"
}

Neyl, thank you very much! I was hoping for some direction on whether or not it was possible, but you appear to have given a complete solution. I’ll test this as soon as I can, hopefully this evening or the next.

First, the billboard shader works great!
The first screenshot below is from how I was doing it before. Soft edge one from the wiki:

http://wiki.unity3d.com/index.php/Transparent_Diffuse_Two_Pass_Lit_Shadows

I was using two planes bisecting, so more planes. No billboarding. 2.4M triangles, 236 draw calls, 4.1m vertices. 35 fps (still not bad, seeing as how there are SO many trees).

The one Neyl posted, using billboards:
991k triangles (less than half), 155 draw calls (the first shader was two pass, for softer trees), 1.4M vertices. 95 FPS.
So…in terms of memory use, I would say it is probably a huge thing.

Here are screenshot comparisons. Open in another tab for more detail.

Old:

New:

Ok, so…sadly, quality suffers a little because it’s not using a softer edge shader, and the normals of the vertices do not seem to affect lighting.

Neyl, I hate to impose further, so I’ll just ask in general…would anyone be willing to add to this shader, to provide better shading and soft edges? I’m working on packaging this up for the community, it features both terran, grass and distant tree generation. It would be shame to lose the normal lighting and soft edges, I feel they are very attractive. They don’t need shadows, just normals that I can set to match the terrain normal, thus giving them a much better shading.

I’ll also dig around and see if I can find a simpler shader than the two pass one, that I can merge with Nyel’s.
Thanks Neyl, I’m very excited that the billboard shader is not only possible but saves a ton on memory and performance.

Now I’m not an expert with shaders (actually, I just started with learning the syntax etc.) But here is the following that I do know to get a basic normal-based vertex lighting (anyone, please correct me if I’m wrong at anything):

In the appdata structure, Define a variable as follows:

float3 normal : NORMAL;

This will make the shader aware of the normal of the faces

In the vertex shader, this normal has to be multiplied with some matrix (dont know which one though, there are a lot of them) and then stored v2f struct (I think as a float3)

Then in the fragment shader, you apply a Lambert lighting function: “the normal of the point dot the light direction * the attenuation * 2.”

A tutorial of it can be found here (scroll to about halfway) : http://unitygems.com/noobs-guide-shaders-5-bumped-diffuse-shader/

I don’t understand all of it though, so these are just some pointers to the things I know. I don’t know where the attenuation is coming from and what it really represents. You also have to think about “how many” or what lights you want to support. For the purpose of far-distance billboards, I guess only a directional light would suffice in most cases.

Thanks, wasstraat65. I would expect only the main directional light to be taken into account.
And, I think I could get by without the soft edges, as long as we could take the normal lighting into account.

Very optimized shader.
Cool. Need just to complete with speed var . now there is no wind :wink:
However there are other crankshaft shader or pack to share or sell in asset store?

Thanks

This is what I changed it to, still nor lighting based on normals :frowning:

Shader "Crankshaft/Vegetation/Billboard Tree Tinted"
{
	Properties
	{
		_Color ("Tint Color", Color) = (1,1,1,1)
		_MainTex ("Texture", 2D) = "white" { }
		_Speed ("Wave Speed", Float) = 0.12
	} 

	SubShader 
	{
		Tags { "Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="Opaque"}
		Cull Back Lighting On// ZWrite On Fog// { Mode Off }
		//Blend One One
		Pass 
		{
			AlphaTest Greater 0.25
			
			CGPROGRAM 

			#pragma vertex vert
			#pragma fragment frag			 

			#include "UnityCG.cginc"
			//#include "../CrankshaftShared.cginc" 			 

			float4 _Color;
			sampler2D _MainTex;
			float _Speed;			 

			struct appdata
			{
				float4 vertex : POSITION;
				float4 texcoord : TEXCOORD0;//texture - uv1
				float4 texcoord1 : TEXCOORD1;//billboard offset - uv2
				float3 normal: NORMAL;
				float4 color : COLOR;
			};

			struct v2f 
			{
				float4  pos : SV_POSITION;
				float2  uv : TEXCOORD0;
				float3 color : COLOR;
			};

			v2f vert (appdata v)
			{
				v2f o;
				//vertical billboarding
				float3 eyeVector = ObjSpaceViewDir( v.vertex );
				float3 upVector = float3(0,1,0);
				float3 sideVector = normalize(cross(eyeVector,upVector));
				float3 finalposition = v.vertex;
				finalposition += (v.texcoord1.x) * sideVector;
				finalposition += (v.texcoord1.y) * upVector;							
				float4 pos = float4(finalposition, 1);
				o.pos = mul( UNITY_MATRIX_MVP, pos );
				o.uv = v.texcoord.xy;
				o.color = ShadeVertexLights(v.vertex, v.normal);
				//o.color.a = 1;//fragment shader need one, we use alpha in vertex shader to waving
				return o;
			}
			
			half4 frag (v2f i) : COLOR
			{
				return tex2D(_MainTex, i.uv) *  _Color;
			}
			
			ENDCG
		}

	}

	Fallback "VertexLit"

}

I added passing in the normal in the vertex shader, and using ShadeVertexLights. Also, I guess ShadeVertexLights returns a Vector3, so I had to lose the alpha channel.

I am interested in using this shader but not sure how to set up the uv’s. Any chance of some help?

I’m unfamiliar with this, but i’ve done some similar stuff where i had to recalculate the normal based on the deformation.

I can’t quite make out the texcoord stuff here without trying it, but it seems like you are rotating your billboard, but keeping normals the same.

//this should be your new normal
float3 newNormal = normalize(cross(sideVector,upVector));

I cant make out the newposition stuff in the shader, i’ll have to sketch it, But theoretically, you don’t need to use uv2, you can do all your transformations in the shader. The same way this shader has a new position, and still knows what v.vertex are. You can go like

myUV2 = uv;

and then do whatever you want, like, i would offset it by -0.5 so i get the 0 in the middle, and vertices having +/- 0.5,

myUV2.x -= 0.5;

will center the ‘pivot’ of sorts,

btw

// this can become...
float3 finalposition = v.vertex;
//....
float4 pos = float4(finalposition, 1);

//...this
float4 finalPos = v.vertex;

finalPos.xyz += .... //stuff in here was float3


//this way you store one less variable, finalPos.w just stays 1, from v.vertex.w because you call it as float 4 (x,y,z,w) above

I can give it some more thought, since im not sure what this particular shader does yet.

Here’s a link to the dropbox package I have that uses the shader, hope it is useful!

http://forum.unity3d.com/threads/188495-Procedural-Terrain-with-Dense-Forests/page4

Got the bill-boarding to work at last but not getting any lighting, also how would you go about rotation on all axis not just Y?

I bought a book on Unity shaders this week, it looks interesting. Hopefully I can fix the lighting issue.
In the terrain project, I believe it uses the terrain normal for the tree polys, so that the shading matches. This should improve the visual quality a lot. I just need to get back home and try it out (been out of town this past week).
As far as rotation on other axis…can’t say. You mean to have them always face the camera, with no axis alignment?

Yeah I could do with always facing the camera from any angle, I think it’s possible but I have no idea how.

try this:
http://forum.unity3d.com/threads/181287-Billboard-sprite-shader

Had a look at your link but seems to have trouble with multiple billboards. The shader here works with combined meshes also.

I’m trying to combine this thread shader with the one Lulucifer linked, but for now I’m failing miserably. Only managed to center the pivot thats it.

Well I have had some success in getting lighting to work. Not sure what I was doing as I’m not good with code, basically I found a vertex lighting cg shader and pasted in the uv offset transforms. Also had to recalculate the normals.
Still cant figure out always face camera?.

Shader "Custom/Billboard Tree Tinted" {
      Properties {
        _MainTex ("Texture 1", 2D) = "white" {}
        _Ambient("Ambient Color",Color) = (0.3,0.3,0.3,1)
      }
      SubShader {
      Tags { "RenderType"="Opaque"}
      Pass {
           Blend SrcAlpha OneMinusSrcAlpha  
           Tags { "LightMode" = "Vertex" }//otherwise no light related values will be filled
           CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #include "UnityCG.cginc"
   
           sampler2D _MainTex;
           fixed4 _Ambient;
           half4 _MainTex_ST;
           float4 _Color;
           struct appdata
                {
                    float4 vertex : POSITION;
                    float4 texcoord : TEXCOORD0;//texture - uv1
                    float4 texcoord1 : TEXCOORD1;//billboard offset - uv2
                    float3 normal: NORMAL;
                    float4 color : COLOR;
                };
   
           struct v2f{
               fixed4 position: POSITION;
               half2 uv_MainTex:TEXCOORD0;
               fixed4 color: COLOR;
           };
   
           v2f vert (appdata_full v) {
               v2f o;
               
               
        float3 eyeVector = ObjSpaceViewDir(v.vertex);
                    float3 upVector =float3(0,1,0);
                    float3 sideVector = normalize(cross(eyeVector,upVector));
                    float3 finalposition = v.vertex;
                    finalposition += (v.texcoord1.x) * sideVector;
                    finalposition += (v.texcoord1.y) * upVector;                           
                    float4 pos = float4(finalposition, 1);
               o.position = mul(UNITY_MATRIX_MVP, pos );
               v.normal = float4(finalposition, 1);
               
               o.uv_MainTex = TRANSFORM_TEX(v.texcoord.xy , _MainTex);
    
               // per vertex light calc
               fixed3 lightDirection;
               fixed attenuation;
               // add diffuse
               if(unity_LightPosition[0].w == 0.0)//directional light
               {
                  attenuation = 2;
                  lightDirection = normalize(mul(unity_LightPosition[0],UNITY_MATRIX_IT_MV).xyz);
               }
               else// point or spot light
               {
                  lightDirection = normalize(mul(unity_LightPosition[0],UNITY_MATRIX_IT_MV).xyz - v.vertex.xyz);
                  attenuation = 1.0/(length(mul(unity_LightPosition[0],UNITY_MATRIX_IT_MV).xyz - v.vertex.xyz)) * 0.5;
               }
               fixed3 normalDirction = normalize(v.normal);
               fixed3 diffuseLight =  unity_LightColor[0].xyz * max(dot(normalDirction,lightDirection),0);
               // combine the lights (diffuse + ambient)
               
               o.color.xyz = diffuseLight * attenuation + _Ambient.xyz;
               o.color.a = 1;
               return o;
           }
   
           fixed4 frag(v2f i):COLOR{
              fixed4 c = tex2D(_MainTex, i.uv_MainTex);   
              return  c * i.color;
           }
           ENDCG
      }
      }
Fallback "Mobile/Diffuse"
}

This shader is not working as a billboard shader and it also dsnt have that alpha chanel in it which the shader before has why?

Edited shader so alpha should work now. Have you set up your UVs correctly?