in Shaders, Tutorial, Unity

3D Printer Shader Effect – Part 1

This tutorial will recreate the 3D printer effect seen in games such as Astroneer and Planetary Annihilation. It’s an interesting effect that shows an object in the process of being created. Despite looking simple, there are many challenges that are far from being trivial.

a1

This is a two part tutorials:

A link to download the Unity package (code, shader and 3D models) is provided at the end of the tutorial.

Introduction: A First Attempt

In order to replicate this effect, let’s start with something simpler. A shader that colours an object differently, depending on its position. To achieve this, we need to access the world position of the pixels being drawn. This is possible by adding the field worldPos to the Input structure of a Unity 5 surface shader.

struct Input {
	float2 uv_MainTex;
	float3 worldPos;
};

In the surface function, we can then use the Y coordinate of the world position to change the colour of the object. This is done by changing the Albedo property of the SurfaceOutputStandard structure.

float _ConstructY;
fixed4 _ConstructColor;

void surf (Input IN, inout SurfaceOutputStandard o) {
	
	if (IN.worldPos.y < _ConstructY)
	{
		fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
		o.Albedo = c.rgb;
		o.Alpha  = c.a;
	}
	else
	{
		o.Albedo = _ConstructColor.rgb;
		o.Alpha  = _ConstructColor.a;
	}

	o.Metallic = _Metallic;
	o.Smoothness = _Glossiness;
}

The result is a first approximation to the effect seen in Astroneer. The main problem is that the coloured part is still shaded.

01

Unlit Surface Shader

In a previous tutorial, PBR and Lighting Models, we have explored how to create custom lighting models for surface shaders. An unlit shader always produces the same colour, regardless of external lighting and view direction. We can implement it as follow:

#pragma surface surf Unlit fullforwardshadows
inline half4 LightingUnlit (SurfaceOutput s, half3 lightDir, half atten)
{
	return _ConstructColor;
}

It’s only purpose is to return a single, solid colour. As we can see, it refers to SurfaceOutput, which is used in Unity 4. If we want to create a custom lighting model that works with PBR and global illumination, we need to implement a function that takes SurfaceOutputStandard as an input. In Unity 5, it is the following function:

inline half4 LightingUnlit (SurfaceOutputStandard s, half3 lightDir, UnityGI gi)
{
	return _ConstructColor;
}

The gi parameter is there for the global illumination; but it serves no purpose in our unlit shader. Despite working, there is a big problem with this approach. Unity does not allow surface shader to selectively change lighting function. We cannot use the standard Lambertian lighting to the bottom part of the object, and the unlit for the top part. We can only specify a single lighting function for the entire object. It’s up to us to change the way the object is rendered depending on its position.

02

Passing Paramters to the Lighting Function

Unfortunately, the lighting function has no access to the object position. The easiest way to provide that information is to use a boolean variable (called building) that we set in the surface function. The variable can then be queried by our new lighting function.

int building;
void surf (Input IN, inout SurfaceOutputStandard o) {
	
	if (IN.worldPos.y < _ConstructY)
	{
		fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
		o.Albedo = c.rgb;
		o.Alpha  = c.a;

		building = 0;
	}
	else
	{
		o.Albedo = _ConstructColor.rgb;
		o.Alpha  = _ConstructColor.a;

		building = 1;
	}

	o.Metallic = _Metallic;
	o.Smoothness = _Glossiness;
}

⭐ Suggested Unity Assets ⭐
Unity is free, but you can upgrade to Unity Pro or Unity Plus subscriptions plans to get more functionality and training resources to power up your projects.

Extending the Standard Lighting Function

The last challenge that we have to face is a tricky one. As explained in the previous paragraph, we can use building to change the way lighting is calculated. The section of the object that is currently being built will be unlit, while the other should have proper lighting. If we our material to use PBR, we cannot possibly re-write the entire code for photorealistic lighting. The only reasonable solution is to invoke the standard lighting function that Unity has already implemented.

In  a traditional standard surface shader, the #pragma directive that specifies to use the PBR lighting function is the follow:

#pragma surface surf Standard fullforwardshadows

Following Unity’s naming convention, it’s easy to see that the function that they use should be called LightingStandard. This function is found in a file called UnityPBSLighting.cginc, which can be included if necessary.

The plan is to create a custom lighting function called LightingCustom. In normal conditions it simply invokes the Unity standard PBR LightingStandard. When necessary, however, it adopts the previously defined LightingUnlit.

inline half4 LightingCustom(SurfaceOutputStandard s, half3 lightDir, UnityGI gi)
{
	if (!building)
		return LightingStandard(s, lightDir, gi); // Unity5 PBR
	return _ConstructColor; // Unlit
}

In order for this code to compile, Unity 5 requires an additional function to be defined:

inline void LightingCustom_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
{
	LightingStandard_GI(s, data, gi);		
}

That is used to calculate the lighting contributions for global illumination, but is unnecessary for the purposes of this tutorial.

The result is exactly what we expect:

03

Conclusion

This first post focused on how to use two different lighting models in the same shader. This allowed us to have half of the model rendered with PBR, while the other half uses is unlit. The next post will conclude this tutorial, showing how to animate and improve the effect.

08

Patreon You can download the Unity project for this tutorial on Patreon.

A big thanks goes to the guys at System Era, and in particular to Jacob Liechty.

💖 Support this blog

This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.

Patreon Patreon_button
Twitter_logo

YouTube_logo
📧 Stay updated

You will be notified when a new tutorial is released!

📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

Write a Comment

Comment

  1. Hey Alan,
    Thanks for the tutorial! I am currently stuck on the part with the unlit surface shader. After following your steps, there is an error saying that the surface shader lighting model ‘Unlit’ is missing a GI function, and I am unsure of how to proceed. Could you help me with this? Thanks again!

      • Turns out all I had to do was replace ‘Standard’ in the pragma line with ‘Custom’ to indicate that I am using the new lighting function. Everything works now, yay! Thanks again for the tutorial!

        • Hey!

          Every Lighting function should also have its own _GI companion.
          But the unlit function was just an example. The real and final code use the Custom function only. Inside, there is a condition that determines whether to use the lit or unlit version!

  2. Hi Alan,

    Your tutorials are great, there is one part I don’t understand though. In the part where the unlit lighting is being applied to the whole model, why is the entire thing not lit orange because the lighting function always returns the construct color? I guess I don’t know the way the surface and lighting functions intermingle.

    • Hey!

      If you use “return _ConstructColor;” as your lighting function then YES, your object will have a single, unlit colour.

      The picture where the object is half orange / half white with flat lighting comes from the fifth code snippet, which uses “int building”. That lighting function has a condition inside!

Webmentions

  • Homeworld 2 – Hyperspace | Simon schreibt. – Tech Gyan March 26, 2017

    […] Ben for mentioning this great article created by Alan Zucconi. It’s about printing stuff in 3D. […]

  • 3D Printer Shader Effect - Part 2 - Alan Zucconi March 26, 2017

    […] The first part of this tutorial can be found here. […]