{"id":483,"date":"2019-11-29T08:42:35","date_gmt":"2019-11-29T08:42:35","guid":{"rendered":"https:\/\/www.danielparente.net\/en\/2019\/11\/29\/simple-subsurface-scattering-harry-alisavakis\/"},"modified":"2019-11-29T08:42:35","modified_gmt":"2019-11-29T08:42:35","slug":"simple-subsurface-scattering-harry-alisavakis","status":"publish","type":"post","link":"https:\/\/www.danielparente.net\/en\/2019\/11\/29\/simple-subsurface-scattering-harry-alisavakis\/","title":{"rendered":"Simple Subsurface Scattering \u2013 Harry Alisavakis"},"content":{"rendered":"<p> [ad_1]<br \/>\n<\/p>\n<div>\n<h2>Patrons<\/h2>\n<p>This tutorial post is brought to you by:<\/p>\n<p>erich binder <\/p>\n<p>Djinnlord <\/p>\n<h2>Introduction<\/h2>\n<p>For quite a long time I wanted to do something like SSS for some neat effects, and I had this shader ready for some time too. However, it\u2019s needless to say that I feel weird covering this subject in a tutorial. Mostly because, as with a bunch of my approaches, this one is fairly straightforward and not too physically correct. I\u2019ve shown other effects that are also not physically correct, but this one feels weirder cause it\u2019s a very real effect in nature, so going at it with such a simple approach feels like trying to simulate gravity by moving objects downwards with a constant speed of 9 m\/s.<\/p>\n<p>Nevertheless, while one can argue that all objects incorporate some form of SSS, this one effect can be useful in more specific use cases and for certain specific effects. I personally enjoy using it by having lights <strong>inside<\/strong> some objects, like lamps or paper lanterns.<\/p>\n<h2>Important note<\/h2>\n<p>This shader uses a custom lighting model with all the bells and whistles. It\u2019s quite important to have a sense of how all that stuff works. Luckily, I have a post explaining some stuff around custom lighting models in surface shaders. You can give it a read here:<\/p>\n<p><a rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\" href=\"https:\/\/halisavakis.com\/my-take-on-shaders-whats-the-deal-with-surface-shaders\/\" target=\"_blank\">https:\/\/halisavakis.com\/my-take-on-shaders-whats-the-deal-with-surface-shaders\/<\/a><\/p>\n<p>I won\u2019t go through the surface shader details I explain there. So if you have any questions about how all the lighting stuff works, I\u2019d highly suggest going through that post first. If it doesn\u2019t cover your questions, don\u2019t hesitate to reach out!<\/p>\n<h2>Le code<\/h2>\n<p>Let\u2019s take a look at the code then:<\/p>\n<pre class=\"wp-block-code\"><code><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nShader \"Custom\/SSSShader\" {\n\tProperties {\n\t\t_Color (\"Color\", Color) = (1,1,1,1)\n\t\t_MainTex (\"Albedo (RGB)\", 2D) = \"white\" {}\n\t\t[Normal]_Normal(\"Normal\", 2D) = \"bump\" {}\n\t\t_Glossiness (\"Smoothness\", Range(0,1)) = 0.5\n\t\t_Metallic (\"Metallic\", Range(0,1)) = 0.0\n\n\t\t_ThicknessMap(\"Thickness map\", 2D) = \"black\" {}\n\t\t_Distortion(\"Normal Distortion\", float) = 0\n\t\t_SSSConcentration(\"SSS Area Concentration\", float) = 0\n\t\t_SSSScale(\"SSS Scale\", float) = 0\n\t\t[HDR]_SSSColor(\"SSS color\", Color) = (1,1,1,1)\n\t}\n\tSubShader {\n\t\tTags { \"RenderType\"=\"Opaque\" }\n\t\tLOD 200\n\n\t\tCGPROGRAM\n\t\t#pragma surface surf SSS fullforwardshadows\n\t\t#include \"UnityPBSLighting.cginc\"\n\t\t\/\/ Use shader model 3.0 target, to get nicer looking lighting\n\t\t#pragma target 3.0\n\n\t\tsampler2D _MainTex;\n\t\tsampler2D _Normal;\n\n\t\tstruct Input {\n\t\t\tfloat2 uv_MainTex;\n\t\t\tfloat2 uv_Normal;\n\t\t};\n\n\t\tstruct SurfaceOutputSSS\n\t\t{\n\t\t\tfixed3 Albedo;\n\t\t\tfixed3 Normal;\n\t\t\thalf3 Emission;\n\t\t\thalf Metallic;\n\t\t\thalf Smoothness;\n\t\t\thalf Occlusion;\n\t\t\tfixed Alpha;\n\t\t\tfloat Thickness;\n\t\t};\n\n\t\thalf _Glossiness;\n\t\thalf _Metallic;\n\t\tfixed4 _Color;\n\n\t\tsampler2D _ThicknessMap;\n\t\tfloat _Distortion;\n\t\tfloat _SSSConcentration;\n\t\tfloat _SSSScale;\n\t\tfixed4 _SSSColor;\n\n\t\thalf4 LightingSSS(SurfaceOutputSSS s, half3 viewDir, UnityGI gi){\n\t\t\tfloat3 lightColor = gi.light.color;\n\t\t\tfloat3 lightDir = gi.light.dir + s.Normal * _Distortion;\n\t\t\thalf sssAmount = pow(saturate(dot(normalize(viewDir), -normalize(lightDir))), _SSSConcentration) * _SSSScale * (1.0 - s.Thickness);\n\t\t\tfixed4 sssColor = sssAmount * _SSSColor * fixed4(lightColor, 1);\n\t\t\n\t\t\tSurfaceOutputStandard r;\n\t\t\tr.Albedo = s.Albedo;\n\t\t\tr.Normal = s.Normal;\n\t\t\tr.Emission = s.Emission;\n\t\t\tr.Metallic = s.Metallic;\n\t\t\tr.Smoothness = s.Smoothness;\n\t\t\tr.Occlusion = s.Occlusion;\n\t\t\tr.Alpha = s.Alpha;\n\t\t\treturn LightingStandard(r, viewDir, gi) + sssColor;\n\t\t}\n\n\t\tinline void LightingSSS_GI(SurfaceOutputSSS s, UnityGIInput data, inout UnityGI gi )\n\t\t{\n\t\t\tUNITY_GI(gi, s, data);\n\t\t}\n\n\t\t\/\/ Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.\n\t\t\/\/ See https:\/\/docs.unity3d.com\/Manual\/GPUInstancing.html for more information about instancing.\n\t\t\/\/ #pragma instancing_options assumeuniformscaling\n\t\tUNITY_INSTANCING_BUFFER_START(Props)\n\t\t\t\/\/ put more per-instance properties here\n\t\tUNITY_INSTANCING_BUFFER_END(Props)\n\n\n\t\tvoid surf (Input IN, inout SurfaceOutputSSS o) {\n\t\t\t\/\/ Albedo comes from a texture tinted by color\n\t\t\tfixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;\n\t\t\to.Thickness = tex2D(_ThicknessMap, IN.uv_MainTex);\n\t\t\to.Albedo = c.rgb;\n\t\t\to.Normal = UnpackNormal(tex2D(_Normal, IN.uv_Normal));\n\t\t\t\/\/ Metallic and smoothness come from slider variables\n\t\t\to.Metallic = _Metallic;\n\t\t\to.Smoothness = _Glossiness;\n\t\t\to.Alpha = c.a;\n\t\t}\n\t\tENDCG\n\t}\n\tFallBack \"Diffuse\"\n}\n<\/pre>\n<p><\/code><\/p>\n<h3>Properties<\/h3>\n<p>The shader is basically the default surface shader with standard lighting, only adding the SSS to the final color. Therefore, it has the properties of the standard surface shader, along with some extras:<\/p>\n<ul>\n<li>\u201c_Normal\u201d for the normal map. Notice the \u201c[Normal]\u201d tag here, used to add the \u201cFix now\u201d button if the texture is not imported as a normal map.<\/li>\n<li>\u201d _ThicknessMap\u201d: A texture that defines the thickness of the object, to add variety to the SSS effect. The darker the texture is, the less thick the object is at that point, therefore the SSS is more visible. The texture is initialized to \u201cblack\u201d instead of \u201cwhite\u201d, so if there\u2019s no thickness map provided, the SSS will be visible uniformly.<\/li>\n<li>\u201c_Distortion\u201d: This is not the classic distortion\/displacement we\u2019re used to. It\u2019s a property that adjusts the distortion of the SSS based on the object\u2019s normals. Without it (or with it set to 0), the additional lighting from SSS will completely ignore the object\u2019s normals.<\/li>\n<li>\u201c_SSSConcentration\u201d: A property that adjusts how spread out the SSS contribution will be.<\/li>\n<li>\u201c_SSSScale\u201d: Determines the contribution of the SSS.<\/li>\n<li>\u201c_SSSColor\u201d: The HDR color that will be multiplied with the light color for the SSS effect.<\/li>\n<\/ul>\n<h3>The in-between stuff<\/h3>\n<p>In line 20 I declare that this is a surface shader using the \u201cSSS\u201d lighting model. As discussed in the <a rel=\"noreferrer noopener\" aria-label=\"post about surface shaders (opens in a new tab)\" href=\"https:\/\/halisavakis.com\/my-take-on-shaders-whats-the-deal-with-surface-shaders\/\" target=\"_blank\">post about surface shaders<\/a>, since the lighting model is something other than the built-in ones (e.g. Standard), we\u2019ll have to define the lighting model. While the lighting model is technically in the \u201cin-between\u201d stuff, I\u2019ll cover it last, since it\u2019s the most \u201ccomplex\u201d one.<\/p>\n<p>In line 21 I also include a cginc file called \u201cUnityPBSLighting.cginc\u201d. It\u2019s necessary to use some lighting functions later on.<\/p>\n<p>In lines 25-31 I redeclare the \u201c_MainTex\u201d, and \u201c_Normal\u201d properties while also defining the \u201cInput\u201d struct. For each of the two aforementioned samplers I add the corresponding UV coordinates, so that the tiling and offset from the material inspector works separately for the albedo and the normal texture respectively. I could add another one for the thickness map, but since the thickness map will probably be pretty specific to the object and similar to the albedo texture, I\u2019m using the UV coordinates of the _MainTex to sample it later.<\/p>\n<p>For our custom lighting model we\u2019ll need a custom structure that expands on the \u201cSurfaceOutputStandard\u201d struct. That\u2019s why, in lines 33-43 I declare the \u201cSurfaceOutputSSS\u201d struct. It contains all the field of the \u201cSurfaceOutputStandard\u201d struct while also adding a \u201cThickness\u201d field that we\u2019ll need pass to our lighting function.<\/p>\n<p>In lines 45-53 I redeclare the rest of the properties and afterwards I move on to defining the lighting model, which I\u2019ll explain in a bit.<\/p>\n<h3>The surf function \ud83c\udfc4\u200d\u2642\ufe0f<\/h3>\n<p>No, I\u2019ll never stop with the surfing puns.<\/p>\n<p>The \u201csurf\u201d function is at most indifferent. It\u2019s almost identical to the default one, with some minor additions:<\/p>\n<ul>\n<li>In line 88 I \u201ccalculate\u201d the object\u2019s thickness by sampling the thickness texture. I store the result to the \u201cThickness\u201d property of the \u201cSurfaceOutputSSS\u201d object \u201co\u201d, which is passed as an argument to the \u201csurf\u201d method. Do notice that in the arguments \u201co\u201d is \u201cSurfaceOutputSSS\u201d instead of \u201cSurfaceOutputStandard\u201d.<\/li>\n<li>In line 90 I sample the normal map for the object\u2019s normals.<\/li>\n<\/ul>\n<p>That\u2019s it, really.<\/p>\n<h3>The lighting model<\/h3>\n<p>In lines 55-70 I define the custom lighting model function called \u201cLightingSSS\u201d. It\u2019s important that it\u2019s named that way, since in the \u201c#pragma surface surf\u201d directive we mentioned that our lighting model will be called \u201cSSS\u201d. If we called it \u201cSubsurfaceScattering\u201d, the function should be called \u201cLightingSubsurfaceScattering\u201d.<\/p>\n<p>Since we use the color of the lights affecting our object, in the arguments of \u201cLightingSSS\u201d I include a field called \u201cUnityGI gi\u201d along with \u201cSurfaceOutputSSS s\u201d and \u201chalf3 viewDir\u201d. To access the global illumination struct, we need to declare a custom GI function called \u201cLightingSSS_GI\u201d, even if it does nothing besides its default function. As you  have probably guessed, this is what lines 72-75 are for. For now, don\u2019t worry about its arguments and what those structs contain.<\/p>\n<p>Moving into the lighting model function, in line 56 I get the light color to use it with the SSS. I could just use the \u201c_SSSColor\u201d property, but that way the SSS color would only depend on the material and not on the lights, and we wouldn\u2019t be able to have multiple light colors per object.<\/p>\n<p>In line 57 I get the light\u2019s direction\/position. If the light is a directional light, this stores the direction, otherwise it stores the position of the light. The result of \u201cgi.light.dir\u201d is then offset along the normals of the object, based on the \u201c_Distortion\u201d property.<\/p>\n<p>The amount of SSS is calculated in line 58. Let\u2019s break it down a bit:<\/p>\n<ul>\n<li>Firstly I calculate the dot product of the view direction and the inverted \u201cdirection\u201d of the light, as we want the light to contribute to SSS if it\u2019s <strong>behind<\/strong> the object. That\u2019s what \u201cdot(normalize(viewDir), -normalize(lightDir))\u201d  does.<\/li>\n<li>The result is then clamped from 0 to 1 with \u201csaturate\u201d.<\/li>\n<li>The clamped result is raised to a power of \u201c_SSSConcentration\u201d. The higher the value of the property, the more smaller the \u201ccircle\u201d of SSS will be.<\/li>\n<li>The result is multiplied by \u201c_SSSScale\u201d to adjust its intensity.<\/li>\n<li>The multiplied result is then multiplied by 1 minus the thickness of the object, as passed in the \u201cSurfaceOutputSSS\u201d struct. This value was not stored in a property of the shader as it differs from pixel to pixel based on the thickness texture. This is the only real reason we needed a custom surface output struct.<\/li>\n<\/ul>\n<p>After the amount of SSS is calculated, in line 59 I calculate the overall color that I\u2019ll add to the standard lighting. The result comes from multiplying the SSS amount with the \u201c_SSSColor\u201d property and the color of the light.<\/p>\n<p>Lines 61-68 are pretty straightforward. Since we want to calculate the lighting the object would have with the standard lighting model, we need to call the \u201cLightingStandard\u201d function, which takes a \u201cSurfaceOutputStandard\u201d struct as an argument. Therefore, I create an object of that struct in line 61 and copy all the values in it from my \u201cSurfaceOutputSSS\u201d struct.<\/p>\n<p>Finally, in line 69 I call \u201cLightingStandard\u201d to get all the sweet PBR lighting goodness, and add my SSS color to it before returning it.<\/p>\n<h2>Conclusion<\/h2>\n<p>That\u2019s it for this one! It might not be the most accurate or flexible SSS implementation out there, but I hope if gives you a good idea about things you can do with custom lighting models. <\/p>\n<p>Also, try making some lamps or paper lanterns with it and I hope you\u2019ll like it as much as I did.<\/p>\n<p>See you in the <a href=\"https:\/\/halisavakis.com\/my-take-on-shaders-water-shader\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"next one (opens in a new tab)\">next one<\/a>!<\/p>\n<h2>Footer<\/h2>\n<p>The code in the shader tutorials is under the CC0 license, so you can freely use it in your commercial or non-commercial projects without the need for any attribution\/credits.<\/p>\n<p>These posts will never go behind a paywall. But if you really really super enjoyed this post, consider buying me a coffee, or becoming my patron to get exciting benefits!\n <\/p>\n<p><a href=\"https:\/\/www.patreon.com\/bePatron?u=19923710\" data-patreon-widget-type=\"become-patron-button\" target=\"_blank\" rel=\"noopener\">Become a Patron!<\/a> <\/p>\n<p>Or don&#8217;t, I&#8217;m not the boss of you.\n<\/p><\/div>\n<p>[ad_2]<br \/>\n<br \/><a href=\"https:\/\/halisavakis.com\/my-take-on-shaders-simple-subsurface-scattering\/\" target=\"_blank\" rel=\"noopener\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>[ad_1] Patrons This tutorial post is brought to you by: erich binder Djinnlord Introduction For quite a long time I wanted to do something like SSS for some neat effects, and I had this shader ready for some time too. However, it\u2019s needless to say that I feel weird covering this subject in a tutorial. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"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-483","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"blocksy_meta":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p2TFCd-7N","jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/posts\/483","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=483"}],"version-history":[{"count":0,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/posts\/483\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/media?parent=483"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/categories?post=483"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.danielparente.net\/en\/wp-json\/wp\/v2\/tags?post=483"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}