{"id":169,"date":"2013-02-06T11:52:53","date_gmt":"2013-02-06T16:52:53","guid":{"rendered":"http:\/\/www.defectivestudios.com\/devblog\/?p=169"},"modified":"2015-07-27T15:32:00","modified_gmt":"2015-07-27T19:32:00","slug":"quad-patch-tessellation-in-unity","status":"publish","type":"post","link":"https:\/\/www.defectivestudios.com\/devblog\/quad-patch-tessellation-in-unity\/","title":{"rendered":"Quad Patch Tessellation in Unity"},"content":{"rendered":"<p>Howdy there, Defectfans!<\/p>\n<p><em>Edit: This shader does not work in Unity 5.  Naturally, a lot has changed in the Unity shader codebase, and this code simply doesn&#8217;t compile anymore.  It probably could be fixed easily, but it was a hack at the time, and I haven&#8217;t revisited it since its original posting.  One more update of note: at some point around Unity 4.6\/5.0, they re-introduced the &#8220;Keep Quads&#8221; import setting on mesh importers, which makes a large portion of what you read here obsolete!! I believe that you can get the best of both worlds in Unity 4.6, where the shader should compile, and you can also import meshes with quads natively, and you don&#8217;t need all of that OBJ importer nonsense.<\/em><\/p>\n<p>By popular demand, I&#8217;m posting the source and explanation of the tessellation shader on display in my <a title=\"Quad Patch Tessellation in Unity\" href=\"http:\/\/www.youtube.com\/watch?v=5WuWVmWXcos\">HIT YOUTUBE VIDEO!!!<\/a> OK 1,754 views hardly constitutes a hit in YouTube terms, but some people have commented and asked for source, so here is! Wait, first a little explanation of WTF is going on and why I went through all the work to hack together quad-based tessellation when Unity already has some perfectly good tessellation shaders with source available. A very helpful discovery (pointed out by a wonderful chap on the IRC channel) is that by adding #pragma debug to any Unity shader will produce shader soucre when you click to Open Compiled Shader. This was truly helpful in figuring this all out, and really on every bit of shader work I&#8217;ve done on shaders since then. As a disclaimer\/warning, everything that I&#8217;m talking about in this post requires Unity 4 and a DirectX 11 capable graphics card on Windows. I honestly haven&#8217;t tried any of this on OS X but I&#8217;m pretty sure that all of this stuff uses DirectX-specific shaders. The effect might be possible in OpenGL, but I don&#8217;t think it is possible in Unity using OpenGL. I&#8217;m sure Aras will have that taken-care-of soon enough though \ud83d\ude42<br \/>\n<!--more--><\/p>\n<p>First of all, the why was quad patch tessellation so much better than tri-patch? Perhaps a diagram can help (courtesy of a vital <a title=\"Tessellation Article\" href=\"http:\/\/fgiesen.wordpress.com\/2011\/09\/06\/a-trip-through-the-graphics-pipeline-2011-part-12\/\">source<\/a> which helped me work this out):<\/p>\n<div style=\"width: 237px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" loading=\"lazy\" alt=\"\" src=\"http:\/\/fgiesen.files.wordpress.com\/2011\/09\/quad_tess_simple1.png\" width=\"227\" height=\"227\" \/><p class=\"wp-caption-text\">What quad tessellation might look like<\/p><\/div>\n<div style=\"width: 237px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" loading=\"lazy\" alt=\"\" src=\"http:\/\/fgiesen.files.wordpress.com\/2011\/09\/tri_tess_simple1.png\" width=\"227\" height=\"197\" \/><p class=\"wp-caption-text\">What triangle tessellation might look like<\/p><\/div>\n<div style=\"width: 237px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" loading=\"lazy\" alt=\"\" src=\"http:\/\/fgiesen.files.wordpress.com\/2011\/09\/tri_tess3.png?w=497\" width=\"227\" height=\"197\" \/><p class=\"wp-caption-text\">What triangle tessellation ends up looking like<\/p><\/div>\n<p>So what&#8217;s going on there? First I should mention that the first image isn&#8217;t exactly what you might see with quad patch tessellation&#8211;or more precisely isn&#8217;t something you would see with my shader. That said, it wouldn&#8217;t be a difficult result to produce if it was worth producing. Specifically, what you would see is that square quad being divided evenly in X and Y, as opposed to unevenly (you may notice there are 4 divisions in X and 3 in Y). The difference between the second two image has to do with how tessellation levels &#8220;repeat.&#8221; If you had to tessellate the middle image further, what would you do? Split each triangle into 4 smaller triangles? That&#8217;s not very convenient if you maybe just want to tessellate &#8220;a bit,&#8221; which the third representation allows. If you&#8217;re confused how, just play around with the &#8220;Edge Length&#8221; parameter of Unity&#8217;s built-in tessellation shader to see what&#8217;s going on.<\/p>\n<p>It turns out that this method of tessellation works great on meshes that are &#8220;naturally&#8221; triangulated but it puts verts in odd places on meshes that are supposed to be quads. What do I mean by &#8220;supposed&#8221; to be quads? Think about what the third diagram would look like on a triangulated square. I&#8217;d get out a drawing program and draw this but I trust your imagination. If I get the time, I&#8217;ll come back later and draw it. Anyway, the case that I was working on was a mesh exported out of Mudbox with a displacement map (later a vector displacement map). Mudbox works with quads, meaning that when you move up\/down in levels of subdivision, it simply takes a rectangle and turns it into 4 similar (in a Geometry sense) rectangles. When you work with your model in Mudbox, you&#8217;re working with such a topology. Well, you&#8217;re technically working with a displacement map, but Mudbox is showing you a mesh that is so-subdivided.<\/p>\n<p>What happens when you apply the mudbox displacement map to triangle patch tessellation? Well, check out the difference:<\/p>\n<div style=\"width: 570px\" class=\"wp-caption aligncenter\"><img decoding=\"async\" loading=\"lazy\" alt=\"\" src=\"http:\/\/www.defectivestudios.com\/experiments\/tessellation.png\" width=\"560\" height=\"240\" \/><p class=\"wp-caption-text\">Yes, I know I mis-spelled &#8220;tessellation&#8221; on the right there. I don&#8217;t have the original anymore so it will forever be the case<\/p><\/div>\n<p>It&#8217;s impossible to say which is &#8220;better&#8221; except as a matter of preference. Please ignore the difference in lighting and the fact that one uses a normalmap and the other doesn&#8217;t. This was pretty early on in the process, and I hadn&#8217;t added the bump channel yet. What you&#8217;re supposed to see is how the example on the left has much more even subdivision, which ends up matching the original (not shown&#8230; d&#8217;oh!) much more closely. Pay particular attention to the &#8220;ridges&#8221; on his neck. In the triangular tessellation, they are kind of &#8220;jagged&#8221; due to where the &#8220;extra&#8221; vertices end up being placed. Granted, you might notice that there are a whole lot more triangles in the legs and feet on the left vs. the left. I ended up adding a distance parameter to later versions of the shader, which helped a lot, but it&#8217;s still not perfect.<\/p>\n<p>So, if you&#8217;re not yet convinced that quad patch tessellation is superior, well, that&#8217;s all I&#8217;ve got. I&#8217;m perfectly happy to admit that I might be doing something wrong, but my first instinct that was quads would be the answer, and I ran with that. So, now you can see why I was convinced to go down the insane road of circumventing Unity&#8217;s mesh import process (which some would argue is half of why you should use Unity in the first place). Here&#8217;s how:<\/p>\n<p>After deciding that I wasn&#8217;t missing something, and that you can&#8217;t just import a mesh with quads, I started to despair. After some back-and-forth on the forums, and learning some other stuff I can&#8217;t talk about, I&#8217;m pretty sure that I&#8217;m not just doing something wrong, and quad import really isn&#8217;t supported (though quad meshes themselves are). After some quick googling, to my rescue came a GitHub project for importing OBJ meshes at runtime into Unity. Thanks a bunch hammmm @ github! With a couple of quick modifications, which can be found in my own fork of that repo, I got real-live quad meshes in Unity. I&#8217;ll also point out that this was a very quick and easy solution that I turned to after trying to convert tri-based meshes back to quads after normal import. I tried a few tactics like just joining triangles with the &#8220;first available&#8221; adjacent triangles, and doing this while filtering for coplanarity, neither of which worked for a sufficiently complex mesh. Wasted a lot of time there \ud83d\ude1b<\/p>\n<p>If you&#8217;re looking for a better explanation of my procedure for getting quad-based meshes into Unity, let me know, and I&#8217;ll draft up another post about just that. Suffice it to say it was an essential step, but is immaterial to this post. Now on to the shader. I won&#8217;t claim any ownership or authorship of this code. It was shamelessly lifted from <a href=\"http:\/\/pastebin.com\/bfpEMHRA\">this pastebin<\/a>. It&#8217;s not line-for-line the same, but I pretty much let that example hold my hand. To my everlasting shame, I can&#8217;t for the life of me find the post that linked me to that PB! I think it might have been on the Unity forums, or a blog somewhere. I&#8217;ll include the text in this post, just in case the original dies at some point. I wish I could credit a person and not an anonymous PB, but in lieu of a person, I still want to give credit where it&#8217;s due. If this is your code, come forward silent rogue, and receive thy prize!<\/p>\n<p>Moving on, let me break down my understanding of this shader, and my own. Tessellation shaders take advantage of a DX11-specific shader pass called the Domain pass. I won&#8217;t pretend to understand more than I actually do. Essentially the &#8220;missing link&#8221; as it were was the ability to create extra vertices in the shader, rather than modifying the actual mesh data. How exactly this differs from a geometry shader, which is possible with DX9, I&#8217;m honestly not sure. Either way, I&#8217;ll break the shader down by its different passes:<\/p>\n<p>Vertex: Really nothing of note here. I used a pass-through geometry shader which only applies the ModelView matrix transformation equally to all vertics.<br \/>\nHull: This pass provides information to the Domain shader. The only &#8220;computation&#8221; that is done here uses a distance parameter to vary the &#8220;amount&#8221; of tessellation (number of subdivisions). You don&#8217;t see that in most of the earlier images\/videos, but it&#8217;s there now! \ud83d\ude42 For some reason this is broken up into the actual hull function (one line, just like vertex) and a constant function. Yeah, I guess I should learn more about stuff before writing it up =\\<br \/>\nDomain: Here&#8217;s where the &#8220;magic&#8221; happens. Essentially, this function is responsible for &#8220;splitting up&#8221; all of the mesh data for delivery to the fragment function. It takes in a parameter that I apparently don&#8217;t use (go figure), a float2 that represents &#8220;how far&#8221; along either edge of the quad the we are here (I assume &#8220;at this pixel&#8221;), and an array of vertices for &#8220;source data.&#8221; In this case, since the shader works on quad meshes (the whole point here!) it takes in 4 verts per pass of the domain shader, representing one quad of the mesh. For each of the various data points, we essentially call lerp 3 times to interpolate the value. For example:<\/p>\n<pre lang=\"C\" line=\"1\">float3 topMidpoint = lerp(patch[0].vertex, patch[1].vertex, UV.x);\r\nfloat3 bottomMidpoint = lerp(patch[3].vertex, patch[2].vertex, UV.x);\r\n\r\nfloat3 pos = lerp(topMidpoint, bottomMidpoint, UV.y);<\/pre>\n<p>TopMidPoint and BottomMidPoint are the midpoint positions along the top and bottom edge (using UV.x as the proportion for how far along), and pos is the midpoint between those two points. A diagram:<br \/>\nlerp1<br \/>\n0&#8211;|&#8211;1<br \/>\n| _ | lerp3<br \/>\n| |<br \/>\n3&#8211;|&#8211;2<br \/>\nlerp2<\/p>\n<p>There&#8217;s a better one out there, or I could draw one, but yeah. I need this post to not take up much more of my time.<\/p>\n<p>The above Lerp process is repeated for Normals, UVs, and tangents, after which some other work happens which I took from various decompiled bump map shaders.<br \/>\nFragment: Again, the fragment shader, while it looks complicated, is actually rather standard. What I didn&#8217;t get from the existing Tessellation shader in StandardAssets I got from decompiled shaders.<\/p>\n<p>And now, without further adieu, Appendix A: The quad patch tessellation shader. You might notice that the code is there twice in a row! This is apparently what you have to do if you want normal maps to respond to more than one light. I&#8217;m sure that I&#8217;m also missing a whole lot of fallback code, extra lighting stuff, and whatever. This was a very quick effort put together over a few days. What you see is what you get!<\/p>\n<pre lang=\"C\" line=\"1\">Shader \"Tessellation\/Vector Displacement Quad\" {\r\n\tProperties {\r\n\t\t_MainTex (\"Main Texture\", 2D) = \"white\" {}\r\n\t\t_Color (\"Main Color\", Color) = (1,1,1,1)\r\n\t\t_SpecColor (\"Specular Color\", Color) = (0.5, 0.5, 0.5, 1)\r\n\t\t_SpecMap (\"Specmap\", 2D) = \"white\" {}\r\n\t\t_BumpMap (\"Normalmap\", 2D) = \"bump\" {}\r\n\t\t_DispTex (\"Disp Texture\", 2D) = \"gray\" {}\r\n\t\t_TessEdge (\"Edge Tess\", Range(1,25)) = 2\r\n\t\t_Displacement (\"Displacement\", Range(0, 1)) = 0.1\r\n\t\t_fadeDist (\"Start Distance\", Range(0, 15)) = 0.1\r\n\t}\r\n\tSubShader {\r\n\t\tPass {\r\n\t\t\tTags {\"LightMode\" = \"ForwardBase\"}\r\n\r\n\t\t\tCGPROGRAM\r\n\t\t\t#pragma vertex vert\r\n\t\t\t#pragma fragment frag\r\n\t\t\t#pragma hull hull\r\n\t\t\t#pragma domain domain\r\n\r\n\t\t\t#pragma fragmentoption ARB_precision_hint_fastest\r\n\t\t\t#pragma multi_compile_fwdbase\r\n\t\t\t#include \"HLSLSupport.cginc\"\r\n\t\t\t#include \"UnityShaderVariables.cginc\"\r\n\t\t\t#define UNITY_PASS_FORWARDBASE\r\n\t\t\t#include \"UnityCG.cginc\"\r\n\t\t\t#include \"Lighting.cginc\"\r\n\t\t\t#include \"AutoLight.cginc\"\r\n\r\n\t\t\tfloat _TessEdge;\r\n\t\t\tfloat _Displacement;\r\n\t\t\tfloat _distFalloff;\r\n\t\t\tfloat _fadeDist;\r\n\t\t\tfixed4 _Color;\r\n\t\t\thalf _Shininess;\r\n\r\n\t\t\tsampler2D _MainTex;\r\n\t\t\tSamplerState\tsampler_MainTex;\r\n\t\t\tuniform float4 _MainTex_ST;\r\n\t\t\tuniform float4 _BumpMap_ST;\r\n\r\n\t\t\tTexture2D _DispTex;\r\n\t\t\tSamplerState\tsampler_DispTex;\r\n\t\t\t\/\/Texture2D _BumpMap;\r\n\t\t\tsampler2D _BumpMap;\r\n\t\t\tSamplerState\tsampler_BumpMap;\r\n\t\t\tTexture2D _SpecMap;\r\n\t\t\tSamplerState\tsampler_SpecMap;\r\n\r\n\t\t\tstruct Input {\r\n\t\t\t\tfloat2 uv_MainTex;\r\n\t\t\t\tfloat2 uv_BumpMap;\r\n\t\t\t};\r\n\r\n\t\t\tvoid surf (Input IN, inout SurfaceOutput o) {\r\n\t\t\t\tfixed4 tex = tex2D(_MainTex, IN.uv_MainTex);\r\n\t\t\t\to.Albedo = tex.rgb * _Color.rgb;\r\n\t\t\t\to.Gloss = tex.a;\r\n\t\t\t\to.Alpha = tex.a * _Color.a;\r\n\t\t\t\to.Specular = _Shininess;\r\n\t\t\t\to.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));\r\n\t\t\t}\r\n\t\t\tstruct appdata {\r\n\t\t\t\tfloat4 vertex : POSITION;\r\n\t\t\t\tfloat4 tangent : TANGENT;\r\n\t\t\t\tfloat3 normal : NORMAL;\r\n\t\t\t\tfloat2 texcoord : TEXCOORD0;\r\n\t\t\t\tfloat2 texcoord1 : TEXCOORD1;\r\n\t\t\t};\r\n\t\t\tstruct tess_appdata {\r\n\t\t\t\tfloat4 vertex : POS;\r\n\t\t\t\tfloat4 tangent : TANGENT;\r\n\t\t\t\tfloat3 normal : NORMAL;\r\n\t\t\t\tfloat2 texcoord : TEXCOORD0;\r\n\t\t\t\tfloat2 texcoord1 : TEXCOORD1;\r\n\t\t\t};\r\n\t\t\tstruct PS_INPUT\r\n\t\t\t{\r\n\t\t\t\tfloat4 pos   : POSITION;\r\n\t\t\t\tfloat3 normal     : NORMAL;\r\n\t\t\t\tfloat4 tangent     : TANGENT;\r\n\t\t\t\tfloat4 uv   : TEXCOORD;\r\n\t\t\t\tfloat3 lightDir : TEXCOORD1;\r\n\t\t\t\tfloat3 viewDir : COLOR;\r\n\t\t\t\tfixed3 vlight : TEXCOORD2;\r\n\t\t\t\tLIGHTING_COORDS(3,4)\r\n\t\t\t};\r\n\t\t\tstruct PS_RenderOutput{\r\n\t\t\t\tfloat4 f4Color      : SV_Target0;\r\n\t\t\t};\r\n\t\t\tstruct HS_CONSTANT_OUTPUT\r\n\t\t\t{\r\n\t\t\t\tfloat edges[4]  : SV_TessFactor;\r\n\t\t\t\tfloat inside[2] : SV_InsideTessFactor;\r\n\t\t\t};\r\n\r\n\t\t\tstruct HS_OUTPUT\r\n\t\t\t{\r\n\t\t\t\tfloat3 pos  : POS;\r\n\t\t\t\tfloat3 normal : NORMAL;\r\n\t\t\t\tfloat4 uv : TEXCOORD;\r\n\t\t\t\tfloat4 tangent: TANGENT;\r\n\t\t\t\tfloat3 lightDir : TEXTCOORD1;\r\n\t\t\t\tfloat3 viewDir : COLOR;\r\n\t\t\t\tfixed3 vlight : TEXCOORD2;\r\n\t\t\t};\r\n\r\n\t\t\tvoid vert(inout appdata v){\r\n\t\t\t\tv.vertex = mul(UNITY_MATRIX_MV, v.vertex);\r\n\t\t\t}\r\n\t\t\tHS_CONSTANT_OUTPUT HSConstant( InputPatch&lt;appdata, 4&gt; ip )\r\n\t\t\t{\r\n\t\t\t\tHS_CONSTANT_OUTPUT output;\r\n\r\n\t\t\t\toutput.edges[0] = _TessEdge \/ ((-ip[0].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.edges[1] = _TessEdge \/ ((-ip[1].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.edges[2] = _TessEdge \/ ((-ip[2].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.edges[3] = _TessEdge \/ ((-ip[3].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\r\n\t\t\t\toutput.inside[0] = _TessEdge \/ ((-ip[0].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.inside[1] = _TessEdge \/ ((-ip[0].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\r\n\t\t\t\treturn output;\r\n\t\t\t}\r\n\r\n\t\t\t[domain(\"quad\")]\r\n\t\t\t[partitioning(\"integer\")]\r\n\t\t\t[outputtopology(\"triangle_cw\")]\r\n\t\t\t[outputcontrolpoints(4)]\r\n\t\t\t[patchconstantfunc(\"HSConstant\")]\r\n\t\t\tappdata hull( InputPatch&lt;appdata, 4&gt; ip, uint cpid : SV_OutputControlPointID, uint pid : SV_PrimitiveID )\r\n\t\t\t{\r\n\t\t\t\treturn ip[cpid];\r\n\t\t\t}\r\n\r\n\t\t\t[domain(\"quad\")]\r\n\t\t\tPS_INPUT domain( HS_CONSTANT_OUTPUT input, float2 UV : SV_DomainLocation, const OutputPatch&lt;tess_appdata, 4&gt; patch )\r\n\t\t\t{\r\n\t\t\t\tPS_INPUT o;\r\n\r\n\t\t\t\tfloat3 topMidpoint = lerp(patch[0].vertex, patch[1].vertex, UV.x);\r\n\t\t\t\tfloat3 bottomMidpoint = lerp(patch[3].vertex, patch[2].vertex, UV.x);\r\n\r\n\t\t\t\tfloat3 pos = lerp(topMidpoint, bottomMidpoint, UV.y);\r\n\r\n\t\t\t\tfloat2 uvtopMidpoint = lerp(patch[0].texcoord, patch[1].texcoord, UV.x);\r\n\t\t\t\tfloat2 uvbottomMidpoint = lerp(patch[3].texcoord, patch[2].texcoord, UV.x);\r\n\r\n\t\t\t\to.uv.xy = lerp(uvtopMidpoint, uvbottomMidpoint, UV.y);\r\n\r\n\t\t\t\ttopMidpoint = lerp(patch[0].normal, patch[1].normal, UV.x);\r\n\t\t\t\tbottomMidpoint = lerp(patch[3].normal, patch[2].normal, UV.x);\r\n\r\n\t\t\t\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\t\t\t\tfloat3 normal = lerp(topMidpoint, bottomMidpoint, UV.y);\r\n\t\t\t\to.normal= normal;\/\/ unity_LightColor[0].rgb * max( 0, dot( normal, unity_LightPosition[0].xyz ) ) + UNITY_LIGHTMODEL_AMBIENT.rgb;\r\n\r\n\t\t\t\t\/\/pos = mul(UNITY_MATRIX_MV, pos);\r\n\t\t\t\tfloat3 disp = _DispTex.SampleLevel (sampler_DispTex, o.uv, 0).rgb * _Displacement;\r\n\t\t\t\tpos += normal * disp;\r\n\r\n\t\t\t\to.pos = mul (UNITY_MATRIX_P, float4(pos, 1));\r\n\r\n\t\t\t\tfloat4 tangenttopMidpoint = lerp(patch[0].tangent, patch[1].tangent, UV.x);\r\n\t\t\t\tfloat4 tangentbottomMidpoint = lerp(patch[3].tangent, patch[2].tangent, UV.x);\r\n\r\n\t\t\t\to.tangent = lerp(tangentbottomMidpoint, tangenttopMidpoint, UV.y);\r\n\t\t\t\tappdata v;\r\n\t\t\t\tv.normal = o.normal;\r\n\t\t\t\tv.tangent = o.tangent;\r\n\t\t\t\tTANGENT_SPACE_ROTATION;\r\n\t\t\t\t\/\/ To view space\r\n\t\t\t\to.uv.xy = TRANSFORM_TEX(o.uv.xy, _MainTex);\r\n\t\t\t\to.uv.zw = TRANSFORM_TEX(o.uv.xy, _BumpMap);\r\n\t\t\t\to.lightDir = mul(rotation, ObjSpaceLightDir(o.pos));\r\n\t\t\t\to.viewDir = mul(rotation, ObjSpaceViewDir(o.pos));\r\n\t\t\t\tfloat3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);\r\n\t\t\t\to.vlight = ShadeSH9(float4(worldN,1.0));\r\n\t\t\t\tfloat3 worldPos = mul(_Object2World, o.pos).xyz;\r\n\t\t\t\t  o.vlight += Shade4PointLights (\r\n\t\t\t\t\tunity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,\r\n\t\t\t\t\tunity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,\r\n\t\t\t\t\tunity_4LightAtten0, worldPos, worldN );\r\n\t\t\t\t\/\/ \/\/input.uv = I.uv;\r\n\t\t\t\to.tangent = o.tangent;\r\n\t\t\t\t\/\/ o.uv = v.uv;\r\n\t\t\t\tTRANSFER_VERTEX_TO_FRAGMENT(o);\r\n\r\n\t\t\t\treturn o;    \r\n\t\t\t}\r\n\r\n\t\t\tfixed4 frag( PS_INPUT input) : COLOR{\r\n\t\t\t\t\tInput surfIN;\r\n\t\t\t\tsurfIN.uv_MainTex = input.uv.xy;\r\n\t\t\t\tsurfIN.uv_BumpMap = input.uv.zw;\r\n\t\t\t\t#ifdef UNITY_COMPILER_HLSL\r\n\t\t\t\tSurfaceOutput o = (SurfaceOutput)0;\r\n\t\t\t\t#else\r\n\t\t\t\tSurfaceOutput o;\r\n\t\t\t\t#endif\r\n\t\t\t\to.Albedo = 0.0;\r\n\t\t\t\to.Emission = 0.0;\r\n\t\t\t\to.Specular = 0.0;\r\n\t\t\t\to.Alpha = 0.0;\r\n\t\t\t\to.Gloss = 0.0;\r\n\t\t\t\tsurf (surfIN, o);\r\n\t\t\t\t\/\/o.Albedo = tex2D(_MainTex, input.uv);\r\n\t\t\t\t  fixed atten = LIGHT_ATTENUATION(input);\r\n\t\t\t\t  fixed4 c = 0;\r\n\t\t\t\t  \/\/c = LightingBlinnPhong (o, input.lightDir, normalize(half3(input.viewDir)), atten);\r\n\t\t\t\t  c = LightingLambert (o, input.lightDir, atten);\r\n\t\t\t\t  c.rgb += o.Albedo * input.vlight;\r\n\t\t\t\t  c.a = o.Alpha;\r\n\t\t\t\t  return c;\r\n\t\t\t}\r\n\r\n\t\t\tENDCG\r\n\t\t}\r\n\t\tPass {\r\n\t\t\tTags {\"LightMode\" = \"ForwardAdd\"}\r\n\t\t\tBlend One One\r\n\r\n\t\t\tCGPROGRAM\r\n\t\t\t#pragma vertex vert\r\n\t\t\t#pragma fragment frag\r\n\t\t\t#pragma hull hull\r\n\t\t\t#pragma domain domain\r\n\r\n\t\t\t#pragma fragmentoption ARB_precision_hint_fastest\r\n\t\t\t#pragma multi_compile_fwdadd_fullshadows\r\n\t\t\t#include \"HLSLSupport.cginc\"\r\n\t\t\t#include \"UnityShaderVariables.cginc\"\r\n\t\t\t#define UNITY_PASS_FORWARDADD\r\n\t\t\t#include \"UnityCG.cginc\"\r\n\t\t\t#include \"Lighting.cginc\"\r\n\t\t\t#include \"AutoLight.cginc\"\r\n\r\n\t\t\tfloat _TessEdge;\r\n\t\t\tfloat _Displacement;\r\n\t\t\tfloat _distFalloff;\r\n\t\t\tfloat _fadeDist;\r\n\t\t\tfixed4 _Color;\r\n\t\t\thalf _Shininess;\r\n\r\n\t\t\tsampler2D _MainTex;\r\n\t\t\tSamplerState\tsampler_MainTex;\r\n\t\t\tuniform float4 _MainTex_ST;\r\n\t\t\tuniform float4 _BumpMap_ST;\r\n\r\n\t\t\tTexture2D _DispTex;\r\n\t\t\tSamplerState\tsampler_DispTex;\r\n\t\t\t\/\/Texture2D _BumpMap;\r\n\t\t\tsampler2D _BumpMap;\r\n\t\t\tSamplerState\tsampler_BumpMap;\r\n\t\t\tTexture2D _SpecMap;\r\n\t\t\tSamplerState\tsampler_SpecMap;\r\n\r\n\t\t\tstruct Input {\r\n\t\t\t\tfloat2 uv_MainTex;\r\n\t\t\t\tfloat2 uv_BumpMap;\r\n\t\t\t};\r\n\r\n\t\t\tvoid surf (Input IN, inout SurfaceOutput o) {\r\n\t\t\t\tfixed4 tex = tex2D(_MainTex, IN.uv_MainTex);\r\n\t\t\t\to.Albedo = tex.rgb * _Color.rgb;\r\n\t\t\t\to.Gloss = tex.a;\r\n\t\t\t\to.Alpha = tex.a * _Color.a;\r\n\t\t\t\to.Specular = _Shininess;\r\n\t\t\t\to.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));\r\n\t\t\t}\r\n\t\t\tstruct appdata {\r\n\t\t\t\tfloat4 vertex : POSITION;\r\n\t\t\t\tfloat4 tangent : TANGENT;\r\n\t\t\t\tfloat3 normal : NORMAL;\r\n\t\t\t\tfloat2 texcoord : TEXCOORD0;\r\n\t\t\t\tfloat2 texcoord1 : TEXCOORD1;\r\n\t\t\t};\r\n\t\t\tstruct tess_appdata {\r\n\t\t\t\tfloat4 vertex : POS;\r\n\t\t\t\tfloat4 tangent : TANGENT;\r\n\t\t\t\tfloat3 normal : NORMAL;\r\n\t\t\t\tfloat2 texcoord : TEXCOORD0;\r\n\t\t\t\tfloat2 texcoord1 : TEXCOORD1;\r\n\t\t\t};\r\n\t\t\tstruct PS_INPUT\r\n\t\t\t{\r\n\t\t\t\tfloat4 pos   : POSITION;\r\n\t\t\t\tfloat3 normal     : NORMAL;\r\n\t\t\t\tfloat4 tangent     : TANGENT;\r\n\t\t\t\tfloat4 uv   : TEXCOORD;\r\n\t\t\t\tfloat3 lightDir : TEXCOORD1;\r\n\t\t\t\tfloat3 viewDir : COLOR;\r\n\t\t\t\tfixed3 vlight : TEXCOORD2;\r\n\t\t\t\tLIGHTING_COORDS(3,4)\r\n\t\t\t};\r\n\t\t\tstruct PS_RenderOutput{\r\n\t\t\t\tfloat4 f4Color      : SV_Target0;\r\n\t\t\t};\r\n\t\t\tstruct HS_CONSTANT_OUTPUT\r\n\t\t\t{\r\n\t\t\t\tfloat edges[4]  : SV_TessFactor;\r\n\t\t\t\tfloat inside[2] : SV_InsideTessFactor;\r\n\t\t\t};\r\n\r\n\t\t\tstruct HS_OUTPUT\r\n\t\t\t{\r\n\t\t\t\tfloat3 pos  : POS;\r\n\t\t\t\tfloat3 normal : NORMAL;\r\n\t\t\t\tfloat4 uv : TEXCOORD;\r\n\t\t\t\tfloat4 tangent: TANGENT;\r\n\t\t\t\tfloat3 lightDir : TEXTCOORD1;\r\n\t\t\t\tfloat3 viewDir : COLOR;\r\n\t\t\t\tfixed3 vlight : TEXCOORD2;\r\n\t\t\t};\r\n\r\n\t\t\tvoid vert(inout appdata v){\r\n\t\t\t\tv.vertex = mul(UNITY_MATRIX_MV, v.vertex);\r\n\t\t\t}\r\n\t\t\tHS_CONSTANT_OUTPUT HSConstant( InputPatch&lt;appdata, 4&gt; ip )\r\n\t\t\t{\r\n\t\t\t\tHS_CONSTANT_OUTPUT output;\r\n\r\n\t\t\t\toutput.edges[0] = _TessEdge \/ ((-ip[0].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.edges[1] = _TessEdge \/ ((-ip[1].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.edges[2] = _TessEdge \/ ((-ip[2].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.edges[3] = _TessEdge \/ ((-ip[3].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\r\n\t\t\t\toutput.inside[0] = _TessEdge \/ ((-ip[0].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\t\t\t\toutput.inside[1] = _TessEdge \/ ((-ip[0].vertex.z - _ProjectionParams.y)\/_fadeDist);\r\n\r\n\t\t\t\treturn output;\r\n\t\t\t}\r\n\r\n\t\t\t[domain(\"quad\")]\r\n\t\t\t[partitioning(\"integer\")]\r\n\t\t\t[outputtopology(\"triangle_cw\")]\r\n\t\t\t[outputcontrolpoints(4)]\r\n\t\t\t[patchconstantfunc(\"HSConstant\")]\r\n\t\t\tappdata hull( InputPatch&lt;appdata, 4&gt; ip, uint cpid : SV_OutputControlPointID, uint pid : SV_PrimitiveID )\r\n\t\t\t{\r\n\t\t\t\treturn ip[cpid];\r\n\t\t\t}\r\n\r\n\t\t\t[domain(\"quad\")]\r\n\t\t\tPS_INPUT domain( HS_CONSTANT_OUTPUT input, float2 UV : SV_DomainLocation, const OutputPatch&lt;tess_appdata, 4&gt; patch )\r\n\t\t\t{\r\n\t\t\t\tPS_INPUT o;\r\n\r\n\t\t\t\tfloat3 topMidpoint = lerp(patch[0].vertex, patch[1].vertex, UV.x);\r\n\t\t\t\tfloat3 bottomMidpoint = lerp(patch[3].vertex, patch[2].vertex, UV.x);\r\n\r\n\t\t\t\tfloat3 pos = lerp(topMidpoint, bottomMidpoint, UV.y);\r\n\r\n\t\t\t\tfloat2 uvtopMidpoint = lerp(patch[0].texcoord, patch[1].texcoord, UV.x);\r\n\t\t\t\tfloat2 uvbottomMidpoint = lerp(patch[3].texcoord, patch[2].texcoord, UV.x);\r\n\r\n\t\t\t\to.uv.xy = lerp(uvtopMidpoint, uvbottomMidpoint, UV.y);\r\n\r\n\t\t\t\ttopMidpoint = lerp(patch[0].normal, patch[1].normal, UV.x);\r\n\t\t\t\tbottomMidpoint = lerp(patch[3].normal, patch[2].normal, UV.x);\r\n\r\n\t\t\t\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\t\t\t\tfloat3 normal = lerp(topMidpoint, bottomMidpoint, UV.y);\r\n\t\t\t\to.normal= normal;\/\/ unity_LightColor[0].rgb * max( 0, dot( normal, unity_LightPosition[0].xyz ) ) + UNITY_LIGHTMODEL_AMBIENT.rgb;\r\n\r\n\t\t\t\t\/\/pos = mul(UNITY_MATRIX_MV, pos);\r\n\t\t\t\tfloat3 disp = _DispTex.SampleLevel (sampler_DispTex, o.uv, 0).rgb * _Displacement;\r\n\t\t\t\tpos += normal * disp;\r\n\r\n\t\t\t\to.pos = mul (UNITY_MATRIX_P, float4(pos, 1));\r\n\r\n\t\t\t\tfloat4 tangenttopMidpoint = lerp(patch[0].tangent, patch[1].tangent, UV.x);\r\n\t\t\t\tfloat4 tangentbottomMidpoint = lerp(patch[3].tangent, patch[2].tangent, UV.x);\r\n\r\n\t\t\t\to.tangent = lerp(tangentbottomMidpoint, tangenttopMidpoint, UV.y);\r\n\t\t\t\tappdata v = (appdata)0;\r\n\t\t\t\tv.normal = o.normal;\r\n\t\t\t\tv.tangent = o.tangent;\r\n\t\t\t\tTANGENT_SPACE_ROTATION;\r\n\t\t\t\t\/\/ To view space\r\n\t\t\t\to.uv.xy = TRANSFORM_TEX(o.uv.xy, _MainTex);\r\n\t\t\t\to.uv.zw = TRANSFORM_TEX(o.uv.xy, _BumpMap);\r\n\t\t\t\to.lightDir = mul(rotation, ObjSpaceLightDir(o.pos));\r\n\t\t\t\to.viewDir = mul(rotation, ObjSpaceViewDir(o.pos));\r\n\t\t\t\t\/\/ \/\/input.uv = I.uv;\r\n\t\t\t\to.tangent = o.tangent;\r\n\t\t\t\t\/\/ o.uv = v.uv;\r\n\t\t\t\tTRANSFER_VERTEX_TO_FRAGMENT(o);\r\n\r\n\t\t\t\treturn o;    \r\n\t\t\t}\r\n\r\n\t\t\tfixed4 frag( PS_INPUT input) : COLOR{\r\n\t\t\t\t\tInput surfIN;\r\n\t\t\t\tsurfIN.uv_MainTex = input.uv.xy;\r\n\t\t\t\tsurfIN.uv_BumpMap = input.uv.zw;\r\n\t\t\t\t#ifdef UNITY_COMPILER_HLSL\r\n\t\t\t\tSurfaceOutput o = (SurfaceOutput)0;\r\n\t\t\t\t#else\r\n\t\t\t\tSurfaceOutput o;\r\n\t\t\t\t#endif\r\n\t\t\t\to.Albedo = 0.0;\r\n\t\t\t\to.Emission = 0.0;\r\n\t\t\t\to.Specular = 0.0;\r\n\t\t\t\to.Alpha = 0.0;\r\n\t\t\t\to.Gloss = 0.0;\r\n\t\t\t\tsurf (surfIN, o);\r\n\t\t\t\t\/\/fixed4 c = LightingBlinnPhong (o, normalize(input.lightDir), normalize(half3(input.viewDir)), LIGHT_ATTENUATION(input));\r\n\t\t\t\tfixed4 c = LightingLambert (o, normalize(input.lightDir), LIGHT_ATTENUATION(input));\r\n\t\t\t\tc.a = 0.0;\r\n\t\t\t\treturn c;\r\n\t\t\t}\r\n\r\n\t\t\tENDCG\r\n\t\t}\r\n\r\n\t}\r\n}<\/pre>\n<p>And Appendix B: the original post that started it all. Again, I&#8217;m re-posting this from PasteBin to account for the eventuality that it&#8217;ll be taken down.<\/p>\n<pre lang=\"C\" line=\"1\">#ifndef TESS_CG_INCLUDED\r\n#define TESS_CG_INCLUDED\r\n\r\n#include \"UnityCG.cginc\"\r\n#include \"AutoLight.cginc\" \r\n#include \"HLSLSupport.cginc\"\r\n\r\nfloat _TessEdge;\r\nfloat _Displacement;\r\nfloat _distFalloff;\r\nfloat _fadeDist;\r\n\r\nuniform float4 _LightColor0; \r\n\r\nTexture2D _MainTex;\r\nSamplerState\tsampler_MainTex;\r\nuniform float4 _MainTex_ST;\r\nuniform float4 _BumpMap_ST;\r\n\r\nTexture2D _DispTex;\r\nSamplerState\tsampler_DispTex;\r\nTexture2D _BumpMap;\r\nSamplerState\tsampler_BumpMap;\r\n\/\/Texture2D _SpecMap;\r\n\/\/SamplerState\tsampler_SpecMap;\r\nstruct appdata\r\n{\r\n\tfloat4 position   : POSITION;\r\n\tfloat3 normal     : NORMAL;\r\n\tfloat4 tangent     : TANGENT;\r\n\tfloat2 uv   : TEXCOORD;\r\n\tfloat3 lightDir : TEXCOORD1;\r\n\tfloat3 viewDir : COLOR;\r\n};\r\nstruct PS_INPUT\r\n{\r\n\tfloat4 position   : POSITION;\r\n\tfloat3 normal     : NORMAL;\r\n\tfloat4 tangent     : TANGENT;\r\n\tfloat2 uv   : TEXCOORD;\r\n\tfloat3 lightDir : TEXCOORD1;\r\n\tfloat3 viewDir : COLOR;\r\n};\r\nstruct PS_RenderOutput{\r\n\tfloat4 f4Color      : SV_Target0;\r\n};\r\nstruct HS_CONSTANT_OUTPUT\r\n{\r\n\tfloat edges[4]  : SV_TessFactor;\r\n\tfloat inside[2] : SV_InsideTessFactor;\r\n};\r\n\r\nstruct HS_OUTPUT\r\n{\r\n\tfloat3 position  : POS;\r\n\tfloat3 normal : NORMAL;\r\n\tfloat2 uv : TEXCOORD;\r\n\tfloat4 tangent: TANGENT;\r\n\tfloat3 lightDir : TEXTCOORD1;\r\n\tfloat3 viewDir : COLOR;\r\n};\r\n\r\nvoid vert(inout appdata v){\r\n\tTANGENT_SPACE_ROTATION;\r\n\t\/\/ To view space\r\n\tv.position = mul(UNITY_MATRIX_MV, v.position);\r\n\tv.normal = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);\r\n\t\/\/v.uv = TRANSFORM_TEX(v.uv, _MainTex);\r\n\tv.lightDir = mul(rotation, ObjSpaceLightDir(v.position));\r\n\tv.viewDir = mul(rotation, ObjSpaceViewDir(v.position));\r\n\t\/\/input.uv = I.uv;\r\n}\r\nHS_CONSTANT_OUTPUT HSConstant( InputPatch&lt;appdata, 4&gt; ip, uint pid : SV_PrimitiveID )\r\n{\r\n\tHS_CONSTANT_OUTPUT output;\r\n\r\n\t\/\/float4 viewPos = mul(glstate.matrix.modelview[0], v.vertex);\r\n\tfloat distCoeff = (-ip[0].position.z * _distFalloff - _ProjectionParams.y)\/_fadeDist; \/\/ip[0].position.z * _distFalloff;\/\/(ip[0].position.z  - _ProjectionParams.y) * _distFalloff;\r\n\toutput.edges[0] = _TessEdge \/ ((-ip[0].position.z - _ProjectionParams.y)\/_fadeDist);\r\n\toutput.edges[1] = _TessEdge \/ ((-ip[1].position.z - _ProjectionParams.y)\/_fadeDist);\r\n\toutput.edges[2] = _TessEdge \/ ((-ip[2].position.z - _ProjectionParams.y)\/_fadeDist);\r\n\toutput.edges[3] = _TessEdge \/ ((-ip[3].position.z - _ProjectionParams.y)\/_fadeDist);\r\n\r\n\toutput.inside[0] = _TessEdge \/ ((-ip[0].position.z - _ProjectionParams.y)\/_fadeDist);\r\n\toutput.inside[1] = _TessEdge \/ ((-ip[0].position.z - _ProjectionParams.y)\/_fadeDist);\r\n\r\n\treturn output;\r\n}\r\n\r\n[domain(\"quad\")]\r\n[partitioning(\"integer\")]\r\n[outputtopology(\"triangle_cw\")]\r\n[outputcontrolpoints(4)]\r\n[patchconstantfunc(\"HSConstant\")]\r\nHS_OUTPUT hull( InputPatch&lt;appdata, 4&gt; ip, uint cpid : SV_OutputControlPointID, uint pid : SV_PrimitiveID )\r\n{\r\n\tHS_OUTPUT Output;\r\n\tOutput.position = ip[cpid].position;\r\n\tOutput.normal = ip[cpid].normal;\r\n\tOutput.uv = ip[cpid].uv;\r\n\tOutput.lightDir = ip[cpid].lightDir;\r\n\tOutput.tangent = ip[cpid].tangent;\r\n\treturn Output;\r\n}\r\n\r\n[domain(\"quad\")]\r\nappdata domain( HS_CONSTANT_OUTPUT input, float2 UV : SV_DomainLocation, const OutputPatch&lt;HS_OUTPUT, 4&gt; patch )\r\n{\r\n\tappdata Output;\r\n\tfloat3 topMidpoint = lerp(patch[0].position, patch[1].position, UV.x);\r\n\tfloat3 bottomMidpoint = lerp(patch[3].position, patch[2].position, UV.x);\r\n\r\n\tfloat3 position = lerp(topMidpoint, bottomMidpoint, UV.y);\r\n\r\n\t\/\/Output.position = float4(lerp(topMidpoint, bottomMidpoint, UV.y), 1);\r\n\t\/\/\r\n\t\/\/O.f4Diffuse.a = 1.0f;\r\n\tfloat2 uvtopMidpoint = lerp(patch[0].uv, patch[1].uv, UV.x);\r\n\tfloat2 uvbottomMidpoint = lerp(patch[3].uv, patch[2].uv, UV.x);\r\n\r\n\tOutput.uv = lerp(uvtopMidpoint, uvbottomMidpoint, UV.y);\r\n\r\n\tfloat3 normaltopMidpoint = lerp(patch[0].normal, patch[1].normal, UV.x);\r\n\tfloat3 normalbottomMidpoint = lerp(patch[3].normal, patch[2].normal, UV.x);\r\n\r\n\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\tfloat3 normal = lerp(normaltopMidpoint, normalbottomMidpoint, UV.y);\r\n\tOutput.normal= unity_LightColor[0].rgb * max( 0, dot( normal, unity_LightPosition[0].xyz ) ) + UNITY_LIGHTMODEL_AMBIENT.rgb;\r\n\r\n\tfloat disp = _DispTex.SampleLevel (sampler_DispTex, Output.uv, 0).r * _Displacement;\r\n\tposition += normal * disp;\r\n\r\n\tOutput.position = mul (UNITY_MATRIX_P, float4(position, 1));\r\n\r\n\tfloat3 lighttopMidpoint = lerp(patch[0].lightDir, patch[1].lightDir, UV.x);\r\n\tfloat3 lightbottomMidpoint = lerp(patch[3].lightDir, patch[2].lightDir, UV.x);\r\n\r\n\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\tOutput.lightDir = lerp(lighttopMidpoint, lightbottomMidpoint, UV.y);\r\n\r\n\tfloat4 tangenttopMidpoint = lerp(patch[0].tangent, patch[1].tangent, UV.x);\r\n\tfloat4 tangentbottomMidpoint = lerp(patch[3].tangent, patch[2].tangent, UV.x);\r\n\r\n\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\tOutput.tangent = lerp(tangentbottomMidpoint, tangenttopMidpoint, UV.y);\r\n\r\n\treturn Output;    \r\n}\r\n\r\n[domain(\"quad\")]\r\nappdata vec_domain( HS_CONSTANT_OUTPUT input, float2 UV : SV_DomainLocation, const OutputPatch&lt;HS_OUTPUT, 4&gt; patch )\r\n{\r\n\tappdata Output;\r\n\tfloat3 topMidpoint = lerp(patch[0].position, patch[1].position, UV.x);\r\n\tfloat3 bottomMidpoint = lerp(patch[3].position, patch[2].position, UV.x);\r\n\r\n\tfloat3 position = lerp(topMidpoint, bottomMidpoint, UV.y);\r\n\r\n\t\/\/Output.position = float4(lerp(topMidpoint, bottomMidpoint, UV.y), 1);\r\n\t\/\/\r\n\t\/\/O.f4Diffuse.a = 1.0f;\r\n\tfloat2 uvtopMidpoint = lerp(patch[0].uv, patch[1].uv, UV.x);\r\n\tfloat2 uvbottomMidpoint = lerp(patch[3].uv, patch[2].uv, UV.x);\r\n\r\n\tOutput.uv = lerp(uvtopMidpoint, uvbottomMidpoint, UV.y);\r\n\r\n\tfloat3 normaltopMidpoint = lerp(patch[0].normal, patch[1].normal, UV.x);\r\n\tfloat3 normalbottomMidpoint = lerp(patch[3].normal, patch[2].normal, UV.x);\r\n\r\n\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\tfloat3 normal = lerp(normaltopMidpoint, normalbottomMidpoint, UV.y);\r\n\tOutput.normal= unity_LightColor[0].rgb * max( 0, dot( normal, unity_LightPosition[0].xyz ) ) + UNITY_LIGHTMODEL_AMBIENT.rgb;\r\n\r\n\tfloat disp = _DispTex.SampleLevel (sampler_DispTex, Output.uv, 0).r * _Displacement;\r\n\tposition += normal * disp;\r\n\r\n\tOutput.position = mul (UNITY_MATRIX_P, float4(position, 1));\r\n\r\n\tfloat3 lighttopMidpoint = lerp(patch[0].lightDir, patch[1].lightDir, UV.x);\r\n\tfloat3 lightbottomMidpoint = lerp(patch[3].lightDir, patch[2].lightDir, UV.x);\r\n\r\n\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\tOutput.lightDir = lerp(lighttopMidpoint, lightbottomMidpoint, UV.y);\r\n\r\n\tfloat4 tangenttopMidpoint = lerp(patch[0].tangent, patch[1].tangent, UV.x);\r\n\tfloat4 tangentbottomMidpoint = lerp(patch[3].tangent, patch[2].tangent, UV.x);\r\n\r\n\t\/\/float4 pNormal = _BumpMap.Sample( sampler_BumpMap, input.uv );\r\n\r\n\tOutput.tangent = lerp(tangentbottomMidpoint, tangenttopMidpoint, UV.y);\r\n\r\n\treturn Output;    \r\n}\r\nfloat4 frag( PS_INPUT input) : SV_TARGET{\r\n\r\n\t\/\/float3 lightDir = normalize( _WorldSpaceLightPos0);\r\n\thalf3 normal = UnpackNormal(_BumpMap.Sample( sampler_BumpMap, TRANSFORM_TEX(input.uv, _BumpMap) ));\r\n\tfloat4 Albedo = _MainTex.Sample( sampler_MainTex, TRANSFORM_TEX(input.uv, _MainTex) );\r\n\tfloat pxlAtten = dot( normal, normalize(input.lightDir ));\r\n\tfloat3 diff = Albedo * pxlAtten;\r\n\t\/\/half4 c = SpecularColorLight( input.lightDir, input.viewDir, normal, Albedo, Albedo.rgb, Albedo.r, pxlAtten );\r\n\treturn float4( diff, 1 ) * 0.5;\r\n\t\/\/return Albedo;\r\n\r\n\t\/\/ float3 lightColor = float3(0,0,0);\r\n\r\n\t\/\/ float4 c = _MainTex.Sample( sampler_MainTex, TRANSFORM_TEX(input.uv, _MainTex) );\r\n\t\/\/ float3 n =  UnpackNormal(_BumpMap.Sample( sampler_BumpMap, input.uv ));\r\n\t\/\/ float lengthSq = dot(input.lightDir, input.lightDir);\r\n\t\/\/ float atten = 1.0 \/ (1.0 + lengthSq * unity_LightAtten[0].z);\r\n\t\/\/ Angle to the light\r\n\t\/\/ float diff = saturate (dot (n, normalize(input.lightDir)));   \r\n\t\/\/ lightColor += _LightColor0.rgb * (diff * atten);\r\n\t\/\/ c.rgb = lightColor * c.rgb; \r\n\t\/\/ return c; \r\n\r\n}\r\n#endif<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Howdy there, Defectfans! Edit: This shader does not work in Unity 5. Naturally, a lot has changed in the Unity shader codebase, and this code simply doesn&#8217;t compile anymore. It probably could be fixed easily, but it was a hack at the time, and I haven&#8217;t revisited it since its original posting. One more update [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts\/169"}],"collection":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/comments?post=169"}],"version-history":[{"count":10,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts\/169\/revisions"}],"predecessor-version":[{"id":517,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/posts\/169\/revisions\/517"}],"wp:attachment":[{"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/media?parent=169"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/categories?post=169"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.defectivestudios.com\/devblog\/wp-json\/wp\/v2\/tags?post=169"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}