<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by NedMakesGames on Medium]]></title>
        <description><![CDATA[Stories by NedMakesGames on Medium]]></description>
        <link>https://medium.com/@nedmakesgames?source=rss-67c6e1219596------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*xMSVIh0CjnnpClVC</url>
            <title>Stories by NedMakesGames on Medium</title>
            <link>https://medium.com/@nedmakesgames?source=rss-67c6e1219596------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 22 Jun 2026 02:06:26 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@nedmakesgames/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Mapping Our Way to PBR: Writing Unity URP Shaders With Code, Part 4]]></title>
            <link>https://nedmakesgames.medium.com/mapping-our-way-to-pbr-writing-unity-urp-shaders-with-code-part-4-6c4ae9875529?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/6c4ae9875529</guid>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Mon, 19 Dec 2022 04:08:55 GMT</pubDate>
            <atom:updated>2023-05-30T03:12:40.513Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y08TWXdg1bBJsW669gc_MQ.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F5GGISvt4KEA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D5GGISvt4KEA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F5GGISvt4KEA%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/23b00b09e22ef6a7595152d5d37b110d/href">https://medium.com/media/23b00b09e22ef6a7595152d5d37b110d/href</a></iframe><p>Hi, I’m Ned, and I make games! Have you ever wondered how shaders work in Unity? Or, do you want to write your own shaders for the Universal Render Pipeline, but without Shader Graph? Either because you need some special feature or just prefer writing code, this tutorial has you covered.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nkkk531edtRWRTwWcTD0Gw.png" /></figure><p><strong>This is the fourth tutorial in a series on HLSL shaders for URP</strong>. If you’re starting here, I recommend <a href="https://gist.github.com/NedMakesGames/4c9bc4ae54c8c481f299538ce420b29a">downloading the starting scripts</a>, so you have something to work from.</p><ol><li><a href="https://medium.com/@nedmakesgames/798cbc941cea"><em>Introduction to shaders</em>: simple unlit shaders with textures.</a></li><li><a href="https://nedmakesgames.medium.com/let-there-be-light-writing-unity-urp-shaders-with-code-part-2-112d370c2b75"><em>Simple lighting and shadows</em>: directional lights and cast shadows.</a></li><li><a href="https://nedmakesgames.medium.com/transparent-and-crystal-clear-writing-unity-urp-shaders-with-code-part-3-f6ccd6686507"><em>Transparency</em>: blended and cut out transparency.</a></li><li><strong><em>Physically based rendering</em>: normal maps, metallic and specular workflows, and additional blend modes.</strong></li><li><em>Advanced lighting</em>: spot, point, and baked lights and shadows.</li><li><em>Advanced URP features</em>: depth, depth-normals, screen space ambient occlusion, single pass VR rendering, batching and more.</li><li><em>Custom lighting models</em>: accessing and using light data to create your own lighting algorithms.</li><li><em>Vertex animation</em>: animating meshes in a shader.</li><li><em>Gathering data from C#</em>: additional vertex data, global variables and procedural colors.</li></ol><p>If you prefer video tutorials, here’s a link to a <a href="https://youtu.be/5GGISvt4KEA">video version of this article</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QQGK6LaaimMxWsu4oAqtaA.png" /></figure><p>Today, we’re diving deep into material options to give our models more realistic options. With physically based rendering (PBR), we’ll implement normal mapping, metallic workflows, more transparency blending modes, parallax occlusion, and clear coat effects. I’ll end with a section outlining techniques to optimize texture use and make the shader your own.</p><p>Before I move on, I want to thank all my patrons for helping make this series possible, and give a big shout out to my “next-gen” patron: Crubidoobidoo! Thank you all so much.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sWQQ-wguVZXC2sfzJHQglg.png" /></figure><p>Well, let’s get started!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IMinBgllkB6TEvXR45AD1Q.png" /></figure><p><strong>Physically Based Rendering</strong>. So far, we’ve used the Blinn-Phong shading algorithm in MyLit. It’s great for simple objects, but we can do better. Most rendering engines have settled on a standard shading paradigm called “physically based rendering,” commonly abbreviated as PBR. It simulates realistic lighting using a “bidirectional reflectance distribution function.” Thankfully, Unity programmed this already for the default Lit shader!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K2bWoCexlwBKrBsQUlYzGg.png" /><figcaption>PBR supports many different material types, like metal, plastic, and glass.</figcaption></figure><p>It’s easy to reuse their implementation in our MyLit shader.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f015f75dae56155bec7a75be8b0cbf04/href">https://medium.com/media/f015f75dae56155bec7a75be8b0cbf04/href</a></iframe><p>In the ForwardLitPass <em>Fragment </em>function, change the final function call to <em>UniversalFragmentPBR</em>. It accepts the same arguments in Unity 2020 and 2021, making it safe to remove the <em>#if</em> branch.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/18c6d0a11e7fb2fadd5a53485966d966/href">https://medium.com/media/18c6d0a11e7fb2fadd5a53485966d966/href</a></iframe><p>PBR includes specular lighting by default — the <em>#define</em> in the shader file is not necessary.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dq1WsKDfIdK8zKUKLqKitQ.png" /></figure><p>In the scene view, things look very similar.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fivPrXfppdGTNca_hznwgw.png" /><figcaption>In Unity 2020, smoothness values should range from 0–1 when using PBR.</figcaption></figure><p>If you’re on Unity 2020, lower the material’s smoothness to a value between 0 and 1. In fact, we can enforce that in the inspector.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/29205f61518114d8a7e990afb187ed70/href">https://medium.com/media/29205f61518114d8a7e990afb187ed70/href</a></iframe><p>In the .shader file, change the <em>_Smoothness </em>property to be the <em>Range </em>type, with bounds 0 and 1.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JYrjmQijwnT_3NoWIpJqqg.png" /></figure><p>The inspector renders a slider and clamps the value between the min and max.</p><p>That’s really all it takes to use PBR in your shaders, albeit a bare-bones version. We’ll implement more features shortly. Right now, notice the more realistic specular highlights. I won’t go into the math behind the BRDF or PBR algorithms in this tutorial, but if that sounds interesting, please let me know!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TbMuVaWUWHc_dOFh7MGjDw.png" /><figcaption>The Frame Debugger.</figcaption></figure><p><strong>Debugging Views.</strong> As a shader gets more and more sophisticated, the need for better debugging tools grows. We used the frame debugger in the previous section. It’s useful for viewing draw order, but what if we need to check the value of a variable? In C#, there’s <em>Debug.Log</em>, but HLSL has no similar function.</p><p>Well, the fragment function outputs colors, and as we know, colors are just numbers. We can use them to visualize any value — as long as the value ranges from zero to one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3EKl-dXcX9lNWzPqzaQkuA.png" /></figure><p>For instance, it’s helpful to output a normal vector as a color. There are a couple small steps to make that happen.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/054486659c39dfb48f06a2484ffb24df/href">https://medium.com/media/054486659c39dfb48f06a2484ffb24df/href</a></iframe><p>First, remap each component to range from 0 to 1. Since normal vectors are normalized, the length is always 1, and each component ranges from -1 to 1. Remap them by adding 1 and dividing by 2.</p><p>Second, <em>Fragment </em>outputs a <em>float4</em>, while normal vectors are <em>float3</em>s. Fix this by appending a 1 to the normal vector using a <em>float4 </em>constructor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5ITTA9ZliF_3MsvKAhkgkw.png" /></figure><p>When viewing the results, you can interpret redder values as normals pointing in the positive X axis, green in the Y axis, and blue in the Z axis. Hey! The colors handily match Unity’s XYZ gizmo!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GIlT6P04vsM85AckSPs6OA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*46jRWQmv443y4BiFPuSReQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cVawnDvqWF8jjBOWnxSzTg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T-kHINo4ANaP5-apcrTuPQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Kjn0dnwRroi6j4WXeyvYiQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DA5WhfhwmbVM_JbRHlS5Rg.png" /><figcaption>Starting from the top left, the normal colors for +X, +Z, -X, -Z, +Y, and -Y.</figcaption></figure><p>Here are the colors corresponding to each cardinal direction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zrq5gCS8w--lJz5KgmeXgQ.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6426a156486b1b6c9401f6463a095fab/href">https://medium.com/media/6426a156486b1b6c9401f6463a095fab/href</a></iframe><p>You can use this technique to visualize any value! Try outputting smoothness, UVs, or scaled world position. This is the <em>Debug.Log</em> of shader development — it’s a little crude, but a good place to start if you run into a problem!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*28jvq9zn2iim8fLRusyeDg.png" /></figure><p><strong>The Rendering Debugger</strong>. Unity 2021 has a new feature called the Rendering Debugger which makes many common outputs automatically available. MyLit should already support a bunch of these!</p><p>The logic is inside <em>UniversalFragmentPBR</em>. Many views just output data fed into the <em>InputData </em>and <em>SurfaceData </em>structs.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/471f468c1287660ea01abc14c82c174a/href">https://medium.com/media/471f468c1287660ea01abc14c82c174a/href</a></iframe><p>To support a few more views, set the clip space position field in <em>InputData</em>…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2f08c3fa75b7eca1cf3f622d5fc16feb/href">https://medium.com/media/2f08c3fa75b7eca1cf3f622d5fc16feb/href</a></iframe><p>…and add a <em>DEBUG_DISPLAY</em> shader feature to the forward lit pass.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mMtJeAtYBqQeYH3TIAVHgg.png" /><figcaption>Viewing albedo, or base color.</figcaption></figure><p>There are still some views MyLit doesn’t support, in which case it will appear either black or white. As we progress through this tutorial series, these gaps will be filled. But, unfortunately, if you’re using Unity 2020, you will need to stick with old fashioned debugging methods.</p><p><strong>Normal Mapping. </strong>You’ve probably used normal maps already if you’ve worked with 3D models at all. Also called bump maps, these textures encode directions, and modify the model’s normal vectors. Remember that normal vectors determine the strength of diffuse lighting; you can cheaply and easily add detail to a model using normal maps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VXTIGtFTxa_pf2UJ_PyZ6w.png" /><figcaption>The left has a normal map applied. Notice the extra lighting details on the forehead and arms.</figcaption></figure><p>Before we implement normal mapping, let’s understand how they work. Textures store colors, but once again, these colors are just numbers. We can interpret them as normalized vectors, similarly to how we output debug normals a few paragraphs ago.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-lPKDbnu3ksQA97MVuyhuA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3ONUa5TC7IoZtCx_-F6Zag.png" /><figcaption>An example texture encoding the normal vectors of a sphere.</figcaption></figure><p>Each pixel stores one vector, remapped to range between zero and one. To reverse this, multiply by two and subtract by one. This gives us a vector centered around zero.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d496247759e7011ff4eb956ac9badcd/href">https://medium.com/media/1d496247759e7011ff4eb956ac9badcd/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QCYPfU6WJTjLF1899QvvxQ.png" /></figure><p>If you plugged in a normal map and tried to output these vectors, they would not look correct. There are two reasons. Number one: Unity encodes normal maps in a special format, optimized for normal vector storage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Erslrc3stzZ_M4NBzwc1gw.png" /></figure><p>This is why Unity frequently bugs you about flagging textures as normal maps in their import settings.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9e2b0d3b3f4c7684c8744243013152c1/href">https://medium.com/media/9e2b0d3b3f4c7684c8744243013152c1/href</a></iframe><p>URP has a function to decode normal map texture samples, <em>UnpackNormal</em>, which also includes the remapping step.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bgPWKaseaJiJaHasoJxOig.png" /></figure><p>These look slightly better, but there’s still something wrong. The normals are all bluish (pointing in the z-direction), and don’t change when the object rotates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6izAKitP7Vw-G4VTd7dllg.png" /></figure><p>If you took the vector stored in the normal map and used it to apply lighting, it’s obvious we’re missing a step.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XAtEGvvgY4z8wzCZiXDBMA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xdkRpK8nK8L87Qv3zAy-dA.png" /></figure><p><strong>Tangent Space.</strong> Vectors in normal maps exist in a special frame of reference called tangent space. The easiest way to visualize it is to picture a globe. Up points outwards from the north pole, while right and forward point in perpendicular directions out the equator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nQtjrFJr5C-sXXThnVjPow.png" /></figure><p>Now, picture yourself standing on the globe’s surface. From your perspective, up points above your head, right along your right arm, and forward in front of you. You exist in tangent space! It’s the perspective of an object standing on the surface of the mesh.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r7fxUu0OpQ26y5N1TK_uyw.png" /></figure><p>In tangent space, the up, right and forward vectors have special names. The up vector is pretty obviously the mesh’s normal vector. The forward and right vectors are called the tangent and bitangent.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V1eqtAnoJ_N0gYc_hy2T5A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X-7-2BRMAD_Lb9M2EEhRaQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IYQoJeEUcY5UkymXblhthA.png" /></figure><p>Notice that these directions change (relative to world space) depending on where you stand on the globe.</p><p>That’s all well and good, but in the world of shaders, the light, camera, and all other data used in rendering are in world space. The normal vector must be as well if we want to use it in lighting calculations! Thankfully, it’s easy to convert a vector from tangent space to world space using a change of basis transform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cL-SJexrascWloNRDZGTZw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iywyi9Ms0ac6JvmQWdA7mA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9J-UiIHjNl09cnzFNQAJSg.png" /><figcaption>A visual representation of a tangent space to world space calculation.</figcaption></figure><p>A basis is the mathematical name for the coordinate axes: X-Y-Z in world space or tangent-bitangent-normal in tangent space. If you have the basis directions of one space given in terms of another, it’s easy to convert points between the two spaces.</p><p>In the 2D example above, we want to convert a point from tangent space to world space. Given the tangent and bitangent directions in world space, multiply each basis direction with the corresponding component in the point vector and add the products. The point (2, 1) equals the tangent vector times two plus the bitangent vector times one.</p><p>So, we need to get the tangent-bitangent-normal basis directions in world space. We already have the normal, given to us through the mesh data. With that, we could technically pick any two vectors that are perpendicular for the tangent and bitangent. However, in shader dev, we define the tangent and bitangent so they correspond to the model’s UV coordinates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h4c8Dm_4rwHqhnWESW17Nw.png" /></figure><p>Return to the globe and overlay it with simple UV values, resembling latitude and longitude. The U-coordinate is red while the V-coordinate is green.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ljlgyEu3la3zFg-UturuHg.png" /></figure><p>The tangent vector points in the direction of growing U-value. In other words, if you walk in the tangent direction, the U-value under your feet will always increase. Similarly, the bitangent points in the direction of growing V-value.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ixpx2EiZITV2tVg_qrMCEA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cUxa4QRV3yGKEPTPl3NkDQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eDOM1WCMLoQzuH9-Y72G7g.png" /></figure><p>This works out really well. Since normal maps are a texture applied to the model with UVs, the tangent and bitangent align with the texture. If you unwrap the globe and watch where the tangent and bitangent point as you walk around, from the perspective of the normal texture, they always point in the same direction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7Ri8MJyQ3mR96y6Txwo2hA.png" /></figure><p>Meshes store the tangent and bitangent vectors as a mesh data stream, just like normals or positions. With that, we now have the entire tangent space basis in world space, can convert a vector extracted from the normal map to world space, and use it for lighting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jKNlLSpowfGr8CLyjjPN_Q.png" /></figure><p>Voila!</p><p>Phew, that was a lot of math, but I feel it’s important to really understand how normal maps work. They’re fundamental to shader development! Let’s get started programming!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/67e8733155851d4c21f0707e1cb26470/href">https://medium.com/media/67e8733155851d4c21f0707e1cb26470/href</a></iframe><p><strong>Adding Normal Mapping.</strong> First open MyLit.shader and add another texture property. Flag it with two attributes. First, the <em>NoScaleOffset </em>attribute hides the tiling fields in the inspector. This is preferable because the normal map should match the color map. Second, this Normal attribute prompts Unity to check that any texture placed inside is correctly encoded.</p><p>White is not a good default color for normal maps. White, or one one one, will generate weird unnormalized normals pointing diagonally. The default value should act as if no normal map is present — just using the mesh vertices’s normal vectors for lighting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3x9zhPcz-h1G68BZU2YITg.png" /><figcaption>Default normal map blue.</figcaption></figure><p>The <em>“bump”</em> default has a value of <em>(0.5, 0.5, 1)</em>, a periwinkle-like color, which resolves to the <em>(0, 0, 1)</em> tangent space vector.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7526e98846eeee88534e8354bd5cc5fd/href">https://medium.com/media/7526e98846eeee88534e8354bd5cc5fd/href</a></iframe><p>In MyLitCommon.hlsl, add the texture declaration.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/53bf9e86a9fef76d3b1ab21b86520938/href">https://medium.com/media/53bf9e86a9fef76d3b1ab21b86520938/href</a></iframe><p>In the forward lit pass, we need the mesh’s tangent and bitangent vectors. Tangents are stored in a vertex data stream with the <em>TANGENT </em>semantic. Like <em>NORMAL</em>, these tangent vectors are in object space.</p><p>I did lie earlier… bitangents are not stored in the mesh, at least not directly. Instead, we can derive them from the normal vector, tangent vector, and a special bitangent sign value. Unity stores this sign value in the <em>TANGENT </em>semantic’s w-coordinate.</p><p>We also need the tangent and bitangent in the fragment stage, requiring a new tangent field in the <em>Interpolators </em>struct. It will be in world space by now, and also still contain the bitangent sign. Tag it with the next available <em>TEXCOORD </em>semantic.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/40eebcb6e836e3e1c221b1307b512f69/href">https://medium.com/media/40eebcb6e836e3e1c221b1307b512f69/href</a></iframe><p>In the <em>Vertex </em>function, we need to convert the tangent vector to world space. The <em>GetVertexNormalInputs </em>function has an overload taking the tangent vector and bitangent sign, outputting world space tangent vectors. Store this in output, passing along the bitangent sign as the W-component.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/76e4df373fc5eb218e478cdbcce7f7fe/href">https://medium.com/media/76e4df373fc5eb218e478cdbcce7f7fe/href</a></iframe><p>Now the <em>Fragment </em>function has everything it needs. First, calculate the tangent space normal vector. As mentioned earlier, Unity encodes normal maps in a special way; use the URP function <em>UnpackNormal </em>to decode the tangent space normal vector inside.</p><p>Next up, calculate the tangent space basis. We have the normal and tangent, but not the bitangent!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rhGGyV5bWGSmbo1a8RRAUg.png" /><figcaption>The two blue vectors are perpendicular to the red and green vectors.</figcaption></figure><p>In 3D space, given any two different directions, there are exactly two directions perpendicular to both — a direction and it’s opposite! Use the bitangent sign to choose a single vector between them. URP will calculate this for us in the <em>CreateTangentToWorld </em>function. Pass it the normal vector, tangent vector, and bitangent sign.</p><p>It returns a <em>float3x3 </em>matrix, which is simply three <em>float3 </em>vectors stacked together. You can think of a matrix as a two-dimensional array or a list of vectors. Remember the formula to convert a vector from tangent space to world space? It’s easy to implement this in HLSL using matrix multiplication.</p><p>The<em> </em>function <em>TransformTangentToWorld </em>multiplies the tangent space normal vector (from the normal map) with the tangent to world matrix, producing a world space vector. We will use this to calculate lighting!</p><p>One final step: normalize this new normal to prevent rounding errors. Some testing is in order! Return the normal vector, remapping it appropriately.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kQLpxTbgC6Rek3WYrW-oQQ.png" /></figure><p>In the scene editor, find a test normal map and, in the texture importer, set it as a normal map. Apply it to your material and make sure everything makes sense. If you remove the normal map, the displaying color should not wildly change. Rotating the model should result in rotated normal vectors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q58inzddPmLiH_9uOLejeA.png" /></figure><p>It even works with double sided rendering! Perfect!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b5be5ffaace86ecc299fd4e94824e170/href">https://medium.com/media/b5be5ffaace86ecc299fd4e94824e170/href</a></iframe><p>Return to the code and remove the test line. Then set the normal vector in <em>lightingData</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lCYTC3JEax8QDxSlX_cATA.png" /></figure><p><strong>Normal Strength.</strong> Apply the normal map to your material again and marvel at the added detail! Sometimes the lighting effect is too strong. There’s an easy way to adjust normal map strength without editing the texture.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9ffe4e04994ddd8e1b39af136b8ce9c0/href">https://medium.com/media/9ffe4e04994ddd8e1b39af136b8ce9c0/href</a></iframe><p>Add a normal strength property in MyLit.shader - usually a range from zero to one is recommended.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c76b6a509f1306c107edebc29cd9f010/href">https://medium.com/media/c76b6a509f1306c107edebc29cd9f010/href</a></iframe><p>Add the declaration to the common hlsl file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/09663b25fbd901b0a0cadd9f14fedd65/href">https://medium.com/media/09663b25fbd901b0a0cadd9f14fedd65/href</a></iframe><p>Then, switch the <em>UnpackNormal </em>function with <em>UnpackNormalScale</em>, passing the strength.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qKRxFANOhzJ1MJPTOp0P-Q.png" /></figure><p>That’s all there is to it!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R0yOlwmW86w2D1lEOUSrPA.png" /><figcaption>The left uses OpenGL-style bitangents, where the bitangent points from bottom to top. The right uses DirectX-style bitangents, the opposite, where the bitangent points from top to bottom.</figcaption></figure><p><strong>Debugging Normal Maps.</strong> There’s one more wrinkle you should know about. When choosing a normal map, URP and our shader assume the bitangent points in the direction of growing-V, or from the bottom to top of the normal map. Some other programs use the opposite bitangent. Make sure your normal map follows the “OpenGL” convention and you’re good to go.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fe62b2d7e83be5c10cb8c774d16444c9/href">https://medium.com/media/fe62b2d7e83be5c10cb8c774d16444c9/href</a></iframe><p>Finally, in Unity 2021, we can do a bit more to support additional views in the renderer debugger. Set the tangent to world transformation matrix in <em>lightingData</em>, and the tangent space normal in <em>surfaceInput</em>. (Even though it’s unused in 2020, <em>normalTS </em>does exist in <em>SurfaceData </em>in that version.)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ca93126143b63331c3c7bffdb2296da/href">https://medium.com/media/2ca93126143b63331c3c7bffdb2296da/href</a></iframe><p>Then, signal to the debugger that the shader supports normal maps by defining this <em>_NORMALMAP </em>keyword in the .shader file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GbqVz5uchRVzY34wtjlW0g.png" /></figure><p>Now, the renderer debugger can display tangent space normals and remove normal mapping from lighting.</p><p>That’s the basics of normal mapping. There is complex math behind it, but URP does a good job helping you get by without thinking about it too much. Be sure to leverage normal maps to help your models shine.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SGCgLG9cnLhZerCpwh-zjA.png" /><figcaption>The left side has metallic maps enabled. Notice the richer colors and sharper highlights around the nose.</figcaption></figure><p><strong>Metallic Workflows. </strong>One cool aspect of PBR shaders is their ability to create metallic surfaces. URP’s <em>UniversalFragmentPBR </em>function makes metals really easy to implement in our shaders!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c144a0b00b53ddaaad702166ad486f1c/href">https://medium.com/media/c144a0b00b53ddaaad702166ad486f1c/href</a></iframe><p>First, add a <em>_Metalness </em>float property in MyLit.shader, which ranges from zero to one.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/812c01e195444f1efc9cde69f08eb64c/href">https://medium.com/media/812c01e195444f1efc9cde69f08eb64c/href</a></iframe><p>In MyLitCommon.hlsl, declare the new property.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d16be25ec59f75919303e4bc5b8a1e7d/href">https://medium.com/media/d16be25ec59f75919303e4bc5b8a1e7d/href</a></iframe><p>Then, in MyLitForwardLitPass.hlsl, sync the metallic field in <em>surfaceData </em>to this property.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aHn2jQlplj1IFm_LrJmOtA.png" /></figure><p>In the scene editor, try it out! Notice how metallic strength changes the way highlights affect the material. It looks quite dark right now because reflections dominate lighting on metallic surfaces. We will set those up in the next part of this series!</p><p><strong>Metalness Masks.</strong> Objects are rarely completely metallic, and it would be helpful to vary metalness across the mesh. This is pretty easy to do with another texture! Store metalness values in the texture and read them using UVs, like the color and normal map. Using a texture to turn on and off shader features is commonly called masking.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b87a655a23e9be0e6583bbe57423db62/href">https://medium.com/media/b87a655a23e9be0e6583bbe57423db62/href</a></iframe><p>In your shader file, add another texture property for the metalness mask…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d43f1556637da7a86d8f5e4b33a825c3/href">https://medium.com/media/d43f1556637da7a86d8f5e4b33a825c3/href</a></iframe><p>…and the new property to the common HLSL file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4616c717b725d749bdd3f353b54fe172/href">https://medium.com/media/4616c717b725d749bdd3f353b54fe172/href</a></iframe><p>In the <em>Fragment </em>function, sample the metalness mask. Since metalness is a float, only save the red channel’s value. Multiply that with the <em>_Metalness </em>property and set it in <em>surfaceData</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2VHEU0RRoo77J-wv1xRrhg.png" /></figure><p>Back in Unity, set a metallic texture in your material. Since this texture encodes special data and not colors, it’s a good idea to turn off the sRBG setting in the texture importer. This should be off for any texture that stores data, like masks or look up textures.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*22w08iWjmL8LRvpGYweKTw.png" /></figure><p>Regardless, only areas where the mask is white should appear metallic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0y-vGF8X3uoQTk3mR00l7Q.png" /></figure><p>You can verify this by looking at the metallic view mode in the rendering debugger (or outputting metallic strength). Compare this with your mask by placing the mask in the color map slot.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DQbi2VYWzD1AKvKkO--_Tw.png" /><figcaption>This violin uses the specular workflow to specify highlight colors per-metal.</figcaption></figure><p><strong>The Specular Workflow.</strong> The Lit shader actually has two distinct modes to handle metallic surfaces: the metallic workflow and the specular workflow. The specular workflow uses a specular color texture to determine the color of specular highlights over the model. The brightness of the specular color controls the metalness at that point.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8966c5ab5236c11990f1c936675268d/href">https://medium.com/media/b8966c5ab5236c11990f1c936675268d/href</a></iframe><p>To enable specular workflow, first <em>#define</em> this keyword in the forward lit pass block of MyLit.shader. We’ll also need properties for the specular texture and a tint.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8fef39004c69fd787d2a88f5ff09eac2/href">https://medium.com/media/8fef39004c69fd787d2a88f5ff09eac2/href</a></iframe><p>Define them in MyLitCommon.hlsl too.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b5b86c9ed73a9a00d5522b7a75a0699/href">https://medium.com/media/9b5b86c9ed73a9a00d5522b7a75a0699/href</a></iframe><p>In MyLitForwardLitPass.hlsl’s <em>Fragment </em>function, sample the specular map and multiply the color by the specular tint. In specular workflow mode, <em>UniversalFragmentPBR </em>actually ignores the metallic value. Use a <em>#if</em> block to set the specular color only if <em>_SPECULAR_SETUP </em>is enabled, and <em>metallic</em> otherwise.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q3roIjbjDZYHjH0EZaHIww.png" /></figure><p>In the scene view. Try adding a colorful specular texture and see how it affects things! Trippy. Notice that the specular highlight is kind of the inverse of the surface color — this is optics at work!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WDsVvRxff_s86Bvqri4fJQ.png" /></figure><p>You can also inspect specular color in the renderer debugger.</p><p>You might have noticed we used a keyword to switch between metallic and specular workflows. It’s easy to add a property to toggle keywords on and off, no custom inspector code required!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/00f09253337933f4c6d31f4d6ff8881f/href">https://medium.com/media/00f09253337933f4c6d31f4d6ff8881f/href</a></iframe><p>In MyLit.shader, add a float property with the special <em>Toggle </em>attribute. The keyword name to enable and disable goes inside the attribute. The property name doesn’t really matter, just choose something relevant. Finally, set the property value to zero to disable the keyword by default.</p><p>In the forward lit pass block, replace the <em>#define</em> with a <em>#pragma shader_feature_local_fragment</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iPc4QTY83vKHf_Ng8F6XtA.png" /></figure><p>In the scene editor, you can now easily switch between metallic and specular modes.</p><p><strong>Smoothness Masks.</strong> While on the topic of specular lighting, it would be very nice to vary a model’s smoothness across its surface. We can do that with another mask texture, this time containing smoothness values.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8e4a655e00cb839600381154d1b25a6/href">https://medium.com/media/b8e4a655e00cb839600381154d1b25a6/href</a></iframe><p>Add a smoothness texture to the shader properties…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cd50a47dc50c96311e9e5ed872ace063/href">https://medium.com/media/cd50a47dc50c96311e9e5ed872ace063/href</a></iframe><p>…declare it in the common file…</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7968a277b282603ad72a60a2dfd3e65b/href">https://medium.com/media/7968a277b282603ad72a60a2dfd3e65b/href</a></iframe><p>…and sample it in the forward lit pass fragment function. Using the red channel, multiply it with the preexisting <em>_Smoothness </em>property and set it in <em>surfaceData</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ulxR8eqPwlG6ziHV3faj9w.png" /></figure><p>In the scene editor, try adding a smoothness mask. Again turn off sRGB. Pretty cool, these textures go a long way!</p><p>If you work with other 3D programs, you might see some use a gloss or glossiness map — this is just another name for a smoothness map. Some others use a roughness mask, which is just the inverse of smoothness. In other words, smoothness equals one minus roughness.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c74d0bbc2817fe8ac5a379fc373dc637/href">https://medium.com/media/c74d0bbc2817fe8ac5a379fc373dc637/href</a></iframe><p>If you want to use roughness in your shader, just invert the texture sample before setting it in <em>surfaceData</em>. You could even add a property to optionally do this! However, for the rest of this tutorial, I will just support simple smoothness masks.</p><p>I know those of you with shader experience might be cringing at our use of separate textures for all these masks. At the end of this tutorial, I will explain how we can optimize a bit. Put this in the back of your mind for now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jFPcZR4fIB6yWKgk0sP00w.png" /></figure><p><strong>Transparent Blending Modes. </strong>MyLit already has full transparency support, but URP’s lit shader has a few extra transparency modes we could add: additive, multiply, and premultiplied. Additive and multiply modes are very useful for particles, while premultiplied mode helps simulate glass.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mtXYgHmpAvtJC3sTw4KbAQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xwJJnUOycBcpuO6c0wsQ4Q.png" /><figcaption>Additive particles and multiplicative particles.</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/84d7bf8828fc0dfb71066e23006768ce/href">https://medium.com/media/84d7bf8828fc0dfb71066e23006768ce/href</a></iframe><p>The bulk of the work lies in the custom inspector. Add a new <em>BlendType</em> enumeration with the four modes we’ll support: <em>Alpha</em>, <em>Premultiplied</em>, <em>Additive</em>, and <em>Multiply</em>. To store the currently selected blend mode, we’ll add another property, <em>_BlendType</em>, similarly to how we handled <em>_SurfaceType</em>. Get and set this new property in the <em>OnGUI </em>function.</p><p>The main difference between all these blend modes is the source and destination blend settings they employ. Remember, these instruct the renderer how to combine colors from the fragment function with colors on the screen. In <em>UpdateSurfaceType</em>, read the <em>blendType </em>property. In the <em>switch</em> statement where we set blend modes, add another <em>switch </em>in the<em> TransparentBlend </em>case. Inside, set the appropriate source and destination blend modes for each case.</p><p>The <em>Alpha </em>mode works like we’re used to, blending based on the alpha value of the source pixel. In this mode, the rasterizer multiplies the source color by the alpha value, the destination color by one minus the alpha value, and then adds them together.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b8UJ1UjXls9iwq8bsNYljw.png" /><figcaption>The camera lens and viewfinder window use premultiplied alpha to simulate glass.</figcaption></figure><p><em>Premultiplied </em>mode assumes alpha has already been multiplied with color and stored in a texture’s RGB values. That explains the name — alpha has been <strong>pre</strong>viously multiplied. In this case, the source blend type should be one, so alpha isn’t applied again, but the destination should still behave like normal. Premultiplied alpha gives artists better control over how a transparent object looks — effectively, brighter pixels are more opaque.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mtXYgHmpAvtJC3sTw4KbAQ.png" /></figure><p><em>Additive </em>mode is mathematically the inverse of <em>Premultiplied </em>mode. In this mode, the source is affected by alpha, but the destination is not. The destination color is only ever added to. Additive mode lightens the scene, and is great for particle effects like lighting and fire.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xwJJnUOycBcpuO6c0wsQ4Q.png" /></figure><p>Finally, <em>Multiply </em>mode is for specialized use. It utilizes a new blend mode, <em>SrcColor</em>, which is simply the source pixel’s RGB values. <em>Multiply </em>mode completely ignores alpha, just multiplying the source and destination together. It tends to darken the scene; sometimes useful for otherworldly effects and masking.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c94be89dd3e8c15a89132a7df402d94d/href">https://medium.com/media/c94be89dd3e8c15a89132a7df402d94d/href</a></iframe><p>In MyLit.shader, don’t forget to add the new <em>_BlendType </em>property.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0GgTjPnfPnuQz8q42vljmg.png" /></figure><p>Check the new modes out in the scene! It’s cool how we can get so many effects just by manipulating blending mode.</p><p><strong>Premultipled Mode Specular Opacity.</strong> URP further enhances <em>Premultiplied </em>mode by having lighting affect the material’s alpha. It’s again wonderful for glass, where specular highlights appear opaque. <em>UniversalFragmentPBR </em>handles everything, we only need to enable a keyword: <em>_ALPHAPREMULTIPLY_ON</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/eff30b9046f23f79d259d58c88e4c612/href">https://medium.com/media/eff30b9046f23f79d259d58c88e4c612/href</a></iframe><p>Back in the custom inspector, enable or disable the keyword appropriately.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7cf6810a0459819f62735063ee05cc84/href">https://medium.com/media/7cf6810a0459819f62735063ee05cc84/href</a></iframe><p>Then, in MyLit.shader, add a shader feature to the forward lit pass.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HTFeD8Jx5eBgWV3r5HtW7Q.png" /></figure><p>In the scene editor, try it out! The specular highlight definitely appears more opaque.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sWQQ-wguVZXC2sfzJHQglg.png" /><figcaption>Emission gives the illusion that the headlights are glowing.</figcaption></figure><p><strong>Emission. </strong>Some objects, like electronics or magical artifacts, have small glowing parts. It’s too expensive to use real lights for these fine details, but we can do our best with something called an emission texture! It defines glowing areas on a mesh.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4c07fdf918774a0c485db5503c2fc814/href">https://medium.com/media/4c07fdf918774a0c485db5503c2fc814/href</a></iframe><p>Emission is easy to implement! Add an emission texture property in MyLit.shader, and an emission tint.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BUWnhQoe8Yp5c3lT6-lyew.png" /></figure><p>The <em>HDR </em>attribute flags the tint as a high-dynamic-range color, meaning that its components can take values greater than one. This is useful in combination with some post-processing effects, like bloom. We’ll talk more about it later in the tutorial series.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/71c756084599a29b906bcc0319795855/href">https://medium.com/media/71c756084599a29b906bcc0319795855/href</a></iframe><p>Add the new properties to the common HLSL file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3d22594fccc356df0d3673424f43e45e/href">https://medium.com/media/3d22594fccc356df0d3673424f43e45e/href</a></iframe><p>Then sample the emission texture in the forward lit <em>Fragment </em>function. Multiply it with <em>_EmissionTint </em>and store it in <em>surfaceData</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SQl258vu7PPueY8wo8MOcw.png" /></figure><p>And that’s all you need to support emission. Test it out by grabbing an emission texture and adjusting the emission tint. Note that if emission tint is black, no emission will appear.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tu_Gugz9bXXQCTuD7QrQDg.png" /></figure><p>You can check out emission in the renderer debugger. If you want to disable it temporarily, there’s an option to do that with the lighting features enumeration.</p><p>Emission works by ignoring shadows and overexposing affected areas, it doesn’t actually light the scene, unfortunately. We can fix this by tying it into baked lighting, which will happen in part five of this series!</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FaaHYWN_k2fs%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DaaHYWN_k2fs&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/ab2e19bbf11f88c225c5140036506349/href">https://medium.com/media/ab2e19bbf11f88c225c5140036506349/href</a></iframe><p><strong>Parallax Occlusion Mapping. </strong>URP’s Complex Lit shader has a few cool options we should look at, the first being parallax occlusion. Parallax is a method of simulating depth by scrolling textures based on their distance from the camera. We can move UVs around to simulate small divots in our material!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rb4b3TsDn_XAn-_yFjSUZg.png" /><figcaption>The height map of the sand texture.</figcaption></figure><p>Let’s see how it works. First, we need another texture called a height or displacement map to determine which areas are depressed below the surface. White pixels are at the mesh’s surface, while black pixels are lowered below the surface.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9atGBExFh_xhGNN2avUK-Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mr_sdK9FDN0x6F2g-w0BMQ.png" /><figcaption>The blue line is the view ray. It casts through the original UV. The collision with the deformed surface gives a new UV.</figcaption></figure><p>Next, pretend this height map actually deforms the mesh. When evaluating the parallax for a specific UV coordinate, cast a ray from the camera, through the UV, until it hits the imaginary deformed surface. Project the impact point back onto the texture and use that new UV coordinate to sample the color map, normal map, etc.</p><p>In this way, UVs change based on view direction — more for areas depressed by the height map. The effect gives the illusion of depth.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bb2954103d4f5061c02b87d8129abbb0/href">https://medium.com/media/bb2954103d4f5061c02b87d8129abbb0/href</a></iframe><p>Luckily, all the math behind the raycasting and such is handled by URP functions. To get started, add a height map and parallax strength property to MyLit.shader.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e2649224bfeea1b54828a46995f58429/href">https://medium.com/media/e2649224bfeea1b54828a46995f58429/href</a></iframe><p>In MyLitCommon.hlsl, add declarations for these new properties.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/15fa5f16288d3128cf23690c47559358/href">https://medium.com/media/15fa5f16288d3128cf23690c47559358/href</a></iframe><p>In MyLitForwardLitPass.hlsl, rearrange code so the normal and view direction are calculated before any texture sampling. We need to calculate a new UV based on these values! In the process, use these <em>positionWS </em>and <em>viewDirectionWS </em>variables throughout the function, instead of the values in <em>input</em>.</p><p>The parallax occlusion mapping algorithm needs view direction in tangent space, since the imaginary surface formed by the height map exists in tangent space. URP provides a <em>GetViewDirectionTangentSpace </em>function to do that. Include SRP’s ParallaxMapping.hlsl file to make it available, and pass it the world space tangent (with bitangent sign), normal, and view direction vectors. <em>GetViewDirectionTangentSpace </em>needs the vertex normal in an unnormalized state, an important bit of info to keep in mind!</p><p>Now call the magic function, <em>ParallaxMapping, </em>to do the work. It requires the height map and sampler. To pass textures and samplers to functions, you must use this special macro: <em>TEXTURE2D_ARGS</em>. It exists to get around platform differences, similarly to texture declarations.</p><p>Then, pass <em>ParallaxMapping</em> the tangent-space view direction, parallax strength property, and current UV. <em>ParallaxMapping </em>returns the amount to offset UVs, according to the parallax algorithm explained above. Add the offset to the current UV, and be sure to use this new UV while sampling all other textures.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FXbNaCcJmKzA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DXbNaCcJmKzA&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2cd8a6212076373bab3cbc5bff3acd6a/href">https://medium.com/media/2cd8a6212076373bab3cbc5bff3acd6a/href</a></iframe><p>Check it out in the scene! Find a height map and set the parallax strength. It doesn’t take much for a really strong effect! Usually a parallax strength around 0.05 is plenty.</p><p>This is as far as Complex Lit’s parallax mapping goes, but there’s a lot more we could do with it. Would you be interested in learning more in another tutorial series? Please let me know.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZkogZm36AzsU-TYrpwWfuA.png" /><figcaption>The right side has a clear coat mask applied.</figcaption></figure><p><strong>Clear Coat. </strong>Besides parallax, there’s another advanced feature hidden away in the Complex Lit shader: clear coat! You know the shiny coating on car paint? Clear coat can recreate that effect.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cff89bf4f6211e0593ebe3d5c1cd315c/href">https://medium.com/media/cff89bf4f6211e0593ebe3d5c1cd315c/href</a></iframe><p>Like emission before, it’s easy to implement. Add a clear coat strength mask and float property. Clear coat emulates a transparent coat of paint on top of a surface, so it can have its own, independent smoothness value. Add a clear coat smoothness mask and strength property as well. In the forward lit pass block, define the <em>_CLEARCOATMAP </em>keyword so <em>UniversalFragmentPBR </em>includes the calculation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c8e8004c03e6bcbc8ec8c05c4f0a089a/href">https://medium.com/media/c8e8004c03e6bcbc8ec8c05c4f0a089a/href</a></iframe><p>In MyLitCommon.hlsl, define the new textures and float properties.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/867c06b1cadd89bdea6498465985f803/href">https://medium.com/media/867c06b1cadd89bdea6498465985f803/href</a></iframe><p>In MyLitForwardLitPass.hlsl, sample the masks, taking their red channels and multiplying with their float properties. Set the <em>clearCoatMask </em>and <em>clearCoatSmoothness </em>fields in <em>surfaceInput</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hMd9e-XX8UOFjVEB0_Iwug.png" /></figure><p>That’s basically it! Clear coat is useful for many different objects, like cars, furniture, or even candy. It is expensive though, basically requiring two BRDF calculations per material. Consider leaving this out if you don’t need it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Huas7bySQ3CRyaH2eGarkA.png" /></figure><p><strong>Optimizing Texture Use. </strong>With clear coat under our belt, we support just about all surface options built into URP. However, our shader has ballooned quite a bit, and now is a good time to think about slimming it down.</p><p>In future parts of this tutorial series, I’m going to stick with this general purpose, unoptimized shader. However, once you know exactly which features your personal shader requires, revisit the techniques in this section to speed it up and make it easier to use.</p><p>Step one: figure out which features you need. Always use the metallic workflow? Remove support for the specular workflow, including the specular color texture and tint. Removing expensive features like clear coat, parallax, or normal mapping is worth it. It’s also possible to toggle a feature on or off using a keyword.</p><p><strong>Optional Features.</strong> For example, say only some of our materials need normal mapping. We can set up the inspector to disable a keyword when no normal map texture is assigned.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/caa0564a7f1221dbea87e7d184670f0c/href">https://medium.com/media/caa0564a7f1221dbea87e7d184670f0c/href</a></iframe><p>In <em>MyLitCustomInspector</em>’s <em>UpdateSurfaceType </em>function, use <em>GetTexture </em>on the material. Enable or disable the <em>_NORMALMAP </em>keyword based on if it returns null.</p><p>In order to <em>call UpdateSurfaceType </em>when the normal map changes, move the <em>base.OnGui</em> call in between <em>Begin-</em> and <em>EndChangeCheck</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/24af9db26e684bd815be276801b717fe/href">https://medium.com/media/24af9db26e684bd815be276801b717fe/href</a></iframe><p>In MyLit.shader, change the <em>#define</em> to a <em>#pragma shader_feature</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88e1206df763f2eb057e1c1393bc2df4/href">https://medium.com/media/88e1206df763f2eb057e1c1393bc2df4/href</a></iframe><p>In MyLitForwardLitPass.hlsl, use the mesh’s normal vector if <em>_NORMALMAP </em>is not defined. This corresponds to a tangent-space normal vector of <em>float3(0, 0, 1)</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/db2cba8642d5fe2e4e7840dfb837ce6c/href">https://medium.com/media/db2cba8642d5fe2e4e7840dfb837ce6c/href</a></iframe><p>If you go a step farther and remove parallax mapping, you have no need of tangents in general. It’s important for performance to keep the <em>Interpolators </em>struct as small as possible, since each variable is more that the rasterizer has to interpolate. Using a keyword there works great.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3RwmBy9UoaWeU1VIbiiYwg.jpeg" /><figcaption>Four mask textures combined into one.</figcaption></figure><p><strong>Texture Channel Packing.</strong> Another useful technique is texture channel packing. Notice how the various mask textures only use the texture’s red channel. What if we combined several masks into one texture? For instance, we can put the metallic mask into red, smoothness into green, clear coat strength into blue, and clear coat smoothness into alpha.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6cfac893dabbc8ecd691d917b49eee83/href">https://medium.com/media/6cfac893dabbc8ecd691d917b49eee83/href</a></iframe><p>Texture sampling is a big resource hog, especially on mobile platforms, and it’s very nice to merge four samples into one! You can construct this texture using Photoshop or any other photo editing software. Treat each mask as a grayscale texture and place them each in one of the color channels.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c843e746e041a83ea33af1963d13fea3/href">https://medium.com/media/c843e746e041a83ea33af1963d13fea3/href</a></iframe><p>Many times, alpha channels aren’t used — they’re prime real estate for masks. If your material is always opaque, you can hide a mask in the color map’s alpha channel. The specular color and emission texture’s alpha channels are usually unused as well!</p><p>Heads up! We will be adding one more mask texture later on in this series: an occlusion mask. This controls the strength of ambient lighting across the model. If you go ahead and optimize your shader now, leave room for this texture as it’s quite important.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nkkk531edtRWRTwWcTD0Gw.png" /></figure><p>Adding cool surface features is one of my favorite aspects of shader development. Now, we can make metal objects, glass objects, bumpy objects, and a brand new car!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sWQQ-wguVZXC2sfzJHQglg.png" /></figure><p>We’ve done a lot with our own material, it’s time to look outward! In the next tutorial, I will focus on a hotly anticipated topic: advanced lighting. We will learn how to support multiple point and cone lights, baked lights, occlusion textures, reflection probes, light cookies and more!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hDRJgDWpH6voQ1q2A38F7g.png" /></figure><p>For reference, <a href="https://gist.github.com/NedMakesGames/78993a8b2b184a6e27ce2732f3db82a0">here are the final versions of the shader files after completing this tutorial</a>, without any optimizations (as mentioned before).</p><p>If you enjoyed this tutorial, consider <strong>following </strong>me here to receive an email when the next part goes live.</p><p>If you want to see this tutorial from another angle, I created a <a href="https://youtu.be/5GGISvt4KEA">video version you can watch here</a>.</p><p>I want to thank my next gen patrons Michael Samuels and Kasey Vann for all their support, as well as all my patrons during the development of this tutorial: Adam R. Vierra, Alexander Thornborough, AlmostFamous Bit, Amin, Anja Irniger, Arturo Pulecio, Ben Luker, Ben Wander, Bert, bgbg, Bohemian Grape, Boscayolo, Brannon Northington, Brian Knox, Brooke Waddington, Cameron Horst, Charlie Jiao, Christopher Ellis, CongDT7, Connor Wendt, Crubidoobidoo, Dan Pearce, Daniel Sim, Danik Tomyn, Davide, Derek Arndt, Drew O’Meara, Elmar Moelzer, Eta Nol, Fabian Watermeier, Henry Chung, Howard Day, Isobel Shasha, Jack Phelps, Jay Liu, Jean-Sébastien Habigand, John Lism Fishman, John Luna, Jon Mayoh, Joseph Hirst, JP Lee, jpzz kim, JY, Kat, Kel Parke, Kyle Harrison, Lasserino, Leah Lycorea, Levino Agaser, lexie Dostal, Lhong Lhi, Lien Dinh, Lukas Schneider, Mad Science, Makoto Fujiwara, Marcin Krzeszowiec, Mattai, Max Thorne, Michael Samuels, Minh Triết Đỗ, MrShiggy, Nazarré Merchant, NotEvenAmatueR Streams, Oliver Davies, P W, Parking Lot Studio, Patrick, Patrik Bergsten, Phoenix Smith, rafael ludescher, Richard Pieterse, Robin Benzinger, Sailing On Thoughts, Sam CD-ROM, Samuel Ang, Sandro Traettino, santhosh, SausageRoll, SHELL SHELL, Shot Out Games, Simo Kemmer, Simon Jackson, starbi, Steph, Stephan Maier, Sung yup Jo, Syll art-design, T, Taavi Varm, Team 21 Studio, thearperson, Thomas Terkildsen, Tim Hart, Tomasz Patek, Tortilla Laser, ultraklei, vabbb, vertex, Vincent Loi, Voids Adrift, Wei Suo, Wojciech Marek, Yuriy T., and 杉上哲也.</p><p>If you would like to download all the shaders showcased in this tutorial inside a Unity project, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p>If you have any questions, feel free to leave a comment or contact me at any of my social media links:</p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> ☕ <a href="https://ko-fi.com/nedmakesgames">Ko-fi</a> 📧 E-mail: nedmakesgames gmail</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>Change log:</p><ul><li>May 29th 2023: Update custom inspector code to add support for Unity 2022’s material variants.</li><li>Unity Technologies: <a href="https://assetstore.unity.com/packages/templates/tutorials/shader-calibration-scene-25422">Shader Calibration Scene</a></li><li>tonyflanagan: <a href="https://sketchfab.com/3d-models/thailand-girl-animated-a41f779fe45b4a04bdbf212e49b7f6b5">Thailand Girl — Animated</a></li><li>Aleksei Vlasov ⚡ CRWDE: <a href="https://sketchfab.com/3d-models/phonograph-1503329e69034a0fbb360504a1c4fba9">Phonograph</a></li><li>Helindu: <a href="https://sketchfab.com/3d-models/the-ultimate-glass-pack-cups-and-bottles-1ea37815fb0145e49487386d6924b341">The ultimate glass pack (cups and bottles)</a></li><li>Leandro Nicolas: <a href="https://sketchfab.com/3d-models/snow-globe-58a0f7e834c14413bb1a7e4262051180">Snow Globe</a></li><li>Martijn Vaes: <a href="https://sketchfab.com/3d-models/dae-bilora-bella-46-camera-game-ready-asset-eeb9d9f0627f4783b5d16a8732f0d1a4">DAE — Bilora Bella 46 Camera — Game Ready Asset</a></li><li>Jasper Flick “Catlike Coding”: <a href="https://catlikecoding.com/unity/tutorials/procedural-meshes/uv-sphere/texturing-a-sphere/latlon-normal-map.png">Latlon Normal Map</a></li><li>Joey de Vries: <a href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Learn OpenGL Parallax Mapping</a></li><li>Francesco Coldesina: <a href="https://sketchfab.com/3d-models/violin-burito-1c5c16934893497eb2db8ec82e04e8bb">Violin Burito</a></li><li>wallon: <a href="https://sketchfab.com/3d-models/audi-r8-e17e438f076f4427a58d93aa779edaed">Audi R8</a></li><li>Juani Forn: <a href="https://sketchfab.com/3d-models/korean-ornamental-dragon-a2b3bc23d51f46ba9b6108348f0c1098">Korean Ornamental Dragon</a></li><li>Lennart Demes: <a href="https://ambientcg.com/view?id=Ground054">Ground 054</a></li></ul><p>©️ Timothy Ned Atton 2023. All rights reserved.</p><p>All code appearing in GitHub Gists is distributed under the MIT license.</p><p><em>Timothy Ned Atton is a game developer and graphics engineer with ten years experience working with Unity. He is currently employed at GOLF+ working on the VR golf game, GOLF+. This tutorial is not affiliated with nor endorsed by GOLF+, Unity Technologies, or any of the people and organizations listed above. Thanks for reading!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6c4ae9875529" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Transparent and Crystal Clear: Writing Unity URP Shaders with Code, Part 3]]></title>
            <link>https://nedmakesgames.medium.com/transparent-and-crystal-clear-writing-unity-urp-shaders-with-code-part-3-f6ccd6686507?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/f6ccd6686507</guid>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[graphics]]></category>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Tue, 27 Sep 2022 01:38:46 GMT</pubDate>
            <atom:updated>2023-05-30T03:01:24.219Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7Pm7kLxFuDant3AE0to8yw.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F4zw6Vq5CzLY%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D4zw6Vq5CzLY&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F4zw6Vq5CzLY%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/48096d284e6aa77af7e11ac748c994d3/href">https://medium.com/media/48096d284e6aa77af7e11ac748c994d3/href</a></iframe><p>Hi, I’m Ned, and I make games! Have you ever wondered how shaders work in Unity? Or, do you want to write your own shaders for the Universal Render Pipeline, but without Shader Graph? Either because you need some special feature or just prefer writing code, this tutorial has you covered.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jFPcZR4fIB6yWKgk0sP00w.png" /></figure><p><strong>This is the third tutorial in a series on HLSL shaders for URP</strong>. If you’re starting here, I would recommend at least <a href="https://gist.github.com/NedMakesGames/0fc3cc299443d7efe81f25486c7178ec">downloading the starting scripts here</a>, so you have something to work from.</p><ol><li><a href="https://medium.com/@nedmakesgames/798cbc941cea"><em>Introduction to shaders</em>: simple unlit shaders with textures.</a></li><li><a href="https://nedmakesgames.medium.com/let-there-be-light-writing-unity-urp-shaders-with-code-part-2-112d370c2b75"><em>Simple lighting and shadows</em>: directional lights and cast shadows.</a></li><li><strong><em>Transparency</em>: blended and cut out transparency.</strong></li><li><a href="https://nedmakesgames.medium.com/6c4ae9875529"><em>Physically based rendering</em>: normal maps, metallic and specular workflows, and additional blend modes.</a></li><li><em>Advanced lighting</em>: spot, point, and baked lights and shadows.</li><li><em>Advanced URP features</em>: depth, depth-normals, screen space ambient occlusion, single pass VR rendering, batching and more.</li><li><em>Custom lighting models</em>: accessing and using light data to create your own lighting algorithms.</li><li><em>Vertex animation</em>: animating meshes in a shader.</li><li><em>Gathering data from C#</em>: additional vertex data, global variables and procedural colors.</li></ol><p>If you prefer video tutorials, <a href="https://youtu.be/4zw6Vq5CzLY">here’s a link to a video version of this article</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eZkIkfelUCchGuGnrwkwKw.png" /></figure><p>If you talk to shader devs, few words strike more fear into their hearts than “transparency!” Well, in this video, I want to demystify transparent shaders a bit. In the process, we’ll learn about render queues, custom inspectors, blend modes, alpha clipping, winding culling, and double sided normals.</p><p>Before I move on, I want to thank all my patrons for helping make this series possible, and give a big shout out to my “next-gen” patron: Crubidoobidoo! Thank you all so much.</p><p>Let’s delay no further and get to programming!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K2bWoCexlwBKrBsQUlYzGg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZSqQKYV9EjVMJEjct8q27A.png" /></figure><p><strong>Alpha Blending</strong>. So far, our shaders have ignored the alpha channel of the main texture, staying completely opaque. I say it’s time we changed that! Transparency is a complicated topic for many reasons, but it’s pretty easy to get started with it — just add one line to your pass block.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cab124bca8595d8c10b03e2766267efb/href">https://medium.com/media/cab124bca8595d8c10b03e2766267efb/href</a></iframe><p>This <em>Blend </em>command determines how the rasterizer combines fragment function outputs with colors already present on the screen (or render target). These were drawn by shaders that have run before! The color returned by the fragment function is called the source color, while the color stored on the render target is called the destination color.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1wkLA7v9oZmtbii8NSrF3g.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2fV_GOmE08NxvzoGrw2ZMQ.png" /><figcaption>The source color is returned by the Fragment function.</figcaption></figure><p>The rasterizer multiplies each color by some number and adds the products together, storing the result in the render target and overwriting what was already there. You can specify these multipliers using the <em>Blend</em> command: first the source color multiplier, then the destination color multiplier.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ep0sGYDfKhsuD2xjhn-6vw.png" /></figure><p><em>Blend One Zero</em> results in fully opaque materials — the default. For transparency, we need to linearly interpolate between the source and destination colors based on the source color’s alpha. Thankfully, ShaderLab has a “source alpha” and “one minus source alpha” multiplier, perfect for our goals. Add this to your forward lit pass block.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*akgzrV3hnYEM3c0k2KEC5g.png" /><figcaption>The red ball is supposed to be transparent, but does not blend correctly.</figcaption></figure><p>In the scene, test out blending by lowering the alpha on your material’s color tint or slotting in a texture with an alpha channel. Unfortunately, it doesn’t take long to notice some issues. First, transparent objects don’t always blend with objects behind them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ei0Fd0udmHYgnQmiu8-iDA.png" /><figcaption>The depth buffer stores surface depths of each drawn pixel.</figcaption></figure><p><strong>ZWrite Modes.</strong> Remember the depth buffer from our discussion of shadow mapping? Currently, the rasterizer stores transparent objects’ positions in the depth buffer, preventing fragments behind it from ever running. We can’t blend colors with a material that was never drawn! We need a way to prevent transparent surfaces from being stored in the depth buffer.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3149e047263b7d09c86d2f47a32171aa/href">https://medium.com/media/3149e047263b7d09c86d2f47a32171aa/href</a></iframe><p>Thankfully, this is also easy to do. Add this <em>ZWrite Off</em> command to your forward lit pass block. This prevents the rasterizer from storing any of this pass’s data in the depth buffer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*45sc1jVYUCtOyfVrHgkJdA.png" /></figure><p>Now, surfaces behind this shader will always draw.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*413ZjdVsmr6NHScy0m77Pw.png" /></figure><p><strong>Render Queues.</strong> Hmm, but there’s still some weirdness. The skybox completely overwrites all transparent objects — due to render order.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*efoPLBOq5B_862OSabhrwQ.png" /><figcaption>The fully opaque green and red pixels result in different final colors depending on draw order (of course, only if the depth buffer is turned off).</figcaption></figure><p>The blend operation depends on the order objects draw to the render target. Objects behind a transparent object must draw first in order to blend with them! Thankfully, we can control draw order using render queues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Be_h7gdLKtQI8WpLPcsMMQ.png" /></figure><p>When preparing to render a scene, URP takes a look at all renderable objects and sorts them by render queue. There are a few queues you can place shaders into using a <em>Queue</em> tag, set in the <em>SubShader </em>tags block. The default setting is “Geometry”, used for opaque materials. The “Transparent<em>” </em>queue tag comes after Geometry. By placing MyLit into this queue, we can ensure opaque objects draw first.</p><p>There’s also a <em>Skybox </em>queue, which runs in-between <em>Geometry </em>and <em>Transparent</em>. Previously, the skybox rendered after MyLit, and since MyLit had <em>ZWrite Off</em>, the rasterizer allowed the skybox shader to overwrite it. Using the transparent queue, this is not an issue.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/379c209c09b4c7f52811552519691056/href">https://medium.com/media/379c209c09b4c7f52811552519691056/href</a></iframe><p>For debugging and a few other more advanced systems, Unity also has a <em>RenderType </em>tag. This should be set to either “Opaque” or “Transparent.” RenderType doesn’t affect render order, but let’s go ahead and set it now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l5hiRkn5ZtfWygOxcR-gQg.png" /></figure><p>Now, both transparent spheres should show up in front of the skybox. Notice that the spheres even draw over each other correctly. Knowing what we do about render order, you might guess — correctly — that URP sorts objects within the same queue by distance from the camera, back to front. This is crucial for transparent shaders.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dCPsUz911M7xPCnKiY0dPA.png" /><figcaption>The left model writes to the depth buffer, the right model does not.</figcaption></figure><p>Unfortunately, this sorting does not extend to triangles within a single mesh. If you have a mesh with many pieces that overlap, they might overwrite one another. The only reliable way to fix this is to break your mesh into many pieces. Just one of the headaches you must endure while working with transparent materials…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zjyNWSQfnp6ov1dMnZ0gMg.png" /></figure><p>Before moving on, take a look at the frame debugger. You can see render order and verify that each object is in the correct queue. Look under “DrawTransparentObjects!”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xNgAqglBLObf0qpW6ATyLg.png" /></figure><p>If for some reason you need to tweak the draw order on a material basis, every material has a queue field at the bottom of its inspector. You can change queues and even give priority to certain materials. The “Geometry+1” queue runs after all other objects in the geometry queue, but still before the skybox queue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BvhnUzQ_m4WPyuRQrRcH0A.png" /><figcaption>The white cube uses a transparent Lit material and casts no shadows!</figcaption></figure><p>There’s one more bug you might have noticed: transparent objects cast fully opaque shadows. Transparent, or partial, shadows are very complicated, and I will not be covering them in this series. For better or worse, the Lit shader does not support transparent shadows either. The best we can do is disable shadows for transparent objects. But, to do that, we need to set up some C# infrastructure…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CuxEAzfy1UGqjw717AV1QQ.png" /></figure><p><strong>Custom Material Inspectors</strong>. If you wanted to use this shader for opaque materials like before, you could use an opaque texture and tint. However, that’s hardly an optimal solution. Since we turned z-writing off, there would be no protection from overdraw. The queue and render type would also be incorrect.</p><p>Is there a way to change these using material properties? Well, yes, but we would have to remember to set them all correctly in unison. Let’s create a custom material inspector script to easily switch between opaque and transparent modes using a single dropdown menu.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dTqoJGBqhHxs1bH53Il4QQ.png" /></figure><p>Unity editor scripts are placed in folders named “Editor,” so create that first. Inside, create a C# script called “MyLitCustomInspector.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8651a40b1d56ced88fe554d4ab0113d7/href">https://medium.com/media/8651a40b1d56ced88fe554d4ab0113d7/href</a></iframe><p>Open it, and delete the Start and Update functions. Change <em>MyLitCustomInspector </em>to inherit from <em>ShaderGUI</em>, which is located in the <em>UnityEditor </em>namespace.</p><p>There’s a lot to editor scripts, but we only need to use a couple key features. First, override the <em>OnGUI </em>method, which Unity calls whenever it needs to draw a material inspector. We can get the currently viewed material using the target field of <em>MaterialEditor</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9d6ce11085d5f458b532ae0e536547a4/href">https://medium.com/media/9d6ce11085d5f458b532ae0e536547a4/href</a></iframe><p>Before going any further, set up some properties in the shader file. Properties don’t only store values used in HLSL code — they also store metadata about a material. Create a <em>Float _SurfaceType</em> property to remember if this material is opaque or transparent. Add a <em>HideInInspector </em>attribute to ensure this property won’t show up in the user-facing inspector.</p><p>Next, create three <em>Float </em>properties for the source blend, destination blend, and z-write modes. To direct ShaderLab to use a property value in the <em>Blend </em>and <em>ZWrite </em>commands, surround the name with brackets. Do this for the ForwardLit pass. The shadow caster can use the default values of <em>Blend One Zero</em> and <em>ZWrite On</em>.</p><p>Now we need to change the <em>Queue </em>and <em>RenderType </em>tags! Unfortunately, pure ShaderLab doesn’t support variable tags - take care of this in the C# custom inspector! For now, reset the “RenderType” to “Opaque” and remove the “Queue” tag — it will default to “Geometry.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6876ef14137cfc25cbc6f5641235d470/href">https://medium.com/media/6876ef14137cfc25cbc6f5641235d470/href</a></iframe><p>Return to the custom inspector. Create an <em>enum </em>containing all “surface types” we’d like to support: opaque and transparent. Then, to add a dropdown containing all values in this enum, call <em>EditorGUILayout.EnumPopup</em> in <em>OnGUI</em>. The first argument is the UI label, while the second is the current value.</p><p>Unity stored the current value in the material’s “_SurfaceType” property. However we can’t just read it directly if we want to support serialization, undo, shader variants, and other editor features.</p><p>First, Unity encapsulates a property inside a <em>MaterialProperty </em>class. It passes a list to <em>OnGUI</em> containing a <em>MaterialProperty </em>instance for each property of the shader, and provides a <em>BaseShaderGUI.FindProperty </em>method to easily grab the one corresponding to “_SurfaceType.”</p><p><em>MaterialProperty </em>stores property values as floats, but its easy to cast them to <em>SurfaceType</em>. Pass that as the second argument to <em>EnumPopup — </em>the current value. <em>EnumPopup </em>returns the value displayed in the popup— either what was passed or a new value the user selects. Either way, cast it back to a <em>float </em>and reset the value in <em>MaterialProperty</em>.</p><p>Surround the <em>EnumPopup</em> call with <em>Begin-</em> and <em>EndProperty</em> functions. These enable material variants and all their cool features — only in Unity 2022. Unfortunately, these functions don’t exist before then. Be sure to either surround them in <em>#if</em> blocks or just omit them if you’re working in Unity 2021 or earlier.</p><p>Now, to listen for user input and set material properties appropriately, surround <em>EnumPopup </em>with these <em>Begin</em>- and <em>EndChangeCheck </em>functions. <em>EndChangeCheck </em>returns true if the user picks a new value from the dropdown.</p><p>Create an <em>UpdateSurfaceType </em>function, taking the material as an argument. Call it inside the <em>EndChangeCheck if</em> block. <em>UpdateSurfaceType </em>will refresh the <em>ZWrite </em>mode, <em>Blend </em>modes, tags and shadow caster based on the <em>_SurfaceType</em>. Since the function runs post-input, its safe to read properties directly from the material.</p><p>Use a <em>switch </em>statement to refresh the material based on the SurfaceType enum. Set render queue using <em>Material.renderQueue</em>. Override the “RenderType” tag using <em>SetOverrideTag</em>. Set <em>ZWrite </em>and <em>Blend </em>properties using <em>SetInt. </em>There’s a handy enumeration from Unity for blend modes- include the <em>Unity.Rendering</em> namespace to access it. As for ZWrite, 1 corresponds to <em>On</em> and 0 to <em>Off</em>.</p><p>Lastly, to turn off shadows, we can disable the shadow caster pass. There’s <em>SetShaderPassEnabled </em>to handle this.</p><p>Finally, back in <em>OnGUI</em>, ensure a <em>base.OnGUI</em> call comes at the end of the method. This draws the default inspector, which includes all properties of your material without the <em>HideInInspector </em>attribute.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fbd537ad326452a82d16b46314b28b31/href">https://medium.com/media/fbd537ad326452a82d16b46314b28b31/href</a></iframe><p>All that’s left to do is register this inspector class to our shader. In the .shader file, use the <em>CustomEditor </em>command inside the <em>Shader </em>block.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*An56MOYN_F6-_UdcanjgGg.png" /></figure><p>Check it out in the scene! You can now easily switch between opaque and transparent modes. Verify that everything looks good in the frame debugger too.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GP429fBxNUOtPWBK_s1pDw.png" /><figcaption>Switching to Lit and back to MyLit breaks the synchronization between properties and the dropdown menu!</figcaption></figure><p>You might notice one issue. If you switch shaders, the material might not initialize correctly until you mess with the surface type dropdown.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3738333436fa0cac5ccddddb566bdf9b/href">https://medium.com/media/3738333436fa0cac5ccddddb566bdf9b/href</a></iframe><p>The <em>ShaderGUI </em>class has a callback when a new shader is assigned to a material: <em>AssignNewShaderToMaterial</em>. Override it and leave the base call at the top. Check if the new shader is the “MyLit” shader using its name field. If true, call <em>UpdateSurfaceType</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*An56MOYN_F6-_UdcanjgGg.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/977cc1ad70f555a528efae8559e2e8fa/href">https://medium.com/media/977cc1ad70f555a528efae8559e2e8fa/href</a></iframe><p>In addition, in Unity 2022, material variants will not update correctly unless we add one more function: <em>ValidateMaterial</em>. Unity calls this whenever a material property changes, either when the user edits it or a parent variant changes. Have it call <em>UpdateSurfaceType</em>.</p><p>Another bug squashed!</p><p><strong>Alpha Cutouts</strong>. Is there a way to combine the flexibility of a transparent material with the performance of an opaque one? Sort of! Another common transparency strategy is called alpha testing, alpha clipping or alpha cutouts. In this mode, we use a texture like a cookie cutter to render only certain parts of a mesh.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sbaqtJJzeCb7DLHCF7ZYxg.png" /><figcaption>The leaves here are rendered using alpha cutouts.</figcaption></figure><p>It’s a hybrid mode! Each fragment is either fully opaque or fully transparent. No blending is required, making it safe to write to the depth buffer and cast shadows.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/71f5ebdcfd2871f933b9b28132e09e4b/href">https://medium.com/media/71f5ebdcfd2871f933b9b28132e09e4b/href</a></iframe><p>Supporting cutouts requires small adjustments to just about all code we’ve written so far, but let’s start with the custom inspector. Add a <em>TransparentCutout </em>member to the <em>SurfaceType enum</em>. While we’re here, rename the <em>Transparent </em>mode to <em>TransparentBlend</em>, to be more clear.</p><p>In <em>UpdateSurfaceType</em>, we need to set everything up for cutouts. They have the same <em>Blend </em>and <em>ZWrite </em>modes as opaque surfaces, but a different queue and render type tag. They also do cast shadows. Reorganize the <em>switch </em>statement to take all this into account.</p><p><strong>AlphaTest Queue</strong>. Notably, cutouts should run in a new queue, called “AlphaTest.” This queue runs after geometry but before skybox. But, since cutouts don’t need blending, why not just use geometry? It’s a question of optimization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uxwI25n3OB3Cz2KzDwv9Ig.png" /><figcaption>The cheaper white sphere prevents the expensive rainbow sphere from rendering behind it.</figcaption></figure><p>Beyond enabling transparency, draw order is a powerful optimization tool! Imagine this situation: two objects have opaque shaders, but one is much more resource intensive. We’d like to minimize overdraw to prevent shading the expensive material when it’s not visible. Unity tries to achieve this with depth sorting, but it’s not always reliable.</p><p>By placing the expensive shader into a later queue, we ensure it’s drawn when more of the depth buffer is filled. Alpha cutouts are more expensive than opaque shaders, so Unity orders them later to save time!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5f33c8c8438cb087def72ddc2e9c66ad/href">https://medium.com/media/5f33c8c8438cb087def72ddc2e9c66ad/href</a></iframe><p><strong>Clip and Discard</strong>. But back to the code. In MyLitForwardLitPass.hlsl, utilize this <em>clip</em> HLSL function to do the work. If you pass it a number less than or equal to zero, it will discard the currently running fragment. What does that mean?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*svYkmGqGvgeiAM3LUvM4Og.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*edupr2tjUlstt0xPABW2zQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lCCvIbywMx5apQh0Y0J5pQ.png" /></figure><p><em>discard </em>is a command to the rasterizer, causing it to pretend like it never invoked a particular fragment. It will short circuit the fragment function, returning immediately after <em>clip</em>, and throw out all data pertaining to the fragment — not writing to the depth buffer or render target. It’s as if the fragment function was never called!</p><p>We want to clip this fragment if the alpha value is less than a threshold — let’s use one-half for now. Subtract the alpha component by 0.5 and pass that to clip. To apply the color tint’s alpha, multiply it with the texture sample.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_gcMxUFsQtI-a-mcgnTKNg.png" /></figure><p>Go ahead and try it out! Grab a texture with an alpha channel and change your material to “Transparent Cutout” mode. Pretty cool, but there are several things to fix.</p><p><strong>Alpha Cutoff</strong>. Let’s start with the easiest. Right now, we always clip pixels below 50% alpha, but that might not be appropriate. Let’s add a property to make this adjustable.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/663b164f424a61c31094618871ff25a3/href">https://medium.com/media/663b164f424a61c31094618871ff25a3/href</a></iframe><p>Define a “_Cutoff” property in the .shader file. It should always range between zero and one, so use the special <em>Range</em> property type to create a slider.</p><p>By the way, this property has a “magic” name, meaning Unity always expects the alpha cutout threshold in a property named “_Cutoff.” This is important to some advanced features, like baked lighting. For now, just make sure your property is named “_Cutoff.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/39e9b7396fd44e7d76de4e07e1dbd493/href">https://medium.com/media/39e9b7396fd44e7d76de4e07e1dbd493/href</a></iframe><p>Then in MyLitForwardLitPass.hlsl, define <em>_Cutoff </em>near the other properties and subtract <em>_Cutoff </em>from alpha, instead of 0.5.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jDyqlQ_fWNizGy6-pOCXPA.png" /><figcaption>The left sphere has _Cutoff = 0.001, the right sphere has _Cutoff = 1.</figcaption></figure><p>Now, you can edit your material to better match the alpha values in the texture. Next problem: we’re clipping even in blended materials! This is not only incorrect, simply having a clip function in your shader can drastically decrease performance. We should use a keyword to remove the clip line when in opaque or blended modes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/00cdd31e7f3ea41a7aaa8c20126ea578/href">https://medium.com/media/00cdd31e7f3ea41a7aaa8c20126ea578/href</a></iframe><p><strong>Shader Features</strong>. This will be the first time we’ve actually used a keyword to change our own code. These keywords have no value, not even true or false, so an <em>#if </em>block cannot parse them. Instead, test if the keyword is defined, using the <em>defined </em>function. A keyword is defined when it is enabled, and it is not defined if its disabled.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7c1b8d6a86b3d86e56650ab6a10a2b87/href">https://medium.com/media/7c1b8d6a86b3d86e56650ab6a10a2b87/href</a></iframe><p>There’s a shortcut for this: <em>#ifdef</em>. Keep in mind that there is not an <em>#elifdef</em> for whatever reason, so I use the entire <em>defined </em>syntax for clarity sometimes. Anyway, use an <em>_ALPHA_CUTOUT </em>keyword to include or omit the clip function. Wrap the <em>clip </em>call in a <em>#ifdef</em> block and we’re good to go.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/08af7f13e0717457a1a4ed9d95ced3df/href">https://medium.com/media/08af7f13e0717457a1a4ed9d95ced3df/href</a></iframe><p>It’s possible to enable and disable keywords from C#, so take care of this in the custom inspector. In <em>UpdateSurfaceType</em>, enable or disable <em>_ALPHA_CUTOUT </em>based on the surface type.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0437e668540cfb0dded8058b59b3d856/href">https://medium.com/media/0437e668540cfb0dded8058b59b3d856/href</a></iframe><p>Now, we need shader variants based on <em>_ALPHA_CUTOUT</em>. Moving to the .shader file, add a #<em>pragma </em>to generate them. Instead of <em>multi_compile</em>, use a <em>shader_feature_local </em>command. Shader features are just like multi compiles, in that they generate shader variants based on a list of keywords. The difference is in game builds.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kYAdiXL60VmjN_CGMpW4CA.png" /></figure><p>When you build your game, or create a distributable, Unity must determine which compiled shader variants to include in the build. It gathers all shaders used by your game and starts filtering shader variants.</p><p>Unity includes all variants generated by <em>multi_compile </em>commands, but before including a <em>shader_feature </em>variant, it checks to make sure some material has the required keywords enabled. For instance, Unity only includes MyLit variants with <em>_ALPHA_CUTOUT</em> enabled if a material has a cutout surface type (and thus enables the <em>_ALPHA_CUTOUT </em>keyword).</p><p>Since this check happens at build-time, keywords which change dynamically at runtime (like the URP lighting keywords) should use <em>multi_compile</em>. Otherwise, use <em>shader_feature</em>.</p><p>Why bother with this? Well, shader variants are not cheap to compile! Shader features help optimize build time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WdXWmrUIEPVSlhJVYaPSiA.png" /></figure><p>Shader features always have an implicit “_” in their keyword list; in other words, they always trigger a variant with none of the listed keywords enabled. Of course, that variant may or may not be used by your game, but Unity is ready for the possibility!</p><p>Finally, the <em>local </em>suffix indicates that the keyword is unique to this shader and will not be set globally. We set <em>_ALPHA_CUTOUT </em>on a material-by-material basis, so it can use local variants. Keywords set globally by URP, like <em>_MAIN_LIGHT_SHADOWS</em>, cannot be local. Unity has a hard limit on the number of global keywords it can support, so it’s good practice to use local variants when possible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_NMDXlf64AD9AqwBoJfZKg.png" /></figure><p>Your shader should look the same as before, but your FPS will thank you for this optimization!</p><p><strong>Common HLSL Files</strong>. The next bug to tackle is shadows: objects no longer cast shadows that match their cutout shape! To fix that, clip fragments in the shadow caster pass.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c2de91ad53893438aa6ef1b000226f4a/href">https://medium.com/media/c2de91ad53893438aa6ef1b000226f4a/href</a></iframe><p>First, let’s take the alpha clipping logic in MyLitForwardLitPass.hlsl and move it to a function, called <em>TestAlphaClip</em>. Pass the color texture sample as an argument. To make this available in MyLitShadowCasterPass.hlsl, we should add it to a separate file and #include it in both files.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3c04365f5f7688727c372628ca9228e8/href">https://medium.com/media/3c04365f5f7688727c372628ca9228e8/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/38acf4765465e0d9da1f156c58820004/href">https://medium.com/media/38acf4765465e0d9da1f156c58820004/href</a></iframe><p>Create a new “MyLitCommon.hlsl” file. Remove <em>TestAlphaClip </em>from MyLitForwardLitPass.hlsl and paste it here. <em>TestAlphaClip </em>requires a couple of properties, <em>_Tint </em>and <em>_Cutoff</em>. Properties are defined shader-wide, so it makes sense to move property definitions to the common file. Include the URP library to have access to the <em>TEXTURE2D </em>macros.</p><p>In MyLitForwardPass.hlsl, trim duplicated code and #include the new common code file.</p><p>Let’s take a moment and think about this. HLSL is very C++-like, and<em> #include</em> causes the compiler to literally copy and paste the contents of the common file over the <em>#include</em> line. What happens if I accidentally <em>#include</em> a file twice? Unity will throw an error saying a variable has already been declared.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K0CECD7Ru936LMEhvmk5Ig.png" /></figure><p>OK, but duplicating <em>#include</em> lines is not common. Well, what if MyLitCommon and MyLitForwardPass also <em>#include</em> a file called MyMath.hlsl? Then MyMath would be duplicated!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kXTmBJ0KzqJJKo4frxpxaQ.png" /></figure><p>To simplify things and prevent errors, it’s customary to wrap all code in an HLSL file inside something called a guard keyword block. First, check if a keyword is not defined (that’s what <em>#ifndef</em> does, it’s shorthand for <em>#if !defined()</em>). On the very next line, <em>#define</em> the guard keyword. Don’t forget the <em>#endif</em> at the end of the file!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8d877e684b203c1c5cfc4092a262dc3a/href">https://medium.com/media/8d877e684b203c1c5cfc4092a262dc3a/href</a></iframe><p>Now, the first time MyMath is included, the compiler defines the guard keyword. The second time, the guard keyword is enabled, and it skips the code inside. Pretty smart! Go ahead and add guard keywords to MyLitCommon. I also add them to all HLSL files to be safe.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vG5zJmrNTceZr2vYYIB3Jg.png" /></figure><p><strong>Clip in the Shadow Caster</strong>. After all this cleanup, your shader should still work the same. Let’s finally add clipping to the shadow caster.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/41efd471952887732d651e595cb41c9b/href">https://medium.com/media/41efd471952887732d651e595cb41c9b/href</a></iframe><p>First, in the .shader file, add the <em>_ALPHA_CUTOUT </em>shader feature to the shadow caster pass.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/da66ba2cd36fc1edf5835f0678a078cf/href">https://medium.com/media/da66ba2cd36fc1edf5835f0678a078cf/href</a></iframe><p>In MyLitShadowCasterPass.hlsl, add <em>#include “MyLitCommon.hlsl”</em>. At the moment, the shadow caster has no UVs to sample the main texture. We will need to pass them all the way to the fragment stage.</p><p>To do that, add a <em>uv </em>field to <em>Interpolators</em>. Each field here is another that the rasterizer has to interpolate, and it’s best to keep this struct as small as possible. Surround the <em>uv </em>field with an <em>#if</em> block, ensuring it’s only interpolated when needed. It’s not as important to do this in the <em>Attributes </em>struct, but it can’t hurt.</p><p>In the vertex function, transfer the UV to the output struct, again only if <em>_ALPHA_CUTOUT </em>is defined.</p><p>Then, in the fragment function, sample the color texture and call <em>TestAlphaClip</em>. We can wrap all this in an <em>#if </em>block too. Remember, <em>clip</em> discards fragments if passed a value below zero, causing the rasterizer to throw them out and not write to the depth buffer. Since the depth buffer becomes the shadow map, clipped fragments won’t show up there either.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e0_CQ8gfNWfbu_ec5Auidw.png" /></figure><p>With that, your shadows correctly match the clipped shape. Make sure you test out all modes on your material, to make sure all the #if blocks are set up correctly.</p><p>There’s one last problem I want to fix, but it requires a little more explanation…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eSRRGBS7Qd7asvmt_WdSRA.png" /></figure><p><strong>Double Sided Rendering</strong>. Often, games use alpha clipping on flat planes, in which case everything looks fine. But if you turn on alpha clipping on a sphere, you might notice the inside becomes invisible!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RgfURGRUJ01EF_os-Ant6Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w73pBM1406R5-9ZC71zNRg.png" /><figcaption>A half sphere with culling enabled and disabled.</figcaption></figure><p><strong>Face Culling</strong>. This is another optimization technique called face culling, winding culling, or simply “culling.” The particulars aren’t important, but basically, the rasterizer determines whether it’s rendering the front or back of a triangle on the mesh. The back face is usually on the inside of a model, so the rasterizer “culls” it, or decides not to render it. If you’ve ever had weird issues importing from Blender and had to reverse faces, culling is the culprit.</p><p>In our case, we’d like to render the inside of the sphere. Luckily, it’s easy to turn off culling.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/09e9599655a82d535b62939df81ef6eb/href">https://medium.com/media/09e9599655a82d535b62939df81ef6eb/href</a></iframe><p>In MyLit.shader, add a new float property called <em>_Cull</em>. We’ll use this to set another ShaderLab command, <em>Cull</em>. <em>Cull </em>can take three different values: <em>Off</em>, <em>Front </em>and <em>Back</em>. <em>Off </em>completely turns off culling, while the other two cull one side of a triangle.</p><p>Unity has a C# <em>enum </em>for these options, and we can instruct the default material inspector to create a dropdown using it. In the <em>enum</em>, <em>Back </em>has an int value of two, so let’s set that as the default.</p><p>Then, add a <em>Cull </em>command to your forward lit and shadow caster passes, with the <em>_Cull </em>property in brackets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bufKC35wCqJUYkxzdn5GuA.png" /></figure><p>Return to the scene and try it out. Turning off culling reveals both sides of the sphere!</p><p><strong>Double Sided Normals</strong>. But, a new bug! The lighting is incorrect. Notice that both sides of a triangle receive the same amount of light, as if the sphere is made of paper. This is because both sides have the same normal vector — it is not automatically flipped for the back face. We need to do that ourselves.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ae87ae074b0526182e1b59c2db16b4cc/href">https://medium.com/media/ae87ae074b0526182e1b59c2db16b4cc/href</a></iframe><p>In MyLitForwardLitPass.hlsl, flip the normal vector given to <em>InputData </em>if rendering the back of a triangle. To flip a vector, simply multiply it by negative one. But, how do we know which side of the triangle is rendering? The rasterizer has this information and makes it available through a special, fragment-stage-only semantic.</p><p>To grab it, simply add another argument to the <em>Fragment </em>function. Both the exact semantic and the argument type depend on the current platform. Thankfully, Unity provides macros to sidestep the issue. Regardless<em>, frontFace’s </em>type is essentially a boolean, true if the triangle front face is visible.</p><p>Unity also provides another macro to choose a value based on <em>frontFace, </em>like the ternary operator in C#. Use that to either multiply the normal vector by 1 or -1 before setting it in <em>lightingInput</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W3L6xuGAWVfcw6rVf2Nt3Q.png" /></figure><p>So, that fixes lighting, but triangle back faces now have shadow acne. Since shadow biases also depend on normals, we need to flip them too. Unfortunately, rasterizer front face data is not available in the vertex stage — it runs before the rasterizer!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vaCWxYNlU2O-UtSYGwM_hg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ESXpVCn0wyC4Sii7-hpZPA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m6D6guvF3Y_BZsnVBmfrqw.png" /><figcaption>In the first image, the red normal vector is within ninety degrees of the blue view direction. In the second image, it’s outside the range — flip it!</figcaption></figure><p>Luckily there’s another way. If we assume the normal vector should always point roughly towards the camera, we can flip it if it does not. Let’s try to keep the angle between the normal vector and the view direction less than ninety degrees. If the angle is greater, flip the normal. This brings it back within the acceptable range!</p><p>There’s a very simple function to find the angle between two vectors: the dot product. It returns the cosine of that angle. Cosine of ninety degrees is zero, and if the dot product is less than zero, the angle between the vectors is greater than ninety degrees.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b7701dee0c30a545739c3582066cabaf/href">https://medium.com/media/b7701dee0c30a545739c3582066cabaf/href</a></iframe><p>Let’s implement this In MyLitShadowCasterPass.hlsl using a new <em>FlipNormalBasedOnViewDir </em>function. Pass it position and normal. Calculate the view direction using a built in URP function (which we also used in the forward lit fragment function). Then, only if the dot product of the normal and view direction is less than zero, multiply the normal by negative one. Return the normal.</p><p>Modify the clip space calculation to call <em>FlipNormalBasedOnViewDir</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KO7znJbI9KTJgGudF9LGig.png" /></figure><p>Back in the scene editor, it looks like there’s no more shadow acne! Mission accomplished!</p><p>This technique isn’t perfect — sometimes it creates shadows that kind of flicker as you rotate around the object. But, that’s probably preferable to shadow acne.</p><p><strong>Face Rendering Mode</strong>. Double sided normals are not free — both the flipping operations and the front face semantic have a bit of overhead. It’s a good idea to use a keyword to turn this feature on and off. Thinking about it, there’s also no reason to ever flip normals if culling is on. This leaves only three useful configurations: back culling, no culling, and no culling with normal flipping.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c906b461ba8370682b91764b6d074e08/href">https://medium.com/media/c906b461ba8370682b91764b6d074e08/href</a></iframe><p>Let’s address these concerns by updating the custom inspector. Create a new <em>enum</em>, <em>FaceRenderingMode</em>, to encapsulate the aforementioned modes. Similarly to the surface mode, use another hidden property to keep track of this setting. In <em>OnGUI</em>, add another <em>enum </em>dropdown controlling this new property: <em>_FaceRenderingMode </em>(and the appropriate surrounding functions for Unity 2022).</p><p>In <em>UpdateSurfaceType</em>, update the <em>_Cull </em>property and either enable or disable a keyword, <em>_DOUBLE_SIDED_NORMALS</em>. If the face rendering mode is <em>FrontOnly</em>, set <em>_Cull </em>to <em>Back </em>using Unity’s <em>CullMode</em> <em>enum</em>. Otherwise, turn culling off. Then, enable or disable our keyword appropriately.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1d281f3faa2d61000cc9b152a113b7db/href">https://medium.com/media/1d281f3faa2d61000cc9b152a113b7db/href</a></iframe><p>Moving on to MyLit.shader, first hide the <em>_Cull </em>property in the inspector and remove the <em>Enum </em>attribute (it’s handled by code now). Second, add another hidden property for <em>_FaceRenderingMode</em>. Third, add a shader feature for <em>_DOUBLE_SIDED_NORMALS </em>to both passes.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9cc3c33d81c3b2518634a82265eefbb5/href">https://medium.com/media/9cc3c33d81c3b2518634a82265eefbb5/href</a></iframe><p>In MyLitForwardLitPass, wrap all the normal flipping code in <em>#if</em> blocks. There’s a bit of trickery to hide the front face semantic when it’s not needed. It’s ugly, but it works!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/190732c3138ae1408210d3a8cf555d8d/href">https://medium.com/media/190732c3138ae1408210d3a8cf555d8d/href</a></iframe><p>In MyLitShadowCasterPass, similarly wrap the call to <em>FlipNormalBasedOnViewDir </em>within a <em>#if</em> block.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dyTSQmRlsCAuQxy8OWwmlA.png" /><figcaption>Each sphere has a different face rendering mode setting.</figcaption></figure><p>In the scene editor, try out different face rendering modes to make sure everything works as expected!</p><p>In the future, think carefully whether an object actually needs any of these options. Turning off culling can dramatically increase performance cost, and double sided normals aren’t exactly cheap either. These options are very useful for things like foliage — just don’t blindly enable them for all materials!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2Z6GLuEui6IAFYrkdrS21w.png" /></figure><p>With that, I would consider this a fully functional shader! It supports all the basic needs: textures, lighting, shadows, transparency, and even goes above and beyond the Lit shader with double sided normals! But, of course, we’re far from done.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b8UJ1UjXls9iwq8bsNYljw.png" /></figure><p>In the next tutorial, I will focus on more surface options! We’ll implement a new lighting model called PBR — physically based rendering — which gives more customization options to make lighting more realistic. Rough, metallic, glassy, smooth, shiny, and glowing materials are in our future!</p><p>For reference, <a href="https://gist.github.com/NedMakesGames/4c9bc4ae54c8c481f299538ce420b29a">here are the final versions of the shader files</a>.</p><p>If you enjoyed this tutorial, consider <strong>following </strong>me here to receive an email when the next part goes live. <a href="https://nedmakesgames.medium.com/6c4ae9875529">Part 4 is available here!</a></p><p>If you want to see this tutorial from another angle, I created a <a href="https://youtu.be/4zw6Vq5CzLY">video version you can watch here</a>.</p><p>I want to thank <strong>Crubidoobidoo </strong>for all their support, as well as all my patrons during the development of this tutorial: Adam R. Vierra, Amin, autumnboy, Ben Luker, Ben Wander, bgbg, Bohemian Grape, Boscayolo, Brannon Northington, Brooke Waddington, Cameron Horst, Charlie Jiao, Christopher Ellis, CongDT7, Connor Wendt, Crubidoobidoo, Dan Pearce, Daniel Sim, Davide, Derek Arndt, Dongsik Gang, Elmar Moelzer, Eren Aydin, far few giants, Henry Chung, Howard Day, Isobel Shasha, Jack Phelps, John Lism Fishman, John Luna, Joseph Hirst, JP Lee, jpzz kim, JY, Kat, Kyle Harrison, Lasserino, Leafenzo (Seclusion Tower), lexie Dostal, Lhong Lhi, Lien Dinh, Lukas Schneider, Mad Science, Marcin Krzeszowiec, Mattai, Minh Triết Đỗ, Oliver Davies, P W, Patrick, Patrik Bergsten, rafael ludescher, Richard Pieterse, Robin Benzinger, Sam CD-ROM, Samuel Ang, Sandro Traettino, santhosh, SHELL SHELL, Simon Jackson, starbi, Steph, Stephan Maier, Steve DeBusschere, Syll art-design, Taavi Varm, Team 21 Studio, thearperson, Thomas Terkildsen, Tim Hart, Tomasz Patek, ultraklei, Vincent Thémereau, Voids Adrift, Wei Suo, Wojciech Marek, Xavier Larrosa Rogel</p><p>If you would like to download all the shaders showcased in this tutorial inside a Unity project, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p>If you have any questions, feel free to leave a comment or contact me at any of my social media links:</p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> ☕ <a href="https://ko-fi.com/nedmakesgames">Ko-fi</a> 📧 E-mail: nedmakesgames gmail</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>Change log:</p><ul><li>May 29th 2023: Material inspector changes, required for material variants in Unity 2022.</li><li>Unity Technologies: <a href="https://assetstore.unity.com/packages/templates/tutorials/shader-calibration-scene-25422">Shader Calibration Scene</a></li><li>Martijn Vaes: <a href="https://sketchfab.com/3d-models/dae-bilora-bella-46-camera-game-ready-asset-eeb9d9f0627f4783b5d16a8732f0d1a4">DAE — Bilora Bella 46 Camera — Game Ready Asset</a></li><li>tonyflanagan: <a href="https://sketchfab.com/3d-models/thailand-girl-animated-a41f779fe45b4a04bdbf212e49b7f6b5">Thailand Girl — Animated</a></li><li>gugusheep: <a href="https://sketchfab.com/3d-models/vr-showroom-gallery-for-product-placement-d8f967fb44ea46cb9b62e720c6485ac0">VR Showroom Gallery for product placement</a></li><li>Helindu: <a href="https://sketchfab.com/3d-models/the-ultimate-glass-pack-cups-and-bottles-1ea37815fb0145e49487386d6924b341">The ultimate glass pack (cups and bottles)</a></li><li>Leandro Nicolas: <a href="https://sketchfab.com/3d-models/snow-globe-58a0f7e834c14413bb1a7e4262051180">Snow Globe</a></li><li>Andriy Shekh: <a href="https://sketchfab.com/3d-models/pine-tree-e52769d653cd4e52a4acff3041961e65">Pine tree</a></li><li>Sergej Majboroda: <a href="https://polyhaven.com/a/studio_small_09">Studio Small 09</a></li></ul><p>©️ Timothy Ned Atton 2022. All rights reserved.</p><p>All code appearing in GitHub Gists is distributed under the MIT license.</p><p><em>Timothy Ned Atton is a game developer and graphics engineer with ten years experience working with Unity. He is currently employed at Golf+ working on the VR golf game, Golf+. This tutorial is not affiliated with nor endorsed by Golf+, Unity Technologies, or any of the people and organizations listed above. Thanks for reading!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f6ccd6686507" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Let There Be Light: Writing Unity URP Shaders with Code, Part 2]]></title>
            <link>https://nedmakesgames.medium.com/let-there-be-light-writing-unity-urp-shaders-with-code-part-2-112d370c2b75?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/112d370c2b75</guid>
            <category><![CDATA[graphics]]></category>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[game-development]]></category>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Mon, 18 Jul 2022 23:49:15 GMT</pubDate>
            <atom:updated>2023-05-30T02:34:26.296Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Flt7Ex8_2EVtX5SatPs1MQ.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F1bm0McKAh9E%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D1bm0McKAh9E&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F1bm0McKAh9E%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/f7d35ebe36397e7b0f63b75703402bf8/href">https://medium.com/media/f7d35ebe36397e7b0f63b75703402bf8/href</a></iframe><p>Hi, I’m Ned, and I make games! Have you ever wondered how lighting and shadows work in Unity? Or, do you want to write your own shaders for the Universal Render Pipeline, but without Shader Graph? Either because you need some special feature or just prefer writing code, this tutorial has you covered.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QQGK6LaaimMxWsu4oAqtaA.png" /></figure><p>In fact, this is the second part in a series about writing HLSL shaders for URP. In this video, I will show how to add lighting to a shader. This includes a simple explanation of shadow mapping — how objects cast and receive shadows in URP — as well as an introduction to keywords and shader variants — an important concept when writing shaders!</p><p>As I publish future sections, I will update this page with links! You can also subscribe here to receive a notification when I finish part three. If you’re starting here, <a href="https://medium.com/@nedmakesgames/798cbc941cea">I would recommend following the first part, where we write a basic unlit shader</a>. This tutorial will continue directly from it.</p><ol><li><a href="https://medium.com/@nedmakesgames/798cbc941cea"><em>Introduction to shaders</em>: simple unlit shaders with textures.</a></li><li><em>Simple lighting and shadows</em>: directional lights and cast shadows.</li><li><a href="https://nedmakesgames.medium.com/f6ccd6686507"><em>Transparency</em>: blended and cut out transparency.</a></li><li><a href="https://nedmakesgames.medium.com/6c4ae9875529"><em>Physically based rendering</em>: normal maps, metallic and specular workflows, and additional blend modes.</a></li><li><em>Advanced lighting</em>: spot, point, and baked lights and shadows.</li><li><em>Advanced URP features</em>: depth, depth-normals, screen space ambient occlusion, single pass VR rendering, batching and more.</li><li><em>Custom lighting models</em>: accessing and using light data to create your own lighting algorithms.</li><li><em>Vertex animation</em>: animating meshes in a shader.</li><li><em>Gathering data from C#</em>: additional vertex data, global variables and procedural colors.</li></ol><p>If you prefer video tutorials, here’s a link to a <a href="https://youtu.be/1bm0McKAh9E">video version of this article</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2hchAb2iIYhYlVoH7HISrg.png" /></figure><p>Before I move on, I want to thank all my patrons for helping make this series possible, and give a big shout out to my “next-gen” patron: Crubidoobidoo! Thank you all so much.</p><p>With that, let’s get started!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K2bWoCexlwBKrBsQUlYzGg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kTfl53iPOwIKm8sh1vjycQ.png" /></figure><p><strong>Blinn-Phong Shading</strong>. So far, we’ve learned to write unlit shaders, or shaders not affected by lights. Obviously, lighting is a very important aspect of rendering; programmers devote a lot of shader code to it. Luckily for us, URP provides a helper function which deals with much of it.</p><p>In URP’s “lighting.hlsl” file, there’s a function called <em>UniversalFragmentBlinnPhong</em>. It computes a standard lighting algorithm called the Blinn-Phong lighting model. Blinn-Phong is actually made of two components.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_sqKNoynCUibX1Y5PBpphA.png" /></figure><p>The first calculates diffuse lighting — what illuminates the side of an object facing towards a light.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kkdDHDdrGcSEq75uFuPHKg.png" /></figure><p>The second calculates specular lighting — the shine or highlight that brings smooth objects to life.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/222dbf6b05ecebaae62b77c70f79aeff/href">https://medium.com/media/222dbf6b05ecebaae62b77c70f79aeff/href</a></iframe><p>Open MyLitForwardLitPass.hlsl, and in the <em>Fragment</em> function, call <em>UniversalFragmentBlinnPhong</em>. It returns a color, which we can simply return as well. <em>UniversalFragmentBlinnPhong </em>takes many arguments, but to keep things neat, it bundles them up into two structures. The first, called <em>InputData</em>, holds information about the position and orientation of the mesh at the current fragment. The second, called <em>SurfaceData</em>, holds information about the surface material’s physical properties, like color.</p><p>Define a variable for both. These structures have nearly a dozen fields each, but we don’t need to set them all yet. Unlike C#, structure fields must be manually initialized. To set all fields to zero, cast zero to the structure type. This looks strange, but it’s an easy way to initialize a structure without having to know all its fields.</p><p>Now, pass <em>inputData </em>and <em>surfaceData </em>to <em>UniversalFragmentBlinnPhong</em>.</p><p><strong>Version Differences.</strong> Back in part one, I mentioned that there are several differences between Unity 2020 and Unity 2021. Well, here’s the first that affects our shader. In Unity 2020, URP does not have an overload of <em>UniversalFragmentBlinnPhong </em>that takes a <em>SurfaceData </em>struct. You’ll have to pass the fields individually, like below. For now, don’t worry about what each field means, we will get to them soon.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d26e18ec55ac345d316ad7cabd007cc8/href">https://medium.com/media/d26e18ec55ac345d316ad7cabd007cc8/href</a></iframe><p>Both to keep this tutorial more organized and to help you upgrade projects in the future, I want the same code to run in Unity 2020 and Unity 2021. Thankfully, there’s an easy way to run different code depending on the current Unity version.</p><p>You might have seen a <em>#if</em> preprocessor command in C# — usually it omits code that should only run in the editor. <em>#if</em> is also available in ShaderLab and HLSL, where it’s a common sight! If the expression following #if is true, then the code in between <em>#if</em> and <em>#endif</em> will be compiled. Otherwise, the compiler will ignore it.</p><p><em>#if </em>can only depend on values that are known before compiling code, like constants and number literals. Unity provides a constant called <em>UNITY_VERSION </em>which contains the current Unity version as an integer — basically the version with periods omitted.</p><p>So, in our fragment function, we want to switch between passing the surface data struct or its individual fields based on the Unity version. If it’s greater than or equal to “202102,” we can pass the structure. To define an else block, which works just like you would expect, use <em>#else</em>. Inside, call the version with individual arguments.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/16ad677f91c0a78f5aecdfe416aa8ef2/href">https://medium.com/media/16ad677f91c0a78f5aecdfe416aa8ef2/href</a></iframe><p>Anyway, now the shader code will dynamically change depending on which Unity version we’re working with. Neat!</p><p>For the future, if you need to support another possibility, you can use <em>#elif</em>, which is short for else-if. Here’s an example for a hypothetical Unity 2030 version.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/40cb393ce8606671e266d72ca50c3388/href">https://medium.com/media/40cb393ce8606671e266d72ca50c3388/href</a></iframe><p>Check out your shader in the scene editor. It’s now just a black sphere!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ykB5e3P1I4flHSM0SY5l7w.png" /></figure><p>To get back to where we were before, we need to fill some fields in the input structs. From the color properties, we can set <em>albedo </em>and <em>alpha</em>, which are fancy names for base color and transparency. But, remember that the shader doesn’t support transparency just yet, so don’t expect it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/de1165ed38b204802a9e11c3facd1409/href">https://medium.com/media/de1165ed38b204802a9e11c3facd1409/href</a></iframe><p><strong>Normal Vectors. </strong>Next, we need something called a “normal vector.” You may know what normal vectors are from math or Unity’s physics systems, but they’re vectors that point directly outwards from a surface. Blinn-Phong uses them to find where the mesh faces a light source.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*97L7KxyMEMqjESQinLHLwQ.png" /><figcaption>Normal vectors visualized on faces of a cube model.</figcaption></figure><p>Normal vectors apply to faces, but they’re organized into a mesh vertex stream, like position or UVs. This can complicate things. There’s no problem on a sphere, but on sharp cornered meshes, like a cube, it can look like vertices have multiple normal vectors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cQkOy3OUCUNAHvPz_9AELg.png" /><figcaption>Normal vectors stored on vertices of a sphere.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6UMHAp29CliWMWPNX31NHg.png" /><figcaption>Normal vectors stored on vertices of a cube. Notice the duplicated vertices!</figcaption></figure><p>In reality, Unity duplicates vertices — one for each normal vector. This way, a vertex’s normal always matches the face it applies to.</p><p>Regardless, the input assembler will take care of gathering normal data. Add a new field to the <em>Attributes </em>struct tagged with the <em>NORMAL </em>semantic. These normals are also in object space, like position.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/49ec743678822c48aa60bd198471f16d/href">https://medium.com/media/49ec743678822c48aa60bd198471f16d/href</a></iframe><p>When adding a new data source to a shader, it’s useful to plan out its “journey” through your code. Blinn-Phong needs normals in the fragment stage, but they’re only accessible through the input assembler. We need to pass them through the vertex stage and interpolate them with the rasterizer.</p><p>In addition, <em>UniversalBlinnPhong </em>expects normals in world space, and it’s necessary to transform them at some point. We could do that in the fragment stage, but we don’t need object space normals there at all. It’s a bit more optimal to calculate world space normals in the vertex function, since it runs fewer times than the fragment function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wqEt7d2r_wx0AoyeRoMq8Q.png" /></figure><p>Using this plan, go through the code section by section and modify it as needed.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5bcf7d76233d84e348329d01ef0d0e93/href">https://medium.com/media/5bcf7d76233d84e348329d01ef0d0e93/href</a></iframe><p>We already added a <em>normalOS </em>field to <em>Attributes</em>. Add a <em>normalWS </em>field to <em>Interpolators</em>. The rasterizer will interpolate any field tagged with a <em>TEXCOORD </em>semantic, so tag normal with <em>TEXCOORD1</em>.</p><p>Why 1 and not 0? Well, <em>TEXCOORD0 </em>was already taken by UVs, and two fields should not have the same semantic. The rasterizer can handle many <em>TEXCOORD </em>variables — two is no problem.</p><p>In the vertex function, transform the normal vector from object to world space. URP provides another function to do this, <em>GetVertexNormalInputs</em>, similar to the one we used for positions. Call it, and set the world space normal in the <em>Interpolators </em>struct.</p><p>In the fragment function, set <em>normalWS </em>in the <em>InputData </em>struct.</p><p>Before moving on, let’s think a little about what happens to the normal vector when it’s interpolated. When the rasterize interpolates vectors, it interpolates each component individually. This can cause a vector’s length to change, like in this example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y70iqgdx_5OT9Zz3T4qPxg.png" /><figcaption>When interpolating between normals pointing in opposite directions, the middle values will change length.</figcaption></figure><p>For lighting to look its best, all normal vectors must have a length of one. This requirement is common when a vector encodes a direction. We can bring any vector to a length of one using the aptly named <em>normalize </em>function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*efACb_PYoKsQhSmHrYr0Nw.png" /><figcaption>These normals have been normalized to always have a length of one.</figcaption></figure><p><em>normalize </em>is kind of slow, since it has an expensive square root calculation inside. I think this step is worth it for smoother lighting — it’s especially noticeable on specular highlights — but if you’re pressed for processing power, you can skip it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f500db8fd1e0971fbe048495898b70dc/href">https://medium.com/media/f500db8fd1e0971fbe048495898b70dc/href</a></iframe><p>In the scene editor, we finally have lighting!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XtaKqfZ8WI2Ru4cK2IxsLQ.png" /></figure><p><strong>Specular Lighting.</strong> But, it’s a little flat, with only diffuse lighting. For specular highlights, URP needs a little more data, specifically world space position.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/67a8509894edb8ee00b0749fea5d1c28/href">https://medium.com/media/67a8509894edb8ee00b0749fea5d1c28/href</a></iframe><p>Right now, the fragment function does not have access to world space position, only pixel positions. There’s not an easy way to transform those back to world space; it’s best to pass it as another field in the <em>Interpolators </em>struct. Tag it with another free <em>TEXCOORD </em>variable. (I reorganized them a little here, just for personal preference.)</p><p>Set position in the vertex stage using URP’s handy transform function! Then, in the fragment function, set <em>positionWS </em>in <em>InputData</em>. No need to normalize here of course, since position is not a direction and can have any length.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FW-Zu7F8bsJ0%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DW-Zu7F8bsJ0&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FW-Zu7F8bsJ0%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/9b809543411ee95cd483ed8b73c2bff9/href">https://medium.com/media/9b809543411ee95cd483ed8b73c2bff9/href</a></iframe><p>If you move around an object using the default lit shader, you’ll notice the highlights also move slightly. This is because specular lighting depends on the view direction, or the direction from the fragment to the camera.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a576480bccb9f1e742cbc1187f910b8/href">https://medium.com/media/8a576480bccb9f1e742cbc1187f910b8/href</a></iframe><p>We can calculate this in the fragment function from world space position using another handy URP function, <em>GetWorldSpaceNormalizeViewDir</em>. Call it and set <em>viewDirectionWS </em>in <em>InputData</em>.</p><p>Highlights can sometimes have different colors than the albedo, and URP allows you to specify this with a <em>specular </em>field in the <em>SurfaceData </em>struct. For now, set it to white.</p><p>If you take a peek at the scene, there’s still no highlights! It turns out <em>UniversalFragmentBlinnPhong </em>uses a <em>#if</em> command internally to toggle highlights on and off. It uses a special type of constant called a keyword to do so. Keywords are sort of like boolean constants you enable using a <em>#define</em> command.</p><p>Shaders make extensive use of keywords to turn on and off different features. It’s faster to disable specular lighting instead of, for instance, setting specular color to black. Either option has the same visual effect, but not evaluating something is obviously quicker than throwing out the result.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bc00af58876c1128f86f32c73871310c/href">https://medium.com/media/bc00af58876c1128f86f32c73871310c/href</a></iframe><p>However, I want specular lighting in this shader. For organization, I define keywords in the ShaderLab file for each pass, making it obvious which keywords are enabled at a glance. Add <em>#define _SPECULAR_COLOR</em> to your pass block.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0W9p8MffjAb7sVDks7TPVQ.png" /></figure><p>Now — finally — highlights! But, they’re too big! URP provides an easy way to shrink them using a value called smoothness. The higher the smoothness, the smaller the highlight. Visualize a perfectly smooth metal ball; the highlight is quite focused!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9f8d833a660218c72c8f633c1af38857/href">https://medium.com/media/9f8d833a660218c72c8f633c1af38857/href</a></iframe><p>For now, let’s define smoothness using a material property. Add a property called <em>_Smoothness </em>of the <em>Float </em>type to your shader.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/657077dc649ba6a83c13c16c488e23c8/href">https://medium.com/media/657077dc649ba6a83c13c16c488e23c8/href</a></iframe><p>In “MyLitForwardLitPass,” declare <em>_Smoothness </em>at the top of the file and set it in the <em>SurfaceData</em> structure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s2G3_USYiey1NONJxRXpqg.png" /></figure><p>Using the material inspector, you can control the size of the highlight using the smoothness property. Note that smoothness works differently depending on the Unity version. 2021’s implementation is much more sensitive. This is just a consequence of how URP calculates lighting behind the scenes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bkXC39biHbI1vbtDAErs2A.png" /></figure><p>One note before we move on. The shader only supports the main light for now. We should get the basics down before complicating things with additional lights, but I will show how to add support for them in part 5 of this series!</p><p><strong>The Shadow Mapping Algorithm.</strong> So far, we’ve worked with just one object. If you create another, you’ll notice that objects with our shader neither cast nor receive shadows. These are separate concepts in the world of shaders, and we’ll need to implement both.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qCaVhtoRBeSwRiqrLSIXbg.png" /></figure><p>First, let’s investigate how URP handles shadows using an algorithm called “shadow mapping.”</p><p>The goal is finding a cheap way to check if a fragment is in shadow, with respect to a light source. Again, let’s only consider the main light.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aT4il3xZSKs02mTRfPDXvg.png" /><figcaption>We want to calculate if several surfaces are in shadow.</figcaption></figure><p>A naive approach is to check for an object between the fragment and the light. This is very slow, since the shader needs to execute a raycast, looping through all objects in the scene. There’s got to be a faster way.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vdbFqN14lnnVboBDAh0fNA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OGorJJxdsXcub1VKuR485w.png" /><figcaption>The middle surface is in shadow. A raycast from the surface to the light intersects another surface.</figcaption></figure><p>First, let’s restructure our algorithm to orient the ray starting from the light and shooting out in a straight line, intersecting our fragment and any other surfaces on the same line.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8TE4DP6MmRwJoCgNHIpDFw.png" /></figure><p>Second, notice that only one surface on the ray is lit. For every surface but the one closest to the light, there is an object between it and the light. To determine if a fragment is in shadow, simply test if the distance to the light is greater than the minimum distance among all surfaces to the light.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e5v4zpNxc11jmz04DTbQHA.png" /><figcaption>Only the closest surface is not in shadow.</figcaption></figure><p>This reduces the problem to finding the distance from the light to the closest surface along all light rays. This kind of sounds familiar… When rendering, we draw the color of the closest surface to the camera along all “view rays.” Switch color with distance and the camera with a light and we’re in business!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*swiVbm4flGeR5iJexjmL6A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vPblKcO2WyOqJU3VfFJX4Q.png" /><figcaption>The left image is a normal render, while the right image draws distance from the camera. Both are from the light’s perspective.</figcaption></figure><p>How can we draw distance? Remember that colors are just numbers, so we can store distance inside the red channel of a color.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JX-s6MNL3_uZmvg2kSwC0g.png" /></figure><p>URP’s shadow mapping system does this behind the scenes. Before rendering color, it switches the camera to match the perspective of the main light. Then, it utilizes another shader pass, the shadow caster pass, to draw depth for each pixel.</p><p>We don’t want these depths to draw to the screen though. URP hijacks the presentation stage and directs it to draw to a special texture, called a render target. This specific render target, containing distances from a light, is called a shadow map, hence the algorithm name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SAKLoFTk3J5D-xwN_ZOk1w.png" /><figcaption>The shadow map texture has UV coordinates like any other texture.</figcaption></figure><p>To calculate if a fragment is in shadow, we need our distance from the light and the distance stored in the shadow map. To sample the shadow map, we need to calculate the shadow map UV — also known as a shadow coord — corresponding to this fragment’s position. URP again comes through with a function, <em>TransformWorldToShadowCoord</em>, to convert world space position to a shadow coord.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/061f53cd62cbfdfa6950eb8cc4fb040a/href">https://medium.com/media/061f53cd62cbfdfa6950eb8cc4fb040a/href</a></iframe><p>URP will deal with comparing distances and sampling the shadow map if we set <em>shadowCoord </em>in the <em>InputData </em>struct. In the fragment function of “MyLitForwardLitPass.hlsl,” go ahead and do that.</p><p><strong>Shader Variants.</strong> Similarly to specular lighting, URP toggles shadows on and off with a keyword called <em>_MAIN_LIGHT_SHADOWS</em>. However, what if I’m making a dark scene with no main light? In that case, I’d like to turn off shadows, but I don’t want to create a whole new shader with only this keyword undefined.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8c84572c1831f9697c8c451bd2f0d0c/href">https://medium.com/media/b8c84572c1831f9697c8c451bd2f0d0c/href</a></iframe><p>Luckily, Unity has the system of shader variants for this use case. Using this <em>#pragma multi_compile</em> command, we can have Unity compile a version of our shader with and without <em>_MAIN_LIGHT_SHADOWS </em>enabled. These two versions are called variants of our shader, or more specifically, variants of the forward lit pass.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PSJ9WlfKIrfNdPgy0Sp63g.png" /><figcaption>Adding variants creates another subdivision below passes with slightly different vertex and fragment functions.</figcaption></figure><p>Multi compile can also take a whole list of keywords, in which case it will create multiple variants, one with each individual keyword enabled</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jc8OISYyCOJpW1FVSHyXEw.png" /></figure><p>By adding a single underscore, it will also compile a variant with none of the keywords enabled.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wv3OtD2AElo1ESQy4BKxtA.png" /></figure><p>Even more conveniently, materials will automatically choose the correct variant for the situation at hand.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bb430dcccafbe82190e2d8ace6f4fb2c/href">https://medium.com/media/bb430dcccafbe82190e2d8ace6f4fb2c/href</a></iframe><p>You see, if you want to use a variant with <em>_MAIN_LIGHT_SHADOWS </em>defined, simply call <em>EnableKeyword(“_MAIN_LIGHT_SHADOWS”)</em> on the material in C#. <em>DisableKeyword </em>will undefine the keyword. URP does this automatically if it detects a directional light in the scene.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ps-YfAC8aQuWrVc08xvsFw.png" /></figure><p>Check it out! Create an object with a default lit material and move it in between your my lit object and the light.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iZRXtwoXaKftb8RQZmooBg.png" /></figure><p><strong>Shadow Cascades and Soft Shadows.</strong> If you don’t see shadows, turn off cascades and soft shadows on your URP settings asset. It would be nice to support both of those options for better quality though.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_BIOaHvxOoKwLdYkOFpzyw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y9c-AO8Wnty2rcv8OWPYqA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JX-s6MNL3_uZmvg2kSwC0g.png" /><figcaption>Three shadow cascades, each with more focused view of the scene.</figcaption></figure><p>We’ve been talking like the main light has a position, but since it models the sun, it is actually infinitely far away from everything in the scene. This makes it difficult to create a shadow map containing the entire scene while keeping enough detail for good quality. Unity tries to balance this with cascades, where it renders multiple shadow maps, each containing larger slices of the scene, and samples the one with the most detail at any position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V4LFqLutTQDjakryUVnNXg.png" /><figcaption>Unity samples the highest detail cascade that contains a specific world position. Each color here represents shadow data taken from a different cascade.</figcaption></figure><p>Since the shadow map has square pixels, you sometimes see their jagged edges manifest on surfaces. Soft shadows help eliminate these artifacts by sampling the shadow map a few times around the given shadow coord. It averages these samples, effectively blurring the shadow map a bit.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CVi2uVyXLF3Hjknamuibrg.png" /></figure><p>We don’t need to worry about the details of either system though. We only need to enable two keywords and URP will take care of the rest.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/aae8f58dd02f53ff271f2e5133eb7247/href">https://medium.com/media/aae8f58dd02f53ff271f2e5133eb7247/href</a></iframe><p>Add more multi compile commands for these new keywords. With multiple multi compile commands, Unity will permute each and create a variant for every possible combination of keywords. We’ve barely started, but that’s already six variants. Each takes time to compile, so it’s worth it to keep this number low.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*awcKlu0BEN7Iw3A4uSS_iw.png" /></figure><p>With that in mind, Unity 2021.2 tweaked the cascade system a little. In 2020, we must enable keywords for main light shadows and cascades, but in 2021, enabling cascades implies main light shadows are enabled. We can handle both using a <em>#if</em> block and reduce variant count in 2021.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e3dcea6534691b9a68b3f08c4884b75a/href">https://medium.com/media/e3dcea6534691b9a68b3f08c4884b75a/href</a></iframe><p>Also, notice the <em>_fragment</em> suffix in the soft shadows pragma? We can save a little more compile time by signaling the <em>_SHADOWS_SOFT </em>keyword is only used in the fragment stage. Unity will have the variants created by this multi compile command share a vertex function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CVi2uVyXLF3Hjknamuibrg.png" /></figure><p>With that, let’s test things out. Be sure to tweak shadow cascades and enable soft shadows to see all your shader variants at work. You can see URP dynamically compiling shader variants when your shader momentarily flickers magenta.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o4Cx9Io1yoSFN5920iEfzg.png" /></figure><p>Unity 2022 has additional options for shadow quality: high quality soft shadows and a “conservative enclosing sphere” for shadow cascades. Try enabling them to see what they do — no code changes required.</p><p><strong>The Frame Debugger.</strong> Now seems like a good time to introduce a powerful debugging tool: the Frame Debugger! Find it in the “Window” dialog under “Analysis.” Enable it using this button in the top left. Make sure your game view is visible — It will also automatically pause if it’s running.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HCcWcb62Ty2EK3y9pfpUAQ.png" /></figure><p>This useful window tells you all kinds of information about how Unity renders your scene. It renders objects in the order they appear in the menu. You can see Unity creating the shadow map before rendering lit passes. You can even check out how the shadow map looks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AyDVfzTvGs3DJiCP_lrLBA.png" /><figcaption>The shadow map with four cascades.</figcaption></figure><p>The frame debugger also tells you which shader variant is currently active for any object. Navigate to the “DrawOpaqueObjects” dropdown and find your sphere. Check the shader name, it will be “MyLit!”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0Ttuh4D95ZKRXJGstCNH-A.png" /></figure><p>You can see the current subshader and pass, and under that a list of defined keywords determining the shader variant. Try disabling soft shadows, cascades, and the main light game object and to see how that influences things.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lfZYPjaKLvEp1Qy-jQ6Chg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bskuznl3M2_o7_VPqqo5hg.png" /><figcaption>The MyLit shader with the main light disabled and then with soft shadows disabled.</figcaption></figure><p>The window doesn’t do a good job of keeping the same object selected, and you’ll have to find your shader again as you toggle things on and off. There will also be slight differences depending on your Unity version, so keep that in mind.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6ZLIURn5JF5gcduHptnO9Q.png" /></figure><p><strong>The Shadow Caster Pass.</strong> You may have tried to apply the MyLit shader to your shadow caster sphere and noticed it no longer casts shadows. That’s because casting and receiving shadows are completely different processes in 3D rendering, and we haven’t dealt with casting yet!</p><p>In the previous section, I mentioned URP creates a shadow map texture using another shader pass, called the shadow caster pass. All we have to do to add shadow casting to MyLit is write this pass.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mXFR2YaBCPiKcps7yENkmQ.png" /></figure><p>Remember, passes are shader subdivisions with their own vertex and fragment functions. Passes can also have their own multi compile keywords and shader variants. Each shader pass has a specific job, as given by URP. The <em>UniversalForward </em>(forward lit)<em> </em>pass calculates final pixel color, while the <em>ShadowCaster </em>pass calculates data for the shadow map.</p><p>Honestly, it’s much simpler in practice than it sounds. URP takes care of calling the correct pass at the correct time and routing the output colors into the correct target. These abstract passes are hard to visualize, so let’s get something written.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ba3a6e5210967229e7fe0c870277eb18/href">https://medium.com/media/ba3a6e5210967229e7fe0c870277eb18/href</a></iframe><p>Begin by adding another Pass block to the “MyLit.shader” file. Duplicate the <em>ForwardLit </em>pass, changing the name and light mode tag to <em>ShadowCaster</em>. There will be no lighting here; delete the <em>_SPECULAR_COLOR</em> define and the shader variant pragmas.</p><p>For organization, I like to write each pass in its own HLSL file. Change the <em>#include</em> to refer to “MyLitShadowCasterPass.hlsl.” Then, create a new HLSL file called “MyLitShadowCasterPass.hlsl.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a2b20c2fde3c522b3af11e8a42b7d2c1/href">https://medium.com/media/a2b20c2fde3c522b3af11e8a42b7d2c1/href</a></iframe><p>Inside, start by defining the data structs. In <em>Attributes</em>, we’ll only need position, while <em>Interpolators </em>only needs clip space position. In the vertex function, call the URP function to convert position to clip space, set it in the output structure, and return it. In the fragment function, simply return zero.</p><p><strong>The Depth Buffer.</strong> But wait, isn’t the shadow map supposed to encode distance from the light-camera? It does, but the renderer handles this automatically. See, clip space positions encode something called depth, which is related to distance from the camera. When interpolating, the rasterizer stores the depth of each fragment in a data structure called the depth buffer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mbCd-2Vsy4FsFEfBxN_X7w.png" /><figcaption>The Z-component of clip space position is (related to) a fragment’s depth.</figcaption></figure><p>Unity utilizes the depth buffer to help reduce overdraw. Overdraw occurs when two or more fragments with the same pixel position are rendered during a frame. When everything is opaque, as it is now, only the closer fragment is ultimately displayed. Any other fragments are discarded, leading to wasted work. The rasterizer can avoid calling a fragment function if its depth is greater than the stored value in the depth buffer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FJ9HZ5QAZzhINJ_zwaaAKQ.png" /><figcaption>The overlapping section of these two spheres would be rendered twice if not for the depth buffer.</figcaption></figure><p>URP reuses the depth buffer resulting from the shadow caster pass as the shadow map. But, most other passes have a depth buffer of their own as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ei0Fd0udmHYgnQmiu8-iDA.png" /><figcaption>The depth buffer of the windowed gallery scene.</figcaption></figure><p><strong>Shadow Acne</strong>. Our MyLit objects should now cast shadows, but you will see some ugly artifacts called shadow acne covering them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ct5oazEyrobt2hRfNiOyTw.png" /></figure><p>This occurs mostly on the surface of a shadow casting object. It’s another consequence of every programmer’s bane: floating point errors. In this case, the shadow map depth and the mesh depth are nearly equal, so the system sometimes draws the shadow on top of the casting surface.</p><p>To fix acne, apply a bias, or offset the shadow caster vertex positions. When calculating clip space positions, there’s no rule that they must exactly match the mesh. We can offset the positions away from the light and also along the mesh’s normals. Both of these biases help prevent shadow acne.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qTZ7XME59yLjB8uq1-MaVA.png" /><figcaption>The girl’s vertices offset along their normal vectors.</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c907e5639c3365145ec3ac925a487eb4/href">https://medium.com/media/c907e5639c3365145ec3ac925a487eb4/href</a></iframe><p>The shadow caster now needs normals, so add a normal field to the <em>Attributes</em> struct. Then, write a new function, <em>GetShadowCasterPositionCS</em>, to calculate the offset clip space position. It requires world space position and normal.</p><p><em>ApplyShadowBias </em>from URP’s library will read and apply shadow bias settings. It requires world space position and normal, as well as the rendering light’s direction. URP provides this in a global variable called <em>_LightDirection. </em>We need to define it, like a material property. Do that above the function and pass it to <em>ApplyShadowBias</em>. <em>ApplyShadowBias </em>returns a position in world space. Transform it to clip space using another URP function, <em>TransformWorldToHClip</em>.</p><p>Clip space has depth boundaries, and if we accidentally overstep them when applying biases, the shadow could disappear or flicker. The boundary of depth is defined by something called the “light near clip plane.” Clamp the clip space z-coordinate by the near plane value, defined by <em>UNITY_NEAR_CLIP_VALUE</em>.</p><p>To make things more complicated, certain graphics APIs reverse the clip space z-axis. Thankfully, URP provides another boolean constant, <em>UNITY_REVERSED_Z</em>, to tell us if the boundary is a minimum or maximum. Use a <em>#if</em> statement to handle both cases and return the final clip space position.</p><p>In the vertex function, calculate the world space position and normal using URP’s conversion functions, then call your custom shadow caster clip space function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FkOVCMKAoqufdEsv2OW-hw.png" /></figure><p>Back in the scene editor, things might look immediately better. If not, edit the shadow bias settings and light near clip plane on the main light component.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MAKJCzv57H2OR2QKHeagJA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oJ2S860L7530MCe-iUNpDw.png" /></figure><p>You can set global bias settings on the URP settings asset too.</p><p><strong>ColorMask.</strong> Before wrapping up, we can optimize the shadow caster a little by adding some metadata to the pass block. Since the shadow caster only uses the depth buffer, we can basically turn off color using the <em>ColorMask</em> command.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/560a854bcc19124dc7bbf4127236a633/href">https://medium.com/media/560a854bcc19124dc7bbf4127236a633/href</a></iframe><p>This <em>ColorMask 0</em> command does just that, basically directing the renderer to write no color. By default, <em>ColorMask </em>is set to <em>RGBA</em>, which is what we want in the forward lit pass, for example. With this setting it draws all color channels.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2hchAb2iIYhYlVoH7HISrg.png" /></figure><p><strong>Lighting and shadows are some of the most fun aspects of shaders</strong>, and we will be revisiting them much more throughout this series. After this tutorial, you have a good groundwork to continue with more complicated features. We’ve got point lights, spot lights, baked lights, emission, screen space shadows and more to get to!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CZpWFd8MNkw0NPFOXtFF0g.png" /></figure><p>However, in the next tutorial, I will pivot to transparency. We’ll learn how to create transparent shaders and how to handle their idiosyncrasies. I’ll also introduce some of URP’s powerful optimization tools.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NqKzDsiBI1FMItD9xd4S1g.png" /></figure><p>For reference, <a href="https://gist.github.com/NedMakesGames/0fc3cc299443d7efe81f25486c7178ec">here are the final versions of the shader files</a>.</p><p>If you enjoyed this tutorial, consider <strong>following </strong>me here to receive an email when the next part goes live.</p><p>If you want to see this tutorial from another angle, I created a video version you can watch <a href="https://youtu.be/1bm0McKAh9E">here</a>.</p><p>I want to thank <strong>Crubidoobidoo </strong>for all their support, as well as all my patrons during the development of this tutorial: 42 Monkeys, Adam R. Vierra, Adoiza, Amin, Andrei Hingan, Ben Luker, Ben Wander, bgbg, Bohemian Grape, Brannon Northington, bruce li, Cameron Horst, Charlie Jiao, Christopher Ellis, Connor Wendt, Constantine Miran, Crubidoobidoo, Davide, Derek Arndt, Elmar Moelzer, Eric Bates, etto space, Evan Malmud, far few giants, FELIX, Florian Faller, gamegogojo, gleb lobach, Howard Day, Huynh Tri, Isobel Shasha, Jack Phelps, Jesse Comb, jesse herbert, John Lism Fishman, JP Lee, jpzz kim, Kyle Harrison, Leafenzo (Seclusion Tower), lexie Dostal, Lhong Lhi, Lien Dinh, Lukas Schneider, Luke Hopkins, Luke O Reilly, Mad Science, Marcin Krzeszowiec, martin.wepner, maxo, Minori Freyja, Nate Ryman, Oliver Davies, Oskar Kogut, P W, Patrick, Patrik Bergsten, Paul, Petr Škoda, Petter Henriksson, rafael ludescher, Rhi E., Richard Pieterse, roman, Ryan Smith, Sam CD-ROM, Samuel Ang, Sebastian Cai, Seoul Byun, shaochun, SHELL SHELL, Simon Jackson, starbi, Steph, Stephan Maier, teadrinker, Team 21 Studio, thearperson, Tim Hart, Tomáš Jelínek, Vincent Thémereau, Voids Adrift, Wei Suo, Wojciech Marek, Сергей Каменов, Татьяна Гайдук, 智則 安田, 이종혁.</p><p>If you would like to download all the shaders showcased in this tutorial inside a Unity project, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p>If you have any questions, feel free to leave a comment or contact me at any of my social media links:</p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> ☕ <a href="https://ko-fi.com/nedmakesgames">Ko-fi</a> 📧 E-mail: nedmakesgames gmail</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>Changelog:</p><ul><li>May 29th 2023: Mention new shadow quality options available in Unity 2022.</li><li>Unity Technologies: <a href="https://assetstore.unity.com/packages/templates/tutorials/shader-calibration-scene-25422">Shader Calibration Scene</a></li><li>Martijn Vaes: <a href="https://sketchfab.com/3d-models/dae-bilora-bella-46-camera-game-ready-asset-eeb9d9f0627f4783b5d16a8732f0d1a4">DAE — Bilora Bella 46 Camera — Game Ready Asset</a></li><li>tonyflanagan: <a href="https://sketchfab.com/3d-models/thailand-girl-animated-a41f779fe45b4a04bdbf212e49b7f6b5">Thailand Girl — Animated</a></li><li>Aleksei Vlasov ⚡ CRWDE: <a href="https://sketchfab.com/3d-models/phonograph-1503329e69034a0fbb360504a1c4fba9">Phonograph</a></li><li>gugusheep: <a href="https://sketchfab.com/3d-models/vr-showroom-gallery-for-product-placement-d8f967fb44ea46cb9b62e720c6485ac0">VR Showroom Gallery for product placement</a></li><li>Andreas Mischok: <a href="https://polyhaven.com/a/drackenstein_quarry">Drackenstein Quarry</a></li></ul><p>©️ Timothy Ned Atton 2022. All rights reserved.</p><p>All code appearing in GitHub Gists is distributed under the MIT license.</p><p><em>Timothy Ned Atton is a game developer and graphics engineer with ten years experience working with Unity. He is currently employed at Golf+ working on the VR golf game, Golf+. This tutorial is not affiliated with nor endorsed by Golf+, Unity Technologies, or any of the people and organizations listed above. Thanks for reading!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=112d370c2b75" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Writing Unity URP Shaders with Code Part 1: The Graphics Pipeline and You]]></title>
            <link>https://nedmakesgames.medium.com/writing-unity-urp-shaders-with-code-part-1-the-graphics-pipeline-and-you-798cbc941cea?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/798cbc941cea</guid>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[graphics]]></category>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[tutorial]]></category>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Tue, 12 Apr 2022 02:26:29 GMT</pubDate>
            <atom:updated>2023-05-30T02:23:47.684Z</atom:updated>
            <content:encoded><![CDATA[<h3>The Graphics Pipeline and You: Writing Unity URP Shaders with Code Part 1</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1jbqpTnVvvlVzuyANnBizA.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FKVWsAL37NGw%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DKVWsAL37NGw&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FKVWsAL37NGw%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/c6b16c021640f2052469e1184ccef5b5/href">https://medium.com/media/c6b16c021640f2052469e1184ccef5b5/href</a></iframe><p>Hi, I’m Ned, and I make games! Would you like to start writing shaders but don’t know where to start? Or, have you encountered a limitation of URP’s Shader Graph you need to overcome? In this tutorial series, I’ll walk you through writing a fully featured, general purpose shader for Unity’s Universal Render Pipeline — exclusively in code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K2bWoCexlwBKrBsQUlYzGg.png" /></figure><p>This tutorial is broken into nine sections, outlined below. You’re reading the first part, where I introduce shaders and how to write them. As I publish future sections, I will update this page with links! You can also subscribe here to receive a notification when I finish another part.</p><ol><li><em>Introduction to shaders</em>: simple unlit shaders with textures.</li><li><a href="https://nedmakesgames.medium.com/112d370c2b75"><em>Simple lighting and shadows</em>: directional lights and cast shadows.</a></li><li><a href="https://nedmakesgames.medium.com/f6ccd6686507"><em>Transparency</em>: blended and cut out transparency.</a></li><li><a href="https://nedmakesgames.medium.com/6c4ae9875529"><em>Physically based rendering</em>: normal maps, metallic and specular workflows, and additional blend modes.</a></li><li><em>Advanced lighting</em>: spot, point, and baked lights and shadows.</li><li><em>Advanced URP features</em>: depth, depth-normals, screen space ambient occlusion, single pass VR rendering, batching and more.</li><li><em>Custom lighting models</em>: accessing and using light data to create your own lighting algorithms.</li><li><em>Vertex animation</em>: animating meshes in a shader.</li><li><em>Gathering data from C#</em>: additional vertex data, global variables and procedural colors.</li></ol><p><strong>The examples in this tutorial were tested in Unity 2020.3, 2021.3, and 2022.3</strong>. As you follow the tutorial, you will come across many features only possible in 2021 or later. However, I have written the shaders so the same code runs in both Unity versions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2Z6GLuEui6IAFYrkdrS21w.png" /></figure><p>Let’s lay out some goals of this tutorial series. My aim is to teach how to write shaders. I will show several examples, writing them step by step and explaining as we go. Do not view these as ready-for-production shaders, but rather as blueprints you can customize to your game’s needs.</p><p>After completing the series, you’ll know how to write your own version of URP’s standard lit shader, as well as customize it with your own lighting algorithm and more. You’ll also know several optimization techniques and how to leverage Unity’s debugging tools to diagnose and fix rendering bugs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b8UJ1UjXls9iwq8bsNYljw.png" /></figure><p>There’s quite a lot to set up and understand before anything displays on screen, which can make learning shaders difficult. To speed up the process, I’ll only explain information as needed, and I might gloss over some edge cases or technicalities. Don’t worry, I’ll address them later if they become important.</p><p>To keep this series from becoming even longer, I will assume you have some general game development knowledge. You should be comfortable using Unity and its 3D game tools - models, textures, materials, and mesh renderers. And, although you need no prior experience writing shaders, you should know how to program. C# experience will definitely be useful.</p><p>With all that out of the way, let’s get started!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XQ7iNou8yi9T529aRPyVbg.png" /></figure><p><strong>Project Set Up. </strong>In this series, we will use Unity’s Universal Render Pipeline. It’s one of several rendering frameworks Unity provides. I chose URP for this project due to its recency and ability to support a wide variety of platforms while remaining lightweight.</p><p>I would highly suggest creating a fresh project for this tutorial. You can either select the URP project template…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yQeU5qzKg1PI9bPN7-Hwkg.png" /></figure><p>Or use the blank template, add URP manually through the package manager, and activate it in Graphics settings. In the settings object, make sure that “Depth Priming Mode” is set to “Disabled” and that the rendering mode is “Forward.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-3YK_8QtBgmi9p_TwZY29g.png" /></figure><p>Either way, create a new standard scene to work with.</p><p><strong>The Anatomy of a Shader. </strong>Large shaders, like the one we will write, are made of several files. To keep things organized, create a new folder called “MyLit” to contain them all. Create a shader file by selecting “Until Shader” from the create dialog, then name it “MyLit.shader.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z0rYtGaMBIXSMGWrEgFdew.png" /></figure><p>If you create a material, you’ll see that your shader already shows up in the shader selection menu of the material inspector (under the Unlit submenu).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0jAVzG73OjiDy07c7c-aKg.png" /></figure><p>Open the shader file in your code editor of choice. Sometimes Unity doesn’t generate Visual Studio projects if there are no C# scripts present, so create an empty C# script if your shader doesn’t appear in the solution explorer.</p><p>Regardless, inside “MyLit.shader” is a lot of automatically generated code — delete it all for now. This part of a shader is written in a language called ShaderLab, and it defines meta information about the drawing code.</p><p>This first line opens a shader block and defines the shader’s name in the material inspector. Any slashes will create subsections in the selection menu — useful for organization. The block is bound by curly braces, like classes in C#.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c5f50a8c15d9d54e4f7b50937dd35c23/href">https://medium.com/media/c5f50a8c15d9d54e4f7b50937dd35c23/href</a></iframe><p>A shader is more than just code that draws. A single shader is actually made of many — sometimes thousands — of smaller functions. Unity can choose to run any of them depending on the situation. They’re organized into several subdivisions. The top-most subdivisions are known as subshaders. Subshaders allow you to write different code for different render pipelines. Unity automatically picks the correct subshader to use.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Gyt1343lcCkPf6m-WtyY5A.png" /></figure><p>Define a subshader with a <em>Subshader </em>block, and then add a <em>Tags </em>block to set the render pipeline. Tags blocks hold user defined metadata in a format kind of like a C# dictionary.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5028733453164ce29bad912343bff019/href">https://medium.com/media/5028733453164ce29bad912343bff019/href</a></iframe><p>Tell Unity to use this subshader when the Universal Rendering Pipeline is active by setting “RenderPipeline” to “UniversalPipeline.” That’s the only subshader we’ll ever need in this tutorial.</p><p>Subshaders are just the first subdivision; below them are passes. Passes’ purpose is more abstract. Each pass has a specific job to help draw the entire scene — like calculating lighting, cast shadows, or special data for post processing effects. Unity expects all shaders to have specific passes to enable all of URP features. For now, let’s focus on the most important pass: the one that draws a material’s lit color.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g4BgXq2QumoRbG_5BCm_BA.png" /></figure><p>To signal that this pass will draw color, add a <em>Tags </em>block inside. The pass type key is “LightMode”, and the value for our lit color pass is “UniversalForward.” You can also name passes, which helps a lot when debugging.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b93052ed6e2c3ac81b1b494f27230f10/href">https://medium.com/media/b93052ed6e2c3ac81b1b494f27230f10/href</a></iframe><p>OK, we’re almost ready to write some code. URP shader code is written in a language called HLSL, which is similar to a streamlined C++. To mark a section of a shader file as containing HLSL, surround it with the <em>HLSLPROGRAM</em> and <em>ENDHLSL</em> code words.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5beb866a4f4630e6ede5fb581d77aaa5/href">https://medium.com/media/5beb866a4f4630e6ede5fb581d77aaa5/href</a></iframe><p>To keep things organized, I like to keep HLSL code in a separate file from the .shader metadata. Thankfully, this is easy to do. You can’t create an HLSL file directly in Unity, but you can in Visual Studio (choose any template and change the extension to “.hlsl”) or your file system (create a text file and <a href="https://www.wikihow.com/Change-a-File-Extension#Making-File-Extensions-Visible-on-Windows">change the extension</a> to “.hlsl”).</p><p>Name this new file “MyLitForwardLit.hlsl,” and open it in your code editor. I just want to mention that many code editors don’t work well with URP shaders. While working through this tutorial, ignore any errors you see in the code editor and just rely on Unity’s console.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ui_-duzBy8nUrQjylTy0uw.png" /></figure><p><strong>The Graphics Pipeline.</strong> When writing shaders, you need to have a different mindset than you would working with C#. For one, there’s no “heap,” meaning most variables work like numeric primitives. You also can’t define classes or methods, or use inheritance. Structs and functions are still available to help organize your code!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c4c1d7e19ec07f0a0210f9542a59d4f1/href">https://medium.com/media/c4c1d7e19ec07f0a0210f9542a59d4f1/href</a></iframe><p>If you’ve ever worked with data driven design, you will feel at home writing shaders. The focus is gathering data and transforming it from one form to another.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oHEABwLBegIvSGB2PPdl1w.png" /></figure><p>In the broadest sense, shaders transform meshes, materials, and orientation data into colors on the screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gwIXOzWu5nswfvLtLNn3rg.png" /></figure><p>There are two standard functions which the system calls, kind of like <em>Start</em> and <em>Update </em>in <em>MonoBehaviours</em>. These functions are called the vertex and fragment function. Every pass must have one of each.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8iOmqLboMfrdKmy0_dm1VA.png" /></figure><p>These both transform data from one form to another. The vertex function takes mesh and world position data and transforms it into positions on the screen. The fragment function takes those positions, as well as material settings, and produces pixel colors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c92HoB3CieVkU4iBJ7iM-A.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NqAAol_NiZX87YYuvXhdag.png" /></figure><p>Unity’s rendering system employs something called the graphics pipeline to link together these functions and handle low level logic. The pipeline gathers and prepares your data, calls your vertex and fragment functions, and displays the final colors on the screen. It’s made of several stages, running one after another, like an assembly line.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gF0D6A_-28kZ90-IgzUMnA.png" /></figure><p>Each stage has a specific job, transforming data for stages down the assembly line. There are two special stages, the vertex and fragment stages, which are “programmable,” running the vertex and fragment functions you write. The others are not programmable and run the same code for all shaders, although you can influence them with various settings.</p><p>Let’s take a look at each stage, starting at the beginning: the input assembler. It prepares data for the vertex stage, gathering data from meshes and packaging it in a neat struct. Structs in HLSL are very similar to C# — a pass-by-value variable containing various data fields. This struct is customizable — you can determine what data the input assembler gathers by adding fields to the struct.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TEkgZ5uRxEnahSinUEzz0Q.png" /><figcaption>Each vertex has data for each field: position, normal, and UVs.</figcaption></figure><p>What data can the input assembler access? The input assembler works with meshes, specifically mesh vertices. Each vertex has a bunch of data assigned to it, such as a position, normal vector, texture UVs, etc. Each data type is known as a “vertex data stream.” To access any stream in your input structure, you simply need to tag it and the assembler will automatically set it for you.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/993d53aab3a9d79312191c37da8addd2/href">https://medium.com/media/993d53aab3a9d79312191c37da8addd2/href</a></iframe><p>For example, here’s an input struct for our forward pass’s vertex function. It defines a struct called “<em>Attributes</em>”. It has a field called “position,” with a type called “<em>float3</em>.” <em>float3</em> is the HLSL term for a C# <em>Vector3</em>, or a vector containing three float numbers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*keRst9Jd_sn3xyoFN4RdlA.png" /><figcaption>Use the POSITION semantic to access position data.</figcaption></figure><p>Use semantics to tag variables — the input assembler will automatically set them to a particular vertex data stream. For example, the <em>POSITION</em> semantic corresponds to vertex position. Keep in mind, only the semantic determines what data the input assembler will choose — the variable name does not matter. Feel free to name variables however you wish. We’ll see more semantics later on — HLSL uses them often to help the graphics pipeline.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nfadyhlXSeKKwIz18zkaTQ.png" /></figure><p>With that, let’s move to the vertex stage. As a programmable stage, you get to define the code that runs here.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/90c48d1271dddaa2d30859417241af3f/href">https://medium.com/media/90c48d1271dddaa2d30859417241af3f/href</a></iframe><p>Defining a function in HLSL is very similar to C#, with a return type — void this time — a function name, and a list of arguments. An argument’s type precede the variable name. This function only needs a single argument of <em>Attributes </em>type.</p><p>The vertex stage’s primary objective is to compute where mesh vertices appear on screen. However, notice that the Attributes struct only contains a single position — only data for a single vertex. The render pipeline actually calls the vertex function multiple times, passing it data for each vertex until all are placed on screen. In fact, many calls will run in parallel!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jQlbHOsuE7_HTADNVR1jDw.png" /></figure><p>If you’ve ever programmed multi-threaded systems, you know that parallel processing can introduce a lot of complexity. Shaders bypass much of this by forbidding storage of state information. Because of this, each vertex function call is effectively isolated from all others. You cannot pass the result of one vertex function — or any data computed inside — to another. Each can only depend on data in the input struct (and other global data; more on this later).</p><p>In addition, each vertex function call only knows about data for a single vertex. This is for efficiency: the GPU doesn’t have to load an entire mesh at once.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xKID2tfrYwpqcZR33B5DeQ.png" /><figcaption>Viewing a vertex’s object space position in Blender.</figcaption></figure><p>We need to compute the screen position for a vertex described in the <em>Attributes </em>struct. When talking about positions, it’s important to determine the coordinate system it’s defined in: its “space.” The position vertex data stream gives values in object space, which is another name for local space that you’re accustomed to in Unity’s scene editor. If you view a mesh in a 3D modeling program, these positions are also displayed there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3VJmNuTsZVrWMtWcsIvtcw.png" /><figcaption>Unity’s Transform component describes how to move a vertex from object to world space.</figcaption></figure><p>Another common space is world space. This is a common space that all objects exist in. To get world space from object space, just apply an objects <em>Transform</em> component. Unity provides this data to shaders, as we’ll see soon.</p><p>However, a vertex’s position on screen is described using a space called “clip space.” An explanation of clip space could fill an entire tutorial, but luckily we don’t have to work with it directly. URP provides a nice function to convert an object space position into clip space. To access it we first need to access the URP shader library.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/42a32b4eabb86b714fe8bfe7c5d8a4a4/href">https://medium.com/media/42a32b4eabb86b714fe8bfe7c5d8a4a4/href</a></iframe><p>In HLSL, we can reference any other HLSL file with “<em>#include</em>” directives. These commands tell the shader processor to read the file located at a given location and copy its contents onto this line. If you’re curious what’s inside Lighting.hlsl, or any other URP source file, you can read it yourself in the packages folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xJYZ9u8ddFzCu01Oo4eEQQ.png" /><figcaption>Lighting.hlsl in Unity 2020.3</figcaption></figure><p>Included files can themselves <em>#include</em> many other files, leading to a kind of tree structure. For instance, Lighting.hlsl will pull in many helpful functions from across the entire URP library.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/51aff9e81e7bf58944d1748599f570f6/href">https://medium.com/media/51aff9e81e7bf58944d1748599f570f6/href</a></iframe><p>One such function, <em>GetVertexPositionInputs</em>, is located in ShaderVariableFunctions.hlsl. Its source code isn’t important now, but it returns a structure containing the passed object space position converted into various other spaces. Clip space is one of them!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/08b7d507a1d435d78bd693b844bb1370/href">https://medium.com/media/08b7d507a1d435d78bd693b844bb1370/href</a></iframe><p>Note that clip space is a <em>float4 </em>type. If you tried to store it in a <em>float3</em>, Unity would give you a warning that data will be truncated — or lost. This is a common source of bugs, so always heed these warnings and use the correct vector size!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fe95821ea159ed25f363a10e730a672c/href">https://medium.com/media/fe95821ea159ed25f363a10e730a672c/href</a></iframe><p>Keeping track of which space a position is in can get tricky, fast! Standard URP code adds a suffix to all position variables indicating the space. “OS” denotes object space, “CS” clip space, etc. Let’s follow this pattern as well.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/78b9e3717b0cb6c81733d10f0b2a22d9/href">https://medium.com/media/78b9e3717b0cb6c81733d10f0b2a22d9/href</a></iframe><p>Next, we must fulfill the vertex stage’s job and output the clip space position for the input vertex. To do that, define another struct, called “<em>Interpolators</em>,” to serve as the vertex stage’s return type. Write a <em>float4 positionCS </em>field inside with the <em>SV_POSITION</em> semantic. The semantic signals that this field contains clip space vertex positions.</p><p>Have the <em>Vertex </em>function return an <em>Interpolators </em>struct, declare a variable of <em>Interpolators </em>type, set the <em>positionCS </em>field, and return the structure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R0fL5mypnqSDITXrW-ZYCQ.png" /></figure><p>With that, the vertex stage is complete. The next stage in the rendering pipeline is called the rasterizer. The rasterizer takes vertex screen positions and calculates which of the mesh’s triangles appear in which pixels on screen. If a triangle is entirely off screen, the renderer is smart enough to ignore it!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*v2nli-Ar6CaxW8k0-XlVgA.png" /><figcaption>The rasterizer will identify the light-gray pixels as covering the triangle and pass this data down the pipeline.</figcaption></figure><p>The rasterizer then gathers data for the next stage in the pipeline: the fragment stage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6e2wQ2E5YrbeWPur5QjAtA.png" /></figure><p>The fragment stage is also programmable, and the fragment function runs once for every pixel the rasterizer determines contains a triangle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tOKVdc0Hb4NkNtN9X6uc6g.png" /></figure><p>The fragment function calculates and outputs the final color each pixel should display, but of course, each call only handles one pixel.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0f11bdd4e582eaae4456eb0755c8518b/href">https://medium.com/media/0f11bdd4e582eaae4456eb0755c8518b/href</a></iframe><p>The fragment function has a form like above. It takes a struct as an argument, which contains data output from the vertex function. Naturally, the types should match.</p><p>The values inside <em>input </em>have been modified by the rasterizer. For instance, <em>positionCS </em>no longer contains clip space position, but rather this fragment’s pixel position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yf-4EZ1NZuPBeBEZKS_HmA.png" /><figcaption>Pixel positions for fragments. Pixel (0, 0) is at the bottom left in this diagram.</figcaption></figure><p>You can pass other data from the vertex function to the fragment function through the <em>Interpolators </em>struct, a technique we’ll investigate later on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MU0q20W6UNtKgaGrGMxerg.png" /><figcaption>The color yellow is encoded as the vector (1, 1, 0, 0). Red = 1, green = 1, blue = 0, and alpha (opaqueness) = 1.</figcaption></figure><p>The fragment function outputs a float4 — the color of the pixel. It may be strange to think about, but colors are just vectors as well. They contain a red, green, blue, and alpha value, each ranging from zero to one.</p><p>To let the pipeline know we’re returning the final pixel color, tag the entire function with the <em>SV_TARGET </em>semantic. When tagging a function with a semantic, the compiler interprets the return value as having the semantic.</p><p>So we can finally display something on screen, let’s just color all pixels white. Return a <em>float4 </em>with all components set to one. Note that you don’t need to write “<em>new</em>” in HLSL when constructing vectors — just the type name is fine.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4pz9QKngqsbWoybVxzvbzA.png" /></figure><p>The last stage in the graphics pipeline is the presentation stage. It takes the output of the fragment function and, together with information from the rasterizer, colors all pixels accordingly.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fbbea13fbbf3b3c504c8b976c3cc31cf/href">https://medium.com/media/fbbea13fbbf3b3c504c8b976c3cc31cf/href</a></iframe><p>There’s one last thing to do to complete a functioning shader: we need to register our vertex and fragment functions to a shader pass. Open your MyLit.shader file.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/84bf3724c41779bb1187ec674b63eefb/href">https://medium.com/media/84bf3724c41779bb1187ec674b63eefb/href</a></iframe><p>Tell the compiler to read the code inside your MyLitForwardLitPass.hlsl file using a <em>#include</em> command. Next, register the vertex and fragment functions using a “<em>#pragma</em>” command. <em>#pragma</em> has a variety of uses relating to shader metadata, but the <em>vertex </em>and <em>fragment </em>subcommands register the corresponding functions to the containing pass.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8iOmqLboMfrdKmy0_dm1VA.png" /></figure><p>Make sure that the function names match those in your HLSL file!</p><p>And now we’re finally ready to view your shader! Make sure your material has MyLit selected, create a sphere in your scene, and give it the material. It should now appear as a flat white circle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LgoMhy4IfKT8JHbpM6o5_g.png" /></figure><p>If there’s any issue, check Unity’s console and the shader asset to see if there are any errors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sK0bdX8MQgqvtPfXGQW5jg.png" /></figure><p><strong>Adding Color with Material Properties</strong>. We have a flat white circle now — a good starting place! Let’s make the color adjustable from the material inspector. This is possible through a system Unity calls “material properties.”</p><p>Material properties are essentially HLSL variables that can be set and edited through the material inspector. They are specified per material and allow objects using the same shader to look different. If you’re wondering “What is the difference between a shader and a material?,” this is it! A material is a shader with specific property settings.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b0f73603179b515c8f409fe56ef5b8f8/href">https://medium.com/media/b0f73603179b515c8f409fe56ef5b8f8/href</a></iframe><p>We can define properties inside the .shader file with a <em>Properties </em>block. The syntax for these is… inconsistent, but I will explain. To define a color property, first decide on a reference name. This is how you’ll access this property in HLSL. By convention, properties have an underscore prefix.</p><p>Follow that with a parentheses pair, like you’re writing function arguments. The first argument is a string. This is the label — how it will display in the material inspector. The next argument is the property type. There are various, but “Color” defines a color property.</p><p>Close the parentheses and set a default value. The syntax is different for each property type, but for colors, start with an equals sign and then the red-green-blue-alpha values inside parentheses.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jJaAuKJuuYA4XUmVrU1arw.png" /></figure><p>As mentioned before, colors are just four <em>float </em>values, each corresponding to a color channel — red, green, blue and alpha. Each number ranges from zero to one, where white is all ones and black is all zeroes. For alpha, 1 is opaque and 0 is invisible. If you’d like more info on how these numbers combine to create a color, check <a href="https://www.w3schools.com/css/css_colors_rgb.asp">this link</a>.</p><p>You can now see your property in the material inspector!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-dTRqudKZcyUKzA9332v-Q.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/824c01e70b2fd671d2902905eaec7a49/href">https://medium.com/media/824c01e70b2fd671d2902905eaec7a49/href</a></iframe><p>Later, this shader will have many properties, so add a header label denoting surface options. To do that, use a <em>Header </em>command. Strangely, the label is not enclosed in quotation marks here.</p><p>There’s one last thing we should do. Properties can also be tagged with attributes, like classes in C#, which give the properties special features. Tag <em>_ColorTint</em> with <em>[MainColor]</em>. Now it’s possible to easily set this property from C# using <em>Material.color</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/27a8e5c09d9d1e47960ba4abb1f604cd/href">https://medium.com/media/27a8e5c09d9d1e47960ba4abb1f604cd/href</a></iframe><p>The property is set up, but the value is not reflected on screen. Open MyLitForwardLitPass.hlsl.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/633165d481ba128c84d7be5b89435092/href">https://medium.com/media/633165d481ba128c84d7be5b89435092/href</a></iframe><p>Although we defined a property in ShaderLab, we also must define it in HLSL — make sure the reference names match exactly. Unity will automatically synchronize this variable with the material inspector.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c92HoB3CieVkU4iBJ7iM-A.png" /><figcaption>The fragment function can also access material properties.</figcaption></figure><p>Earlier, I said that vertex and fragment functions could only access data from a single vertex or fragment. While this is true, they can also access any material properties. These variables are “uniform,” meaning they don’t change while the pipeline is running. Unity sets them before the pipeline begins, and you cannot modify them from a vertex or fragment function.</p><p>With this in mind, have the fragment function return <em>_ColorTint</em> as the final color.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GBuq70mhNCq_ZxMBLrON_w.png" /></figure><p>Return to the scene editor, select your material and change the color tint property. The shader should immediately reflect your choice!</p><p><strong>Varying Colors with Textures</strong>. Flat colors are great, but I’d like to vary color across the sphere. We can do this with textures!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D2439DNiTO9son50nBlbNg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nnFADgSHZy5rIl_xuQlR0w.png" /><figcaption>Zoom in close to a texture (without blending) and the fact that they’re just 2D arrays becomes more apparent. Each position in the array holds a color.</figcaption></figure><p>Shaders love working with textures. They’re just image files, but shaders think of them as 2D arrays of color data. To add a texture to a shader, first add a texture material property.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a9944b09854c4f07b8a404388217720a/href">https://medium.com/media/a9944b09854c4f07b8a404388217720a/href</a></iframe><p><strong>Texture Properties.</strong> Defining a texture property is much the same as a color property. Instead of listing the type as <em>Color</em>, set it as <em>2D</em>. The syntax for default textures is strange. Following the equals sign, type “white” (with quotes) followed by a pair of curly braces. If no texture is set in the material inspector, Unity will fill this property with a small, white texture. You can also set the default color to “black,” “gray,” or “red.”</p><p>Similarly to the <em>[MainColor]</em> attribute, there is a <em>[MainTexture]</em> attribute. Tagging this property makes it easily assignable from C# using the <em>Material.mainTexture</em> field.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_cJaG_g7gh3cOUrYbPelXg.png" /></figure><p>Your property should show up in the material inspector now. Notice the four numbers beside it. They allow you to set an offset and scale for this texture, which is useful for tiling.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ND2OlA_zslgZv8AKqyvISQ.png" /><figcaption>The right sphere has horizontal tiling. (For demonstration, your shader will not do this yet.)</figcaption></figure><p><strong>Textures and UVs in HLSL.</strong> Now, let’s take a look at the HLSL side of things.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4ff7a41ff51c01c6f3013a007672c951/href">https://medium.com/media/4ff7a41ff51c01c6f3013a007672c951/href</a></iframe><p>Defining a texture variable is a bit more complicated than colors. You must use this special syntax to define a 2D texture variable. Once again, the name must match the property reference exactly.</p><p><em>TEXTURE2D </em>here is not a type but something called a “macro.” You can think of macros similarly to functions, except they run on the text making up code. You can create macros yourself using the <em>#define</em> command.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e89fd13fa2108072043eb74ddadb0450/href">https://medium.com/media/e89fd13fa2108072043eb74ddadb0450/href</a></iframe><p>Before compiling, the system will search for any text matching a defined macro and replace the macro name with the text you specify. Macros can also have arguments. The system will replace any occurrences of argument names in the macro definition with whatever text you pass in.</p><p>This is a very simple overview of macros, but they can be quite useful in shader code. HLSL does not have inheritance or polymorphism, so if you want to work with any structure that has a <em>positionOS </em>field but you don’t necessarily know the structure type, a macro can do the trick.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7dbf1b2c22ca0b1720fd3e5bd7683e44/href">https://medium.com/media/7dbf1b2c22ca0b1720fd3e5bd7683e44/href</a></iframe><p>They’re also great at handling platform differences, which is what Unity has done with <em>TEXTURE2D</em>. See, different graphics APIs (DirectX, OpenGL, etc.) use different type names for textures. To make shader code platform independent, Unity provides a variety of macros to deal with textures. That’s one less thing for us to worry about!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/23d5c98d7106805772d704221ef3a6c7/href">https://medium.com/media/23d5c98d7106805772d704221ef3a6c7/href</a></iframe><p>Moving on, there are a couple more variables Unity automatically sets when you define a texture property. Textures have a companion structure called a “sampler,” which defines how to read a texture. Options include the sampling and clamping mode you’re familiar with from the texture importer — point, bilinear etc.</p><p>Unity stores samplers in a second variable which you define with the <em>SAMPLER </em>macro. The name here is important; it must always have the pattern “sampler” followed by the texture reference name.</p><p>Finally, remember the tiling and offset values from the material inspector? Unity stores those in a <em>float4 </em>variable. The name must follow the pattern of the texture name followed by “<em>_ST</em>.” Inside, the X- and Y-components hold the X and Y scales while the Z- and W-components hold the X and Y offsets. (And yes, the fourth component of a <em>float4 </em>vector is referred to as “W” in HLSL.)</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9cd79fa1ee91a34675677c4cbb734bc5/href">https://medium.com/media/9cd79fa1ee91a34675677c4cbb734bc5/href</a></iframe><p>Now, we’d like to sample the texture, or read color data from it. We’ll do that in the fragment stage to apply texture colors to pixels. Use the <em>SAMPLE_TEXTURE2D </em>macro to get the color out of a texture at a specific location. It takes three arguments: the texture, the sampler, and the UV coordinate to sample.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4nu8LizlHClPOa4P2-OVAA.png" /><figcaption>This sphere-like model has UV’s mapping each vertex to a position on a flat plane. We can use them to draw a texture on a 3D shape.</figcaption></figure><p>First, what are UVs? UVs are texture coordinates assigned to all vertices of a mesh which define how a texture wraps around a model. Think of how cartographers try to unwrap a globe to fit on a flat map. They’re basically assigning UVs to positions on the globe.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hebgy7QVoc3ZVYUKCsgjEA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cbpNj6sNMEIlMB6oh6qduQ.png" /><figcaption>This globe model “unwraps” it’s texture onto a flat plane.</figcaption></figure><p>UVs are <em>float2 </em>variables, where the X- and Y-coordinates define a 2D position on a texture. UVs are normalized, or independent of the texture’s dimensions. They always range from zero to one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Se5bfoX0uAJoCbPWwE6AvA.png" /><figcaption>A few UV-coordinates on a texture. Notice (1, 1) is always the top right corner, no matter what dimensions or aspect ratio the texture has.</figcaption></figure><p><strong>Interpolating Vertex Data</strong>. We can’t grab UVs out of thin air in the fragment stage — they’re another vertex data stream the input assembler needs to gather.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3046cfecad09986123e8b11aaffb1d40/href">https://medium.com/media/3046cfecad09986123e8b11aaffb1d40/href</a></iframe><p>Add a <em>float2 uv </em>field to the <em>Attributes </em>structure with the <em>TEXCOORD0</em> semantic, which is short for “texture coordinate set number zero.” Models can have many sets of UVs — Unity uses <em>TEXCOORD1 </em>for lightmap UVs for example, but we’ll get to that later.</p><p>The <em>Attributes </em>struct is not available in the fragment stage either. However, we can store data in the <em>Interpolators </em>struct, which will eventually make its way to the fragment stage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ed862cbcc80e1277e144df3eb0255555/href">https://medium.com/media/ed862cbcc80e1277e144df3eb0255555/href</a></iframe><p>Add another <em>float2 uv </em>field there, also using the <em>TEXCOORD0</em> semantic.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/da81adda51546f23242878e233f5eefc/href">https://medium.com/media/da81adda51546f23242878e233f5eefc/href</a></iframe><p>In the <em>Vertex </em>function, pass the UV from the <em>Attributes </em>struct to the <em>Interpolators </em>struct. We can also apply UV scaling and offset here — might as well! This way we’ll only compute it once per vertex instead of once per-pixel. It’s a good idea to do as much as possible in the vertex function since it generally runs fewer times than the fragment function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d2fe806c9298a3b68c82406c560ab16e/href">https://medium.com/media/d2fe806c9298a3b68c82406c560ab16e/href</a></iframe><p>Unity provides the <em>TRANSFORM_TEX </em>macro to apply tiling. There are two interesting things about it. First, the double hash “##” tells the precompiler to append text to whatever is passed in as an argument. When the macro runs, you can see how it replaces <em>name </em>with <em>_ColorMap</em>, correctly referencing <em>_ColorMap_ST</em>.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0489a7f55777c2a6a33b0465604d4f1a/href">https://medium.com/media/0489a7f55777c2a6a33b0465604d4f1a/href</a></iframe><p>Second, the <em>xy </em>and <em>zw </em>suffixes give easy access to a pair of components. This mechanism is called “swizzling.” You can ask for any combination of the <em>x</em>-, <em>y</em>-, <em>z</em>-, and <em>w</em>-components, in any order. The compiler will construct an appropriately sized float vector variable for you. You can also use <em>r</em>, <em>g</em>, <em>b</em>, and <em>a</em> the same way — more intuitive for colors. It’s even possible to assign values with a swizzle operator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ODXlUQw3Vy5P6rTsiMTDaA.png" /><figcaption>Each vertex has a different value. How does the rasterizer decide which value to give to each Fragment call?</figcaption></figure><p>Anyway, now we have UV data in the fragment stage. But, let’s take a moment to really think about what’s happening here. The vertex function outputs data for each vertex. The rasterizer takes those values, places the vertices on the screen, figures out what pixels cover the formed triangle, and finally generates an input structure for each fragment function call. What value will <em>input.uv</em> have for each fragment?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nn8QfS2Aq15yV8KsfPWkoQ.png" /><figcaption>A value interpolated between two ends of a line. Linear interpolation.</figcaption></figure><p>The rasterizer will interpolate any field tagged with a <em>TEXCOORD </em>semantic using an algorithm called “barycentric interpolation.” You’re probably familiar with linear interpolation, where a value on a number line is expressed as a weighted average of the values at the end points.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_CV1jgTjGW1vUvvyf9ZchQ.png" /><figcaption>A value interpolated between three corners of a triangle. Barycentric interpolation.</figcaption></figure><p>Barycentric interpolation is the same idea except on a triangle. The value at any point inside the triangle is a weighted average of the values at each corner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NG9DTtEa6-Dapi8SchJWhA.png" /><figcaption>An example of how the rasterizer would assign values to each fragment call by interpolating vertex data.</figcaption></figure><p>Luckily, the rasterizer handles this for us, so the algorithm is not important. To recap, the values in <em>Interpolators </em>are a combination of values returned by the vertex function. Specifically, for any fragment, they are a combination of values from the three vertices forming the triangle covering that fragment.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a70cafeca0952aa8a8ba44e97aa4ec77/href">https://medium.com/media/a70cafeca0952aa8a8ba44e97aa4ec77/href</a></iframe><p><strong>Sampling the Texture.</strong> All that for some UVs, but now we have all we need to call <em>SAMPLE_TEXTURE2D</em>. It returns a <em>float4</em>, the color of the texture at the specified UV position. Depending on the sampler’s sample mode (point, bilinear, etc), this color may be a combination of adjacent pixels, to help smooth things out.</p><p>Regardless, multiply the sample with the color tint property and return it. In HLSL, multiplying two vectors is component-wise, meaning the X-components of each vector are multiplied together, then the Y-components, etc. All arithmetic operators work like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kTfl53iPOwIKm8sh1vjycQ.png" /></figure><p>In the scene editor, set a texture on your material and marvel at what you’ve accomplished!</p><p>Do note that if your texture has an alpha component, the shader doesn’t handle transparency yet. The sphere will always be opaque. Stay tuned to fix that!</p><p><strong>I hope this whet your appetite for shader programming</strong>, because we’re just getting started! In the next part of this tutorial series, we’ll add simple lighting to finally give objects dimensionality. Then, we’ll delve into shadows and learn about adding additional passes to a shader. You’ve gotten past a lot of the theory and can now focus on the fun stuff!</p><p>For reference, <a href="https://gist.github.com/NedMakesGames/646b60da8a817a0a084b1a3bf4a45f96">here are the final versions of the shader files</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G9w5Dk3KbBe6Ft8NqKuAiQ.png" /></figure><p>If you enjoyed this tutorial, consider following me here to receive an email when the next part goes live.</p><p>If you want to see this tutorial from another angle, <a href="https://youtu.be/KVWsAL37NGw">I created a video version you can watch here</a>.</p><p>I want to thank <strong>Crubidoobidoo </strong>for all their support, as well as all my patrons during the development of this tutorial: Adam R. Vierra, Alvaro LOGOTOMIA, andrew luers, Andrew Thompson, Ben Luker, Ben Wander, bgbg, Bohemian Grape, Brannon Northington, Brooke Waddington, burma, Cameron Horst, Charlie Jiao, Christopher Ellis, Connor Wendt, Danny Hayes, Danyow Ed, darkkittenfire, Davide, Derek Arndt, Eber Camacho, Electric Brain, Eric Bates, Eric Gao, Erica, etto space, Evan Malmud, Florian Faller, gamegogojo, gleb lobach, Isobel Shasha, J S, Jack Phelps, Jannik Gröger, Jesse Comb, Jessica Harvey, Jiwen Cai, JP Lee, jpzz kim, Justin Criswell, KIMIKO KAWAMORITA, Kyle Harrison, Lanting Dlapan, Leafenzo (Seclusion Tower), Lhong Lhi, Lorg, Lukas Schneider, Luke Hopkins, Lune Snowtail, Mad Science, Maks Kaniewski, Marcin Krzeszowiec, Marco Amadei, martin.wepner, Maximilian Punz, maxo, Microchasm, Minori Freyja, Nick Young, Oliver Davies, Orcs Yang, Oskar Kogut, Óttar Jónsson, Patrick, Patrik Bergsten, Paul, persia, Petter Henriksson, rafael ludescher, Rhi E., Richard Pieterse, rocinante, Rodrigo Uribe Ventura, rookie, Sam CD-ROM, Samuel Ang, Sebastian Cai, Seoul Byun, shaochun, starbi, Stefan Bruins, Steph, Stephan Maier, Stephen Sandlin, Steven Grove, svante gabriel, T, Tim Hart, Tvoyager, Vincent Thémereau, Voids Adrift, Will Tallent, 智則 安田, 이종혁</p><p>If you would like to download all the shaders showcased in this tutorial inside a Unity project, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p>If you have any questions, feel free to leave a comment or contact me at any of my social media links:</p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> ☕ <a href="https://ko-fi.com/nedmakesgames">Ko-fi</a> 📧 E-mail: nedmakesgames gmail</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>Changelog:</p><ul><li>May 29th 2023: Add support for Unity 2022.</li><li>Unity Technologies: <a href="https://assetstore.unity.com/packages/templates/tutorials/shader-calibration-scene-25422">Shader Calibration Scene</a></li><li>shedmon: <a href="https://sketchfab.com/3d-models/wooden-slingshot-a909ff4b85a64acebe23795967aa9fe3">Wooden Slingshot</a></li><li>Martijn Vaes: <a href="https://sketchfab.com/3d-models/dae-bilora-bella-46-camera-game-ready-asset-eeb9d9f0627f4783b5d16a8732f0d1a4">DAE — Bilora Bella 46 Camera — Game Ready Asset</a></li><li>tonyflanagan: <a href="https://sketchfab.com/3d-models/thailand-girl-animated-a41f779fe45b4a04bdbf212e49b7f6b5">Thailand Girl — Animated</a></li><li>Kelvin Valerio: <a href="https://www.pexels.com/photo/brown-cat-with-green-eyes-617278/">Brown Cat with Green Eyes</a></li><li>owowowsam: <a href="https://www.cgtrader.com/free-3d-models/space/planet/mini-earth">Mini Earth</a></li><li>Tom Patterson: <a href="http://www.shadedrelief.com/natural3/pages/textures.html">Natural Earth III</a></li></ul><p>©️ Timothy Ned Atton 2023. All rights reserved.</p><p>All code appearing in GitHub Gists is distributed under the MIT license, unless otherwise specified.</p><p><em>Timothy Ned Atton is a game developer and graphics engineer with ten years experience working with Unity. He is currently employed at Golf+ working on the VR golf game, Golf+. This tutorial is not affiliated with nor endorsed by Golf+, Unity Technologies, or any of the people and organizations listed above. Thanks for reading!</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=798cbc941cea" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mastering Tessellation Shaders and Their Many Uses in Unity]]></title>
            <link>https://nedmakesgames.medium.com/mastering-tessellation-shaders-and-their-many-uses-in-unity-9caeb760150e?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/9caeb760150e</guid>
            <category><![CDATA[tech]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[graphics]]></category>
            <category><![CDATA[unity]]></category>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Wed, 24 Nov 2021 03:19:38 GMT</pubDate>
            <atom:updated>2021-12-01T17:21:11.733Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RMajR0tzp9ELgRTEuIVX-w.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F63ufydgBcIk%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D63ufydgBcIk&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F63ufydgBcIk%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/d0e79068280f325e4949c9e6b1cff073/href">https://medium.com/media/d0e79068280f325e4949c9e6b1cff073/href</a></iframe><p>Hi! I’m Ned, and I make games! In this Unity graphics programming tutorial, I’ll introduce tessellation shaders, advanced shaders which can subdivide triangles, adding details and smoothing out blocky models. Use them for automatic level of detail, procedural models, or height map based terrain. Keep watching to learn how to add tessellation to any shader yourself!</p><p>Thank you Crubidoobidoo and all my patrons for helping make this tutorial possible!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5eqtl_UoK_q_tVJQ2KKWFA.png" /><figcaption>This wireframe shows the actual geometry of the mesh. The left and right are the same model. As the camera gets closer, a tessellation shader adds vertices and smooths the model’s silhouette.</figcaption></figure><p>This tutorial was tested in Unity 2020.3, 2021.1 and 2021.2. I will be using Universal Render Pipeline for all examples; however, none of the techniques in this tutorial are URP specific. Furthermore, I will not use the shader graph in this tutorial, as even in HDRP it does not support all required techniques. If you’re unfamiliar with HLSL shaders in URP, I’m writing a tutorial about that, so check back soon.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X4K9jW5QRZYoxbkU3uRaAQ.png" /><figcaption>Procedural terrain rendered with tessellation and a noise-based height map.</figcaption></figure><p>This tutorial will explain what tessellation shaders are and how to write them in HLSL. I will demonstrate several methods to optimize tessellation, control the amount of subdivision per triangle, smooth a model’s geometry and silhouette, and add details with height maps and procedural techniques. This tutorial aims to explain the topic of tessellation, not so much create a finished shader. Nevertheless, I have provided an example shader <a href="https://gist.github.com/NedMakesGames/808a04367e60947a7966976f918081b2">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FdKh18xz2xlV-e7wYkyWhA.png" /><figcaption>A deformable plane implemented with tessellation.</figcaption></figure><p>As mentioned, this tutorial is more advanced and will expect you to know how to write shaders in HLSL. In addition, it will make use of vector math, so brush up on vector dot and cross products, as well as vector projection and reflection.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oEripTejAbBbbHRm7mU1UA.png" /><figcaption>A flat plane deformed by a height map texture.</figcaption></figure><h3>The Structure of a Tessellation Shader</h3><p>In mathematics, tessellation is the process of fitting shapes together to form a larger surface; however, in graphics programming, it usually refers to subdividing a shape into smaller pieces. Tessellation shaders do just that. Put a mesh in, and the GPU subdivides all its faces, adding many more vertices.</p><p>Why is this useful? Well, you can take these extra vertices and move them around, smoothing out jagged, low-poly edges and adding fine position details not possible otherwise. This enables a kind of reverse workflow for LoD, where you can create low poly models and use tessellation to add complexity.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_BKme_XteLtkPMsn8L0Ihg.png" /><figcaption>Left with no tessellation, right with tessellation. Notice the smoother silhouette on the right.</figcaption></figure><p>Tessellation is also great for terrain. Use a flat plane mesh and tessellate it, adjusting heights with a heightmap. This allows you to adjust the shape using only a texture and render high detail only where the camera can see it. Generate a heightmap procedurally to visualize mathematical surfaces, like SDFs, or easily animate a mesh.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nvAgF44PLzRuP_3QV6DdmQ.png" /></figure><p>Keep in mind that tessellation shaders have a hefty performance cost. The good news is they’re generally cheaper and have better hardware support than their cousins, geometry shaders. Plus they allow you to keep your mesh assets simpler — which can help with batching. If you’re worried about performance, be sure to test things out before committing to tessellation.</p><p>Now, how do you add tessellation to a shader? Tessellation shaders have two additional programmable stages, similar to the vertex and fragment stages. These are called the hull and domain stages. They run in between the vertex and fragment stages, and together with an unprogrammable stage called the tessellator, control how the mesh is subdivided and refined.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PSH2T0xYfqWvjHeRiUt9_Q.png" /></figure><p>The hull function receives data in the form of “patches,” which are simply lists of vertices. What relation these vertices have to one another is configurable; however, in this tutorial I will always work with triangles.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xOOmZ75fYlydhrcFW8nIvg.png" /></figure><p>So, the patch is an array of three vertices that make up a triangle on the mesh. The array contains output data from the vertex function corresponding to each vertex.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KqS8JKkk8uplVzJHlpf59g.png" /></figure><p>Besides the patch, the hull function also receives an index, specifying which vertex in the patch the hull function must output data for. It runs once per vertex in the patch and can look at all vertices in the patch to produce a new data structure for later on in the chain.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lGRiyaxuAFFf1wQ_As5ugw.png" /></figure><p>The hull stage is unique in that it also has another function that runs in parallel: the patch constant function. This separate function runs once per patch, so it’s very useful to calculate data that’s shared between vertices in a triangle. It also must output tessellation factors, which determine how many times to divide the patch. We’ll talk about these extensively later on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jam9ggtNFz9hvQcGRI3i4w.png" /></figure><p>So to summarize, the hull stage is made up of two functions: the hull function and the patch constant function. They receive a patch, which is a collection of vertices usually forming a triangle. The hull function runs once per vertex in a patch, while the patch constant function runs once per patch and must output tessellation factors.</p><p>Next up, a non-programmable stage called the tessellator runs. This takes patch data and the tessellation factors generated during the hull stage to subdivide each patch. The tessellator generates something called barycentric coordinates for all vertices in this new mesh.</p><p>Barycentric coordinates are an easy way to describe a point inside a triangle. Any point can be calculated as a weighted average of the three corner points, and barycentric coordinates are the weights in that formula. They’re usually given as a float3 vector, and the three components always sum to one. Besides position, we can also use barycentric coordinates to calculate normals, UVs, and more for any point in terms of the triangle corners.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S4OeCI60lsgcrBfNtbBm2A.png" /><figcaption>The barycentric coordinates for point P in terms of triangle ABC are (0.55, 0.31, 0.14)</figcaption></figure><p>This brings us to the second programmable stage involved in tessellation, the domain stage. It consists of one function, the domain function, which runs for each vertex on the tessellated mesh. Its job is to output the final data for a vertex. To do so, it receives the barycentric coordinates for a vertex and it’s originating patch. This includes all data generated by the hull and patch constant functions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SupjRwdBeSW8PlGXlZPkxw.png" /></figure><p>The domain function is where a lot of your logic will go. Much of what you’d usually do in the vertex stage should be calculated here instead, including clip space positions. Crucially, you can reposition vertices in the domain stage, something essential for most of tessellation’s use cases.</p><p>If you have a geometry function, it would run after the domain stage. But, usually, the rasterization and fragment stages run next.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AEkO5VAx7KRMaXdXFS3L_Q.png" /></figure><p>To summarize, the vertex stage runs first. The hull stage receives information about triangles on your mesh, called patches, and decides how to subdivide them. The tessellator does the heavy lifting, subdividing the mesh; while the domain stage prepares vertices in the tessellated mesh for the fragment stage, deciding where each appears on screen.</p><p>Let’s pivot to writing code! You can register hull and domain functions similarly to the other programmable stages, using a <em>#pragma</em> directive. Note that tessellation shaders require shader target 5.0, so adjust that or Unity will complain.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d5b6c4b0a1f0b49837f0a2e95e4e42bc/href">https://medium.com/media/d5b6c4b0a1f0b49837f0a2e95e4e42bc/href</a></iframe><p>The vertex function is now pretty plain. It simply converts positions and normals to world space. The output structure will be fed into the hull stage, and can contain basically any data you’ll need later in the pipeline. Notably, the <em>POSITION </em>semantic is forbidden, use the <em>INTERNALTESSPOS </em>semantic instead.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8c5185a91c6689dd352b9f9f22e91fcc/href">https://medium.com/media/8c5185a91c6689dd352b9f9f22e91fcc/href</a></iframe><p>The hull shader’s signature looks like below. It has several C#-attribute-like tags. <em>domain </em>determines the input patch type, while <em>outputtopology </em>and <em>outputcontrolpoints </em>determine the output patch type. I’m always going to use triangles in this tutorial, so these will remain fixed. <em>patchconstantfunc</em> registers the patch constant function, and <em>partitioning </em>tells the tessellator which algorithm to use to subdivide triangles. Keep this in mind for later.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/237c460775ec6758be3e63af79c7f961/href">https://medium.com/media/237c460775ec6758be3e63af79c7f961/href</a></iframe><p>The function itself receives the input patch using a special <em>InputPatch</em> construct. The vertex function output structure and number of vertices in the patch go inside the angle brackets. You can access each structure in the patch like you would an array.</p><p>The hull function also receives the vertex index with the <em>SV_OutputControlPointID </em>semantic. It signals which vertex in the patch to output data for. Finally, don’t forget to set the return structure type. In this example, it’s the same as the vertex output structure, but it could be unique. There are no required fields in this data, but once again, use <em>INTERNALTESSPOS </em>instead of <em>POSITION</em>.</p><p>In this example, the hull function body is extremely simple, simply returning the correct vertex inside the patch.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9a07a8085030b79f8cefaaf7b84b54cb/href">https://medium.com/media/9a07a8085030b79f8cefaaf7b84b54cb/href</a></iframe><p>The patch constant function has a much simpler signature. It receives the input patch similarly to the hull function but outputs its own data structure. This structure should contain the tessellation factors specified per edge on the triangle using <em>SV_TessFactor</em>. Edges are arranged opposite of the vertex with the same index. So, edge zero lies between vertices one and two.</p><p>There’s also a center tessellation factor, tagged with <em>SV_InsideTessFactor</em>. Soon, we’ll visualize how these factors affect the final tessellation pattern, but for now, realize that an edge factor is the number of times an edge subdivides, and the inside factor squared is roughly the number of new triangles created inside the original.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b440d9c2e8d382eb189a2beb6e0d35ea/href">https://medium.com/media/b440d9c2e8d382eb189a2beb6e0d35ea/href</a></iframe><p>The patch constant function can also output other data, but it must be tagged with a semantic like always. The special <em>BEZIERPOS </em>semantic is useful since it can take the form of a <em>float3 </em>array. Later, we’ll use it to output control points for a Bézier-curve-based smoothing algorithm, but you can use it to store anything you need.</p><p>With that, we’re done with the hull stage. Let’s move to the domain stage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b64cd1bee87a980e7b7f98c3d8932ae/href">https://medium.com/media/5b64cd1bee87a980e7b7f98c3d8932ae/href</a></iframe><p>The domain function also has a <em>domain </em>attribute, which should match the hull function’s output topology — triangles in this case. As arguments, it receives the output of the hull function arranged into a patch, as well as the output from the patch constant function. Finally, it receives the barycentric coordinates of the vertex to work with, tagged with <em>SV_DomainLocation</em>.</p><p>The output structure is very similar to what you’d output from a vertex function. It should contain a clip space position (unless you’re using a geometry stage), as well as any fields the fragment function needs for lighting.</p><p>Also, notice the <em>BARYCENTRIC_INTERPOLATE </em>macro. It’s really handy to interpolate any property in the patch structure!</p><p>And that’s it for the general structure. Let’s take a closer look at partitioning modes and tessellation factors. I’ve created a <a href="https://gist.github.com/NedMakesGames/09784830b01e2b7c5f0559e807d48815">simple tessellation shader</a> to test this stuff out. It has a property to assign tessellation factors as well as one to switch between partitioning modes using a keyword.</p><p>Create a material for it and add it to a mesh. To visualize the tessellation, be sure to set the scene render mode to shaded wireframe. Then, play around with the factors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cnUcAdJJwsmgkYMFpWsxVw.png" /></figure><p>You’ll see the edge factor corresponds to roughly the number of times edges will be subdivided, while the inside factor adds complexity to the center. Also notice that setting any factor to zero or less will cause the mesh to disappear. This becomes important later.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*99rgUB3UMW5WYcmhWjl2yQ.png" /></figure><p>Now try setting a factor differently for each edge. When we try more complicated algorithms, It’s important that edges on adjoining triangles have the same tessellation factor. If not, you can get little holes in the mesh where vertices don’t match up. To ensure this doesn’t happen, try restricting edge factors to depend only on vertices connected to that edge.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Halupel2Xan_9ICsxQFYAw.png" /></figure><p>You may have noticed some commented out properties in the shader. Uncomment those and change your patch constant function slightly. Is your mesh flickering, even with positive factors? Why?</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ff04f77fcb9b68150bb7157d8c66b9da/href">https://medium.com/media/ff04f77fcb9b68150bb7157d8c66b9da/href</a></iframe><p>There’s an oddity with the way the shader compiler handles tessellation factors. In a bid to speed things up, the compiler sometimes splits the patch constant function and calculates each factor in parallel. This sometimes causes weird issues. If you look in the Frame Debugger, you’ll see the compiler stripped two edge factor properties from your shader, making them always equal zero!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nV03owaD1wI2sN3DUt_Opg.png" /><figcaption>Look under the “Floats” section. No EdgeFactor2 or 3 in sight.</figcaption></figure><p>You can fix this by using a vector property with each component specifying one edge’s factor, so the compiler doesn’t strip properties it thinks are unused. In general, if your tessellation factors are acting strange, try rewriting this section of the patch constant function.</p><p>The partitioning modes are all interesting. The <em>integer </em>mode divides a number of times equal to the ceiling of the tessellation factor. It has a generally nice pattern! But, if you need tessellation factors to smoothly transition, the <em>fractional_odd </em>and <em>fractional_even </em>modes handle that. They’re so named because they only fully subdivide on either odd or even numbers, which is easier to understand seeing them in motion. A quirk of fractional even is that it always subdivides at least once, since two is the lowest factor it can handle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DgqqFnA3Evyrj_vo4573MA.png" /><figcaption>fractional_odd partitioning with tessellation factors 1.5, 2, 2.5 and 3.</figcaption></figure><p>The <em>pow2 </em>mode seems to be identical to the <em>integer </em>mode, at least on my machine. I would have guessed it only subdivided when a factor is a power of two. Let me know how it works for you!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DxD1smjx7V4JPqTu_2UtUA.png" /><figcaption>All factors set to 3.5. Top row: integer, pow2. Bottom row: fractional_even, fractional_odd.</figcaption></figure><h3>Optimizing with Culling</h3><p>Tessellation can be expensive! But, there are a few ways we can speed it up. Since tessellation happens before the rasterization stage, it cannot take advantage of the automatic frustum and winding culling that happens there. Thankfully, we can implement it ourselves and avoid tessellating triangles that will just be thrown out.</p><p>It’s easy to cull a triangle in the patch constant function. Just set the tessellation factors to zero, and the tessellator will ignore that patch.</p><p>Let’s tackle frustum culling, where we test each point of the triangle to see if they’re all out of view. To do that, we can use the clip space positions of the triangle corners. Be sure to calculate it in your vertex function and pass it to the hull stage.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7c2237b8008ed3908ca8af1ad00a722b/href">https://medium.com/media/7c2237b8008ed3908ca8af1ad00a722b/href</a></iframe><p>Above the patch constant function, write this function to test if a patch should be culled, passing the clip space positions of the triangle. Return false for now.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5ab15a32b00c96d71f32fdf44c30708a/href">https://medium.com/media/5ab15a32b00c96d71f32fdf44c30708a/href</a></iframe><p>Above that, write <em>IsOutOfBounds </em>to check if a point is outside the bounds defined by upper and lower vectors, and <em>ShouldFrustumCull </em>to calculate those bounding vectors.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4759f39c451fcc4a4b68f35889501c7f/href">https://medium.com/media/4759f39c451fcc4a4b68f35889501c7f/href</a></iframe><p>In clip space, the W component contains the outer bounds of the viewing frustum (camera viewable area), so we can use that to create the bounding vectors. The logic slightly differs between graphics API, since some anchor the viewing frustum and zero and some at negative W. Luckily, Unity provides a constant with the correct value.</p><p>Returning to <em>ShouldClipPatch</em>, call <em>ShouldFrustumCull </em>on each point. If they’re all true, the triangle is entirely outside the viewing area and should be culled.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d6424f3c087c8404a1923c69947af07c/href">https://medium.com/media/d6424f3c087c8404a1923c69947af07c/href</a></iframe><p>Moving on to winding culling, also known as backface culling, we need to calculate which side of the triangle is facing the camera, culling if the back side of the triangle is visible. Do that calculating a normal vector for the plane containing the triangle and testing if it’s roughly pointing towards the camera.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dsUs4g-qVW_7bEmij3-16w.png" /></figure><p>To find the normal vector, we need two vectors tangent to the plane — vectors pointing between two triangle corners will do nicely. Their cross product is the normal.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4dae7fd237a44775e5106e20de8c5b92/href">https://medium.com/media/4dae7fd237a44775e5106e20de8c5b92/href</a></iframe><p>Since we’re working in clip space, we need to “normalize” the position and apply perspective by dividing by the W component. This gives rough screen space positions.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/50d1a52d347ccb962e9408242f7e39f1/href">https://medium.com/media/50d1a52d347ccb962e9408242f7e39f1/href</a></iframe><p>Use the dot product of the view direction and triangle normal to find if they’re roughly pointing in the same direction. However, since the camera points along the z axis in clip space, we can simplify everything to a comparison of the normal’s z coordinate.</p><p>I said the camera points along the Z axis, but which way? Turns out, this depends on your graphics API. Usually the view direction is in the negative z direction; however, it’s flipped in OpenGL. Use a keyword to apply the correct comparison either way.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b8692062fbf23ca61bf38d5a29448a27/href">https://medium.com/media/b8692062fbf23ca61bf38d5a29448a27/href</a></iframe><p>Finally, in <em>ShouldClipPatch</em>, call <em>ShouldBackFaceCull</em>. In the patch constant function, if <em>ShouldCullPatch </em>returns true, set all edge factors to zero.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/59ca6203f2bc70a047bdfd3ff4ce3b88/href">https://medium.com/media/59ca6203f2bc70a047bdfd3ff4ce3b88/href</a></iframe><p>In Unity, you might notice the shader culls some faces of your mesh when it shouldn’t. Even if you don’t see it now, you certainly will when adding vertex displacement later on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GI9__qdg9eTvXIiDU6HH0Q.png" /><figcaption>Some triangles culled incorrectly near the bottom of the image.</figcaption></figure><p>Add some leeway to these calculations by introducing frustum and winding cull tolerance properties. For frustum culling, add the tolerance to each bound, while for winding culling, compare with the tolerance instead of zero. Adjust these as needed while adding features!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/034195b3b218c520fc171e5cfa938adf/href">https://medium.com/media/034195b3b218c520fc171e5cfa938adf/href</a></iframe><h3>Dynamic Tessellation Factors</h3><p>Another way to optimize tessellation is to lower factors when and where a mesh doesn’t need to be subdivided. There are a few ways to go about this. Say we’re working with a mesh that has some large faces but many smaller ones — we really only need to tessellate the large faces. One way to do this is to calculate tessellation factors proportionate to edge length.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3569c2e46082106d65b5cc547abac9b2/href">https://medium.com/media/3569c2e46082106d65b5cc547abac9b2/href</a></iframe><p>Above the patch constant function, define this function to calculate the tessellation factor for an edge bound by two vertices. Pass the world space positions of each vertex as well as a scale and bias value. Set the factor to the scale plus the bias, making sure the result is never less than one (so it doesn’t get culled). This creates constant edge factors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U-HW-VMBkFr1vx8jTHv8Pw.png" /><figcaption>A test scene with all factors set to 1.</figcaption></figure><p>Now, to add world space edge length, set the factor to the distance between the vertex positions divided by the scale. In this scheme, the edge subdivides while aiming to keep divided edge lengths roughly equal to the scale value.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b6c9dac09beca0dccea3a940b4ca55a1/href">https://medium.com/media/b6c9dac09beca0dccea3a940b4ca55a1/href</a></iframe><p>Add shader properties for the scale and bias values. Back in the patch constant function, call <em>EdgeTessellationFactor </em>for each edge, passing in the new properties and the appropriate vertex positions. Remember, edges are arranged across from the vertex sharing it’s index in the array.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c745870e2ae586662755b658a3ab1d87/href">https://medium.com/media/c745870e2ae586662755b658a3ab1d87/href</a></iframe><p>The inside factor should be the average of all edge factors. This code worked fine for me, but if you’re seeing weird or inconsistent inside edge factors, it’s probably due to the compiler. Just call <em>EdgeTessellationFactor </em>again instead of using the previously calculated values.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b2e7721a548d27ba2b1d374e67bdac33/href">https://medium.com/media/b2e7721a548d27ba2b1d374e67bdac33/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*frVIlYLh0CyDOFpvRbwfhg.png" /><figcaption>The same test scene with world length tessellation factors. Scale is 0.5.</figcaption></figure><p>OK, neat! But maybe we can tessellate based on an edge’s length in screen space? Due to culling, we already have clip space positions, so it won’t be difficult to do.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e301de430cde2271441eeb7a1ba15459/href">https://medium.com/media/e301de430cde2271441eeb7a1ba15459/href</a></iframe><p>In <em>EdgeTessellationFactor</em>, add arguments for each vertex’s clip space position. Then, calculate the factor using the clip space positions, making two adjustments. First, apply perspective by dividing the positions by their w component. Next, multiply by <em>_ScreenParams.y</em>, which contains the height of the screen in pixels. Now, we can specify scale in pixels!</p><p>In the patch constant function, pass the clip space positions along with the world space positions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4PhiMUVHrxqpM8U4qI0ZTw.png" /><figcaption>The same scene with screen space length tessellation factors. Scale is 100.</figcaption></figure><p>This looks good too, but sometimes not quite right. What if we used the distance to the camera?</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07f5d39f23f208fd06c1b7818892c305/href">https://medium.com/media/07f5d39f23f208fd06c1b7818892c305/href</a></iframe><p>In <em>EdgeTessellationFactor</em>, find the length, in world space, between the two vertices. Then, calculate the distance from the center of this edge to the camera. The camera position is different in the various render pipelines, but get it with <em>GetCameraPositionWS </em>in URP. Divide the length by the scale multiplied by the distance to the camera, lowering the scale when close to the screen, subdividing more. I preferred the effect with a quadratic curve, but that’s up to you.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MZ1mt3ose1mUTCz-qYTXCA.png" /><figcaption>The test scene with camera distance tessellation factors. Scale is 0.02.</figcaption></figure><p>This final approach usually gives me the best results, but your mileage may vary. Use a keyword to switch between algorithms, if you’d like!</p><p>These heuristics try to guess the appropriate tessellation factors, but if you have an idea of how the mesh should tessellate, try storing tessellation factor multipliers in the mesh’s data. This is useful if you have an area with large flat faces where you’ll never need to add detail.</p><p>For demonstration purposes, I’ll store these multipliers in the green channel of the mesh’s vertex colors, but texcoords also work well. In blender or your modeling program of choice, paint the area you don’t want to tessellate black.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oa9AnhJufZUvrbx3n-6ULA.png" /></figure><p>In your shader, pass the vertex colors down to your hull input structure. In the patch constant function, calculate a multiplier for each edge by averaging the green channel of connecting vertices and pass it as a new argument into <em>EdgeTessellationFactor.</em> Multiply it into the final calculation.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/db39d7fe5db031ddc0df1942c5ac3982/href">https://medium.com/media/db39d7fe5db031ddc0df1942c5ac3982/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ekuo4j3rUsCUCv68jKvsXg.png" /><figcaption>The same test scene with the middle flat quad untessellated due to having black vertex colors.</figcaption></figure><p>Here’s another useful technique when using some type of deforming force field or SDF. In this example, I deform this plane based on proximity to the little spheres. We know that if a vertex is far enough away from all spheres, it shouldn’t deform, so we don’t need to tessellate connected triangles.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FdKh18xz2xlV-e7wYkyWhA.png" /></figure><p>For now, ignore the actual deforming logic, we’ll come back to that later. Focus on calculating the tessellation factors.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d04788116c01ac756afee0da488a44fe/href">https://medium.com/media/d04788116c01ac756afee0da488a44fe/href</a></iframe><p>In your patch constant function, evaluate the deformation amount for each vertex. If it results in deformation (the value is positive in this example), then pass a multiplier of one.</p><h3>Silhouette Smoothing</h3><p>An easy way to add detail to a mesh is through high resolution textures. For instance, normal maps vary normal vectors per-pixel which affect the apparent shape of a surface. However, this technique does not really change any geometry. Nowhere is this more apparent than on a mesh’s silhouette. Zoom up close and even 4K textures can’t hide jagged and pointy edges. In this section, I’ll describe a few algorithms to smooth mesh geometry using tessellation!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nlOPdNogqDKgDvR2ncIUlg.png" /><figcaption>This sphere has smooth shading, though its jagged silhouette is very apparent.</figcaption></figure><p>All of these strategies involve offsetting vertices in the domain function. Through simple barycentric interpolation, all new vertices are limited to the original triangle’s plane. However, what if we used the corners’ normal vectors to construct a curved triangle?</p><p>The simplest technique to achieve this is called Phong tessellation. You might have heard of Phong shading, which is the smooth shading technique of linearly interpolated normal vectors. Phong tessellation tries to recapture that simplicity and efficiency in positioning tessellated points.</p><p>It works like this. First, calculate the flat barycentrically interpolated position for a point. We’ll use (1/3, 1/3, 1/3) in this example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DwulUZWEf5pu3CIyc9Ec0w.png" /></figure><p>Then, imagine three tangent planes emanating from each triangle corner, normal to the corner’s respective normal vector.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LOO1Q9txk9XqEqZa_2eWAQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mG_ALh0S3rV4D7NXB0ZoSQ.png" /></figure><p>Next, project the flat position onto each of these planes, which is equivalent to finding the nearest point on the plane.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I4XiyrKimjzy5U7EKN2tPg.png" /><figcaption>Flat (barycentrically interpolated) position and one corner’s tangent plane.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pOPFDjzymzusj3TLkVqr7w.png" /><figcaption>The position projected onto the tangent plane.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zIULt2vEcSmPYPOu-A5Uaw.png" /><figcaption>The position projected onto each corner’s tangent plane.</figcaption></figure><p>Finally, compute the barycentrically interpolated position again, but using the three projected points.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZbMkKJxov0OVhjQfBp4aZA.png" /></figure><p>The math is not too complicated. We already know how to deal with barycentric coordinates. And, to project a point onto a plane, find the difference between the point and any other on the plane — the triangle corners work! Then, project that vector onto the plane’s normal vector and subtract the result from the original point. Here’s the algorithm for Phong tessellation!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2bf3b628132fb52c4a639d0663fb5bff/href">https://medium.com/media/2bf3b628132fb52c4a639d0663fb5bff/href</a></iframe><p>Now update your domain function. Make sure to use this new adjusted position when calculating clip space.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ET88SuVzn4GJNpajSbglVw.png" /><figcaption>The cat on the right has Phong tessellation enabled.</figcaption></figure><p>Try it out on a model! At first, it may look a little too puffed up. We can improve it by adding a smoothing factor property. Interpolate between the flat and Phong position using this factor, which can help quite a bit.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c3f231b0ea124f56c03a88e1c1c69091/href">https://medium.com/media/c3f231b0ea124f56c03a88e1c1c69091/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2NA_p-pwS1owqIQYerrRvw.png" /><figcaption>The cat on the right has a smoothing factor of 1/3.</figcaption></figure><p>Some models may need a little touching up as well. If your model has sharp edges, try adding edge loops very close to the sharp edge, making long, thin faces. Looking at the Phong tessellation algorithm, if the normal vectors of each vertex are very close to parallel, the Phong position will be very close to the flat position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_v7j77aMXQdmZ98x1xT3pQ.png" /><figcaption>This sword model split open due to long, crisp edges and discontinuous normal vectors. The version on the right corrected the issue by adding an extra edge loop near the crisp corner.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tn-SUHYUW5Tw6y1c7Tlatw.png" /><figcaption>Adding the edge loop in Blender.</figcaption></figure><p>Another technique you can try is baking smoothing factors into your mesh’s data, for example, in the red channel of it’s vertex colors. Simply paint the red channel black in areas you don’t want to bend. Pass the vertex colors all the way down to your domain function, calculate the barycentric interpolation of the red vertex color channel, and multiply it with the smoothing factor.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e2ef088336761d6bc989a366700705c3/href">https://medium.com/media/e2ef088336761d6bc989a366700705c3/href</a></iframe><p>Phong tessellation gives pretty good results and is also cheap, all things considered. However, if you need higher quality smoothing, there’s another option: PN Triangles. This technique constructs curved triangles similarly to Bézier curves! It’s quite a bit more expensive than the Phong method, but let’s try it out.</p><p>We can save some time by precomputing Bézier control points for use when positioning tessellated points in the domain function. Control points are constant per triangle, so the patch constant function is perfect. We’ll make use of ten control points: the triangle corners, a pair along each triangle edge, and one in the triangle center.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vhu1kPFdnQIrpSj_sNzS6Q.png" /><figcaption>The ten Bézier control points in their original positions.</figcaption></figure><p>Let’s take a look at calculating each control point. The corners can remain as they are. They’ll help ensure the triangle never escapes its original position too much. For the edge pairs, use a similar algorithm to Phong tessellation. Take this peachy point ⅓ along the edge from corner A to B.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*br6-ZdUqZ7IsWGQGLzOovA.png" /></figure><p>To calculate it’s position, first project B onto the plane defined by A’s normal.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jupfBETXH55fpCM-3m1X9w.png" /></figure><p>Then, take the average of this new point and A, weighing A twice.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ni_oZKmVIpkYMBqaEGLCuQ.png" /></figure><p>For the other point on this edge, do the same operation, mirroring A and B.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uJIwqOfxLt2oQgt9lvCMaQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CLi7sh13EH7jjEyLM6FcfA.png" /></figure><p>Continue with the other two edge pairs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ravv99ZpFCsulS3Ag3AeLQ.png" /></figure><p>Finally, for the center point, find the average of the edge pair control points, which I’ll call E.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qb6LQYjzhFP77t0iFK1yvA.png" /><figcaption>“E” is hovering above the triangle center. It’s the average of the edge control points.</figcaption></figure><p>As well as the average of the triangle corners, T. The center control point is E plus the difference of E and T halved, which gives a nice rounded center.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TamJkPi4jM-iS5YTjIlqKA.png" /><figcaption>The center control point has risen into position. It equals E + (E - T) * 0.5</figcaption></figure><p>Using these control points, it’s possible to compute any point on this bendy triangle using barycentric coordinates.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VjmE1OrINllArrVsZQ899g.png" /></figure><p>This is the formula — which looks similar to that of a cubic Bézier curve. Notice how the barycentric coordinates appear in terms with their corresponding corners — the center point being an even combination of each. If you’d like to learn more about Bézier curves, I’ve linked some excellent resources in the foot notes.</p><p>To program this, store the control points in the patch constant output struct, using the <em>BEZIERPOS </em>semantic. Tag a seven element <em>float3 </em>array with it in the patch constant output structure. Why only seven? The patch contains the triangle corner positions, so there’s no reason to waste the memory.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/10f3edcd10636a2918cb8a877a0fb34f/href">https://medium.com/media/10f3edcd10636a2918cb8a877a0fb34f/href</a></iframe><p><em>CalculateBezierControlPoints </em>calculates Bézier control points using the algorithm described earlier. Call it in the patch constant function, but only if the triangle isn’t culled.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1f491371c147cd969487c126285f2572/href">https://medium.com/media/1f491371c147cd969487c126285f2572/href</a></iframe><p>Calculate the final point in the domain stage. <em>CalculateBezierPosition</em> implements the Bézier curve calculation using the control points from the hull stage. I’ve also added an interpolation between the curved position and the flat position, like in the Phong tessellation function.</p><p>Substitute this function for the Phong tessellation function in your domain function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N-uwt8neYeMpUVFB2CObSQ.png" /><figcaption>Left is Phong with 1/3 smoothing, right is PN triangles with 2/3 smoothing.</figcaption></figure><p>In Unity, you’ll see it does give nice results, usually slightly better than Phong tessellation, especially at higher smoothing values. It’s up to you if the added complexity is worth it!</p><p>Throughout all this, we haven’t touched the normal vectors. Interpolating normal vectors linearly is usually OK, but if your mesh has many divots and inflections, shading might be improved by interpolating normals quadratically!</p><p>Use another Bézier-curve-like algorithm for this. It might seem strange to use a Bézier-curve for normal vectors, but as long as we normalize the final result, it works just fine. Quadratic Bézier curves only need three control points, so place one in the middle of each triangle edge.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OptkCmjbK2H6oJ02g2O8fw.png" /></figure><p>Again, the triangle corners will retain their original normal vectors. To compute a control vector for the point halfway between corners A and B, follow these steps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0vv-1IPFYxHGw8rT4-oEbQ.png" /></figure><p>First, find the average normal of A and B.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9IeVYs4OtyG6yZmozC7Gng.png" /></figure><p>Second, construct a plane perpendicular to the edge connecting A and B.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ogk8EIRQmhPKugl03RSJkg.png" /></figure><p>Finally, reflect the average vector across the plane.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SZMKYh5tXmGGieqFtCZ5qg.png" /></figure><p>Notice that when normals are similar but slanted relative to the triangle plane, the control normal points in the opposite direction. This creates bumpy shading, as if the surface is warping.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H74dB2saSwtO5SVSl5vmaA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8L17PDq1sFIcOKCTJijqhQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IL8JVbQPHbvuXGi-e0jKhQ.png" /></figure><p>Calculate the control vectors for the remaining edges.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IWFQLfwRPN69XzKmP9etSQ.png" /></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f545bfc073eb3cf9eca1ba3afd682b95/href">https://medium.com/media/f545bfc073eb3cf9eca1ba3afd682b95/href</a></iframe><p>To add this to your shader, first add three more slots in the Bézier control point array. Then, call <em>CalculateBezierNormalPoints </em>in your patch constant function, which implements the formula explained above.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JJVuX3t6BTCeBCrT9aFMCQ.png" /></figure><p>In your domain function, calculate a quadratic Bézier similarly to the position. Apply the smoothing factor to interpolate with the flat normal, and be sure to normalize the final result!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/957c446b9b2b38af90c7c2566940353e/href">https://medium.com/media/957c446b9b2b38af90c7c2566940353e/href</a></iframe><p>There’s one other thing to consider, the tangent vector. It must always be perpendicular to the normal, but if we change the normal vector, it might not be. To fix this, find the barycentrically interpolated tangent vector and take its cross product with the barycentrically interpolated normal. Then, take that vector’s cross product with the smoothed normal. This resulting tangent vector is once again orthogonal to the normal vector, as well as the original bitangent. This should preserve tangent space.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2cad2d60e85948b4eccae7342fa82bd9/href">https://medium.com/media/2cad2d60e85948b4eccae7342fa82bd9/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FUhxUzFpjq50PjUXArUUWA.png" /><figcaption>The left has linearly interpolated normals, while the right has quadratically interpolated normals. Notice the details around the eyes and nose?</figcaption></figure><p>And that brings us to the end of this section on silhouette smoothing and Bézier triangles! This is the real magic behind tessellation and makes it a powerful tool combined with appropriately designed models. Experiment and see what you can create!</p><h3>Working with Height Maps</h3><p>Another of tessellation’s most common uses is adding extra geometric details to a mesh. Say you have a rough surface with many bumps. Traditionally, an artist would use a normal map to approximate the lighting such a bumpy surface would receive. The model itself is not actually bumpy, as you can clearly see if you view the surface’s profile or shadow. With tessellation, it’s possible to modify the mesh to more closely resemble a complex surface.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O2vY8v1RVz2VCms3rj_IMA.png" /><figcaption>On the left is a texture applied to a flat plane. On the right, the plane is offset using tessellation and a height map.</figcaption></figure><p>The most common way to do this is with a height map. Also known as bump maps, these grayscale textures encode height offsets in its color data. The idea is simple, read a height from the texture and offset vertices along their normal vectors by this height.</p><p>To implement this in a shader, add a texture property for a height map, then sample it in the domain function. Remember <em>SAMPLE_TEXTURE2D </em>is only available in the fragment stage, due to partial derivatives; use <em>SAMPLE_TEXTURE2D_LOD </em>here. Accordingly, you can turn off mipmaps for height maps used in this way.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6d2c0561e647763de2ae286af531ebab/href">https://medium.com/media/6d2c0561e647763de2ae286af531ebab/href</a></iframe><p>Regardless, add the sampled value to the vertex’s world position by using it to scale the normal vector. You can combine this with the smoothing techniques discussed above, or just use the flat interpolated position and normal. Either way, it’s that simple. Add an altitude property to adjust the height!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2qQVM-G4_shPP0iDkYLIyg.png" /></figure><p>You might notice if you use a height map without a matching normal map that the mesh will look quite flat. This is because the height map does not affect normal vectors. Although I would still recommend using a normal map, it is possible to calculate lower quality tangent space normal vectors from a height map alone.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6KU2dEKp5OSwv5V1B6ndIQ.png" /><figcaption>The left side has no normal map, so normal vectors always point straight up.</figcaption></figure><p>In this case, turn mipmaps back on for your height map, since we’ll sample it for normal vectors in the fragment stage. In the shader, add a <em>_MainTexture_TexelSize </em>variable, which holds the size of one texel, or pixel on a texture, in UV units. Make sure the name matches a texture in your shader and Unity will “automagically” calculate and set it for you!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3e601078a19606772a4ecdaed99c99b9/href">https://medium.com/media/3e601078a19606772a4ecdaed99c99b9/href</a></iframe><p>Write <em>GenerateNormalFromHeightMap</em>, which samples the height map in each neighboring pixel around a given UV coordinate. From this, we can calculate a tangent space normal with a little algebra. Divide the change in height of two pixels across from one another with the change in UV space. This gives us the slope in the U and V directions, which correspond to the X and Y components of the tangent space normal.</p><p>Multiply the XY components with a <em>_NormalStrength </em>scaling factor to adjust the overall strength of this improvised normal map; then normalize the final result. Convert this to world space like any other tangent space vector.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W4cLYLHHgdBQNhGj1ig9vQ.png" /></figure><p>You’ll need to adjust the normal strength until you get something that looks good. Even then, you might notice these normals are not quite as detailed as a normal map. It can’t really be helped without taking more texture samples, which really start to add up. For this reason, only use this technique if you’re in a pinch. There are many free tools online which can generate normal maps from height maps anyway.</p><p>Height maps can also take the form of a function, like Perlin noise or an SDF. In these cases, evaluate the height function instead of sampling a height map. Here, we’re forced to calculate a normal vector. It can be a little tricky to figure out the math, and it differs per function, but on the plus side, they’re often mathematically exact.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X4K9jW5QRZYoxbkU3uRaAQ.png" /></figure><p>In this example, I created a heightmap from Perlin noise. This has well defined partial derivatives, so I was able to calculate the normal vector like so. If you scale the noise in any way, be sure to also scale the resulting normal vector. Just remember that normals must be scaled inversely to geometry — meaning divide instead of multiply!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c4febc53eeff3e6d80a867dabacd902f/href">https://medium.com/media/c4febc53eeff3e6d80a867dabacd902f/href</a></iframe><p>You could calculate the normal along with the position in the domain function and just use the interpolated value in the fragment function. However, you could also calculate the normal in the fragment function for a nicer, more detailed result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Eq5vwXcinwwbzvObnrGHPA.png" /><figcaption>On the left, normals are calculated per-domain-vertex, on the right, per-fragment.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FdKh18xz2xlV-e7wYkyWhA.png" /></figure><p>In this next example, I created a simple SDF, or distance function, from points centered on these three spheres. When the SDF passes a threshold at a point on the mesh, I deform the point backwards. To calculate the normal in this situation, I used another trick.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0c40b9a9eca58865dda44f01c69a300f/href">https://medium.com/media/0c40b9a9eca58865dda44f01c69a300f/href</a></iframe><p>Create two new points offset slightly from the original point along the tangent and bitangent vectors. Calculate the SDF at all three points and apply the offset. Then, form a triangle with these deformed points and calculate the normal vector of the plane containing it, using the cross product. Use that for lighting! This method is usually quite good for continuous functions determined solely by position!</p><p><strong>Closing Remarks and Special Thanks</strong></p><p>As you can see, tessellation is a complicated subject, but it opens up many doors in the world of graphics programming! Although it can be expensive, well-placed tessellation can really polish up your models and give your game that final push over the finish line!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rrGoa6RJPU332wZuqgJGCA.png" /></figure><p>I hope I have shown off interesting uses of tessellation, including early culling, per-triangle math, smooth silhouettes, level of detail, quadratic normal vectors, real height maps, and procedural geometry. Personally, I will be using tessellation for level of detail and wind effects in my upcoming grass system.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zaR6D5oeTIYMCFqQCMBORQ.png" /></figure><p>Once again, <a href="https://gist.github.com/NedMakesGames/808a04367e60947a7966976f918081b2">here is a sample tessellation shader</a> which implements many of the algorithms featured here.</p><p>If you have any questions, feel free to contact me at any of the links at the bottom of this article.</p><p>If you want to see this tutorial from another angle, I created a <a href="https://youtu.be/63ufydgBcIk">video version</a> you can watch here.</p><p>I want to thank <strong>Crubidoobidoo </strong>for all their support, as well as all my patrons over the last month: Adam R. Vierra, Alvaro LOGOTOMIA, Ben Luker, Ben Wander, Bohemian Grape, Brooke Waddington, Cameron Horst, Chris, Christopher Ellis, Connor Wendt, Crubidoobidoo, Danny Hayes, darkkittenfire, Electric Brain, Eric Gao, Erica, Evan Malmud, Isobel Shasha, Jack Phelps, Jesse Comb, JP Lee, jpzz kim, Justin Criswell, Kyle Harrison, Leafenzo (Seclusion Tower), Lhong Lhi, Lorg, Lukas Schneider, Luke Hopkins, Mad Science, Microchasm, Nick Young, Oskar Kogut, Patrik Bergsten, phanurak rubpol, rafael ludescher, rookie, Samuel Ang, Sebastian Cai, starbi, Steph, Stephen Sandlin, Steven Grove, Tvoyager, Voids Adrift, and Will Tallent. Thank you all so much!</p><p>If you would like to download all the shaders and experiments showcased in this tutorial, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> ☕ <a href="https://ko-fi.com/nedmakesgames">Ko-fi</a> 📧 E-mail: nedmakesgames gmail</p><h3>Credits, References and Special Thanks</h3><ul><li>Samcro spm: <a href="https://www.turbosquid.com/3d-models/3d-sword-shield-ready-model-1706509">3D Sword and Shield Game Ready model</a></li><li>SHULDYAKOV: <a href="https://www.turbosquid.com/3d-models/3d-model-animal-cat-farm-1266312">3D model Cat Low Polygon Art Farm Animal Free VR / AR / low-poly</a></li><li>Lennart Demes: <a href="https://ambientcg.com/view?id=Fabric060">Fabric 060</a> and <a href="https://ambientcg.com/view?id=Rocks022">Rocks 022</a></li><li>Iñigo Quilez: <a href="https://www.shadertoy.com/view/4dffRH">Noise — Gradient — 3D — Deriv</a></li><li>Scratchapixel: <a href="https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/perlin-noise-part-2/perlin-noise-computing-derivatives">Perlin Noise</a> and <a href="https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/geometry/transforming-normals">Geometry</a></li><li>Freya Holmér: <a href="https://www.youtube.com/watch?v=aVwxzDHniEw">The Beauty of Bézier Curves</a></li><li>kjpargeter: <a href="https://www.freepik.com/free-vector/detailed-wireframe-terrain-landscape-black-white_7178727.htm">Detailed wireframe terrain landscape in black and white</a></li><li>OpenClipart-Vectors: <a href="https://pixabay.com/vectors/head-wireframe-graphics-model-3d-152528/">Wireframe Human Head</a></li><li>CatLikeCoding: <a href="https://catlikecoding.com/unity/tutorials/advanced-rendering/flat-and-wireframe-shading/">Flat and Wireframe Shading</a>, <a href="https://catlikecoding.com/unity/tutorials/advanced-rendering/tessellation/">Tessellation</a>, and <a href="https://catlikecoding.com/unity/tutorials/advanced-rendering/surface-displacement/">Surface Displacement</a></li><li>Alex Vlachos, Jörg Peter, Chas Boyd, Jason L. Mitchell: <a href="https://alex.vlachos.com/graphics/CurvedPNTriangles.pdf">Curved PN Triangles</a></li><li>Tamy Boubekeur, Marc Alexa: <a href="http://www.klayge.org/material/4_0/PhongTess/PhongTessellation.pdf">Phong Tessellation</a></li><li>Alan Wolfe: <a href="https://blog.demofox.org/2019/12/07/bezier-triangles/">Bézier Triangles</a></li><li>Fabian Giesen: <a href="https://fgiesen.wordpress.com/2010/10/17/view-frustum-culling/">View frustum culling</a></li></ul><p><em>All code appearing in GitHub Gist embeds is Copyright 2021 NedMakesGames, licensed under the </em><a href="https://opensource.org/licenses/MIT"><em>MIT License</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9caeb760150e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating a Foliage Shader in Unity URP Shader Graph]]></title>
            <link>https://nedmakesgames.medium.com/creating-a-foliage-shader-in-unity-urp-shader-graph-5854bf8dc4c2?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/5854bf8dc4c2</guid>
            <category><![CDATA[shaders]]></category>
            <category><![CDATA[art]]></category>
            <category><![CDATA[graphics-programming]]></category>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[unity]]></category>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Thu, 07 Oct 2021 01:32:53 GMT</pubDate>
            <atom:updated>2021-11-22T16:30:19.992Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eROPENG4ivF8HPXnsOnzDQ.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fx4ufs1OzPIw%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dx4ufs1OzPIw&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2Fx4ufs1OzPIw%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/6b2dd1fb74ba26808027f007ed690400/href">https://medium.com/media/6b2dd1fb74ba26808027f007ed690400/href</a></iframe><p>Hi, I’m Ned, and I make games! In this tutorial, I’ll show how to create a foliage shader for Unity’s Universal Render Pipeline using the shader graph! It’s great for low poly trees, complex trees, fields of grass, and more! These types of things don’t look quite right using a default lit shader since it lacks important features, like two-sided normals, translucency and wind deformation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u71FLEDpleKaczHWlriVEg.png" /></figure><p>Tested in Unity 2020.3 and Unity 2021.1</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I3d6cjWkY808pN7aeoIpzg.png" /></figure><p>This tutorial covers creating a foliage shader with the URP shader graph. It is complete with diffuse, specular and translucency lighting. It supports all light types, their shadows, global illumination and fog. We’ll also look at wind deformation and two-sided rendering with normal mapping (for grass billboards).</p><p>This tutorial will <strong>not<em> </em></strong>cover how to build any foliage models or textures. It will <strong>not</strong> show how to place grass blades around the game world or support character interactions through physics or otherwise. I have tutorials planned for all these topics in the near future!</p><p>I’ll assume you know the basics of Unity, URP, and the shader graph. You’ll also need to understand simple HLSL, the shader programming language, and what control keywords are (we’ll use both in custom functions). And, although I’ll give a general overview of lighting techniques, normal mapping, and tangent space, I’ll leave other tutorials to dive deep into them. Check out the bibliography at the end of this article for further reading.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eJ8XVUurfjNROoD-th8OJA.png" /></figure><h3>Project set up</h3><p>Get started by setting up your Unity project. You’ll want to go through <a href="https://nedmakesgames.medium.com/creating-custom-lighting-in-unitys-shader-graph-with-universal-render-pipeline-5ad442c27276">my custom lighting in the shader graph tutorial</a>, which provides the base for our shader. It covers adding light, shadow and global illumination support, as well as diffuse and specular lighting. Once you’ve finished, either duplicate or rename the completed shader files to “FoliageLighting.hlsl,” “FoliageLighting.subgraph,” and “TestFoliage.shadergraph.”</p><p>In FoliageLighting.hlsl, rename mentions of “custom” to “foliage.”</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9c999806fa54ed7d25f9a676977c2a9e/href">https://medium.com/media/9c999806fa54ed7d25f9a676977c2a9e/href</a></iframe><p>Then in the FoliageLighting subgraph, rename the custom function node name field to match.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kROvgsI5WlcMNva5MygTpQ.png" /></figure><p>Alright, set up a simple test scene to test the shader out on various types of common foliage meshes. Create a flat plane for the ground, to test shadows. Then, create a sphere game object to simulate simple, low poly trees. For a grass or bush billboard, create a quad game object and position it attached to the ground.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nGSlAh15ghqesHjUSPsqug.png" /></figure><p>Now, to test for complex trees with many leaf cards — or double sided quads — craft something in Blender, or another 3D modeling program. Create three quads and rotate them so there’s one in each plane. Create edges to split each into quarters and merge vertices to weld the planes together.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E6h6zmpWooII6YIl_H1m-Q.png" /></figure><p>Import this funny cube-like shape back into Unity and add it to your scene. Create three materials using the foliage shader, one for each model, and assign it.</p><p>To better prepare for grass and leaf cards, add alpha clipping support to the shader. In the TestFoliage graph, click “alpha clip” in the graph inspector. Add a float alpha clip threshold property and route it into the alpha threshold field of the master stack. Then, route the alpha output from the albedo texture sample into the alpha field of the master stack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GnRDwiWj5jLqAVTK_UEfVg.png" /></figure><p>In GIMP, or another photo editor, create a simple texture with an alpha channel for the cut out texture. In Unity, set “alpha is transparency” to true.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QfhUsJnvVNDzAsjp5DDfLQ.png" /></figure><p>Set up all your materials, making sure to add the alpha channel texture to the grass material.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8XXWpwZlrppFVGd6_L_XSA.png" /></figure><p>To make sure everything’s working, test out a few lights. To test global illumination, set all the objects to static and make sure double sided global illumination is enabled on the grass and tree materials. Then, bake a light map. All good!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UjnYBJ-UKtLkI5hKZOcW-g.png" /></figure><h3>Double sided materials</h3><p>You might notice the grass and tree meshes are invisible on one side! This is due to something called backface culling. For meshes like the sphere, it allows Unity to ignore triangles on the inside of the model. But for double-sided geometry, like foliage cards, we need to make some adjustments.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yJ_6a3gwmCTepZDxA8eciA.png" /></figure><p>In TestFoliage, turn on the “two-sided” option in the graph inspector.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qT2AuLDV52cJQ_Ov5UO-kw.png" /></figure><p>Now both sides are visible; however, the lighting is incorrect. The mesh’s normals point outwards from the front face, causing both sides to receive the same light. To fix this, flip the mesh’s normal vector when drawing the back face. To support normal mapping, make sure to use this flipped normal when transforming from tangent to world space.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PNDeewFh4IMpSp8lySXPhA.png" /><figcaption>Both sides have roughly the same shading, even though the sun is behind the mesh on the right side. There are slight differences due to specular lighting.</figcaption></figure><p>Create a subgraph called “CalcDoubleSidedNormal.” This will take in a tangent space normal and output a world space normal. This “is front face” node returns true if rendering on the front face. Use a branch node to switch between 1 and -1 depending on this value.</p><p>The Transform node cannot be modified to use a different normal vector, so we must recreate it. Construct a “tangent to world” matrix using the modified normal and multiply it with the tangent space normal, and normalize. Again, for more information on tangent space and normal mapping, check out the links in the video description.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HlM5ASb975tkS6GQ4zAg_w.png" /></figure><p>In the main graph, route your normal map sample through this subgraph instead of the transform node.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aE6O9BAwrXytnoH4sk4hqw.png" /></figure><p>In the scene, lighting should look correct on both sides, but you might have to turn off shadows to tell.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZbudW4DnIni-bPXWrfLvGw.png" /></figure><p>Unfortunately, this shadow problem is difficult to fix within the shader graph, since we cannot modify the shadow caster pass. Try adjusting shadow bias settings on the light or URP asset. If this issue turns out to be a deal breaker, I have a solution in the text shader version of this tutorial.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LgWtyfbqjBCNrMu5sfQ27A.png" /><figcaption>Shadow “acne” artifacts are more visible on the back face since the shadow caster normal still points the wrong way. This is not adjustable in the shader graph. Try changing depth bias to help.</figcaption></figure><h3>“Shape” normals</h3><p>One of the tricky things about foliage is that it should be translucent! Light filters through leaves, changing the way lighting and shadows affect them. Short of raytracing, we can’t have real translucency. But, we can add several techniques to approximate it — the first is a method using what I call “shape” normals.</p><p>Shape normals are separate normal vectors used exclusively for diffuse lighting. As opposed to the regular mesh normals we know and love, (which describe a face’s orientation) shape normals follow the overall shape of a plant. For instance, grass’s shape normals should follow the terrain, while a tree’s should point outward from a central location, following a sphere or cone. Using shape normals for diffuse lighting hides the fact that many foliage meshes are made of a bunch of flat planes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o3eCP8LBOvT492XpqiQOjA.png" /><figcaption>Using mesh normals (left) vs shape normals (right) for diffuse lighting.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Uk3pRO4seyu9vxZsB2J0DA.png" /><figcaption>Using mesh normals (left) vs shape normals (right) for diffuse lighting.</figcaption></figure><p>You might be tempted to use shape normals for all lighting calculations. Regular normals, which I’ll refer to as “mesh normals,” are still useful for specular lighting and another technique we’ll get to later. The best thing to do is keep track of both. I’ll cover strategies to do this, but first let’s prepare the lighting algorithm.</p><p>Open FoliageLighting.hlsl. In the data structure, rename the current <em>normalWS </em>to <em>meshNormalWS</em>, which will hold the regular mesh normals. Add a new <em>float3 shapeNormalWS</em> field.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/50505e8c8b99aa4216bd0dbed5f47b17/href">https://medium.com/media/50505e8c8b99aa4216bd0dbed5f47b17/href</a></iframe><p>Go through the file renaming <em>normalWS </em>to the correct variable. Use the mesh normal in the reflection and fresnel calculations, shape normal in the diffuse formula, mesh normal in the specular dot product, shape and mesh for the diffuse and specular node previews, and shape normal to mix GI.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/765ff3e796201f53030d9be8b6775982/href">https://medium.com/media/765ff3e796201f53030d9be8b6775982/href</a></iframe><p>In the shader graph wrapper function, <em>CalculateFoliageLighting_float</em>, split the Normal argument into <em>MeshNormal </em>and <em>ShapeNormal</em>. Set them in the data structure. Then, pass <em>ShapeNormal </em>in the <em>OUTPUT_SH </em>and <em>SAMPLE_GI</em> macros.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f9431b0e2657503e4fdb68a44519e1d4/href">https://medium.com/media/f9431b0e2657503e4fdb68a44519e1d4/href</a></iframe><p>In the FoliageLighting subgraph, update the custom function to match the wrapper function. Add a new shape normal property and route it into the custom function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7-1-CnSatCLgOZyPzT21VA.png" /></figure><p>In TestFoliage, temporarily route the double sided normal into the shape normal.</p><p>Return to the scene. Things should be clear of errors, but everything will look the same. Now, to calculate the shape normals for each of our example meshes. For the sphere, which models a low poly tree or hedge, the shape normal is equal to the mesh normal. That’s how things are at the moment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rw3nOVCQYosCMhbpz2SIpA.png" /></figure><p>For the grass, we want the shape normal to point upward from the terrain. We need a vector which points upwards along the grass card. This corresponds to the mesh’s bitangent vector!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*21hqFnDwjIsMCwcFDzuFkg.png" /><figcaption>Test shader routing a bitangent node into base color. It’s green! Or (0, 1, 0), pointing up.</figcaption></figure><p>To support both shape normal options in the same shader, we can use an enumeration keyword. These allow you to change code depending on an option in the material. In the blackboard, add a new enum keyword called “ShapeNormal” (the option is under the new property menu). Set the definition to “shader feature,” the scope to “local,” and the exposed setting to checked. Add an entry for “MeshNormal” and “Bitangent.” You can now select an active entry in the material inspector.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-pjWIIcFaVeG4oYDQQE3AA.png" /></figure><p>If you drag this keyword property into the graph, Unity will create a branch node for you. It passes through the value connected to the currently selected keyword entry. Route a normal vector and bitangent vector into the correct fields, and then into the shape normal field of the lighting node.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yKBH0O0NC4AH7BHPrXFi2Q.png" /></figure><p>In the scene editor, select the bitangent option in your grass material. Much better!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*isYBaipc3ly3_U-tI5p2NQ.png" /></figure><p>Create a C# script called “FoliageShaderSupport.cs.” Inside, add a list of transforms called normal foci. This script finds the closest focus to each vertex and calculates a vector pointing from the focus to the vertex. This will be the shape normal.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/019297eb9fa6b66bd93d2af9db45fe02/href">https://medium.com/media/019297eb9fa6b66bd93d2af9db45fe02/href</a></iframe><p>Back in the scene editor, add this script to your mesh game object. Create another GameObject for the normal focus and position it near the center of the tree; then, set it in the script. To test things out, create a vertex color node in your test foliage graph and route it directly to the base color output field.</p><p>Press play, and you should see colors on the tree mesh! Looking at it through the scene view, the colors should roughly correspond to the little XYZ compass in the corner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bMQfJOq5TI2b8IbSMz45RQ.png" /><figcaption>The normal focus game object is selected.</figcaption></figure><p>Back in TestFoliage, remove the vertex color node and route the lighting back into the base color. Using another keyword entry, add the option to use vertex colors as shape normals.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ug4qRXEYLvKM1pCh57wsYw.png" /></figure><p>Back in the scene editor, change the tree material to use vertex colors. Then, press play and check it out! If things look strange, try adjusting the position of the normal focus, adding more foci, or even adding more vertices to the mesh.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bryipdFspsqhE-Q0627WlQ.png" /></figure><h3>Translucency</h3><p>Shape normals create smooth, more realistic lighting, but the meshes still look opaque. If you view a light through a foliage card, it should glow a little! We can simulate this translucency effect using a simple subsurface scattering lighting algorithm.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O4lCCuo7x_ppMlteBbc-9Q.png" /></figure><p>In my lighting formula explorer program, <a href="https://nedmakesgames.itch.io/lighting-explorer">which you can check out here</a>, I created a diagram detailing the algorithm. The effect is strongest when the view direction and light direction are opposite, which we can calculate mathematically by taking the dot product of the view direction and negative light direction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Bte-4xLfsL-LuUtgSRNCaQ.png" /></figure><p>This works well, but can make materials look flat. In reality, when light exits a transparent material, it changes direction slightly towards the surface’s normal vector. By adding the normal, scaled by a scattering coefficient, to the negative light direction, we can simulate this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CWJfuT1-mk2URwXua6aWdw.png" /></figure><p>Let’s ignore scattering for now and add simple translucency to the lighting algorithm. In FoliageLighting.hlsl, add a <em>float3 </em>subsurface color variable — a tint applied to any light filtering through the mesh — and a float thinness variable — the translucent effect’s strength.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/93f83af7440578ebe9c3053507f1867d/href">https://medium.com/media/93f83af7440578ebe9c3053507f1867d/href</a></iframe><p>In the wrapper function, add arguments for these new fields and set them in the data structure.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07699fe0b8fce50998d4b7ced7f16709/href">https://medium.com/media/07699fe0b8fce50998d4b7ced7f16709/href</a></iframe><p>In <em>FoliageLightHandling</em>, set the translucency light radiance, which is just the “regular” radiance tinted by the subsurface color. Calculate the translucency dot from the negative light direction and view direction. Similarly to specular highlights, tighten the dot product with a smoothness power and multiply by thinness. Finally, multiply the albedo, translucency radiance, and translucency strength together and add it to the final color.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/796510c921aff4eec7000a8bbfb434d8/href">https://medium.com/media/796510c921aff4eec7000a8bbfb434d8/href</a></iframe><p>In the FoliageLighting subgraph, add a new subsurface color and thinness custom function argument, and graph properties for each.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NHPA9Be5hCC93-Att8xDbQ.png" /></figure><p>In TestFoliage, add subsurface color and thinness property as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zmfsCcjm8szaH1syCPv5FA.png" /></figure><p>In the scene editor, set your main light to point straight in the Z-direction and take a look at it through any of your meshes. You may or may not see a glow, depending on your shadow settings. The problem is the object’s cast shadows will attenuate translucency lighting as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ioU0dRgNHstjNMJhmJDlFg.png" /></figure><p>The best solution is to remove shadow attenuation from the translucency radiance formula. This can cause translucency to shine in some places it shouldn’t, but In my experience, it’s not very noticeable. The final call is yours!</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cfba06fa610b5dd40cef548887be235c/href">https://medium.com/media/cfba06fa610b5dd40cef548887be235c/href</a></iframe><p>Let’s return to scattering and add it to our algorithm. In FoliageLighting.hlsl, add a new <em>float </em>field to the data structure.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/02765e6e88b88f2d51bb58af56f0691c/href">https://medium.com/media/02765e6e88b88f2d51bb58af56f0691c/href</a></iframe><p>And a corresponding argument in the wrapper function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b711725d128720d71a13b5564653066/href">https://medium.com/media/5b711725d128720d71a13b5564653066/href</a></iframe><p>Then, in FoliageLightHandling, calculate the scattered light direction by adding the scaled mesh normal to the negative light direction. Normalize it and use it in the dot product formula.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88750b4e4b35270a98b352c0b9bfd7a2/href">https://medium.com/media/88750b4e4b35270a98b352c0b9bfd7a2/href</a></iframe><p>Update the custom function and add properties in both graphs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9NHSDFn-G_Thy1lD_vTiMA.png" /></figure><p>Then, in the scene editor, check out your materials.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-pfAt3gEQfoiGP9JDbqiOg.png" /></figure><p>It really adds a lot of depth!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EiAle7QyaQkGSLBo2keoew.png" /></figure><p>So that takes care of most translucent effects, but it would be nice to approximate translucency from indirect lights too. Since we can’t change Unity’s lightmapper, the best way to do this is to add an ambient subsurface lighting strength, which gives foliage a constant glow.</p><p>In FoliageLighting.hlsl, add a new <em>float </em>field to the data structure.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3748c98a7b0cd36ba9bd2e78c77d21d9/href">https://medium.com/media/3748c98a7b0cd36ba9bd2e78c77d21d9/href</a></iframe><p>And a corresponding argument in the wrapper function.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f1e557f1f2fe00a610e37bf4c5716ab2/href">https://medium.com/media/f1e557f1f2fe00a610e37bf4c5716ab2/href</a></iframe><p>In <em>FoliageGlobalIllumination</em>, add a subsurface term to the indirect diffuse formula, multiplying the albedo, subsurface color, thinness and ambient strength.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a5553e31d66dc1029ff36849966a12cc/href">https://medium.com/media/a5553e31d66dc1029ff36849966a12cc/href</a></iframe><p>Update the FoliageLighting custom function and add a property in both graphs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_DccCt-jISCkTtUrRDmBhw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DJ76aAcwAL6i3khf8geYgA.png" /></figure><p>Take a look in the scene editor. A little goes a long way!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8Q9bGcu13wl_dADZHWhD0Q.png" /></figure><h3>Data Maps and Lighting Refinements</h3><p>So far, all of these various lighting properties are constant over the mesh. It would be better to be able to read them from a texture, similarly to smoothness or occlusion maps. However, there are so many properties that putting each in individual textures would slow down the shader. We can combine four into one data texture, storing each in a color channel.</p><p>You can choose to vary any four properties and organize them in any channel order; however, for this tutorial, I will use the following layout: red contains smoothness, blue contains specular highlight strength, green contains translucency thinness, and alpha contains ambient occlusion.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qempAGwpRh2hC4OkbCP0Qw.png" /><figcaption>My test data texture, created using Substance Designer.</figcaption></figure><p>The lighting algorithm is ready to handle this, except there’s no specular highlight strength. I feel that this is useful to prevent highlights in foliage creases, so let’s add it really quick. While we’re in there, let’s also add a multiplier to fade the ambient reflection rim effect, which might not be very prominent on foliage.</p><p>In FoliageLighting.hlsl, in the data structure, add a specular strength field and an environment reflection strength field.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e5a7fef24c9390b7fd4916b8658e7f8b/href">https://medium.com/media/e5a7fef24c9390b7fd4916b8658e7f8b/href</a></iframe><p>Add variables for these in the custom function wrapper.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88e2a6eeb29c7f81d632b28aabef8e2c/href">https://medium.com/media/88e2a6eeb29c7f81d632b28aabef8e2c/href</a></iframe><p>In <em>FoliageGlobalIllumination</em>, multiply the indirect specular result by the environment reflection strength and the specular strength. Then, in <em>FoliageLightHandling</em>, multiply the specular value by the specular strength.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7fdf66d8cafde1f5d9fe6b84e3f34411/href">https://medium.com/media/7fdf66d8cafde1f5d9fe6b84e3f34411/href</a></iframe><p>Update the FoliageLighting subgraph with the new custom function arguments and add corresponding properties. Add properties to TestFoliage as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rJ5X-iNtpIULqJDILu7XMg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YK5OWk7K-VLgyyB3YgFs5g.png" /></figure><p>Then see how it affects your materials. OK, now it’s ready for a data map.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xgytQX5Un3BzgHzGMX0Rlw.png" /><figcaption>No specular highlight or rim lighting!</figcaption></figure><p>In the TestFoliage graph, add a new data map texture2D field and sample it. I like to keep around all the individual properties to use as multipliers for each channel, but you can also delete them for more minimal materials. Route and rearrange everything. Double check that everything is hooked up correctly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g1gUPnRJ-NlKoKWjhrLhcQ.png" /></figure><p>Head into your texture creation program of choice and construct a simple test data texture. Draw a different pattern in each channel, preferably with soft gradients to test a variety of values.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xx7QinBQkOsIp9j1oyjx_Q.png" /></figure><p>Back in Unity, turn off sRGB and “alpha is transparency” in the texture importer and place it in your materials. This makes it really easy to see how each value affects lighting. You’re all set to create intricate plant-life!</p><h3>Wind deformation</h3><p>With that, we’re done with the lighting in our foliage shader; however, it’s too stiff to feel like real plant matter. Besides translucency, plants’ tendency to move with the wind is another challenge. We can animate meshes in the shader very efficiently by passing new vertex positions to the master stack, and it’s perfect for wind!</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F54L9xrDReSE%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D54L9xrDReSE&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F54L9xrDReSE%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/c7537a44fb7177a1703bbd1b31c9517a/href">https://medium.com/media/c7537a44fb7177a1703bbd1b31c9517a/href</a></iframe><p>Create a subgraph called WindDeformation. This will take in a vertex position and output a new, deformed vertex position. Add a <em>Vector3 </em>wind direction property and a <em>float </em>strength property. Multiply the strength by the direction and add it to the position, returning the result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mbvLjkrlJ9u60wk7rvNz3A.png" /></figure><p>We can vary the wind strength over time and space using a noise texture. Add a Texture2D noise texture property and sample it using a sample LOD node (it must be an LOD node, since we’ll use this during the vertex shader stage). Next, calculate a UV. World position should influence it, so use a Swizzle node to select the XZ world space position and multiply it with a new noise scale property. Similarly, multiply time by a noise frequency property.</p><p>The output of the texture sample ranges from zero to one, but that would weight the deformation on one side of the model. Use a remap node to change it to range from -1 to 1. Use a split node to grab the red channel and multiply it with the wind strength.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZvW1K-tlZGcHoXxlpHQR5w.png" /></figure><p>Plants won’t sway in a straight line, due to wind turbulence. To simulate it, add a float turbulence strength property and a cross wind direction. This “cross” direction will be perpendicular to the main direction. Multiply the turbulence strength by the wind strength (so it is proportional), the noise green channel, and finally the cross wind direction. Add the offsets together.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TgvNttJ0WDoaiyKLivW8OA.png" /></figure><p>In test foliage, add the new WindDeformation node. Create properties for wind direction, strength, turbulence, noise texture, noise scale, and noise frequency. Route all these into the wind deformation node, as well as vertex position, in world space.</p><p>For cross direction, we need a perpendicular to the wind direction, but still looks natural (it shouldn’t point into the ground, for example). The cross product of the wind direction and the “shape normal” will do the trick. Finally, route the deformed position through a “world to object” transformation node and into the position field of the vertex stage master stack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6yvqGfeVa6waISgMQ4W8bA.png" /></figure><p>Open your image processor of choice and create a noise texture. Make sure it has independent noise in the red and green channels. You can use any shape you want, but I found simple cloud or Perlin noise does the trick. In Unity, make sure the texture importer has sRGB turned off. You also safely turn off mipmaps, as well as compression if the wind motion ever feels jittery.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l0SlAckvZJvoZJaqjrw_oQ.png" /></figure><p>Set up your materials! So, we have wind, but there are several problems. First off, wind causes the grass to detach from the ground! We need a way to scale wind strength over the mesh so it’s weaker near the bottom of the grass quad.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F12lSIMiqdlo%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D12lSIMiqdlo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F12lSIMiqdlo%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/8cc2eec69041ed1a5a08d960644bd46e/href">https://medium.com/media/8cc2eec69041ed1a5a08d960644bd46e/href</a></iframe><p>Luckily, the UV’s y-coordinate follows exactly the pattern we need.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Us7jTstuZi8B5OOwop2abA.png" /><figcaption>UV y-coordinate displayed as mesh color</figcaption></figure><p>First, in the wind deformation subgraph, add a float dampening property. Multiply it with the final offset before adding to vertex position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gmOUf7GVXtU_wHhm8j4u8w.png" /></figure><p>In the main graph, we need to route the <em>UV Y</em> coordinate into this new field, but only for grass. Otherwise, dampening should be one. Another keyword will do the trick: add an enum keyword called <em>WindStrength</em>, with all the same settings as before. Create two entries: “Constant” and “UV Y.”</p><p>Drag it onto the graph, connect a Float node with a value of 1 into Constant, and a UV node with a Split node, to grab the Y coordinate, into UV Y. Route that into the dampening field.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Rf9MAapURhNTDQAzhH8HOw.png" /></figure><p>In the scene editor, change the wind strength enum on your grass material to UV Y. Much better.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2F4GcB6Rdoi60%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D4GcB6Rdoi60&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2F4GcB6Rdoi60%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/36104756136f45db05efdc2a779e4e58/href">https://medium.com/media/36104756136f45db05efdc2a779e4e58/href</a></iframe><p>Let’s take a look at the tree. I see two problems. First, imagine if a leaf texture was assigned to this material. The wind would cause it to stretch and distort in unnatural ways. To fix this, we should use the same position for all vertices when sampling the wind noise. Second, parts of the mesh attached to the trunk should not move. Imagine the center vertex is anchored in this way.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FkHTym4legcE%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DkHTym4legcE&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FkHTym4legcE%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ffe5352067d13202d1fb45270958baa/href">https://medium.com/media/2ffe5352067d13202d1fb45270958baa/href</a></iframe><p>We can fix both of these problems by storing custom wind noise anchors and dampening values in the mesh vertex data. Meshes can store a bunch of independent UV coordinates, and the shader graph can access four. The last set, referred to as “UV3” or “TexCoord3,” is usually unused, so we can use our C# script to store this wind data inside it.</p><p>Open FoliageShaderSupport.cs. I’ll go over this code quickly, since it’s tangential to the foliage shader. Our goal here is to set the wind noise positions to a constant position (the center of the mesh’s bounds will work), and the wind dampening values to a value proportional to distance from the closest focus. Anchors are stored in the XYZ channels of the TexCoord3 vector, while dampening in the W channel. Set the array in the mesh’s UV stream, and it’s ready to be read by the shader.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9d6c359cfa473d0fbe2a0e0d8a7f648d/href">https://medium.com/media/9d6c359cfa473d0fbe2a0e0d8a7f648d/href</a></iframe><p>In the WindDeformation subgraph, add a new Vector3 noise position property. Use that instead of vertex position when calculating the noise UV.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mo849nkgIZy-2EtJoRCisw.png" /></figure><p>In TestFoliage, add another enum keyword called WindAnchor. Add two entries: Position and TexCoord3. Drag the keyword onto the graph and route a position node, in world space, and a UV node, in UV3 channel mode, into the fields. Connect that into the noise position field of the wind deformation node.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*joM3rec-N6eGktCZnkJHRA.png" /></figure><p>To support precalculated wind dampening values, add a third entry to the wind strength enum keyword called TexCoord3. Use a split node to grab the W-component from another UV node in UV3 channel mode into the new option.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lUwIWYonC-seSN9l1tjefQ.png" /></figure><p>In the scene editor, verify your tree model has the script attached and that the TexCoord index is 3. Switch the tree material keyword modes to TexCoord3 and enter play mode, so the script runs. That’s more like it! Feel free to modify the C# script as you need — this data is highly dependent on each individual model. But for now, time to address the last problem.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FvLhU1MHGd70%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DvLhU1MHGd70&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FvLhU1MHGd70%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2ec385dd12ad1e14e50764bae09468e7/href">https://medium.com/media/2ec385dd12ad1e14e50764bae09468e7/href</a></iframe><p>If you look closely on any model — but it’s especially apparent on the sphere — you’ll notice that wind deformations don’t affect specular lighting. They should, since wind deformations change the apparent normal of the mesh. The normal vector doesn’t automatically update to match new vector positions, so we need to recalculate them — and tangents as well. It’s difficult to do perfectly, since a shader only knows the position of one vertex at a time, but we can estimate it.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FJwwSD1okO1w%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DJwwSD1okO1w&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FJwwSD1okO1w%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/af9a604a6a0024c46882c1d28caf4115/href">https://medium.com/media/af9a604a6a0024c46882c1d28caf4115/href</a></iframe><p>The strategy is to run three points through the wind deformation algorithm — the original position, and the original position slightly offset in the tangent and bitangent directions — and see how the deformation algorithm affects them. We can construct new tangents and bitangents and thus a normal from their cross product.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lx6mdr53dsrc7-0WOWR1zQ.png" /></figure><p>Implement it in a new subgraph called “WindDeformationWithOrientation.” Inside, output a position, normal, and tangent vector, and add properties for wind direction, cross direction, strength, turbulence, noise texture, noise position, noise scale, noise frequency, and dampening. Create a WindDeformation node and route all the properties into it. Duplicate the deformation node twice, leaving everything attached.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ABTyEJNdAY37pYLorTAk_w.png" /></figure><p>Route world space position into the top wind deformation node. The middle node takes the position nudged a small distance in the tangent direction. The bottom node needs the position nudged along the bitangent, but due to some quirks with the way the shader graph bitangent node works, it’s safer to calculate the bitangent yourself. Route the normal and tangent through a cross product node and normalize the result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aFS4rfVn4tdPkatZhbfi7Q.png" /></figure><p>Subtract the top node’s output from both of the other outputs and take their cross product. The order is important. Connect up the deformed position and normal outputs. For the deformed tangent, normalize the first cross vector — it’s guaranteed to be perpendicular to the normal, thanks to the cross product.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XAlhDu1QkcmXBJTg927mSQ.png" /></figure><p>In TestFoliage, replace the WindDeformation node with a WindDeformationWithOrientation node. Route the normal and tangent vectors through a world to object transform node before connecting them to the master stack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0s_aKuVMg5fn0HuVfEKJfw.png" /></figure><p>In the scene editor, you won’t really notice any changes. This is because we separated vertex position from the noise UV, which partially determines wind offsets. In the WindDeformationWithOrientation subgraph, add the tangent and bitangent offsets to the noise positions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V1p17IEt00rNyRQbab4Ryw.png" /></figure><p>This looks great on the sphere!</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FIyNGJ6iFY1M%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIyNGJ6iFY1M&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FIyNGJ6iFY1M%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/5d5a4adf92e6e33ff179c49231b972ab/href">https://medium.com/media/5d5a4adf92e6e33ff179c49231b972ab/href</a></iframe><p>But, not so much on the tree and grass. The shine seems almost random, which is an artifact of the distance between vertices skipping over and not reflecting changes in the wind noise. Adding more vertices would certainly help, but perhaps the right solution is to turn off normal recalculation when using low poly meshes.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FaIAlTKwCFos%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DaIAlTKwCFos&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FaIAlTKwCFos%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ec06839d1e092682309cac0ca7c5e75/href">https://medium.com/media/1ec06839d1e092682309cac0ca7c5e75/href</a></iframe><p>In the main graph, add a boolean keyword called “WindDeformsNormals_On.” The “On” suffix is important to make sure it is editable in the material. Drag this on to the graph twice, connecting the deformed normal and tangent into the “on” fields. Route a normal and tangent node, both in object space, into the “off” fields. Connect these to the master stack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8n02NxVIM3XQLp1DgllE0Q.png" /></figure><p>In the scene editor, turn off normal deformations for the grass and tree materials. As I mentioned, it’s difficult to get normal deformation perfect without employing some costly techniques. If it is important to your game, you can continue refining the algorithm to match your model’s needs. Maybe calculate dampening based on position, or use a function instead of a noise texture (so you can calculate exact normals). Regardless, this option is available.</p><h3>Wrap up</h3><p>I think that’s about it for foliage in the shader graph. You can endlessly tweak things to fit your specific model. Speaking of that, I’d love to see your trees, grassy plans, or video game gardens! Feel free to send me screenshots!</p><p>For inspiration, here are the material settings I used to create the scenes in this video. The tree has a custom support script to set wind data, which finds connected pieces of the mesh, making sure each chunk has the same wind noise position.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AfZWhCsLHn2gX3Sn0_iadw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dfjCPqCG-tp8I9pGoX9aCg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6SEg7ZDqVIaq_NQMdagBUw.png" /></figure><p>Some effects currently not possible in the shader graph, like screen space ambient occlusion, do really improve the final look. You can add support pretty easily when 2021.2 releases, or take a look at the upcoming text shader version of this tutorial, which already supports it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q72knkjQMXYJSOA7SO1jNw.png" /><figcaption>The left side of the tree has SSAO turned on. Note the soft shadows between the leaf cards.</figcaption></figure><p>Thank you so much for reading! Foliage is really fun to create, I think, and I’ve only really scratched the surface of foliage rendering here. I have several more videos planned, including one specifically on covering terrain with fields of grass. <a href="https://nedmakesgames.medium.com/subscribe">Please keep an eye out for it</a>!</p><p><a href="https://gist.github.com/NedMakesGames/5a8b9fc186d62d277f79ab2172457801">Here is the final version of the scripts in the project</a>, for cross referencing.</p><p>If you have any questions, feel free to contact me at any of the links at the bottom of this article.</p><p>If you want to see this tutorial from another angle, I created a video version you can watch <a href="https://youtu.be/x4ufs1OzPIw">here</a>.</p><p>Try out my Interactive Lighting Math Explorer to visualize how diffuse, specular, translucency and other lighting calculations work: <a href="https://nedmakesgames.itch.io/lighting-explorer">https://nedmakesgames.itch.io/lighting-explorer</a></p><p>I want to take a moment to thank <strong>David Cru</strong> for his support, all my patrons from the last few months for making this tutorial possible: Adam R. Vierra, Aleksandr Molchanov, Alina Matson, Alvaro LOGOTOMIA, Andre Schuch, Antonio Ripa, anzhony manrique, Ash Free, Ben Luker, Ben Wander, BM, Bohemian Grape, Brooke Waddington, Cameron Horst, Candemir, ChainsawFilms, Chris, Christopher Ellis, Connor Wendt, Danny Hayes, darkkittenfire, David Cru, Evan Malmud, Eren Aydin, Erica, FABG Team, Georg Schmidbauer, hyunsookim, Isobel Shasha, Jack Phelps, Jerzy Gab, JP Lee, jpzz kim, Justin Criswell, Karthick Gunasekaran, Kyle Harrison, Leafenzo, Lhong Lhi, Luke Hopkins, Mad Science, Mark Davies, masahito nagasaka, Matt Anderson, maxo, Mike Young, NG, nobuhiko yamakoshi, Omar Sadek, Oskar, Oskar Kogut, Pat, Patrik Bergsten, peter janosik, phanurak rubpol, Qi Zhang, Quentin Arragon, rafael ludescher, Sam Slater, Samuel Ang, Sebastian Cai, SlapChop, starbi, Steph, Stephen Sandlin, Tomasz Beneś, Tvoyager, Voids Adrift, Will Tallent, Winberry, 성진 김</p><p>If you would like to download a completed version of this tutorial and many of the files featured here, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> 📧 E-mail: nedmakesgames gmail</p><h3>Credits, references, and special thanks</h3><ul><li>Cyanilux: <a href="https://github.com/Cyanilux/URP_ShaderGraphCustomLighting/blob/main/CustomLighting.hlsl">URP_ShaderGraphCustomLighting</a></li><li>Ada_King: <a href="https://www.turbosquid.com/3d-models/blender-carrot-crystal-oak-tree-3d-model-1189852">LowPoly Trees &amp; Carrot &amp; Crystal 3D model</a></li><li>XfrogPlants: <a href="https://www.cgtrader.com/free-3d-models/plant/other/xfrogplants-curry-leaf-tree">Curry Leaf Tree Free 3D model</a></li><li>Lennart Demes: <a href="https://ambientcg.com/view?id=Grass001">Grass 001</a></li><li>Baptiste Manteau: <a href="https://substance3d.adobe.com/community-assets/assets/e6c4bae683772a06aabb38dc1a13b3203060acd1">Bark old ginko</a></li><li>Render Knight: <a href="https://assetstore.unity.com/packages/2d/textures-materials/sky/fantasy-skybox-free-18353">Fantasy Skybox FREE</a></li><li>Alan Zucconi: <a href="https://www.alanzucconi.com/2017/08/30/fast-subsurface-scattering-1/">Fast Subsurface Scattering in Unity</a></li><li>CatLikeCoding: <a href="https://catlikecoding.com/unity/tutorials/rendering/part-6/">Rendering 6 Bumpiness</a></li><li>Ben Golus: <a href="https://bgolus.medium.com/normal-mapping-for-a-triplanar-shader-10bf39dca05a#0576">Normal Mapping for a Triplanar Shader</a> &amp; <a href="https://bgolus.medium.com/generating-perfect-normal-maps-for-unity-f929e673fc57#c508">Generating Perfect Normal Maps for Unity (and Other Programs)</a></li><li>TotallyRonja: <a href="https://www.ronja-tutorials.com/post/015-wobble-displacement/">Vertex Displacement</a></li></ul><p><em>All code appearing in GitHub Gist embeds is Copyright 2021 NedMakesGames, licensed under the </em><a href="https://opensource.org/licenses/MIT"><em>MIT License</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5854bf8dc4c2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating Custom Lighting in Unity’s Shader Graph with Universal Render Pipeline]]></title>
            <link>https://nedmakesgames.medium.com/creating-custom-lighting-in-unitys-shader-graph-with-universal-render-pipeline-5ad442c27276?source=rss-67c6e1219596------2</link>
            <guid isPermaLink="false">https://medium.com/p/5ad442c27276</guid>
            <category><![CDATA[unity]]></category>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[tutorial]]></category>
            <category><![CDATA[shaders]]></category>
            <dc:creator><![CDATA[NedMakesGames]]></dc:creator>
            <pubDate>Tue, 17 Aug 2021 23:14:51 GMT</pubDate>
            <atom:updated>2022-01-30T22:04:11.456Z</atom:updated>
            <cc:license>https://creativecommons.org/licenses/by-sa/4.0/</cc:license>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lEdgg3qTQUiFU_6MYaUrpg.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FGQyCPaThQnA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DGQyCPaThQnA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FGQyCPaThQnA%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/7859a84ccb12cc1ebfb4a250913ca6a2/href">https://medium.com/media/7859a84ccb12cc1ebfb4a250913ca6a2/href</a></iframe><p>Hi, I’m Ned and I make games! In this tutorial, I show how to implement custom lighting in Unity’s shader graph for the Universal Render Pipeline. As of now, there are no nodes which expose light data or modify the result of the default PBR calculation. Thankfully, you can get around this and handle materials that don’t look quite right on a lit graph, like foliage or skin, or need stylized lighting, like a toon shader.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BWd7lYgCTt8PWWHdxBpaxw.png" /></figure><p>Tested Unity versions: Unity 2020.3 and Unity 2021.1.</p><p>This custom lighting implementation includes a simple lighting algorithm featuring diffuse and specular lighting. It supports lighting and shadows from the sun light and additional point and spot lights, baked lighting, light probes, and fog. Note: <em>point light shadows are only available in Unity 2021.1 and later</em>.</p><p>I’ll assume you know the basics of Unity, URP, and the shader graph. You should also understand simple HLSL (the shader programming language) and what control keywords are. Finally, although I’ll give a general overview of various lighting terms and techniques, I won’t explain them in detail.</p><h3>Project set up</h3><p>First, create a new project using the URP template or set your project up to use URP by creating a settings asset and assigning it in graphics settings. Create the following simple test scene:</p><figure><img alt="A simple Unity scene with a flat plane, floating sphere, and directional light." src="https://cdn-images-1.medium.com/max/1024/1*IuzPk6kbv37yBdVcVHZR1A.png" /></figure><p>Create a plane. Create a sphere floating above it. Create an unlit URP shader graph called “TestLighting.” Create a material. Assign it the TestLighting shader. Set it on both test objects. To enable easy reuse, create a shader subgraph called “CustomLighting.”</p><p>Let’s initialize the graphs to sample a texture and display it with no lighting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gHJOuAlXxxWfzXn7y4RvlA.png" /></figure><p>Open CustomLighting. Create a float3 “Albedo” property. Change the output parameter to a float3 named “Out.” Route Albedo into Out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*upYn8SI4dkJaTJbg0syfTA.png" /></figure><p>Open TestLighting. Add a Texture2D “Albedo” property. Sample it. Add a CustomLighting node. Route the nodes together.</p><p>The bulk of the custom lighting algorithm must happen in a custom function node, which works best with a shader code HLSL file. You cannot create a .hlsl file directly through Unity, so open your asset folder in your operating system. Create a text file called CustomLighting and change the file extension to “.hlsl.” Open it in your script editor.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/01b2327bdb8b4739efa41f71018d2de2/href">https://medium.com/media/01b2327bdb8b4739efa41f71018d2de2/href</a></iframe><p>First, set up a guard keyword to prevent this file from being compiled twice. This file has three main parts: the first, CustomLightingData, is a data structure containing all data needed for the lighting calculation. The second, CalculateCustomLighting, contains and executes the lighting algorithm (right now, just returning albedo). The third, CalculateCustomLighting_float is the custom function wrapper which the shader graph can call. Note the number precision suffix. This wrapper constructs the data struct and calls the main function. Separating the wrapper from the algorithm gives you better control over data flow and also enables easy reuse from non-graph shaders, if you ever have the need.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rHxtAJuVm7CGEq46m-QSXA.png" /></figure><p>Return to the CustomLighting subgraph. Create a CustomFunction node. Set up the input and output to match the wrapper function in CustomLighting.hlsl. Set the name to “CalculateCustomLighting” (the number precision suffix is not required) and the source to your HLSL file. Reroute the graph through your custom function.</p><p>You may see some errors while setting the function up; however, if the preview box is black, you’re good to go. If it’s magenta, an error still exists. Double check your spelling — everything is also case-sensitive. When everything is working, your test scene will have flat shading.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vwYurLljqos8gqcqXSXMBg.png" /></figure><h3>Diffuse Lighting</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*38e93EPs_SweCDjMD-TtwA.png" /><figcaption><a href="https://nedmakesgames.itch.io/lighting-explorer">https://nedmakesgames.itch.io/lighting-explorer</a> — Interactive Lighting Explorer. Try it out to visualize how diffuse lighting works!</figcaption></figure><p>Diffuse lighting refers to the soft light that illuminates the side of an object facing a light source. It’s calculated by taking the dot product of the surface’s normal vector and the light direction. Let’s add shape to our scene by calculating diffuse lighting for the main light.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d6874594d1969010733aeeb0dfe3bac5/href">https://medium.com/media/d6874594d1969010733aeeb0dfe3bac5/href</a></iframe><p>Open CustomLighting.hlsl. Add a normal vector field to CustomLightingData. Rewrite CalculateCustomLighting to call GetMainLight (from URP’s shader library), obtaining a struct containing the main light’s direction and color. Call a new CustomLightHandling function.</p><p>This function computes the color resulting from the passed light. The radiance variable contains the light’s strength — right now purely its color. Calculate the diffuse dot product, then multiply the result with the surface albedo and radiance to compute the final color.</p><p>Finally, in the custom function wrapper, add a normal argument and set it in the struct.</p><p>Back in the CustomLighting graph, you’ll get an error about no function existing. Just add a float3 “normal” input to the custom function and move it above “albedo,” so the inputs match the wrapper function once again. Now, you’ll get an error about “GetMainLight” or the “Light” structure not existing. This occurs because the shader graph doesn’t include the URP lighting library when rendering the node preview windows.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6e9a1eda7320417b69470b7c2473a95c/href">https://medium.com/media/6e9a1eda7320417b69470b7c2473a95c/href</a></iframe><p>To fix this, we need to exclude sections of code from the shadergraph previews. Add an if-not-defined block around the CustomLightHandling function. Then in CalculateCustomLighting, write this estimation of diffuse lighting for the preview windows, placing the old logic in the else block.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZmKVL228njE6B6uR8ngm_A.png" /></figure><p>In your subgraph, the errors should disappear. Add a vector3 “normal” attribute and route it into the custom function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KLB6t36PBdWvlRigkTiR5w.png" /><figcaption>Correction: The transform node should be in “Direction” mode.</figcaption></figure><p>In the TestLighting graph, you can route a normal vector node into the subgraph, or, if you want to use normal maps, a normal map sample transformed into world space. Either way, check out your shaded scene!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5-prb81h2XT5Im6fDhm-6w.png" /></figure><h3>Specular Lighting</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mcx1SKzDqnRGOodixuMCgw.png" /><figcaption><a href="https://nedmakesgames.itch.io/lighting-explorer">https://nedmakesgames.itch.io/lighting-explorer</a> — Interactive Lighting Explorer. Try it out to visualize how specular lighting works!</figcaption></figure><p>Specular lighting models the small highlight visible on a shiny surface. It’s calculated by taking the average of the view direction and light direction, and then, taking the dot product of this “half” vector and the surface’s normal vector. Using this specular value directly sometimes leads to highlights in dark areas. Multiply with the diffuse lighting value to avoid this artifact.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6da8d4f565656456ef5782d0f673623a/href">https://medium.com/media/6da8d4f565656456ef5782d0f673623a/href</a></iframe><p>Open CustomLighting.hlsl. Add a view direction field to CustomLightingData and the wrapper function. In CustomLightHandling, calculate the specular lighting dot product. Then, multiply it with “diffuse” to calculate the final specular value. Finally, to add the specular component of lighting, add “diffuse” and “specular” together in the final color calculation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8HVb0oNd7yz2eNWVS_ZSCg.png" /></figure><p>In the CustomLighting subgraph, create a view direction node, set to world space. In certain Unity versions, view direction is not normalized; route it through a normalize node. Then, update and route into the custom function.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mRZZrDrt18cW-uADaoS7CQ.png" /></figure><p>Looking at your scene now, it will be difficult to see any highlights. We’ve neglected smoothness, which controls how focused the specular highlight is.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/35e3b88e98fd4b46a91ae2c589e51d04/href">https://medium.com/media/35e3b88e98fd4b46a91ae2c589e51d04/href</a></iframe><p>Open CustomLighting.hlsl. Add a float field for smoothness in CustomLightingData and update the custom function wrapper. In CustomLightHandling, tighten the specular dot by taking it to a power related to smoothness, defined by GetSmoothnessPower function. It transforms a value ranging from 0 to 1 on an exponential curve, but the math is ultimately arbitrary. You can edit it as needed. This results in a higher smoothness value shrinking the specular highlight.</p><p>Before moving on, add a specular approximation for the node preview windows to CalculateCustomLighting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fNrsKpsPtp_wYLWeZFaWQw.png" /></figure><p>Back in the CustomLighting subgraph, add a float smoothness property to the custom function and a corresponding graph property.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rHd2JPm6CDemDKL7p0dzVQ.png" /></figure><p>Then, in the TestLighting graph, add another smoothness property. For ease of editing, set it to slider mode, ranging from zero to one. Check out your handiwork!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FR0V1ewRN6oE7eE65IZx1Q.png" /></figure><h3>Shadows</h3><p>Shadows are an important part of lighting, and take a bit to set up. If you move an object using custom lighting over something with a default lit material, you’ll see it does cast a shadow already! However, our custom lit shader does not receive shadows. There are a few things we need to do to fix that.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iYX-B-uqD25GCBmFCVT6Dg.png" /><figcaption>On the left, the ground plane uses the custom lighting shader, while on the right, the default lit shader.</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/682739a28e1772ac57fb92af42ad01f6/href">https://medium.com/media/682739a28e1772ac57fb92af42ad01f6/href</a></iframe><p>First, the programming. Open CustomLighting.hlsl. Shadows, at least for the main light, are held in textures called shadow maps. The system needs a value called a shadow coordinate to accurately read them; as well as this fragment’s world position. Add both in CustomLightingData— notice that the shadow coord is a float4.</p><p>In the custom function wrapper, only add a position argument and set the position in the structure. We can calculate the shadow coord from this position. Set up another preview keyword block. In the preview side, set the coord to zero. In the else side, calculate this fragment’s clip space position (which is related to the pixel it displays in on screen). Depending on your shadow settings, Unity might store shadow maps in different layouts, needing different coordinates. This keyword allows you to calculate the correct value.</p><p>In CalculateCustomLighting, when calling GetMainLight, pass the shadow coord and position. The third argument is something called a shadow mask, which we’ll come back to later. This let’s Unity set a field in the Light structure called shadowAttenuation, which is a multiplier to this light’s radiance due to shadows. In CustomLightHandling, multiply it with the light color when calculating radiance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xd8F54lIf9LJvq1UP788-A.png" /></figure><p>In the CustomLighting subgraph, add a position argument to the custom function and route a position node in world space into it. In the scene, you’ll notice your materials still receive no shadows. This occurs because, to save resources, Unity does not sample shadow maps unless you explicitly tell it to by using various shader keywords.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eI1QZOAvJ5ugcBMN2V9J0w.png" /></figure><p>Return to the CustomLighting subgraph. Add a boolean keyword (the option is under the new property menu). The name isn’t important, but the reference must be _MAIN_LIGHT_SHADOWSexactly — underscores and capitals matter! Set the definition to “multi compile,” the scope to “global,” and the default to on.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e6f8b4a8ecc7c41c606036c3b3c7267a/href">https://medium.com/media/e6f8b4a8ecc7c41c606036c3b3c7267a/href</a></iframe><p>Now in the scene, you’ll get an error about shadowcoords not existing. To fix this, open CustomLighting.hlsl and add this little snippet. Discovered by <a href="https://www.cyanilux.com/">Cyanilux</a>, it removes some unnecessary code from shaders generated by the shader graph, avoiding the error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Jy07Ye9Awk9abAa5JXLKaw.png" /></figure><p>At this point, if you don’t see any shadows, make sure they’re enabled in your URP pipeline settings and on your camera (and if not using a fresh project, that cascade count is one).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A8vngeWPT5sHwBudmqjq4g.png" /></figure><p>To make them look better, add two more boolean keywords to CustomLighting, enabling shadow cascades and soft shadows. Keep the settings the same, but set the references to _MAIN_LIGHT_SHADOWS_CASCADE and _SHADOWS_SOFT. Make sure cascades and soft shadows are also enabled in your settings asset.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J9kv6k02HVn0IDMEoiL-zw.png" /></figure><h3>More Lights</h3><p>There’s only so much you can do with a single light, so let’s add support for point and spot lights. We’ve got the framework ready, so it won’t be too difficult.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bd2ac9eeb114329cd3f65a32b389a2a0/href">https://medium.com/media/bd2ac9eeb114329cd3f65a32b389a2a0/href</a></iframe><p>Open CustomLighting.hlsl. Unity stores additional light data separately from the main light in a buffer, which we need to loop through. This is the primary reason why I prefer implementing custom lighting all in one custom function, since loops are impossible in the shader graph. Anyway, Unity defines a keyword if this object is affected my additional lights, so create an if-def block for it.</p><p>Inside, call GetAdditionalLightsCount to get the number of additional lights, then loop through each. GetAdditionalLight returns a Light data structure for the specified light. It works just like GetMainLight, except it doesn’t require a shadow coord — that’s only for main light shadows. Send the light data to CustomLightHandling and add the returned color to the final color.</p><p>Unlike the main light, point and spot lights’ strength varies based on position. This is encapsulated by the distanceAttenuation field in the Light data structure. In CustomLightHandling, multiply it into the radiance calculation. distanceAttenuation is always 1 for the main light.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4XSyMlyJuqG5GdSgtQeEBw.png" /></figure><p>Back in the CustomLighting subgraph, add two more boolean keywords with the references _ADDITIONAL_LIGHTS and _ADDITIONAL_LIGHT_SHADOWS. Again, set the definition to “multi compile,” the scope to “global,” and the default to on. Then, select your URP settings asset and set additional lights to per pixel and enable cast shadows.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*an17RViNeTHTl-nqLBGDdQ.png" /></figure><p>Create some lights to test things out! Make sure all lights are in realtime mode and that the shadow type is not “no shadows.” Turn up the intensity so things are really plain to see. Note that point light shadows are <em>only available in Unity 2021.1 and later</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M1oai7AHdTi07oMkBvaGHw.png" /></figure><h3>Global Illumination</h3><p>Global illumination is a broad concept including baked lightmaps, light probes, global reflections, shadow masks, and spherical harmonics. We can implement them all in one fell swoop! I won’t spend much time explaining how these various techniques work or how to use them — each deserves its own tutorial. Just know they try to add a lot of inexpensive lighting to a scene.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/6c7c9f9915b5a66d5bb233dea55adfab/href">https://medium.com/media/6c7c9f9915b5a66d5bb233dea55adfab/href</a></iframe><p>In CustomLighting.hlsl, add two new fields into CustomLightingData: ambientOcclusion, which controls how much global lighting this fragment should receive, and bakedGI, which is the baked lighting color for this fragment.</p><p>Moving down to the wrapper function, add an argument for ambient occlusion and set it on the struct. As for baked GI, we’ll calculate it using a few special functions and macros. They require the lightmap UV, which we’ll receive as an additional argument.</p><p>In the not-preview block below, call OUTPUT_LIGHTMAP_UV to get the final lightmap UV using the lightmap scale Unity provides. OUTPUT_SH calculates the spherical harmonics for this fragment, which encode the color of the nearest light probes. Finally, resolve everything into a single baked global illumination value with SAMPLE_GI. It samples the lightmap texture, if available.</p><p>To add global light sources into our lighting algorithm, write a CustomGlobalIllumination function inside the preview-not-defined block. URP did the hard work in the custom function wrapper, so just multiply the albedo, bakedGI, and occlusion values together to calculate indirect diffuse. Return it.</p><p>In CalculateCustomLighting, initialize the color variable with CustomGlobalIllumination. But before that, we need to correct the global GI value. Under some circumstances, the main light’s shading contribution is also baked into global GI. We don’t want to include it twice, so call this URP function, MixRealtimeAndBakedGI, to remove the main light from the globalGI value, if needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mNlJPfqqcFvxl49vGARWRg.png" /></figure><p>Open your subgraph and create arguments for ambient occlusion and lightmap UV. Add a new float property for occlusion and hook it into the custom function. The lightmap UV is usually automatically stored by Unity in the second set of UVs, which is UV1. Route a UV node, in UV1 mode, into the lightmap UV field. Finally, add one more boolean keyword with LIGHTMAP_SHADOW_MIXING as the reference, which enables mixed lighting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0G7Q_uJ3cPyc8gH1JPEcYw.png" /></figure><p>In the TestLighting graph, create an ambient occlusion property. It should be a slider ranging from zero to one. You can, of course, use an occlusion map if you’d prefer!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j0YkxKt0WurX8kMcdQW72w.png" /></figure><p>Now test things out in your scene. Create a few new lights and set them to baked mode. Set your ground plane GameObject to static so it will receive lightmaps. Create a light probe group around your floating sphere. To make things easier to see, have differently colored baked lights appear on opposite sides of the probes. Once ready, save your project and navigate to the lighting panel (Window -&gt; Rendering -&gt; Lighting). Select generate lighting and wait for the lightmapper to finish its work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3OCmESEcADSwZ7lg4jrJ7g.png" /></figure><p>First, check that baked lighting appears on your plane. If you disable realtime lights, you should still see some lighting where the baked lights were. Then, test light probes by moving your sphere around. Again, it might be difficult to notice at first, so turn off realtime lights and look for slightly different coloring on your sphere.</p><p>Moving on to baked reflections. Unity creates a reflection cubemap based on the skybox, and it’s very easy to get a hold of.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/33ccdefb85e81d2eb7fd7aba7e19f15b/href">https://medium.com/media/33ccdefb85e81d2eb7fd7aba7e19f15b/href</a></iframe><p>In CustomLighting.hlsl, add these lines to CustomGlobalIllumination. We first need to sample the cubemap by calculating the sample normal. Visualize yourself standing at its center and looking at the pixel to sample — that’s the sample normal. In this situation, modeling a mirror, we calculate it by reflecting the view direction around the normal vector.</p><p>I found that reflections look nice around the edge of an object, an effect known as a rim or Fresnel light. It’s calculated by finding the dot product of the normal and view direction, subtracting it from one, and tightening it with an exponent (4 in this case).</p><p>Call GlossyEnvironmentReflections to sample the cubemap, passing the sample normal, a roughness value, and the occlusion. Roughness is one minus smoothness, and affects how blurry the reflections are. I’m repurposing this function from URP’s PBR rendering code, which expects something called “perceptual roughness.” Luckily, we can convert to that using RoughnessToPerceptualRoughness. Finally, multiply by fresnel and add the indirect specular result to the final calculation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZMLycyA9z8vYNNEDM1s_iA.png" /></figure><p>That’s it! If the effect is too strong, you can reduce reflection strength in the lighting window.</p><p>Finally, let’s add baked shadows. These are lightweight, precalculated shadows for the main light baked into a texture, like lightmaps. They’re always used for far away objects, but you can turn them on for all objects in the quality settings.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8cbfbda4339d06d157e37514de43bbb3/href">https://medium.com/media/8cbfbda4339d06d157e37514de43bbb3/href</a></iframe><p>Open CustomLighting.hlsl. It’s time to tackle that shadowmask value I spoke about earlier. Add a float4 field in CustomLightingData. In the wrapper function, set the shadow mask to zero in the preview block, and call SAMPLE_SHADOWMASK at the end of the not-preview block. Then, in CalculateCustomLighting, pass the new shadowmask value as the third argument to GetMainLight and GetAdditionalLight.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hMBgoPHoUQpneIZlKR2SfQ.png" /></figure><p>In your CustomLighting subgraph, add one more boolean keyword with SHADOWS_SHADOWMASK as the reference and the same settings as always.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QoNrVq0mXI790DBxSo60Cg.png" /></figure><p>To test shadowmasks, add a large, static object above your plane, set your main light to mixed mode, and bake lighting again. Go to your quality settings and change the Shadow Mask mode to “Shadowmask.” You should see shadows on all static objects, like your ground plane. Note that light probes received baked shadows, so that will affect the lighting of dynamic objects.</p><p>Before moving on, change the shadowmask mode back to distance shadowmask. Now, baked shadows are only used for distant objects.</p><h3>Fog</h3><p>Fog is the final feature I will add in this tutorial, and it’s pretty simple. It’s a method to fade distant objects into a flat color, to help avoid pop-in.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/81d478ae1e584f0be2c2217e6cfced1f/href">https://medium.com/media/81d478ae1e584f0be2c2217e6cfced1f/href</a></iframe><p>In your hlsl file, add a fogFactor field to the data struct, which URP will use to calculate the fog strength. We’ll set it in the function wrapper. Set fogFactor to zero in the preview block. Otherwise, call ComputeFogFactor, which uses this fragment’s the clip space z-position, related to it’s depth from the camera.</p><p>In CalculateCustomLighting, call MixFog. This URP function applies fog to the final color, taking care of all fog modes for us.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wc3EqvFwx0kw8nFuCTzRww.png" /></figure><p>Return to the scene view and test things out by turning on fog in the lighting window. Try each mode and adjust the settings. MixFog can handle them all!</p><h3>Upcoming Changes</h3><p>In version 2021.2, Unity is poised to release many new features to URP and the Shader Graph which will affect this project. The biggest change is the addition of custom vertex interpolators, which will allow us to support vertex lights and more efficient global illumination. They’re also adding light cookies, reflection probe blending, light layers, screen space shadows and ambient occlusion (for unlit graphs), and deferred rendering. Needless to say, expect a follow up tutorial after 2021.2 releases.</p><h3>Wrap Up</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F4CKW91eaHrb__K8YYQ-9Q.png" /></figure><p>Thank you so much for reading! Now you have the convenience of the Shader Graph and complete control over how your game looks. Use these techniques well and your project will really stand out! I’m currently working on a tutorial building on this framework to create a grass, leaf, and general foliage shader. <a href="https://nedmakesgames.medium.com/subscribe">Please keep an eye out for it</a>!</p><p>Here is the <a href="https://gist.github.com/NedMakesGames/13e64bed3a84e81826b05e5fb0214e70">final version of the script</a>, for cross referencing.</p><p>If you have any questions, feel free to contact me at any of the links at the bottom of this article.</p><p>If you want to see this tutorial from another angle, I created a video version you can watch here: <a href="https://www.youtube.com/watch?v=GQyCPaThQnA">https://www.youtube.com/watch?v=GQyCPaThQnA</a></p><p>Try out my Interactive Lighting Math Explorer to visualize how diffuse, specular, and other lighting calculations work: <a href="https://nedmakesgames.itch.io/lighting-explorer">https://nedmakesgames.itch.io/lighting-explorer</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mz8Jo94TqON9t_ardoY6Yg.png" /></figure><p>I want to take a moment to thank <strong>David Cru</strong> for his support, and all my patrons for making this tutorial possible: Adam R. Vierra, Aleksandr Molchanov, Alina Matson, Alvaro LOGOTOMIA, Antonio Ripa, anzhony manrique, Ben Luker, Ben Wander, BM, Bohemian Grape, Cameron Horst, Candemir, ChainsawFilms, Chris, Christopher Ellis, Connor Wendt, Danny Hayes, darkkittenfire, David Cru, Evan Malmud, Eren Aydin, FABG Team, Georg Schmidbauer, hyunsookim, Isobel Shasha, Jerzy Gab, JP Lee, jpzz kim, Karthick Gunasekaran, Kyle Harrison, Leafenzo, Lhong Lhi, Luke Hopkins, Mad Science, Mark Davies, masahito nagasaka, Matt Anderson, maxo, Mike Young, NG, Oskar Kogut, Pat, Patrik Bergsten, phanurak rubpol, Qi Zhang, Quentin Arragon, rafael ludescher, Sam Slater, Sebastian Cai, SlapChop, starbi, Steph, Stephen Sandlin, Tvoyager, Voids Adrift, Will Tallent, Winberry, 성진 김</p><p>If you would like to download a completed version of this tutorial and all the files featured here, <a href="https://patreon.com/nedmakesgames">consider joining my Patreon page</a>. You will also get early access to tutorials, voting power in topic polls, and more. Thank you!</p><p><strong>Thanks so much for reading, and make games!</strong></p><p>🔗 <a href="https://nedmakesgames.github.io">Tutorial list website</a> ▶️ <a href="https://www.youtube.com/nedmakesgames">YouTube</a> 🔴 <a href="https://www.twitch.tv/nedmakesgames">Twitch</a> 🐦 <a href="https://twitter.com/nedmakesgames">Twitter</a> 🎮 <a href="https://discordapp.com/invite/ubxSVBK">Discord</a> 📸 <a href="https://instagram.com/nedmakesgames">Instagram</a> 👽 <a href="https://reddit.com/u/nedmakesgames">Reddit</a> 🎶 <a href="https://www.tiktok.com/@nedmakesgames">TikTok</a> 👑 <a href="https://patreon.com/nedmakesgames">Patreon</a> 📧 E-mail: nedmakesgames gmail</p><p>Credits, references, and special thanks:</p><ul><li>Cyanilux: <a href="https://github.com/Cyanilux/URP_ShaderGraphCustomLighting/blob/main/CustomLighting.hlsl">URP_ShaderGraphCustomLighting</a></li><li>Ida Faber: <a href="https://www.artstation.com/marketplace/p/yyxV/lowpoly-shiba-inu-dog">Lowpoly Shiba Inu Dog</a></li><li>XfrogPlants: <a href="https://www.cgtrader.com/free-3d-models/plant/other/xfrogplants-curry-leaf-tree">Curry Leaf Tree Free 3D model</a></li><li>Lennart Demes: <a href="https://ambientcg.com/view?id=Grass001">Grass 001</a></li><li>Baptiste Manteau: <a href="https://substance3d.adobe.com/community-assets/assets/e6c4bae683772a06aabb38dc1a13b3203060acd1">Bark old ginko</a></li><li>Render Knight: <a href="https://assetstore.unity.com/packages/2d/textures-materials/sky/fantasy-skybox-free-18353">Fantasy Skybox FREE</a></li></ul><p><em>All code appearing in GitHub Gist embeds is Copyright 2021 NedMakesGames, licensed under the </em><a href="https://opensource.org/licenses/MIT"><em>MIT License</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5ad442c27276" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>