Zelda Breath of the Wild Art Style in Unity

40 minutes to consummate

You will learn to write a lit toon shader from scratch. The shader will be lit by a unmarried directional low-cal, and cast and receive shadows.

Toon shading (often called cel shading) is a rendering way designed to make 3D surfaces emulate 2D, flat surfaces. This style entered the mainstream with games like Jet Prepare Radio and The Air current Waker.

This tutorial will describe step-by-stride how to write a toon shader in Unity. The shader will receive light from a single directional source, and have specular reflections and rim lighting. We volition apply the art style of The Legend of Zelda: Breath of the Wild as our reference, although we will not implement some of the more complex rendering techniques used in the game.

Analysis of The Legend of Zelda: Breath of the Wild's toon shading, indicating the specular, rim, and lighting components

Breath of the Wild divides surfaces into two bands of lightness, adding rim and specular on summit. The rim is just applied to surfaces illuminated past the main directional lite.

The completed projection is provided at the cease of the article. Note that it also contains a large amount of comments in the created shader file to help understanding.

Prerequisites

To complete this tutorial, you will need a working knowledge of Unity engine, and a bones understanding of shader syntax and functionality.

Download starter project .zip

Getting started

Download the starter projection provided above and open it in the Unity editor. Open the Main scene, and open the Toon shader in your preferred code editor.

This file contains a elementary shader that outputs a sampled texture, tinted past a color (with the default color prepare to cornflower blue). We will build off this file to create our toon shader.

Blue unlit sphere in Unity engine.

1. Directional lighting

When writing shaders in Unity that interact with lighting information technology is common to use Surface Shaders. Surface shaders utilize code generation to automate the object's interaction with lights and global illumination. However, as our shader will just interact with a single directional light, information technology will non be necessary to use surface shaders.

We will gear up up our shader to receive lighting data. Add the following code at the acme of the Pass, merely after its opening curly brace.

          Tags { 	"LightMode" = "ForwardBase" 	"PassFlags" = "OnlyDirectional" }        

The first line requests some lighting data to be passed into our shader, while the second line further requests to restrict this information to only the main directional calorie-free. You can read more about Laissez passer tags hither.

To calculate our lighting, we will utilize a common shading model called Blinn-Phong, and apply some additional filters to give it a toon wait. The first step is to summate the corporeality of calorie-free received past the surface from the primary directional low-cal. The amount of light is proportional to the direction, or normal of the surface with respect to the light direction.

Blinn-Phong shading vectors, where 50 is the vector to the lite source and N is the normal of the surface.

We'll need admission to the object's normal data inside our shader. Add the following code.

          // Within the appdata struct. float3 normal : NORMAL;  …  // Inside the v2f struct. float3 worldNormal : NORMAL;        

The normals in appdata are populated automatically, while values in v2f must be manually populated in the vertex shader. Also, we want to transform the normal from object space to earth space, equally the light's direction is provided in world space. Add the following line to the vertex shader.

          o.worldNormal = UnityObjectToWorldNormal(v.normal);        

With the world normal now available in the fragment shader, we tin compare it to the light's direction using the Dot Product.

Displaying dot product and angle between two vectors, one animated rotating 360 degrees

The dot product takes in two vectors (of whatsoever length) and returns a single number. When the vectors are parallel in the same management and are unit of measurement vectors (vectors of length 1), this number is 1. When they are perpendicular, it returns 0. Equally you move a vector away from parallel—towards perpendicular—the dot production event will motility from 1 to 0 non-linearly. Note that when the angle between the vectors is greater than 90, the dot product will exist negative.

Add the following to the fragment shader. Notation that existing lawmaking that is modified volition be highlighted in yellowish. New code is non highlighted.

          // At the top of the fragment shader. float3 normal = normalize(i.worldNormal); float NdotL = dot(_WorldSpaceLightPos0, normal);  …  // Change the existing return line. return _Color * sample *                                NdotL;                  

Blue sphere lit with Blinn-Phong in Unity engine.

This has rendered out a realistic style of illumination. To change information technology to be toon-like, we will divide the lighting into two bands: lite and dark.

          // Below the NdotL announcement. float lightIntensity = NdotL > 0 ? i : 0;  …          return _Color * sample *                      lightIntensity;                  

Blue sphere lit with toon style lighting in Unity engine.

2. Ambient light

This looks expert, merely the night side is likewise dark; right now it is completely blackness. Also, the edge between dark and light looks a chip abrupt, but nosotros'll deal with that later. For now, we will add ambient lite.

Ambient low-cal represents light that bounces off the surfaces of objects in the area and is scattered in the atmosphere. We will model it every bit a light that affects all surfaces as and is additive to the main directional lite.

          // Add together every bit a new property. [HDR] _AmbientColor("Ambient Color", Colour) = (0.iv,0.4,0.4,1)  …  // Matching variable, add higher up the fragment shader. float4 _AmbientColor;  …          render _Color * sample *                      (_AmbientColor + lightIntensity);                  

Blue sphere lit with toon style directional and ambient lighting in Unity engine.

You'll notice that modifying the intensity or color of the Directional Light in the scene does not affect our shader. We will add some lawmaking to include this in our lighting calculations.

          // Add below the existing #include "UnityCG.cginc" #include "Lighting.cginc"  …  // Add below the lightIntensity proclamation. float4 light = lightIntensity * _LightColor0;  …  return _Color * sample * (_AmbientColor +                                light                    );        

We multiply our existing lightIntensity value and store information technology in a float4, so that we include the light'south color in our calculation. _LightColor0 is the color of the principal directional calorie-free. It is a fixed4 alleged in the Lighting.cginc file, then we include the file above to make use of the value.

Before going further, we'll soften the border between low-cal and dark to remove the jaggedness. Right at present, the transition from light to dark is immediate and occurs over a single pixel. Instead, we'll smoothly blend the value from ane to zero, using the smoothstep office.

smoothstep takes in three values: a lower bound, an upper jump and a value expected to be between these two bounds. smoothstep returns a value between 0 and 1 based on how far this third value is between the bounds. (If it is outside the lower or upper bound, smoothstep returns a 0 or i, respectively).

Comparison betwixt smoothstep (left) and a linear function (right). The values are mapped to the greyscale background, also every bit the curves in red.

smoothstep is not linear: every bit the value moves from 0 to 0.5, it accelerates, and as it moves from 0.5 to 1, it decelerates. This makes information technology ideal for smoothly blending values, which is how we'll use it to blend our light intensity value.

          float lightIntensity =                                smoothstep(0, 0.01, NdotL);                  

Our lower and upper bounds, 0 and 0.01, are very close together—this helps maintain a relatively sharp, toony border. When NdotL is higher up 0.01 or below 0 information technology returns i and zero like before, respectively. However, in between that range information technology will smoothly alloy betwixt 0 and one.

3. Specular reflection

Specular reflection models the individual, distinct reflections made by lite sources. This reflection is view dependent, in that it is affected past the angle that the surface is viewed at. We will calculate the earth view direction in the vertex shader and pass it into the fragment shader. This is the direction from the current vertex towards the photographic camera.

          // Add to the v2f struct. float3 viewDir : TEXCOORD1;  …  // Add together to the vertex shader. o.viewDir = WorldSpaceViewDir(five.vertex);        

We'll at present implement the specular component of Blinn-Phong. This calculation takes in two properties from the surface, a specular color that tints the reflection, and a glossiness that controls the size of the reflection.

          // Add equally new backdrop.			 [HDR] _SpecularColor("Specular Color", Color) = (0.9,0.nine,0.9,i) _Glossiness("Glossiness", Float) = 32  …  // Matching variables. float _Glossiness; float4 _SpecularColor;        

The forcefulness of the specular reflection is divers in Blinn-Phong every bit the dot product betwixt the normal of the surface and the half vector. The half vector is a vector between the viewing direction and the light source; nosotros can obtain this past summing those 2 vectors and normalizing the result.

          // Add together to the fragment shader, above the line sampling _MainTex. float3 viewDir = normalize(i.viewDir);  float3 halfVector = normalize(_WorldSpaceLightPos0 + viewDir); float NdotH = dot(normal, halfVector);  bladder specularIntensity = pow(NdotH * lightIntensity, _Glossiness * _Glossiness);  …  render _Color * sample * (_AmbientColor + light +                                specularIntensity                    );        

Nosotros control the size of the specular reflection using the pow function. We multiply NdotH by lightIntensity to ensure that the reflection is only drawn when the surface is lit. Note that _Glossiness is multiplied by itself to allow smaller values in the material editor to have a larger upshot, and make it easier to work with the shader.

Blue sphere lit with toon style directional, ambient and specular lighting in Unity engine.

One time once again we will use smoothstep to toonify the reflection, and multiply the final output by the _SpecularColor.

          // Add below the specularIntensity annunciation. bladder specularIntensitySmooth = smoothstep(0.005, 0.01, specularIntensity); float4 specular = specularIntensitySmooth * _SpecularColor;  …  return _Color * sample * (_AmbientColor + light +                                specular                    );        

Blue sphere lit with toon style directional, ambient and specular lighting in Unity engine.

4. Rim lighting

Rim lighting is the add-on of illumination to the edges of an object to simulate reflected lite or backlighting. It is specially useful for toon shaders to help the object's silhouette stand out among the flat shaded surfaces.

The "rim" of an object will be divers as surfaces that are facing abroad from the camera. Nosotros volition therefore summate the rim by taking the dot production of the normal and the view direction, and inverting it.

          // In the fragment shader, below the line declaring specular. float4 rimDot = 1 - dot(viewDir, normal);  …  return _Color * sample * (_AmbientColor + light + specular +                                rimDot                    );        

Blue sphere with rim lighting in Unity engine.

One time once again, we'll toonify the upshot by thresholding the value with smoothstep.

          // Add together as new properties. [HDR] _RimColor("Rim Color", Colour) = (1,1,1,1) _RimAmount("Rim Corporeality", Range(0, ane)) = 0.716  …  // Matching variables. float4 _RimColor; float _RimAmount;  …  // Add beneath the line declaring rimDot. float rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01, rimDot); float4 rim = rimIntensity * _RimColor;  …  return _Color * sample * (_AmbientColor + lite + specular +                                rim                    );        

Blue sphere with toon rim lighting in Unity engine.

With the rim being drawn around the entire object, it tends to resemble an outline more a lighting issue. Nosotros'll modify information technology to only appear on the illuminated surfaces of the object.

          // Add in a higher place the existing rimIntensity declaration, replacing it. float rimIntensity = rimDot * NdotL;                                                    rimIntensity = smoothstep(_RimAmount - 0.01, _RimAmount + 0.01,                                rimIntensity                    );        

This is better, just information technology would be useful to be able to control how far the rim extends along the lit surface. We'll apply the prisoner of war office to scale the rim.

          // Add every bit a new property. _RimThreshold("Rim Threshold", Range(0, ane)) = 0.1  …  // Matching variable. float _RimThreshold;  …  bladder rimIntensity = rimDot *                                prisoner of war(NdotL, _RimThreshold);                  

Blue sphere with single side rim lighting in Unity engine.

5. Shadows

Equally a concluding step, nosotros will add the ability for our shader to cast and receive shadows. Shadow casting is very simple. Add the post-obit line of lawmaking below the unabridged Pass (exterior the curly braces).

          // Insert just after the closing curly brace of the existing Pass. UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"        

Blue sphere with toon shading casting a shadow in Unity engine.

UsePass grabs a laissez passer from a dissimilar shader and inserts it into our shader. In this case, nosotros are adding a pass that is used by Unity during the shadow casting pace of the rendering procedure.

In lodge to receive shadows, we will need to know in the fragment shader whether a surface is in a shadow or non, and factor that in to our illumination calculation. To sample the shadow map cast by a light, nosotros'll need to transfer texture coordinates from the vertex shader to the fragment shader.

          // As a new include, below the existing ones. #include "AutoLight.cginc"  …  // Add to the v2f struct. SHADOW_COORDS(2)  …  // Add together to the vertex shader. TRANSFER_SHADOW(o)        

We include Autolight.cginc, a file that contains several macros we will use to sample shadows. SHADOW_COORDS(ii) generates a 4-dimensional value with varying precision (depending on the target platform) and assigns it to the TEXCOORD semantic at the provided alphabetize (in our case, 2).

TRANSFER_SHADOW transforms the input vertex's infinite to the shadow map'due south infinite, and so stores it in the SHADOW_COORD nosotros declared.

Before we can sample the shadow map, however, we need to ensure our shader is set to handle two different lighting cases: when the principal directional light does and does not cast shadows. Unity will assist us handle these ii configurations by compiled multiple variants of this shader for each utilize case. You can read more about shader variants hither. We will use a congenital-in shortcut to compile our variants. Add together the post-obit line of code but below the #pragma fragment frag line.

          #pragma multi_compile_fwdbase        

This shortcut instructs Unity to compile all variants necessary for forward base of operations rendering. Nosotros can now sample the value in the shadow map, and apply information technology to our lighting calculation.

          // In the fragment shader, higher up the existing lightIntensity declaration. float shadow = SHADOW_ATTENUATION(i);  bladder lightIntensity = smoothstep(0, 0.01, NdotL *                                shadow                    );        

SHADOW_ATTENUATION is a macro that returns a value between 0 and one, where 0 indicates no shadow and 1 is fully shadowed. We multiply NdotL by this value, as it is the variable that stores how much light nosotros received from the main directional calorie-free.

Blue sphere with toon shading receiving a shadow in Unity engine.

Conclusion

Toon shaders come up in a wide variety of graphical styles, merely achieving the effect usually centers around taking a standard lighting setup (equally we did with Blinn-Phong) and applying a step function to it. In fact, when normals and lighting data is available it can be done as a post procedure event. An case of this can exist institute in this tutorial for Unreal Engine iv.

View source GitHub repository

Leave me a message

Ship me some feedback about the tutorial in the grade beneath. I'll go back to you every bit soon as I tin can! Yous tin alternatively message me through Twitter or Reddit.

wadeevencline.blogspot.com

Source: http://vodacek.zvb.cz/archiv/750.html

0 Response to "Zelda Breath of the Wild Art Style in Unity"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel