{"id":574,"date":"2019-12-05T02:28:23","date_gmt":"2019-12-05T02:28:23","guid":{"rendered":"https:\/\/www.danielparente.net\/en\/2019\/12\/05\/directional-lights\/"},"modified":"2019-12-05T02:28:23","modified_gmt":"2019-12-05T02:28:23","slug":"directional-lights","status":"publish","type":"post","link":"https:\/\/www.danielparente.net\/en\/2019\/12\/05\/directional-lights\/","title":{"rendered":"Directional Lights"},"content":{"rendered":"<p> [ad_1]<br \/>\n<\/p>\n<p>This is the third part of a tutorial series about creating a <a href=\"https:\/\/catlikecoding.com\/unity\/tutorials\/custom-srp\/\" target=\"_blank\" rel=\"noopener\">custom scriptable render pipeline<\/a>. It adds support for shading with multiple directional lights.<\/p>\n<p>This tutorial is made with Unity 2019.2.12f1.<\/p>\n<div id=\"\">\n<h2>Lighting<\/h2>\n<p>If we want to create a more realistic scene then we&#8217;ll have to simulate how light interacts with surfaces. This requires a more complex shader than the unlit one that we currently have.<\/p>\n<section>\n<h3>Lit Shader<\/h3>\n<p>Duplicate the <em translate=\"no\">UnlitPass<\/em> HLSL file and rename it to <em translate=\"no\">LitPass<\/em>. Adjust the include guard define and the vertex and fragment function names to match. We&#8217;ll add lighting calculations later.<\/p>\n<pre translate=\"no\">#ifndef \n#define \n\n\u2026\n\nVaryings  (Attributes input) { \u2026 }\n\nfloat4  (Varyings input) : SV_TARGET { \u2026 }\n\n#endif<\/pre>\n<p>Also duplicate the <em translate=\"no\">Unlit<\/em> shader and rename it to <em translate=\"no\">Lit<\/em>. Change its menu name, the file it includes, and the functions it uses. Let&#8217;s also change the default color to gray, as a fully white surface in a well-lit scene can appear very bright. The Universal pipeline uses a gray color by default as well.<\/p>\n<pre translate=\"no\">Shader  {\n\t\n\tProperties {\n\t\t_BaseMap(\"Texture\", 2D) = \"white\" {}\n\t\t_BaseColor(\"Color\", Color) = (, 1.0)\n\t\t\u2026\n\t}\n\n\tSubShader {\n\t\tPass {\n\t\t\t\u2026\n\t\t\t#pragma vertex \n\t\t\t#pragma fragment \n\t\t\t#include \n\t\t\tENDHLSL\n\t\t}\n\t}\n}<\/pre>\n<p>We&#8217;re going to use a custom lighting approach, which we&#8217;ll indicate by setting the light mode of our shader to <em translate=\"no\">CustomLit<\/em>. Add a <code>Tags<\/code> block to the <code>Pass<\/code>, containing <code>\"LightMode\" = \"CustomLit\"<\/code>.<\/p>\n<pre translate=\"no\">\t\tPass {\n\t\t\t\n\t\t\t\t\n\t\t\t\n\n\t\t\t\u2026\n\t\t}<\/pre>\n<p>To render objects that use this pass we have to include it in <code class=\"csharp\">CameraRenderer<\/code>. First add a shader tag identifier for it.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tstatic ShaderTagId\n\t\tunlitShaderTagId = new ShaderTagId(\"SRPDefaultUnlit\")\n\t\t;<\/pre>\n<p>Then add it to the passes to be rendered in <code class=\"csharp\">DrawVisibleGeometry<\/code>, like we did in <code class=\"csharp\">DrawUnsupportedShaders<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\tvar drawingSettings = new DrawingSettings(\n\t\t\tunlitShaderTagId, sortingSettings\n\t\t) {\n\t\t\tenableDynamicBatching = useDynamicBatching,\n\t\t\tenableInstancing = useGPUInstancing\n\t\t};\n\t\t<\/pre>\n<p>Now we can create a new opaque material, though at this point it produces the same results an as unlit material.<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/opaque-material.png\" width=\"320\" height=\"42\"\/><figcaption>Default opaque material.<\/figcaption><\/figure>\n<\/section>\n<section>\n<h3>Normal Vectors<\/h3>\n<p>How well an object is lit depends on multiple factors, including the relative angle between the light and surface. To know the surface&#8217;s orientation we need to access the surface normal, which is a unit-length vector pointing straight away from it. This vector is part of the vertex data, defined in object space just like the position. So add it to <code>Attributes<\/code> in <em translate=\"no\">LitPass<\/em>.<\/p>\n<pre translate=\"no\">struct Attributes {\n\tfloat3 positionOS : POSITION;\n\t\n\tfloat2 baseUV : TEXCOORD0;\n\tUNITY_VERTEX_INPUT_INSTANCE_ID\n};<\/pre>\n<p>Lighting is calculated per fragment, so we have to add the normal vector to <code>Varyings<\/code> as well. We&#8217;ll perform the calculations in world space, so name it <code>normalWS<\/code>.<\/p>\n<pre translate=\"no\">struct Varyings {\n\tfloat4 positionCS : SV_POSITION;\n\t\n\tfloat2 baseUV : VAR_BASE_UV;\n\tUNITY_VERTEX_INPUT_INSTANCE_ID\n};<\/pre>\n<p>We can use <code>TransformObjectToWorldNormal<\/code> from <em translate=\"no\">SpaceTransforms<\/em> to convert the normal to world space in <code>LitPassVertex<\/code>.<\/p>\n<pre translate=\"no\">\toutput.positionWS = TransformObjectToWorld(input.positionOS);\n\toutput.positionCS = TransformWorldToHClip(positionWS);\n\t<\/pre>\n<aside>\n<h3>How does <code>TransformObjectToWorldNormal<\/code> work?<\/h3>\n<div>\n<p>When you check the code you&#8217;ll see that it uses one of two approaches, based on whether <code>UNITY_ASSUME_UNIFORM_SCALING<\/code> is defined.<\/p>\n<p>When <code>UNITY_ASSUME_UNIFORM_SCALING<\/code> is defined it invokes <code>TransformObjectToWorldDir<\/code>, which does the same as <code>TransformObjectToWorld<\/code> except that it ignores the translation part, as we&#8217;re dealing with a direction vector instead of a position. But the vector also gets uniformly scaled, so it should get normalized later.<\/p>\n<p>In the other case uniform scaling is not assumed. This is more complicated, because when an object gets deformed by nonuniform scaling the normal vectors have to get scaled in reverse to match the new surface orientation. This requires a multiplication with the transposed <code>UNITY_MATRIX_I_M<\/code> matrix instead, plus normalization.<\/p>\n<figure><img decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/scaling-incorrect.png\" width=\"150\" height=\"100\" alt=\"incorrect\"\/><img decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/scaling-correct.png\" width=\"160\" height=\"100\" alt=\"correct\"\/><figcaption>Incorrect and correct normal transformation.<\/figcaption><\/figure>\n<p>Using <code>UNITY_ASSUME_UNIFORM_SCALING<\/code> is a slight optimization, which you can enable by defining it yourself. However, it makes more of a difference when GPU instancing is used, because then an array of <code>UNITY_MATRIX_I_M<\/code> matrices has to be send to the GPU. Avoiding that when not needed is worthwhile. You can enable it by adding the <code>#pragma instancing_options assumeuniformscaling<\/code> directive to the shader, but only do this if you&#8217;re exclusively rendering objects with uniform scale.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>To verify whether we get a correct normal vector in <code>LitPassFragment<\/code> we can use it as a color.<\/p>\n<pre translate=\"no\">\t\n\treturn base;<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/world-normals.png\" width=\"180\" height=\"180\"\/><figcaption>World-space normal vectors.<\/figcaption><\/figure>\n<p>Negative values cannot be visualized, so they&#8217;re clamped to zero.<\/p>\n<\/section>\n<section>\n<h3>Interpolated Normals<\/h3>\n<p>Although the normal vectors are unit-lengh in the vertex program, linear interpolation across triangles affects their length. We can visualize the error by rendering the difference between one and the vector&#8217;s length, magnified by ten to make it more obvious.<\/p>\n<pre translate=\"no\">\tbase.rgb = input.normalWS;<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/interpolated-normal-error.png\" width=\"180\" height=\"180\"\/><figcaption>Interpolated normal error, exaggerated.<\/figcaption><\/figure>\n<p>We can smooth out the interpolation distortion by normalizing the normal vector in <code>LitPassFragment<\/code>. The difference isn&#8217;t really noticeable when just looking at the normal vectors, but it&#8217;s more obvious when used for lighting.<\/p>\n<pre translate=\"no\">\tbase.rgb = ;<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/normalization-after-interpolation.png\" width=\"270\" height=\"130\"\/><figcaption>Normalization after interpolation.<\/figcaption><\/figure>\n<\/section>\n<section>\n<h3>Surface Properties<\/h3>\n<p>Lighting in a shader is about simulating the interactions between light that hits a surface, which means that we must keep track of the surface&#8217;s properties. Right now we have a normal vector and a base color. We can split the latter in two: the RGB color and the alpha value. We&#8217;ll be using this data in a few places, so let&#8217;s define a convenient <code>Surface<\/code> struct to contain all relevant data. Put it in a separate <em translate=\"no\">Surface<\/em> HLSL file in the <em translate=\"no\">ShaderLibrary<\/em> folder.<\/p>\n<pre translate=\"no\">\n\n\n\n\t\n\t\n\t\n\n\n<\/pre>\n<aside>\n<h3>Shouldn&#8217;t we define the normal as <code>normalWS<\/code>?<\/h3>\n<div>\n<p>We could, but the surface doesn&#8217;t care in what space the normal is defined. Lighting calculations could be performed in any proper 3D space. So we leave the space undefined. When filling the data we just have to use the same space everywhere. We&#8217;ll use world space, but we could switch to another space later and everything would still work the same.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>Include it in <em translate=\"no\">LitPass<\/em>, after <em translate=\"no\">Common<\/em>. That way we can keep <em translate=\"no\">LitPass<\/em> short. We&#8217;ll put specialized code in its own HLSL file from now on, to make it easier to locate the relevant functionality.<\/p>\n<pre translate=\"no\">#include \"..\/ShaderLibrary\/Common.hlsl\"\n<\/pre>\n<p>Define a <code>surface<\/code> variable in <em translate=\"no\">LitPassFragment<\/em> and fill it. Then the final result becomes the surface&#8217;s color and alpha.<\/p>\n<pre translate=\"no\">\t\n\t = normalize(input.normalWS);\n\t\n\t\n\n\treturn ;<\/pre>\n<aside>\n<h3>Isn&#8217;t that inefficient code?<\/h3>\n<div>\n<p>It makes no difference, because the shader compiler generates highly optimized programs, completely rewriting our code. The struct is purely for our convenience. You can inspect the compiler&#8217;s work via the <em translate=\"no\">Compile and show code<\/em> button in the shader&#8217;s inspector.<\/p>\n<\/p><\/div>\n<\/aside>\n<\/section>\n<section>\n<h3>Calculating Lighting<\/h3>\n<p>To calculate the actual lighting we&#8217;ll create a <code>GetLighting<\/code> function that has a <code>Surface<\/code> parameter. Initially have it return the Y component of the surface normal. As this is lighting functionality we&#8217;ll put it in a separate <em translate=\"no\">Lighting<\/em> HLSL file.<\/p>\n<pre translate=\"no\">\n\n\n\n\t\n\n\n<\/pre>\n<p>Include it in <em translate=\"no\">LitPass<\/em>, after including <em translate=\"no\">Surface<\/em> because <em translate=\"no\">Lighting<\/em> depends on it.<\/p>\n<pre translate=\"no\">#include \"..\/ShaderLibrary\/Surface.hlsl\"\n<\/pre>\n<aside>\n<h3>Why not include <em translate=\"no\">Surface<\/em> in <em translate=\"no\">Lighting<\/em>?<\/h3>\n<div>\n<p>We could do that, but we&#8217;ll end up with multiple files depending on multiple other files. I choose to instead put all include statements in one place, which makes the dependencies clear. That also makes it easy to replace one file with another, to change how the shader works, as long as the new file defines the same functionality that others rely on.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>Now we can get the lighting in <code>LitPassFragment<\/code> and use that for the RGB part of the fragment.<\/p>\n<pre translate=\"no\">\t\n\treturn float4(, surface.alpha);<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/diffuse-lighting-from-above.png\" width=\"180\" height=\"180\"\/><figcaption>Diffuse lighting from above.<\/figcaption><\/figure>\n<p>At this point the result is the Y component of the surface normal, so it is one at the top of the sphere and drops down to zero at its sides. Below that the result becomes negative, reaching \u22121 at the bottom, but we cannot see negative values. It matches the cosine of the angle between the normal and up vectors. Ignoring the negative part, this visually matches diffuse lighting of a directional light pointing straight down. The finishing touch would be to factor the surface color into the result in <code>GetLighting<\/code>, interpreting it as the surface albedo.<\/p>\n<pre translate=\"no\">float3 GetLighting (Surface surface) {\n\treturn surface.normal.y ;\n}<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lighting\/albedo.png\" width=\"180\" height=\"180\"\/><figcaption>Albedo applied.<\/figcaption><\/figure>\n<aside>\n<h3>What does albedo mean?<\/h3>\n<div>\n<p>Albedo means whiteness in Latin. It&#8217;s a measure of how much light is diffusely reflected by a surface. If albedo isn&#8217;t fully white then part of the light energy gets absorbed instead of reflected.<\/p>\n<\/p><\/div>\n<\/aside>\n<\/section>\n<\/div>\n<div id=\"\">\n<h2>Lights<\/h2>\n<p>To perform proper lighting we also need to know the properties of the light. In this tutorial we&#8217;ll limit ourselves to directional lights only. A directional light represents a source of light so far away that its position doesn&#8217;t matter, only its direction. This is a simplification, but it&#8217;s good enough to simulate the Sun&#8217;s light on Earth and other situations where incoming light is more or less unidirectional.<\/p>\n<section>\n<h3>Light Structure<\/h3>\n<p>We&#8217;ll use a struct to store the light data. For now we can suffice with a color and a direction. Put it in a separate <em translate=\"no\">Light<\/em> HLSL file. Also define a <code>GetDirectionalLight<\/code> function that returns a configured directional light. Initially use a white color and the up vector, matching the light data that we&#8217;re currently using. Note that the light&#8217;s direction is thus defined as the direction from where the light is coming, not where it is going.<\/p>\n<pre translate=\"no\">\n\n\n\n\t\n\t\n\n\n\n\t\n\t\n\t\n\t\n\n\n<\/pre>\n<p>Include the file in <em translate=\"no\">LitPass<\/em> before <em translate=\"no\">Lighting<\/em>.<\/p>\n<pre translate=\"no\">\n#include \"..\/ShaderLibrary\/Lighting.hlsl\"<\/pre>\n<\/section>\n<section>\n<h3>Lighting Functions<\/h3>\n<p>Add a <code>GetIncomingLight<\/code> function to <em translate=\"no\">Lighting<\/em> that calculates how much incoming light there is for a given surface and light. For an arbitrary light direction we have to take the dot product of the surface normal and the direction. We can use the <code>dot<\/code> function for that. The result should be modulated by the light&#8217;s color.<\/p>\n<pre translate=\"no\">\n\t\n<\/pre>\n<aside>\n<h3>What&#8217;s a dot product?<\/h3>\n<div>\n<p>The dot product between two vectors is geometrically defined as `A * B =  ||A|| \u00a0 ||B|| \u00a0 cos theta`. This means that it is the cosine of the angle between the vectors, multiplied by their lengths. So in the case of two unit-length vectors `A * B = cos theta`.<\/p>\n<p>Algebraically, it is defined as `A * B = sum_(i=1)^n A_i B_i = A_1 B_1 + A_2 B_2 + \u2026 + A_n B_n`. This means that you can compute it by multiplying all component pairs and summing them.<\/p>\n<pre translate=\"no\">float dotProduct = a\u20ac.x * b\u20ac.x + a\u20ac.y * b\u20ac.y + a\u20ac.z * b\u20ac.z;<\/pre>\n<p>Visually, this operation projects one vector straight down to the other, as if casting a shadow on it. In doing so, you end up with a right triangle of which the bottom side&#8217;s length is the result of the dot product. And if both vectors are unit length, that&#8217;s the cosine of their angle.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lights\/dot-product.png\" width=\"135\" height=\"110\"\/><figcaption>Dot product.<\/figcaption><\/figure>\n<\/div>\n<\/aside>\n<p>But this is only correct when the surface is oriented toward the light. When the dot product is negative we have to clamp it to zero, which we can do via the <code>saturate<\/code> function.<\/p>\n<pre translate=\"no\">float3 IncomingLight (Surface surface, Light light) {\n\treturn dot(surface.normal, light.direction) * light.color;\n}<\/pre>\n<aside>\n<h3>What does <code>saturate<\/code> do?<\/h3>\n<div>\n<p>It clamps the value so it lies between zero and one inclusive. We only need to specify a minimum because the dot product should never be greater than one, but saturation is such a common shader operation that it usually a free operation modifier.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>Add another <code>GetLighting<\/code> function, which returns the final lighting for a surface and light. For now it&#8217;s the incoming light multiplied by the surface color. Define it above the other function.<\/p>\n<pre translate=\"no\">\n\t\n<\/pre>\n<p>Finally, adjust the <code>GetLighting<\/code> function that only has a surface parameter so it invokes the other one, using <code>GetDirectionalLight<\/code> to provide the light data.<\/p>\n<pre translate=\"no\">float3 GetLighting (Surface surface) {\n\treturn ;\n}<\/pre>\n<\/section>\n<section>\n<h3>Sending Light Data to the GPU<\/h3>\n<p>Instead of always using a white light from above we should use the light of the current scene. The default scene came with a directional light that represents the Sun, has a slightly yellowish color\u2014FFF4D6 hexadecimal\u2014and is rotated 50\u00b0 around the X axis and \u221230\u00b0 around the Y axis. If such a light doesn&#8217;t exist create one.<\/p>\n<p>To make the light&#8217;s data accessible in the shader we&#8217;ll have to create uniform values for it, just like for shader properties. In this case we&#8217;ll define two <code>float3<\/code> vectors: <code>_DirectionalLightColor<\/code> and <code>_DirectionalLightDirection<\/code>. Put them in a <code>_CustomLight<\/code> buffer defined at the top of <em translate=\"no\">Light<\/em>.<\/p>\n<pre translate=\"no\">\n\t\n\t\n<\/pre>\n<p>Use these values instead of constants in <code>GetDirectionalLight<\/code>.<\/p>\n<pre translate=\"no\">Light GetDirectionalLight () {\n\tLight light;\n\tlight.color = ;\n\tlight.direction = ;\n\treturn light;\n}<\/pre>\n<p>Now our RP must send the light data to the GPU. We&#8217;ll create a new <code class=\"csharp\">Lighting<\/code> class for that. It works like <code class=\"csharp\">CameraRenderer<\/code> but for lights. Give it a public <code class=\"csharp\">Setup<\/code> method with a context parameter, in which it invokes a separate <code class=\"csharp\">SetupDirectionalLight<\/code> method. Although not strictly necessary, let&#8217;s also give it a dedicated command buffer that we execute when done, which can be handy for debugging. The alternative would be to add a buffer parameter.<\/p>\n<pre class=\"csharp\" translate=\"no\">\n\n\n\n\n\t\n\n\t\n\t\t\n\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\n<\/pre>\n<p>Keep track of the identifiers of the two shader properties.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\t<\/pre>\n<p>We can access the scene&#8217;s main light via <code class=\"csharp\">RenderSettings.sun<\/code>. That gets us the most important directional light by default and it can also be explicitly configured via <em translate=\"no\">Window \/ Rendering \/ Lighting Settings<\/em>. Use <code class=\"csharp\">CommandBuffer.SetGlobalVector<\/code> to send the light data to the GPU. The color is the light&#8217;s color in linear space, while the direction is the light transformation&#8217;s forward vector negated.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid SetupDirectionalLight () {\n\t\t\n\t\t\n\t\t\n\t}<\/pre>\n<aside>\n<h3>Doesn&#8217;t <code class=\"csharp\">SetGlobalVector<\/code> require a <code class=\"csharp\">Vector4<\/code>?<\/h3>\n<div>\n<p>Yes, vectors send to the GPU always have four components, even if we define them with less. The extra components are implicitly masked out in the shader. Likewise, there&#8217;s an implicit conversion from <code class=\"csharp\">Vector3<\/code> to <code class=\"csharp\">Vector4<\/code>, though not in the other direction.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>The light&#8217;s <code class=\"csharp\">color<\/code> property is its configured color, but lights also have a separate intensity factor. The final color is both multiplied.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\tbuffer.SetGlobalVector(\n\t\t\tdirLightColorId, light.color.linear \n\t\t);<\/pre>\n<p>Give <code class=\"csharp\">CameraRenderer<\/code> a <code class=\"csharp\">Lighting<\/code> instance and use it to set up the lighting before drawing the visible geometry.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\n\tpublic void Render (\n\t\tScriptableRenderContext context, Camera camera,\n\t\tbool useDynamicBatching, bool useGPUInstancing\n\t) {\n\t\t\u2026\n\n\t\tSetup();\n\t\t\n\t\tDrawVisibleGeometry(useDynamicBatching, useGPUInstancing);\n\t\tDrawUnsupportedShaders();\n\t\tDrawGizmos();\n\t\tSubmit();\n\t}<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lights\/lit-by-sun.png\" width=\"180\" height=\"180\"\/><figcaption>Lit by the sun.<\/figcaption><\/figure>\n<\/section>\n<section>\n<h3>Visible Lights<\/h3>\n<p>When culling Unity also figures out which lights affect the space visible to the camera. We can rely on that information instead of the global sun. To do so <code class=\"csharp\">Lighting<\/code> needs access to the culling results, so add a parameter for that to <code class=\"csharp\">Setup<\/code> and store it in a field for convenience. Then we can support more than one light, so replace the invocation of <code class=\"csharp\">SetupDirectionalLight<\/code> with a new <code class=\"csharp\">SetupLights<\/code> method.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\n\tpublic void Setup (\n\t\tScriptableRenderContext context\n\t) {\n\t\t\n\t\tbuffer.BeginSample(bufferName);\n\t\t<del>\/\/SetupDirectionalLight();<\/del>\n\t\t\n\t\t\u2026\n\t}\n\t\n\t<\/pre>\n<p>Add the culling results as an argument when invoking <code class=\"csharp\">Setup<\/code> in <code class=\"csharp\">CameraRenderer.Render<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\tlighting.Setup(context);<\/pre>\n<p>Now <code class=\"csharp\">Lighting.SetupLights<\/code> can retrieve the required data via the <code class=\"csharp\">visibleLights<\/code> property of the culling results. It&#8217;s made available as a <code class=\"csharp\">Unity.Collections.NativeArray<\/code> with the <code class=\"csharp\">VisibleLight<\/code> element type.<\/p>\n<pre class=\"csharp\" translate=\"no\">\nusing UnityEngine;\nusing UnityEngine.Rendering;\n\npublic class Lighting {\n\t\u2026\n\n\tvoid SetupLights () {\n\t\t\n\t}\n\n\t\u2026\n}<\/pre>\n<aside>\n<h3>What&#8217;s a <code class=\"csharp\">NativeArray<\/code>?<\/h3>\n<div>\n<p>It&#8217;s a struct that acts like an array, but provides a connection to a native memory buffer. It makes it possible to efficiently share data between managed C# code and the native Unity engine code.<\/p>\n<\/p><\/div>\n<\/aside>\n<\/section>\n<section>\n<h3>Multiple Directional Lights<\/h3>\n<p>Using the visible light data makes it possible to support multiple directional lights, but we have to send the data of all those lights to the GPU. So instead of a pair of vectors we&#8217;ll use two <code class=\"csharp\">Vector4<\/code> arrays, plus an integer for the light count. We&#8217;ll also define a maximum amount of directional lights, which we can use to initialize two array fields to buffer the data. Let&#8217;s set the maximum to four, which should be enough for most scenes.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\n\tstatic int\n\t\t<del>\/\/dirLightColorId = Shader.PropertyToID(\"_DirectionalLightColor\"),<\/del>\n\t\t<del>\/\/dirLightDirectionId = Shader.PropertyToID(\"_DirectionalLightDirection\");<\/del>\n\t\t\n\t\t\n\t\t\n\n\t\n\t\t\n\t\t<\/pre>\n<aside>\n<h3>Why not use structured buffers?<\/h3>\n<div>\n<p>That&#8217;s possible, but I won&#8217;t because shader support for structured buffers isn&#8217;t good enough yet. Either they&#8217;re not supported at all, are only in fragment programs, or perform worse than regular arrays. The good news is that the specifics of how data is passed between CPU and GPU only matter in a few places, so it&#8217;s is easy to change. That&#8217;s another benefit of using the <code>Light<\/code> struct.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>Add an index and a <code class=\"csharp\">VisibleLight<\/code> parameter to <code class=\"csharp\">SetupDirectionalLight<\/code>. Have it set the color and direction elements with the supplied index. In this case the final color is provided via the <code class=\"csharp\">VisibleLight.finalColor<\/code> property. The forward vector can be found via the <code class=\"csharp\">VisibleLight.localToWorldMatrix<\/code> property. It&#8217;s the third column of the matrix and once again has to be negated.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid SetupDirectionalLight () {\n\t\t\n\t\t\n\t}<\/pre>\n<p>The final color already applied the light&#8217;s intensity, but by default Unity doesn&#8217;t convert it to linear space. We have to set <code class=\"csharp\">GraphicsSettings.lightsUseLinearIntensity<\/code> to <code class=\"csharp\">true<\/code>, which we can do once in the constructor of <code class=\"csharp\">CustomRenderPipeline<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tpublic CustomRenderPipeline (\n\t\tbool useDynamicBatching, bool useGPUInstancing, bool useSRPBatcher\n\t) {\n\t\tthis.useDynamicBatching = useDynamicBatching;\n\t\tthis.useGPUInstancing = useGPUInstancing;\n\t\tGraphicsSettings.useScriptableRenderPipelineBatching = useSRPBatcher;\n\t\t\n\t}<\/pre>\n<p>Next, loop through all visible lights in <code class=\"csharp\">Lighting.SetupLights<\/code> and invoke <code class=\"csharp\">SetupDirectionalLight<\/code> for each element. Then invoke <code class=\"csharp\">SetGlobalInt<\/code> and <code class=\"csharp\">SetGlobalVectorArray<\/code> on the buffer to send the data to the GPU.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\tNativeArray&lt;VisibleLight&gt; visibleLights = cullingResults.visibleLights;\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\n\t\t\n\t\t\n\t\t<\/pre>\n<p>But we only support up to four directional lights, so we should abort the loop when we reach that maximum. Let&#8217;s keep track of the directional light index separate from the loop&#8217;s iterator.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\t\n\t\tfor (int i = 0; i &lt; visibleLights.Length; i++) {\n\t\t\tVisibleLight visibleLight = visibleLights[i];\n\t\t\tSetupDirectionalLight(, visibleLight);\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t}\n\n\t\tbuffer.SetGlobalInt(dirLightCountId, dirLightCount);<\/pre>\n<p>Because we only support directional lights we should ignore other light types. We can do this by checking whether the <code class=\"csharp\">lightType<\/code> property of the visible lights is equal to <code class=\"csharp\">LightType.Directional<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\t\tVisibleLight visibleLight = visibleLights[i];\n\t\t\t\n\t\t\t\tSetupDirectionalLight(dirLightCount++, visibleLight);\n\t\t\t\tif (dirLightCount &gt;= maxDirLightCount) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t<\/pre>\n<p>This works, but the <code class=\"csharp\">VisibleLight<\/code> struct is rather big. Ideally we only retrieve it once from the native array and don&#8217;t also pass it as a regular argument to <code class=\"csharp\">SetupDirectionalLight<\/code>, because that copies it. We can use the same trick that Unity uses for the <code class=\"csharp\">ScriptableRenderContext.DrawRenderers<\/code> method, which is passing the argument by reference.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\t\t\tSetupDirectionalLight(dirLightCount++,  visibleLight);<\/pre>\n<p>That requires us to also define the parameter as a reference.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid SetupDirectionalLight (int index,  VisibleLight visibleLight) { \u2026 }<\/pre>\n<\/section>\n<section>\n<h3>Shader Loop<\/h3>\n<p>Adjust the <code>_CustomLight<\/code> buffer in <em translate=\"no\">Light<\/em> so it matches our new data format. In this case we&#8217;ll explicitly use <code>float4<\/code> for the array types. Arrays have a fixed size in shaders, they cannot be resized. Make sure to use the same maximum that we defined in <code class=\"csharp\">Lighting<\/code>.<\/p>\n<pre translate=\"no\">\n\nCBUFFER_START(_CustomLight)\n\t<del>\/\/float4 _DirectionalLightColor;<\/del>\n\t<del>\/\/float4 _DirectionalLightDirection;<\/del>\n\t\n\t\n\t\nCBUFFER_END<\/pre>\n<p>Add a function to get the directional light count and adjust <code>GetDirectionalLight<\/code> so it retrieves the data for a specific light index.<\/p>\n<pre translate=\"no\">\n\t\n\n\nLight GetDirectionalLight () {\n\tLight light;\n\tlight.color = ;\n\tlight.direction = ;\n\treturn light;\n}<\/pre>\n<aside>\n<h3>Is there a difference between <code>rgb<\/code> and <code>xyz<\/code>?<\/h3>\n<div>\n<p>They&#8217;re semantic aliases. Swizzling using <code>rgba<\/code> and <code>xyzw<\/code> is equivalent.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>Then adjust <code>GetLight<\/code> for a surface so it uses a <code>for<\/code> loop to accumulate the contribution of all directional lights.<\/p>\n<pre translate=\"no\">float3 GetLighting (Surface surface) {\n\t\n\t\n\t\t\n\t\n\treturn ;\n}<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/lights\/four-directional-lights.png\" width=\"180\" height=\"180\"\/><figcaption>Four directional lights.<\/figcaption><\/figure>\n<p>Now our shader supports up to four directional lights. Usually only a single directional light is needed to represent the Sun or Moon, but maybe there&#8217;s a scene on a planet with multiple suns. Directional lights could also be used to approximate multiple large light rigs, for example those of a big stadium.<\/p>\n<p>If your game always has a single directional light then you could get rid of the loop, or make multiple shader variants. But for this tutorial we&#8217;ll keep it simple and stick to a single general-purpose loop. The best performance is always achieved by ripping out everything that you do not need, although it doesn&#8217;t always make a significant difference.<\/p>\n<\/section>\n<section>\n<h3>Shader Target Level<\/h3>\n<p>Loops with a variable length used to be a problem for shaders, but modern GPUs can deal with them without issues, especially when all fragments of a draw call iterate over the same data in the same way. However, the OpenGL ES 2.0 and WebGL 1.0 graphics APIs can&#8217;t deal with such loops by default. We could make it work by incorporating a hard-coded maximum, for example by having <code>GetDirectionalLight<\/code> return <code>min(_DirectionalLightCount, MAX_DIRECTIONAL_LIGHT_COUNT)<\/code>. This makes it possible to unroll the loop, turning it into a sequence of conditional code blocks. Unfortunately the resulting shader code is a mess and performance goes down fast. On very old-fashioned hardware all code blocks will always get executed, their contribution controlled via conditional assignments. While we could make this work it makes the code more complex, because we&#8217;d have to make other adjustments as well. So I opt to ignore these limitations and turn off WebGL 1.0 and OpenGL ES 2.0 support in builds for the sake of simplicity. They don&#8217;t support linear lighting anyway. We can also avoid compiling OpenGL ES 2.0 shader variants for them by raising the target level of our shader pass to 3.5, via the <code>#pragma target 3.5<\/code> directive. Let&#8217;s be consistent and do this for both shaders.<\/p>\n<pre translate=\"no\">\t\t\tHLSLPROGRAM\n\t\t\t\n\t\t\t\u2026\n\t\t\tENDHLSL<\/pre>\n<\/section>\n<\/div>\n<div id=\"\">\n<h2>BRDF<\/h2>\n<p>We&#8217;re currently using a very simplistic lighting model, appropriate for perfectly diffuse surfaces only. We can achieve more varied and realistic lighting by applying a bidirectional reflectance distribution function, BRDF for short. There are many such functions. We&#8217;ll use the same one that&#8217;s used by the Universal RP, which trades some realism for performance.<\/p>\n<section>\n<h3>Incoming Light<\/h3>\n<p>When a beam of light hits a surface fragment head-on then all its energy will affect the fragment. For simplicity we&#8217;ll assume that the beam&#8217;s width matches the fragment&#8217;s. This is the case where the light direction `L` and surface normal `N` align, so `N*L=1`. When they don&#8217;t align at least part of the beam misses the surface fragment, so less energy affects the fragment. The energy portion that affects the fragment is `N*L`. A negative results means that the surface is oriented away from the light, so it cannot be affected by it.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/incoming-light.png\" width=\"270\" height=\"165\"\/><figcaption>Incoming light portion.<\/figcaption><\/figure>\n<\/section>\n<section>\n<h3>Outgoing Light<\/h3>\n<p>We don&#8217;t see the light that arrives at a surface directly. We only see the portion that bounces off the surface and arrives at the camera or our eyes. If the surface were a perfectly flat mirror then the light would reflect off it, with an outgoing angle equal to the incoming angle. We would only see this light if the camera were aligned with it. This is known as specular reflection. It&#8217;s a simplification of light interaction, but it&#8217;s good enough for our purposes.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/specular-reflection.png\" width=\"240\" height=\"150\"\/><figcaption>Perfect specular reflection.<\/figcaption><\/figure>\n<p>But if the surface isn&#8217;t perfectly flat then the light gets scattered, because the fragment effectively consists of many smaller fragments that have different orientations. This splits the beam of light into smaller beams that go in different directions, which effectively blurs the specular reflection. We could end up seeing some of the scattered light even when not aligned with the perfect reflection direction.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/scattered-reflection.png\" width=\"240\" height=\"150\"\/><figcaption>Scattered specular reflection.<\/figcaption><\/figure>\n<p>Besides that, the light also penetrates the surface, bounces around, and exits at different angles, plus other things that we don&#8217;t need to consider. Taken to the extreme, we end up with a perfectly diffuse surface that scatters light evenly in all possible directions. This is the lighting that we&#8217;re currently calculating in our shader.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/diffuse-reflection.png\" width=\"240\" height=\"150\"\/><figcaption>Perfect diffuse reflection.<\/figcaption><\/figure>\n<p>No matter where the camera is, the amount of diffused light received from the surface is the same. But this means that the light energy that we observe is far less than the amount that arrived at the surface fragment. This suggests that we should scale the incoming light by some factor. However, because the factor is always the same we can bake it into the light&#8217;s color and intensity. Thus the final light color that we use represents the amount observed when reflected from a perfectly white diffuse surface fragment illuminated head-on. This is a tiny fraction of the total amount of light that is actually emitted. There are other ways to configure lights, for example by specifying lumen or lux, which make it easier to configure realistic light sources, but we&#8217;ll stick with the current approach.<\/p>\n<\/section>\n<section>\n<h3>Surface Properties<\/h3>\n<p>Surfaces can be perfectly diffuse, perfect mirrors, or anything in between. There are multiple ways in which we could control this. We&#8217;ll use the metallic workflow, which requires us to add two surface properties to the <em translate=\"no\">Lit<\/em> shader.<\/p>\n<p>The first property is whether a surface is metallic or nonmetalic, also known as a dielectric. Because a surface can contain a mix of both we&#8217;ll add a range 0\u20131 slider for it, with 1 indicating that it is fully metallic. The default is fully dielectric.<\/p>\n<p>The second property controls how smooth the surface is. We&#8217;ll also use a range 0\u20131 slider for this, with 0 being perfectly rough and 1 being perfectly smooth. We&#8217;ll use 0.5 as the default.<\/p>\n<pre translate=\"no\">\t\t\n\t\t<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/metallic-smoothness.png\" width=\"320\" height=\"92\"\/><figcaption>Material with metallic and smoothness sliders.<\/figcaption><\/figure>\n<p>Add the properties to the <code>UnityPerMaterial<\/code> buffer.<\/p>\n<pre translate=\"no\">UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)\n\tUNITY_DEFINE_INSTANCED_PROP(float4, _BaseMap_ST)\n\tUNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)\n\tUNITY_DEFINE_INSTANCED_PROP(float, _Cutoff)\n\t\n\t\nUNITY_INSTANCING_BUFFER_END(UnityPerMaterial)<\/pre>\n<p>And also to the <code>Surface<\/code> struct.<\/p>\n<pre translate=\"no\">struct Surface {\n\tfloat3 normal;\n\tfloat3 color;\n\tfloat alpha;\n\t\n\t\n};<\/pre>\n<p>Copy them to the surface in <code>LitPassFragment<\/code>.<\/p>\n<pre translate=\"no\">\tSurface surface;\n\tsurface.normal = normalize(input.normalWS);\n\tsurface.color = base.rgb;\n\tsurface.alpha = base.a;\n\t\n\t\n\t\t<\/pre>\n<p>And also add support for them to <code class=\"csharp\">PerObjectMaterialProperties<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tstatic int\n\t\tbaseColorId = Shader.PropertyToID(\"_BaseColor\"),\n\t\tcutoffId = Shader.PropertyToID(\"_Cutoff\")\n\t\t\n\t\t\n\n\t\u2026\n\n\t[SerializeField, Range(0f, 1f)]\n\tfloat alphaCutoff = 0.5f;\n\n\t\u2026\n\n\tvoid OnValidate () {\n\t\t\u2026\n\t\t\n\t\t\n\t\tGetComponent&lt;Renderer&gt;().SetPropertyBlock(block);\n\t}\n}<\/pre>\n<\/section>\n<section>\n<h3>BRDF Properties<\/h3>\n<p>We&#8217;ll use the surface properties to calculate the BRDF equation. It tells us how much light we end up seeing reflected off a surface, which is a combination of diffuse reflection and specular reflection. We need to split the surface color in a diffuse and a specular part, and we&#8217;ll also need to know how rough the surface is. Let&#8217;s keep track of these three values in a <code>BRDF<\/code> struct, put in a separate <em translate=\"no\">BRDF<\/em> HLSL file.<\/p>\n<pre translate=\"no\">\n\n\n\n\t\n\t\n\t\n\n\n<\/pre>\n<p>Add a function to get the BRDF data for a given surface. Start with a perfect diffuse surface, so the diffuse part is equal to the surface color while specular is black and roughness is one.<\/p>\n<pre translate=\"no\">\n\t\n\t\n\t\n\t\n\t\n<\/pre>\n<p>Include <em translate=\"no\">BRDF<\/em> after <em translate=\"no\">Light<\/em> and before <em translate=\"no\">Lighting<\/em>.<\/p>\n<pre translate=\"no\">#include \"..\/ShaderLibrary\/Common.hlsl\"\n#include \"..\/ShaderLibrary\/Surface.hlsl\"\n#include \"..\/ShaderLibrary\/Light.hlsl\"\n\n#include \"..\/ShaderLibrary\/Lighting.hlsl\"<\/pre>\n<p>Add a <code>BRDF<\/code> parameter to both <code>GetLighting<\/code> functions, then multiply the incoming light with the diffuse portion instead of the entire surface color.<\/p>\n<pre translate=\"no\">float3 GetLighting (Surface surface,  Light light) {\n\treturn IncomingLight(surface, light) * ;\n}\n\nfloat3 GetLighting (Surface surface) {\n\tfloat3 color = 0.0;\n\tfor (int i = 0; i &lt; GetDirectionalLightCount(); i++) {\n\t\tcolor += GetLighting(surface,  GetDirectionalLight(i));\n\t}\n\treturn color;\n}<\/pre>\n<p>Finally, get the BRDF data in <code>LitPassFragment<\/code> and pass it to <code>GetLighting<\/code>.<\/p>\n<pre translate=\"no\">\t\n\tfloat3 color = GetLighting(surface);<\/pre>\n<\/section>\n<section>\n<h3>Reflectivity<\/h3>\n<p>How reflective a surface is varies, but in general metals reflect all light via specular reflection and have zero diffuse reflection. So we&#8217;ll declare reflectivity to be equal to the metallic surface property. Light that gets reflected doesn&#8217;t get diffused, so we should scale the diffuse color by one minus the reflectivity in <code>GetBRDF<\/code>.<\/p>\n<pre translate=\"no\">\t\n\n\tbrdf.diffuse = surface.color ;<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/reflectivity.png\" width=\"420\" height=\"100\"\/><figcaption>White spheres with metallic 0, 0.25, 0.5, 0.75, and 1.<\/figcaption><\/figure>\n<p>In reality some light also bounces off dielectric surfaces, which gives them their highlight. The reflectivity of nonmetals varies, but is about 0.04 on average. Let&#8217;s define that as the minimum reflectivity and add a <code>OneMinusReflectivity<\/code> function that adjusts the range from 0\u20131 to 0\u20130.96. This range adjustments matches the Universal RP&#8217;s approach.<\/p>\n<pre translate=\"no\">\n\n\n\t\n\t\n<\/pre>\n<p>Use that function in <code>GetBRDF<\/code> to enforce the minimum. The difference is hardly noticeable when only rendering the diffuse reflections, but will matter a lot when we add specular reflections. Without it nonmetals won&#8217;t get specular highlights.<\/p>\n<pre translate=\"no\">\tfloat oneMinusReflectivity = ;<\/pre>\n<\/section>\n<section>\n<h3>Specular Color<\/h3>\n<p>Light that gets reflected one way cannot also get reflected another way. This is known as energy conservation, which means that the amount of outgoing light cannot exceed the amount of incoming light. This suggests that the specular color should be equal to the surface color minus the diffuse color.<\/p>\n<pre translate=\"no\">\tbrdf.diffuse = surface.color * oneMinusReflectivity;\n\t<\/pre>\n<p>However, this ignores the fact that metals affect the color of specular reflections while nonmetals don&#8217;t. The specular color of dielectric surfaces should be white, which we can achieve by using the metallic property to interpolate between the minimum reflectivity and the surface color instead.<\/p>\n<pre translate=\"no\">\tbrdf.specular = ;<\/pre>\n<\/section>\n<section>\n<h3>Roughness<\/h3>\n<p>Roughness is the opposite of smoothness, so we can simply take one minus smoothness. The <em translate=\"no\">Core RP Library<\/em> has a function that does this, named <code>PerceptualSmoothnessToPerceptualRoughness<\/code>. We&#8217;ll use this function, to make clear that the smoothness and thus also the roughness are defined as perceptual. We can convert to the actual roughness value via the <code>PerceptualRoughnessToRoughness<\/code> function, which squares the perceptual value. This matches the Disney lighting model. It&#8217;s done this way because adjusting the perceptual version is more intuitive when editing materials.<\/p>\n<pre translate=\"no\">\t\n\t\t\n\t<\/pre>\n<p>These functions are defined in the <em translate=\"no\">CommonMaterial<\/em> HLSL file of the <em translate=\"no\">Core RP Libary<\/em>. Include it in our <em translate=\"no\">Common<\/em> file after including the core&#8217;s <em translate=\"no\">Common<\/em>.<\/p>\n<pre translate=\"no\">#include \"Packages\/com.unity.render-pipelines.core\/ShaderLibrary\/Common.hlsl\"\n\n#include \"UnityInput.hlsl\"<\/pre>\n<\/section>\n<section>\n<h3>View Direction<\/h3>\n<p>To determine how well the camera is aligned with the perfect reflection direction we&#8217;ll need to know the camera&#8217;s position. Unity makes this data available via <code>float3 _WorldSpaceCameraPos<\/code>, so add it to <em translate=\"no\">UnityInput<\/em>.<\/p>\n<pre translate=\"no\"><\/pre>\n<p>To get the view direction\u2014the direction from surface to camera\u2014in <code>LitPassFragment<\/code> we need to add the world-space surface position to <code>Varyings<\/code>.<\/p>\n<pre translate=\"no\">struct Varyings {\n\tfloat4 positionCS : SV_POSITION;\n\t\n\t\u2026\n};\n\nVaryings LitPassVertex (Attributes input) {\n\t\u2026\n\tpositionWS = TransformObjectToWorld(input.positionOS);\n\toutput.positionCS = TransformWorldToHClip(positionWS);\n\t\u2026\n}<\/pre>\n<p>We&#8217;ll consider the view direction to be part of the surface data, so add it to <code>Surface<\/code>.<\/p>\n<pre translate=\"no\">struct Surface {\n\tfloat3 normal;\n\t\n\tfloat3 color;\n\tfloat alpha;\n\tfloat metallic;\n\tfloat smoothness;\n};<\/pre>\n<p>Assign it in <code>LitPassFragment<\/code>. It&#8217;s equal to the camera position minus the fragment position, normalized.<\/p>\n<pre translate=\"no\">\tsurface.normal = normalize(input.normalWS);\n\t<\/pre>\n<\/section>\n<section>\n<h3>Specular Strength<\/h3>\n<p>The strength of the specular reflection that we observe depends on how well our view direction matches the perfect reflection direction. We&#8217;ll use the same formula that&#8217;s used in the Universal RP, which is a variant of the Minimalist CookTorrance BRDF. The formula contains a few squares, so let&#8217;s add a convenient <code>Square<\/code> function to <em translate=\"no\">Common<\/em> first.<\/p>\n<pre translate=\"no\">\n\t\n<\/pre>\n<p>Then add a <code>SpecularStrength<\/code> function to <em translate=\"no\">BRDF<\/em> with a surface, BRDF data, and light as parameters. It should calculate `r^2\/(d^2 max(0.1, (L*H)^2)n)`, where `r` is the roughness and all dot products should be saturated. Furthermore, `d=(N*H)^2(r^2-1)+1.0001`, `N` is the surface normal, `L` is the light direction, and `H=L+V` normalized, which is the halfway vector between the light and view directions. Use the <code>SafeNormalize<\/code> function to normalize that vector, to avoid a division by zero in case the vectors are opposed. Finally, `n=4r-2` and is a normalization term.<\/p>\n<pre translate=\"no\">\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n<\/pre>\n<aside>\n<h3>How does that function work?<\/h3>\n<div>\n<p>BRDF theory is too complex to fully explain in short and isn&#8217;t the focus of this tutorial. You can check the <em translate=\"no\">Lighting<\/em> HLSL file of the Universal RP for some code documentation and references.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>Next, add a <code>DirectBRDF<\/code> that returns the color obtained via direct lighting, given a surface, BRDF, and light. The result is the specular color modulated by the specular strength, plus the diffuse color.<\/p>\n<pre translate=\"no\">\n\t\n<\/pre>\n<p><code>GetLighting<\/code> then has to multiply the incoming light by the result of that function.<\/p>\n<pre translate=\"no\">float3 GetLighting (Surface surface, BRDF brdf, Light light) {\n\treturn IncomingLight(surface, light) * ;\n}<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/smoothness.png\" width=\"320\" height=\"320\"\/><figcaption>Smoothness top to bottom 0, 0.25, 0.5, 0.75, and 0.95.<\/figcaption><\/figure>\n<p>We now get specular reflections, which add highlights to our surfaces. For perfectly rough surfaces the highlight mimics diffuse reflection. Smoother surfaces get a more focused highlight. A perfectly smooth surface gets an infinitesimal highlight, which we cannot see. Some scattering is needed to make it visible.<\/p>\n<p>Due to energy conservation highlights can get very bright for smooth surfaces, because most of the light arriving at the surface fragment gets focused. Thus we end up seeing far more light than would be possible due to diffuse reflection where the highlight is visible. You can verify this by scaling down the final rendered color a lot.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/colors-001.png\" width=\"320\" height=\"320\"\/><figcaption>Final color divided by 100.<\/figcaption><\/figure>\n<p>You can also verify that metals affect the color of specular reflections while nonmetals don&#8217;t, by using a base color other than white.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/blue.png\" width=\"320\" height=\"320\"\/><figcaption>Blue base color.<\/figcaption><\/figure>\n<p>We now have functional direct lighting that is believable, although currently the results are too dark\u2014especially for metals\u2014because we don&#8217;t support environmental reflections yet. A uniform black environment would be more realistic than the default skybox at this point, but that makes our objects harder to see. Adding more lights works as well.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/four-lights.png\" width=\"180\" height=\"180\"\/><figcaption>Four lights.<\/figcaption><\/figure>\n<\/section>\n<section>\n<h3>Mesh Ball<\/h3>\n<p>Let&#8217;s also add support for varying metallic and smoothness properties to <code class=\"csharp\">MeshBall<\/code>. This requires adding two float arrays.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tstatic int\n\t\tbaseColorId = Shader.PropertyToID(\"_BaseColor\")\n\t\t\n\t\t;\n\n\t\u2026\n\t\n\t\t\n\t\t\n\n\t\u2026\n\n\tvoid Update () {\n\t\tif (block == null) {\n\t\t\tblock = new MaterialPropertyBlock();\n\t\t\tblock.SetVectorArray(baseColorId, baseColors);\n\t\t\t\n\t\t\t\n\t\t}\n\t\tGraphics.DrawMeshInstanced(mesh, 0, material, matrices, 1023, block);\n\t}<\/pre>\n<p>Let&#8217;s make 25% of the instances metallic and vary smoothness from 0.05 to 0.95 in <code class=\"csharp\">Awake<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\t\tbaseColors[i] =\n\t\t\t\tnew Vector4(\n\t\t\t\t\tRandom.value, Random.value, Random.value,\n\t\t\t\t\tRandom.Range(0.5f, 1f)\n\t\t\t\t);\n\t\t\t\n\t\t\t<\/pre>\n<p>Then make the mesh ball use a lit material.<\/p>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/brdf\/mesh-ball.png\" width=\"320\" height=\"320\"\/><figcaption>Lit mesh ball.<\/figcaption><\/figure>\n<\/section>\n<\/div>\n<div id=\"\">\n<h2>Shader GUI<\/h2>\n<p>We now support multiple rendering modes, each requiring specific settings. To make it easier to switch between modes let&#8217;s add some buttons to our material inspector to apply preset configurations.<\/p>\n<section>\n<h3>Custom Shader GUI<\/h3>\n<p>Add a <code>CustomEditor \"CustomShaderGUI\"<\/code> statement to the main block of the <em translate=\"no\">Lit<\/em> shader.<\/p>\n<pre translate=\"no\">Shader \"Custom RP\/Lit\" {\n\t\u2026\n\n\t\n}<\/pre>\n<p>That instructs the Unity editor to use an instance of the <code class=\"csharp\">CustomShaderGUI<\/code> class to draw the inspector for materials that use the <em translate=\"no\">Lit<\/em> shader. Create a script asset for that class and put it in a new <em translate=\"no\">Custom RP \/ Editor<\/em> folder.<\/p>\n<p>We&#8217;ll need to use the <code class=\"csharp\">UnityEditor<\/code>, <code class=\"csharp\">UnityEngine<\/code>, and <code class=\"csharp\">UnityEngine.Rendering<\/code> namespaces. The class has to extend <code class=\"csharp\">ShaderGUI<\/code> and override the public <code class=\"csharp\">OnGUI<\/code> method, which has a <code class=\"csharp\">MaterialEditor<\/code> and a <code class=\"csharp\">MaterialProperty<\/code> array parameter. Have it invoke the base method, so we end up with the default inspector.<\/p>\n<pre class=\"csharp\" translate=\"no\">\n\n\n\n\n\n\t\n\t\t\n\t\n\t\t\n\t\n<\/pre>\n<\/section>\n<section>\n<h3>Setting Properties and Keywords<\/h3>\n<p>To do our work we&#8217;ll need access to three things, which we&#8217;ll store in fields. First is the material editor, which is the underlying editor object responsible for showing and editing materials. Second is a reference to the materials being edited, which we can retrieve via the <code class=\"csharp\">targets<\/code> property of the editor. It&#8217;s defined as an <code class=\"csharp\">Object<\/code> array because <code class=\"csharp\">targets<\/code> is a property of the general-purpose <code class=\"csharp\">Editor<\/code> class. Third is the array of properties that can be edited.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\n\t\n\n\tpublic override void OnGUI (\n\t\tMaterialEditor materialEditor, MaterialProperty[] properties\n\t) {\n\t\tbase.OnGUI(materialEditor, properties);\n\t\t\n\t\t\n\t\t\n\t}<\/pre>\n<aside>\n<h3>Why are there multiple materials?<\/h3>\n<div>\n<p>Multiple materials that use the same shader can be edited at the same time, just like you can select and edit multiple game objects.<\/p>\n<\/p><\/div>\n<\/aside>\n<p>To set a property we first have to find it in the array, for which we can use the <code class=\"csharp\">ShaderGUI.FindPropery<\/code> method, passing it a name and property array. We can then adjust its value, by assigning to its <code class=\"csharp\">floatValue<\/code> property. Encapsulate this in a convenient <code class=\"csharp\">SetProperty<\/code> method with a name and a value parameter.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t<\/pre>\n<p>Settings a keyword is a bit more involved. We&#8217;ll create a <code class=\"csharp\">SetKeyword<\/code> method for this, with a name and a boolean parameter to indicate whether the keyword should be enabled or disabled. We have to invoke either <code class=\"csharp\">EnableKeyword<\/code> or <code class=\"csharp\">DisableKeyword<\/code> on all materials, passing them the keyword name.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t<\/pre>\n<p>Let&#8217;s also create a <code class=\"csharp\">SetProperty<\/code> variant that toggles a property\u2013keyword combination.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\t\n\t<\/pre>\n<p>Now we can define simple <code class=\"csharp\">Clipping<\/code>, <code class=\"csharp\">PremultiplyAlpha<\/code>, <code class=\"csharp\">SrcBlend<\/code>, <code class=\"csharp\">DstBlend<\/code>, and <code class=\"csharp\">ZWrite<\/code> setter properties.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\n\n\t\n\t\t\n\t\n\n\t\n\t\t\n\t\n\n\t\n\t\t\n\t\n\n\t\n\t\t\n\t<\/pre>\n<p>Finally, the render queue is set by assigning to the <code class=\"csharp\">RenderQueue\u20ac<\/code> property of all materials. We can use the <code class=\"csharp\">RenderQueue<\/code> enum for this.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t<\/pre>\n<\/section>\n<section>\n<h3>Preset Buttons<\/h3>\n<p>A button can be created via the <code class=\"csharp\">GUILayout.Button<\/code> method, passing it a label, which will be a preset&#8217;s name. If the method returns <code class=\"csharp\">true<\/code> then it was pressed. Before applying the preset we should register an undo step with the editor, which can be done by invoking <code class=\"csharp\">RegisterPropertyChangeUndo<\/code> on it with the name. As this code is the same for all presets, put it in a <code class=\"csharp\">PresetButton<\/code> method that returns whether the preset should be applied.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t<\/pre>\n<p>We&#8217;ll create a separate method per preset, beginning with the default <em translate=\"no\">Opaque<\/em> mode. Have it set the properties appropriately when activated.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t<\/pre>\n<p>The second preset is <em translate=\"no\">Clip<\/em>, which is a copy of <em translate=\"no\">Opaque<\/em> with clipping turned on and the queue set to <em translate=\"no\">AlphaTest<\/em>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid  () {\n\t\tif (PresetButton(\"Clip\")) {\n\t\t\tClipping = ;\n\t\t\tPremultiplyAlpha = false;\n\t\t\tSrcBlend = BlendMode.One;\n\t\t\tDstBlend = BlendMode.Zero;\n\t\t\tZWrite = true;\n\t\t\tRenderQueue\u20ac = RenderQueue.;\n\t\t}\n\t}<\/pre>\n<p>The third preset is for standard transparency, which fades out objects so we&#8217;ll name it <em translate=\"no\">Fade<\/em>. It&#8217;s another copy of <em translate=\"no\">Opaque<\/em>, with adjusted blend modes and queue, plus no depth writing.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid  () {\n\t\tif (PresetButton(\"Fade\")) {\n\t\t\tClipping = false;\n\t\t\tPremultiplyAlpha = false;\n\t\t\tSrcBlend = BlendMode.;\n\t\t\tDstBlend = BlendMode.;\n\t\t\tZWrite = ;\n\t\t\tRenderQueue\u20ac = RenderQueue.;\n\t\t}\n\t}<\/pre>\n<p>The fourth preset is a variant of <em translate=\"no\">Fade<\/em> that applies premultiplied alpha blending. We&#8217;ll name it <em translate=\"no\">Transparent<\/em> as it&#8217;s for semitransparent surfaces with correct lighting.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid  () {\n\t\tif (PresetButton(\"Transparent\")) {\n\t\t\tClipping = false;\n\t\t\tPremultiplyAlpha = ;\n\t\t\tSrcBlend = BlendMode.;\n\t\t\tDstBlend = BlendMode.OneMinusSrcAlpha;\n\t\t\tZWrite = false;\n\t\t\tRenderQueue\u20ac = RenderQueue.Transparent;\n\t\t}\n\t}<\/pre>\n<p>Invoke the preset methods at the end of <code class=\"csharp\">OnGUI<\/code> so they show up below the default inspector.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tpublic override void OnGUI (\n\t\tMaterialEditor materialEditor, MaterialProperty[] properties\n\t) {\n\t\t\u2026\n\n\t\t\n\t\t\n\t\t\n\t\t\n\t}<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/shader-gui\/preset-buttons.png\" width=\"320\" height=\"110\"\/><figcaption>Preset buttons.<\/figcaption><\/figure>\n<p>The preset buttons won&#8217;t be used often, so let&#8217;s put them inside a foldout that is collapsed by default. This is done by invoking <code class=\"csharp\">EditorGUILayout.Foldout<\/code> with the current foldout state, label, and <code class=\"csharp\">true<\/code> to indicate that clicking it should toggle its state. It returns the new foldout state, which we should store in a field. Only draw the buttons when the foldout is open.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\n\t\u2026\n\n\tpublic override void OnGUI (\n\t\tMaterialEditor materialEditor, MaterialProperty[] properties\n\t) {\n\t\t\u2026\n\n\t\t\n\t\t\n\t\t\n\t\t\tOpaquePreset();\n\t\t\tClipPreset();\n\t\t\tFadePreset();\n\t\t\tTransparentPreset();\n\t\t\n\t}<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/shader-gui\/foldout.png\" width=\"320\" height=\"132\"\/><figcaption>Preset foldout.<\/figcaption><\/figure>\n<\/section>\n<section>\n<h3>Presets for Unlit<\/h3>\n<p>We can also use the custom shader GUI for our <em translate=\"no\">Unlit<\/em> shader.<\/p>\n<pre translate=\"no\">Shader \"Custom RP\/Unlit\" {\n\t\u2026\n\n\t\n}<\/pre>\n<p>However, activating a preset will result in an error, because we&#8217;re trying to set a property that the shader doesn&#8217;t have. We can guard against that by adjusting <code class=\"csharp\">SetProperty<\/code>. Have it invoke <code class=\"csharp\">FindProperty<\/code> with <code class=\"csharp\">false<\/code> as an additional argument, indicating that it shouldn&#8217;t log an error if the property isn&#8217;t found. The result will then be <code class=\"csharp\">null<\/code>, so only set the value if that&#8217;s not the case. Also return whether the property exists.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t SetProperty (string name, float value) {\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t}<\/pre>\n<p>Then adjust the keyword version of <code class=\"csharp\">SetProperty<\/code> so it only sets the keyword if the relevant property exists.<\/p>\n<pre class=\"csharp\" translate=\"no\">\tvoid SetProperty (string name, string keyword, bool value) {\n\t\tSetProperty(name, value ? 1f : 0f)\n\t\t\t\n\t\t\n\t}<\/pre>\n<\/section>\n<section>\n<h3>No Transparency<\/h3>\n<p>Now the presets also work for materials that use the <em translate=\"no\">Unlit<\/em> shader, although the <em translate=\"no\">Transparent<\/em> mode doesn&#8217;t make much sense in this case, as the relevant property doesn&#8217;t exist. Let&#8217;s hide this preset when it&#8217;s not relevant.<\/p>\n<p>First, add a <code class=\"csharp\">HasProperty<\/code> method that returns whether a property exists.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\n\t\t<\/pre>\n<p>Second, create a convenient property to check whether <em translate=\"no\">_PremultiplyAlpha<\/em> exists.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t<\/pre>\n<p>Finally, make everything of the <em translate=\"no\">Transparent<\/em> preset conditional on that property, by checking it first in <code class=\"csharp\">TransparentPreset<\/code>.<\/p>\n<pre class=\"csharp\" translate=\"no\">\t\tif ( PresetButton(\"Transparent\")) { \u2026 }<\/pre>\n<figure><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/catlikecoding.com\/shader-gui\/no-transparent-preset.png\" width=\"320\" height=\"94\"\/><figcaption>Unlit materials lack <em translate=\"no\">Transparent<\/em> preset.<\/figcaption><\/figure>\n<\/section>\n<section>\n<p>At this point we have functional lighting, but only for the direct illumination of directional lights. There is no environmental lighting, there are no shadows, and no other light types are supported. We&#8217;ll add all that in the future. Want to know when the next tutorial gets released? Keep tabs on my <a href=\"https:\/\/www.patreon.com\/catlikecoding\" target=\"_blank\" rel=\"noopener\">Patreon<\/a> page!<\/p>\n<\/section>\n<p><a href=\"https:\/\/catlikecoding.com\/unity\/tutorials\/license\/\" class=\"license\" target=\"_blank\" rel=\"noopener\">license<\/a><br \/>\n\t\t\t\t\t<a href=\"https:\/\/bitbucket.org\/catlikecodingunitytutorials\/custom-srp-03-directional-lights\/\" class=\"repository\" target=\"_blank\" rel=\"noopener\">repository<\/a><br \/>\n\t\t\t\t\t<a href=\"https:\/\/catlikecoding.com\/Directional-Lights.pdf\" download=\"\" rel=\"nofollow noopener\" target=\"_blank\">PDF<\/a>\n\t\t\t\t<\/div>\n<p>[ad_2]<br \/>\n<br \/><a href=\"https:\/\/catlikecoding.com\/unity\/tutorials\/custom-srp\/directional-lights\/\" target=\"_blank\" rel=\"noopener\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>[ad_1] This is the third part of a tutorial series about creating a custom scriptable render pipeline. It adds support for shading with multiple directional lights. This tutorial is made with Unity 2019.2.12f1. Lighting If we want to create a more realistic scene then we&#8217;ll have to simulate how light interacts with surfaces. This requires [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":575,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-574","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"blocksy_meta":[],"jetpack_featured_media_url":"https:\/\/e928cfdc7rs.exactdn.com\/info\/uploads\/sites\/3\/2019\/12\/Directional-Lights.jpg?strip=all","jetpack_shortlink":"https:\/\/wp.me\/p2TFCd-9g","jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/posts\/574","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/comments?post=574"}],"version-history":[{"count":0,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/posts\/574\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/media\/575"}],"wp:attachment":[{"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/media?parent=574"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/categories?post=574"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/tags?post=574"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}