{"id":723,"date":"2021-02-04T19:18:14","date_gmt":"2021-02-04T19:18:14","guid":{"rendered":"https:\/\/half4.xyz\/?p=723"},"modified":"2024-01-25T10:06:09","modified_gmt":"2024-01-25T10:06:09","slug":"custom-mips","status":"publish","type":"post","link":"https:\/\/half4.xyz\/index.php\/2021\/02\/04\/custom-mips\/","title":{"rendered":"Custom MIP Levels"},"content":{"rendered":"<p>I wanted to research an easy way of creating every MIP level of a texture in Unreal (and any other engine), in such a way that I could create a unique image, or use custom filters or effects at every MIP level. This would let me create several effects, such as:<\/p>\n<ol>\n<li>Use custom filters where alpha is involved, such as chain-link fence or chicken wire, so that the wires look realistic as distance scales.<\/li>\n<li>Apply custom effects as things scale at distance, cheaply. For example, we could have a hand-draw sketch effect where pen width is more consistent at distance.<\/li>\n<\/ol>\n<ul>\n<li>Use custom filters where alpha is involved, such as chain-link fence or chicken wire, so that the wires look realistic as distance scales.<\/li>\n<li>Apply custom effects as things scale at distance, cheaply. For example, we could have a hand-draw sketch effect where pen width is more consistent at distance.<\/li>\n<\/ul>\n<figure class=\"wp-block-video\"><video src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/MIPping.mp4\" autoplay=\"autoplay\" loop=\"loop\" controls=\"controls\" width=\"437\" height=\"437\"><\/video><figcaption>In Action<\/figcaption><\/figure>\n<p>&nbsp;<\/p>\n<p><img fetchpriority=\"high\" decoding=\"async\" class=\"wp-image-735\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-9-1024x715.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-9-1024x715.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-9-300x209.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-9-768x536.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-9-1536x1072.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-9.png 1777w\" alt=\"\" width=\"1024\" height=\"715\" \/><\/p>\n<figure class=\"wp-block-video aligncenter\"><video src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/mip2.mp4\" controls=\"controls\" width=\"406\" height=\"406\"><\/video><figcaption>Left: Custom, Right: Default<\/figcaption><\/figure>\n<p>&nbsp;<\/p>\n<p>The advantage at doing this at a MIP level is that we can use a single texture sample, and the effect with automatically blend seamlessly without having to linearly interpolate between a whole bunch of texture.<\/p>\n<p>I tried a whole swathe of tools and techniques, most of which have problems. I\u2019ll cover these quickly, just so you don\u2019t have to go down the same rabbit holes that I had to:<\/p>\n<ol>\n<li><strong>Intel Texture Works plugin for Photoshop<\/strong>. This supports MIPs as layers, which is useful in some ways. However, it does not export to an uncompressed DDS (8bpc, R8G8B8A8), which means we cannot import it into UE4, which accepts DDS, but only uncompressed, as it applies compression itself.<\/li>\n<li><strong>NVidia Photoshop Plugins.<\/strong> This supports uncompressed DDS export, but it supports MIPs as an extension to the image\u2019s X dimension, in one image. This makes modifying individual MIPs very tricky if you want to automate the process using Photoshop\u2019s Batch feature.<\/li>\n<li><strong>NVidia Texture Tools<\/strong>. This doesn\u2019t support MIP editing<em> at all<\/em>.<\/li>\n<\/ol>\n<p>This leaves <strong>NVidia\u2019s DDS Utilities,<\/strong> a legacy set of command-line tools. Turns out, these do exactly what we need, if we combine them with a bit of sneaky Python and DOS commands.<\/p>\n<p><strong>NVidia\u2019s DDS Utilities<\/strong><\/p>\n<p>These utilities are just a bunch of executable files (.exe) that take some arguments and can combine images into MIPs for a single DDS.<\/p>\n<p>With that all out the way, let\u2019s get started setting it up.<\/p>\n<h2 class=\"wp-block-heading\">Step 1: Prerequisites<\/h2>\n<p><strong>Getting Python <\/strong><strong>Packages<\/strong><\/p>\n<h2 class=\"wp-block-heading\">Step 2: Folder Setup<\/h2>\n<p>Next we\u2019re going to setup our folder structure \u2013 we\u2019ll use these as a cheap way of managing the pipeline. One day, I\u2019ll manage it behind a UI, but that\u2019s a lot of work for a niche use-case.<\/p>\n<p>We\u2019ll use four folders.<\/p>\n<ol>\n<li>\u201csource\u201d for source textures to generate MIP images from.<\/li>\n<li>\u201cmips\u201d, a directory to keep these new MIP images in.<\/li>\n<li>\u201cdds\u201d, where we\u2019ll keep our files to let us convert our MIPs to DDS<\/li>\n<li>\u201cOUTPUT\u201d, for our DDS textures ready for our projects<\/li>\n<\/ol>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" class=\"wp-image-725\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image.png\" alt=\"\" width=\"103\" height=\"114\" \/><figcaption>Make this structure anywhere on your PC.<\/figcaption><\/figure>\n<\/div>\n<p>From your NVidia DDS Utilities installation path, copy these files into your \/<em>dds<\/em> folder:<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" class=\"wp-image-726\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-1.png\" alt=\"\" width=\"237\" height=\"177\" \/><\/figure>\n<\/div>\n<h2><\/h2>\n<h2 class=\"wp-block-heading\">Step 3: Creating MIP Images<\/h2>\n<p>Now we can start. The first step is to create a set of images for each MIP level, so we can manually edit each MIP image later.<\/p>\n<p>To do this, we\u2019ll use Python and the Python Imaging Library, and a batch file.<\/p>\n<p>Head into the \/source\/ directory and create a new Python file, generateMips.py, and a batch file, genMips.bat.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-727\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-2.png\" sizes=\"(max-width: 325px) 100vw, 325px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-2.png 325w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-2-300x171.png 300w\" alt=\"\" width=\"325\" height=\"185\" \/><\/figure>\n<\/div>\n<p>First, let\u2019s do the Python. Open the file in your favourite text editor, or IDE, and type the following:<\/p>\n<pre class=\"wp-block-code\"><code>import sys\nfrom PIL import Image\n\ndroppedFile = sys.argv[1]\n\nimg = Image.open(droppedFile)\n\nsrcSize = img.size\npath = \"..\/mips\/\"\nname = droppedFile.split(\".\")[0]\nimg.save(path+name+\"_00.tif\",format=\"TIFF\")\nmipNum = 1\n\nprint(\"Loaded \"+droppedFile+\", mipping...\")\n\nwhile srcSize[0] &gt; 1 and srcSize[1] &gt; 1:\n    newSize=(int(srcSize[0]\/2),int(srcSize[1]\/2))\n    resized = img.resize(newSize,resample=Image.BILINEAR)\n    newName=\"\"\n    if mipNum &lt; 10:\n        newName=path+name+\"_0\"+str(mipNum)\n    else:\n        newName=path+name+\"_\"+str(mipNum)\n    resized.save(newName+\".tif\",format=\"TIFF\")\n    srcSize = newSize\n    mipNum=mipNum+1<\/code><\/pre>\n<p>&nbsp;<\/p>\n<p>As you can see, nothing too scary. I simply create a new image file at half the resolution of the previous file saved, until we hit 1\u00d71 pixel. You can choose a different a different resample if you want, but we\u2019ll be manually changing the files, so for now, it doesn\u2019t matter.<\/p>\n<p>We take the image as an argument, this is so we can automate it externally if we want to \u2013 in our case, I want to make a batch file that I can drop a file on to and it will parse it with the Python script.<\/p>\n<p>In the batch file add this:<\/p>\n<pre class=\"wp-block-code\"><code>generateMips.py %~nx1 \npause<\/code><\/pre>\n<p>This will take whatever we drop on top of the batch file and make new images for each MIP level in the \/mips\/ directory. Give it a go.<\/p>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-728\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-3.png\" sizes=\"(max-width: 363px) 100vw, 363px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-3.png 363w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-3-300x148.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-3-360x179.png 360w\" alt=\"\" width=\"363\" height=\"179\" \/><figcaption>Drag and drop<\/figcaption><\/figure>\n<\/div>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-729\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-4.png\" sizes=\"(max-width: 737px) 100vw, 737px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-4.png 737w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-4-300x59.png 300w\" alt=\"\" width=\"737\" height=\"145\" \/><figcaption>Wait a short second\u2026<\/figcaption><\/figure>\n<\/div>\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-730\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-5.png\" sizes=\"(max-width: 775px) 100vw, 775px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-5.png 775w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-5-300x102.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-5-768x261.png 768w\" alt=\"\" width=\"775\" height=\"263\" \/><figcaption>Boom! Images as MIPs!<\/figcaption><\/figure>\n<\/div>\n<h2 class=\"wp-block-heading\">Step 4: Edit MIPs<\/h2>\n<p>This part is on you. You can edit your MIPs as you like.<\/p>\n<p>One suggestion, if you want to use a filter is to create an Action in Photoshop, and use File-&gt;Automate-&gt;Batch to apply that action and save out. The important thing here is that your file had just <em>one<\/em> underscore, before the MIP index, for example<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-731\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-6.png\" sizes=\"(max-width: 952px) 100vw, 952px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-6.png 952w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-6-300x141.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-6-768x360.png 768w\" alt=\"\" width=\"952\" height=\"446\" \/><figcaption>In this case, I\u2019ve called it normal, but it doesn\u2019t matter.<\/figcaption><\/figure>\n<p>My output goes into the \/dds\/ folder, this is where you want your modified MIPs.<\/p>\n<h2 class=\"wp-block-heading\">Step 5: Generate DDS<\/h2>\n<p>In that output, create two batch files \u2013 we need two as one is for Linear textures (for masks, etc.) and one for sRGB\/gamma corrected for albedo and colour textures.<\/p>\n<p>Create two batch files from your text editor\/IDE called:<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-732\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-7.png\" sizes=\"(max-width: 506px) 100vw, 506px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-7.png 506w, https:\/\/half4.xyz\/wp-content\/uploads\/2021\/02\/image-7-300x109.png 300w\" alt=\"\" width=\"506\" height=\"184\" \/><\/figure>\n<p>&nbsp;<\/p>\n<p>Here is the content for GenerateDDSColour.bat:<\/p>\n<pre class=\"wp-block-code\"><code>nvdxt -quality_normal -nomipmap -pause -all -u8888 -gamma .\\dds_dir\nset str=%~n1 \n\n\nfor \/f \"tokens=1,2 delims=_\" %%a in (\"%str%\") do set stitchName=%%a&amp;set sfx=%%b\necho.stitchName: %stitchName%\n\nstitch %stitchName%\n\nmd   \"..\\OUTPUT\" &gt;nul 2&gt;&amp;1\ncopy %stitchName%\".dds\" \"..\\OUTPUT\"\ndel %stitchName%*\npause<\/code><code><\/code><\/pre>\n<p>and this into GenerateDDSLinear.bat:<\/p>\n<pre class=\"wp-block-code\"><code>nvdxt -quality_normal -nomipmap -pause -all -u8888\nset str=%~n1 \n\n\nfor \/f \"tokens=1,2 delims=_\" %%a in (\"%str%\") do set stitchName=%%a&amp;set sfx=%%b\necho.stitchName: %stitchName%\n\nstitch %stitchName%\nmd   \"..\\OUTPUT\" &gt;nul 2&gt;&amp;1\ncopy %stitchName%\".dds\" \"..\\OUTPUT\"\ndel %stitchName%*\npause<\/code><\/pre>\n<p>As you can see, the only difference is the -gamma flag for gamma correction. That\u2019s all we need.<\/p>\n<p>Its worth noting, those scripts will delete the images processed in the \/dds\/ folder, to keep it clean.<\/p>\n<p>Populate the folder with your edited MIPs, then, based on whether the image is gamma corrected, drag it on to the correct batch file and you should get a DDS in your \/OUTPUT\/ folder (for normal maps, you\u2019ll want to use linear, for what its worth).<\/p>\n<p>You can then import this into UE4 or other engine of your choice.<\/p>\n<h2 class=\"wp-block-heading\">Files<\/h2>\n","protected":false},"excerpt":{"rendered":"<p>I wanted to research an easy way of creating every MIP level of a texture in Unreal (and any other engine), in such a way that I could create a unique image, or use custom filters or effects at every MIP level. This would let me create several effects, such as: Use custom filters where alpha is involved, such as chain-link fence or chicken wire, so that the wires look realistic as distance scales. Apply custom effects as things scale at distance, cheaply. For example, we could have a hand-draw sketch effect where pen width is more consistent at distance. Use custom filters where alpha is involved, such as chain-link fence or chicken wire, so that the wires look realistic as distance scales. Apply custom effects as things scale at distance, cheaply. For example, we could have a hand-draw sketch effect where pen width is more consistent at distance. In Action &nbsp; Left: Custom, Right: Default &nbsp; The advantage at doing this at a MIP level is that we can use a single texture sample, and the effect with automatically blend seamlessly without having to linearly interpolate between a whole bunch of texture. I tried a whole swathe of tools and techniques, most of which have problems. I\u2019ll cover these quickly, just so you don\u2019t have to go down the same rabbit holes that I had to: Intel Texture Works plugin for Photoshop. This supports MIPs as layers, which is useful in some ways. However, it does not export to an uncompressed DDS (8bpc, R8G8B8A8), which means we cannot import it into UE4, which accepts DDS, but only uncompressed, as it applies compression itself. NVidia Photoshop Plugins. This supports uncompressed DDS export, but it supports MIPs as an extension to the image\u2019s X dimension, in one image. This makes modifying individual MIPs very tricky if you want to automate the process using Photoshop\u2019s Batch feature. NVidia Texture Tools. This doesn\u2019t support MIP editing at all. This leaves NVidia\u2019s DDS Utilities, a legacy set of command-line tools. Turns out, these do exactly what we need, if we combine them with a bit of sneaky Python and DOS commands. NVidia\u2019s DDS Utilities These utilities are just a bunch of executable files (.exe) that take some arguments and can combine images into MIPs for a single DDS. With that all out the way, let\u2019s get started setting it up. Step 1: Prerequisites Getting Python Packages Step 2: Folder Setup Next we\u2019re going to setup our folder structure \u2013 we\u2019ll use these as a cheap way of managing the pipeline. One day, I\u2019ll manage it behind a UI, but that\u2019s a lot of work for a niche use-case. We\u2019ll use four folders. \u201csource\u201d for source textures to generate MIP images from. \u201cmips\u201d, a directory to keep these new MIP images in. \u201cdds\u201d, where we\u2019ll keep our files to let us convert our MIPs to DDS \u201cOUTPUT\u201d, for our DDS textures ready for our projects Make this structure anywhere on your PC. From your NVidia DDS Utilities installation path, copy these files into your \/dds folder: Step 3: Creating MIP Images Now we can start. The first step is to create a set of images for each MIP level, so we can manually edit each MIP image later. To do this, we\u2019ll use Python and the Python Imaging Library, and a batch file. Head into the \/source\/ directory and create a new Python file, generateMips.py, and a batch file, genMips.bat. First, let\u2019s do the Python. Open the file in your favourite text editor, or IDE, and type the following: import sys from PIL import Image droppedFile = sys.argv[1] img = Image.open(droppedFile) srcSize = img.size path = &#8220;..\/mips\/&#8221; name = droppedFile.split(&#8220;.&#8221;)[0] img.save(path+name+&#8221;_00.tif&#8221;,format=&#8221;TIFF&#8221;) mipNum = 1 print(&#8220;Loaded &#8220;+droppedFile+&#8221;, mipping&#8230;&#8221;) while srcSize[0] &gt; 1 and srcSize[1] &gt; 1: newSize=(int(srcSize[0]\/2),int(srcSize[1]\/2)) resized = img.resize(newSize,resample=Image.BILINEAR) newName=&#8221;&#8221; if mipNum &lt; 10: newName=path+name+&#8221;_0&#8243;+str(mipNum) else: newName=path+name+&#8221;_&#8221;+str(mipNum) resized.save(newName+&#8221;.tif&#8221;,format=&#8221;TIFF&#8221;) srcSize = newSize mipNum=mipNum+1 &nbsp; As you can see, nothing too scary. I simply create a new image file at half the resolution of the previous file saved, until we hit 1\u00d71 pixel. You can choose a different a different resample if you want, but we\u2019ll be manually changing the files, so for now, it doesn\u2019t matter. We take the image as an argument, this is so we can automate it externally if we want to \u2013 in our case, I want to make a batch file that I can drop a file on to and it will parse it with the Python script. In the batch file add this: generateMips.py %~nx1 pause This will take whatever we drop on top of the batch file and make new images for each MIP level in the \/mips\/ directory. Give it a go. Drag and drop Wait a short second\u2026 Boom! Images as MIPs! Step 4: Edit MIPs This part is on you. You can edit your MIPs as you like. One suggestion, if you want to use a filter is to create an Action in Photoshop, and use File-&gt;Automate-&gt;Batch to apply that action and save out. The important thing here is that your file had just one underscore, before the MIP index, for example In this case, I\u2019ve called it normal, but it doesn\u2019t matter. My output goes into the \/dds\/ folder, this is where you want your modified MIPs. Step 5: Generate DDS In that output, create two batch files \u2013 we need two as one is for Linear textures (for masks, etc.) and one for sRGB\/gamma corrected for albedo and colour textures. Create two batch files from your text editor\/IDE called: &nbsp; Here is the content for GenerateDDSColour.bat: nvdxt -quality_normal -nomipmap -pause -all -u8888 -gamma .\\dds_dir set str=%~n1 for \/f &#8220;tokens=1,2 delims=_&#8221; %%a in (&#8220;%str%&#8221;) do set stitchName=%%a&amp;set sfx=%%b echo.stitchName: %stitchName% stitch %stitchName% md &#8220;..\\OUTPUT&#8221; &gt;nul 2&gt;&amp;1 copy %stitchName%&#8221;.dds&#8221; &#8220;..\\OUTPUT&#8221; del %stitchName%* pause and this into GenerateDDSLinear.bat: nvdxt -quality_normal -nomipmap -pause -all -u8888 set str=%~n1 for \/f &#8220;tokens=1,2 delims=_&#8221; %%a in (&#8220;%str%&#8221;) do set stitchName=%%a&amp;set sfx=%%b echo.stitchName: %stitchName% stitch %stitchName% md &#8220;..\\OUTPUT&#8221; &gt;nul 2&gt;&amp;1 copy<\/p>\n","protected":false},"author":1,"featured_media":735,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-723","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tuts"],"_links":{"self":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/723"}],"collection":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=723"}],"version-history":[{"count":11,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/723\/revisions"}],"predecessor-version":[{"id":1275,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/723\/revisions\/1275"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media\/735"}],"wp:attachment":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=723"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=723"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}