My posts tend to be 'off the cuff' - meaning I'm just writing out in 'one go' about stuff I'm currently thinking about. Not really a lot of pre-planning (in most cases, save for tutorials). Though I do go back and add bits, correct grammar errors, and put in links, pictures, etc. So apologies if you were expecting highly formalized PR or Marketing spiel. ;) (Yes, I know. You weren't!)

A bit about Shaders, and why I 'roll my own' shaders for mobile device AR (Hint: performance, flexibility and AR VFX)

Well, I'm not quite working on my AR engine yet. I got sick over the weekend and I'm still feeling it - especially in my throat. But I'm about at my limit of sitting in bed watching anime and eating buckets of chicken soup.

So I thought I'd take a sanity break and write about something I love - shaders.  It's going to be a long post, but even so, it doesn't even begin to cover shaders as they deserve to be covered, nor about shaders in AR. But it's a start. So let's see if folks have any interest.

I've had a fascination with them even before they existed. What? Yes, it is possible. You see before shaders were shaders, they were simply part of the graphics engine - and I've been fascinated with graphics rendering for a very long time. I think anyone who cares about visuals, where it be Art or UI or a Formula 1 car, is fascinated with the mechanisms involved in creating that visual.

In fact, I suspect there may be "gearheads" hidden within all of us fixated ones - after all, it's called a graphics engine.  (No, I won't speculate as to why we also call it the rendering pipeline. Another reason I'm almost a full vegetarian now (fish, shellfish, dairy...)! I'm terribly squeamish and guilt-ridden...)

Here's a video clip of some shaders I wrote as I worked on my AR engine. (Use Link. The picture is just for reference).

They were written in Unity's shader language. That language is a mix of Cg and HLSL. Cg was developed by Nvidia with Microsoft to be a sort of universal C-based high level shader language that would be independent of implementation or engine. HLSL (High Level Shading Language) is the shader language Microsoft developed for DirectX. (GLSL is OpenGL's shading language).


Shader Test (AR Engine Plus Shaders)

I love Cg and not just because it's my initials. My fifth programming language was C (the first four were Pascal, Fortran, Ada and Smalltalk). C stayed with me and I used it for years. Cg is C syntax based. So to me, it was like coming home to an old friend. Nothing fancy, no pointers, no object oriented, no frameworks, just very simple, easy, straightforward coding where no one can criticize you for using if-then statements and macros. (Yes, today's programmers often thumb their noses at long if-then statements, switches, macros, functions, even pointers...  Do I often feel like I should be driving a horse and buggy?  Yes, yes, I do. But then again, I think a horse and buggy might be a lot more fun, too!)

  <Yes, this is Ugly Bunny as he normally looks!>

Back to the video clip. It was a test clip so there is a lot going on, but notice the bunny spinning around? Yes, that is Ugly Bunny himself!  But I'm using a special shader I wrote. It's meant to look like glass without actually trying to really be glass. I call it a faux Fresnel shader.

First, why?  That's easy. I need performance. AR takes a lot of processing power, meaning there is less CPU/GPU available for doing other things, like fancy materials (consisting of textures and shaders). If you want real glass, then you need to do ray tracing, i.e. simulate how light zips around the room, how it moves through glass (getting reflected and refracted), how it bounces around giving you tonal changes and distortions. You know, you see reflections as well as light and dark spots. You see glass objects that sparkle or some that act like magnifying glasses. In fact, you see color or a lack of it (shadows). Light is amazing - and complex.

Nature does that without thinking and your brain processes that information without you having to do much more than open your eyes - but try to do it with math (not so bad), yet when you try to implement that math so you can do all those calculations (millions of light rays!!!) ....then whoa!

That is an expensive set of calculations.

Camera-based AR already has to do a lot of expensive calculations for each and every frame of video and there are going to be 24 to 30 frames per second of video (in some cases, more...but we'll stick with the film rate of 24 fps or the video rate of ~30 fps).

We really would like to avoid adding another huge burden on top of the AR stuff, but a bland single color here and there would be a pretty boring user experience, wouldn't it?

So, how do we fake reality?

We could add more processing power. We could add sensors for doing some of the AR requirements. That is what you see with a lot of AR/VR headsets and accessories. But in my case, I'm targeting regular mobile devices with no special equipment. I can't assume the device will be a Samsung Galaxy. It might be something far less expensive. The best I might hope for is that it will have gyroscopic support (for Google Cardboard) but nothing more.

That's what got me into shaders.

Now I know many of you may have heard of PBR (Physically-Based Rendering), which is again, about shaders, but shaders that use terminology that is in line with reality (real light, etc.)  From what I've seen of PBR implementations, too often, it's more about terminology. There may be some different calculations under the hood (I would hope so!), including bringing in some ray tracing algorithms, but if they do, then use of them is going to vary a great deal. The idea is to mimic real light much closer than before, and to use more sophisticated techniques. Why? Because shaders and devices are more powerful now, so you can use more CPU/GPU time doing the expensive stuff.

I'm not going to talk about PBR here beyond that.  But if you want a good starting point (no math) then check out Basic Theory of Physically-Based Rendering. It's a great post and also includes links to other great posts. And this article from gamasutra, even though it was written in 2009, still sums up a lot of problems associated with writing shaders from an artists view (and why fancy graph based GUIs may give you a false sense of empowerment. Hint: be sure to read the comments section, too. It's a good critique of the article.)

Why would there be a lot of variance between implementations of PBR shaders? Well, beyond the usual competitiveness and proprietary this or that between companies, let's put it this way. If you are rendering for a high VFX film, like some blockbuster where it's Marvel's Avengers or Disney's Frozen, you are going to have a huge number of machines dedicated to rendering out all those frames of film (a render farm). And once you've got that single frame, it's one of many of the final finished film. It can use any type of calculation you want, you've got a lot of power and a lot of time to work it out beforehand. It's not real-time.

Move to video games and computers. They are very close to real-time. They can't wait around 4 hours or more per frame. So, to improve performance, desktops had to get more powerful with powerful graphics cards, and video game systems similarly developed into consoles like Xbox and friends (which were focused almost entirely on doing great real-time/near real-time graphics.)

I say near real-time because graphics have to be processed first, so of course, there is a lag. But if you can make that lag imperceptible to the human brain....Bingo! Good enough for video games.  (For other work, where true real-time is critical, well, that's another story.)

So what is a shader?  Kronos (the org that oversees OpenGL) says this: "A Shader is a user-defined program designed to run on some stage of a graphics processor. Its purpose is to execute one of the programmable stages of the rendering pipeline."
They go into more depth, you can find the page here.

More than likely, you didn't find that very helpful. So here is my summary. A shader is code. In the old days, where everything in graphics was hard-coded (i.e. had to be built ahead of time), you didn't have shaders. When the graphics pipeline got more flexible, you could plug in your own code for doing certain parts of it. Them be shaders!  It means you can have a graphics engine/environment like Unity or Unreal  where someone other than the graphics programmer can pick out what materials (textures and shaders) they want for this or that object. If they are really brave, they can also create those textures. And if really, really brave, they can write their own shaders!

So a shader is NOT a list of options you click in Unity's standard shader (there is no standard shader, i.e. there is no single do-it-all shader. Unity's is a program to create a shader based on your choices).

A shader is NOT Unreal Blueprints. It is not a series of little objects you put together in Blueprints or Shader Forge or Substance Designer nor any other tool that is used in creating shaders.  A shader is code that is executed by the graphics pipeline code at the right moment. And to be as fast as possible, shaders, for example, Unity shaders, are compiled first. You can have shaders that are compiled at runtime (when the app is run) but they will be slower.

If you've created shaders in Blueprints or other visual tools, and then wondered why a dev came along or a highly coder-centric TA came along and rewrote your code, then the above reason may be why. A shader is code, and it's important to write that code efficiently. When you can just string together a bunch of objects, it's possible that the tool may have problems optimizing the result. Tools are wonderful things, especially visual shader creation tools, but sometimes, knowing how the code works, makes a difference in your design. Large teams often have a graphics engineer or specialist shader TA, but smaller teams may not, or just go with what the artist designed in with nodes.

Performance varies, too, with shaders, so just because you haven't seen a problem yet - I guarantee with AR or VR you will. Why? Read on.

[p.s. if your shader was done in Maya, then they had to rewrite it for the game engine (and you likely already know that). Maya shaders are for Maya. They don't directly transfer to Unity. Nor Unreal...I think....but there, I'd have to double check since the last time I looked. It would be nice if they did, of course.]

So now we're back to PBR. I don't use it. I happily toss it out the window. Again, why?  You've probably already guessed at what I'm getting at, and you'd be correct.

AR is processor intensive. What I found is that I can't really have the fancy, new PBR shaders. Because I was looking at shaders at the code level, I discovered a lot more about them, including how deeply their abilities are tied to the device's abilities (as is the rendering pipeline).   And believe me, that was hard-earned knowledge as game engine documentation is often quite sparse and sporadic when it comes to shaders. I'm sure it's mainly tied to the fact that you need to really understand shaders to write about them at a deep level, and most writers on doc teams do not have anywhere near that kind of depth. It's a special skills, even among developers. And you really only learn it by doing it - and screwing up and trying again - and so on.

If people want me to show some vertex or fragment code (in Cg or in Unity's Cg/HLSL hybrid) then let me know. It would be snippets because a whole program is kind of boring. I don't here, because this isn't a coding tutorial. I suspect most don't, because they'd prefer to see a graph node system instead. That's OK. I intend to write a bit about graph node shader network tools, like Unity 2018's new shader node system (Unity 2018 is in beta now). So far, it looks like any other graphed node or material node GUI. I still prefer coding directly, but then I'm comfortable with code and I need to deal with Unity's renderqueue system - basically, I need to subvert it to do AR stuff.

So with AR, I've had to fall back to elder days. How did earlier graphics engines mimic reality, before they got so powerful that they could do photo-realism at near real-time?  They faked it. And they tried to minimize expensive calculations, especially if a trick-of-the-eye would suffice instead.

Now look at that video clip again.
Shader Test (AR Engine Plus Shaders)

If you look carefully, you'll realize that Ugly Bunny isn't really glass, but more like 'glass-ish'. He's not reflecting anything in the scene (though with a change to the shader, I could make that happen...another cheat on reality, with some extra cost, but again, minimal compared to real glass effects). In fact, just to make it clearer, I was fiddling with the shader so that it would go in and out of the faux-Fresnel effect. 



The effect is nothing more than a texture pattern and a bit of transparency that fluctuates and moves over time (moves from the user's perspective. Actually, the vertex is picking a different part of the pattern over time. You could get very creative in your patterns and other choices in fact, and make a very flexible shader, or a series of very specialized ones on that alone.) In fact, since vertices truly affect the outcome here, it turns out, the more jagged the surface, the better the illusion!

So, back to the video as a whole, you are actually looking at four different shaders at work. The most obvious one is the faux-Fresnel on the spinning large Ugly Bunny.

The three smaller ugly bunnies are just using UV mapped textures (again, shaders do the actual coloring work of the model, but these colors don't change). That's the second shader.

The fourth is the Unity standard shader. It is used on the cubes that occasionally appear. And is set to its default settings (creating a very basic shader that colors all vertices and fragments - a fragment is, in simplest terms, the stuff in-between vertices. They are colored one color. In this case, gray.)

And the third? The third is another complex, specialty shader attached to a 'floor'. If you look closely, you can see the desktop start to warp at some times, with a flame texture that moves over it at other times. During the flame texture it also happens to be transparent. And then you can see through the floor.

That is when the three cubes will become visible. They are actually beneath the floor. Note that the desktop itself is still visible. A video backboard is running, supplying the viewer with the real world (not real world, video lag prevents that!)

And the floor is defined/oriented by the checkerboard black/white piece of paper there - that's a marker calibration pattern that I use for camera calibration. However, in this case, I'm using individual markers in the pattern to bring up different objects for the test.

That third one, on the floor, is very complex and I won't go into it in this post. But frankly, it was the other big reason to write my own AR engine that plugs into Unity (the first being to have my own markers recognized). That shader is doing a lot in this test, but normally, it wouldn't. Warping the floor would be enough. That's another thing shaders can do, they don't just color the pixels on the screen. They can displace the vertices in an object (or the fragments, but that's very expensive.) They can even create new vertices (and/or fragments) if you need them. In this case, this shader is doing displacement and coloring (color and transparency) of an object that is being simultaneously textured by the video stream. 

Again, why I chose to do displacement with a square 'floor' that actually has a lot of vertices rather than tessellation (creating vertices on-the-fly) is because of performance costs. Tessellation is expensive. More expensive than simply creating the object with enough vertices to do some basic warping. Because, then when actually using the shader in a real app you tailor the experience so that the warping will appear smooth. It doesn't in this video because it's a random pattern, but if I create the floor knowing it has enough vertices to be smooth in some particular warped configurations, then that saves shader costs and still gives a good user experience.

If the third shader still sounds CPU/GPU expensive, then you're right. It is.

And as a last note, I keep saying CPU/GPU rather than GPU because while shaders typically use the graphics processing unit(s), there is always some expense to the computer's processing unit. And in the case of video streaming and interactivity with changing textures or lighting conditions or so on, that means that yes, there is always a cost to both the CPU and GPU.

Well, I'm beginning to cough too much again. I guess I should just give in to the inevitable and go back to watching anime and...sigh...having some more chicken soup.

Oh dear, how I'm beginning to long for solid food!

Comments

Popular posts from this blog

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 6 - Getting Glow/Bloom Effect wihout Post-Processing by Inverting Fresnel...Sort Of...)

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 5 - Exploring Fresnel/Color Rim and Update on Vertex Displacement Attempts)

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 2 - tiling, offsets, blending, subgraphs and custom channel blending)