When I made the “Perfect Tile System,” I had a TON of switches for using more expensive VS. less expensive features. The system is scaleable from a fun, cheap mobile shading tool to an impressive Parallax Occlusion master brickwork shader. There are definitely going to be a lot of permutations of this shader (I think some several thousand), but the shader doesn’t compile all of those several thousand permutations if those permutations are not used. I also have mechanisms to prevent certain options from conflicting with others. For instance, I have a simple feature to remove grout lines at glancing angles for a somewhat cheap cost, and it works great with basic parallax, but this is totally unnecessary to use if you have parallax occlusion, so the option is only available when parallax occlusion is checked off.
There are a lot of different ways to optimize in the material editor. For one, the editor does not like doing the same math more than once. So if you have a more complex material, maybe something that requires Phong shading, and you need to plug the result into both an opacity and an emissive output, it is better to perform the bulk of the math for the opacity and then multiply that result for the emissive. Running two different values for each input would require performing the same phong calculation twice. Some very interesting things are almost free, like multiplying something by itself only requires one instruction, while raising something to the power of two costs four instructions. Because the deferred renderer is automatically required to calculate things like per-pixel world normals and pixel/scene depth, you can perform depth calculations and world position calculations very easily. Addition, subtraction, multiplication, and division are concatenated, but linear interpolation functions are not optimized at all! If you want a cheap halfway blend between two textures, it might be a better idea to simply add them together and divide by two than to use a lerp function. And because concatenating is very good at organizing functions, I find that the more complex your material becomes, the cheaper it becomes to make those effects work. For instance, if you use advanced vertex painting (combination of vertex and masks) to blend between two textures, that same vertex painting code can be repeated for blending roughness, metallic, and subsurface properties as well, so you’re not really saving much by choosing to blend only one property. And of course, if you can use unlit materials, fully rough materials with 0 plugged into specularity, you can save some instructions required for rendering reflections and GGX specular. Unfortunately, I do believe the reflection environment runs on fully rough materials anyways, and the only way to remove it is to use unlit materials or not have any reflections in the scene (no SSR, no reflection captures, no stationary skylight) at all.
I also noticed something interesting: the UDK method of normal blending is not actually the cheapest. In the old days, the suggested solution to blend two normal maps on top of each other was to multiply the overlaid normal by 1,1,0 and then adding that result to the bottom normals. However, because 3-vectors require more instructions than 2-vectors, it is better to mask the RG values of both normals, add them together, and then append the blue channel from the base normal for the final result. The former method required 5 instructions, but my method only requires 4 and the results are exactly the same, you just don’t have to perform any math for the blue channel. I try to use as few channels as I possibly can. I never use the RGB value to manipulate Grayscale textures.