So, I’ve been pretty negligent about documenting this one last feature in the Sprite Lamp shader. It’s rather obscure and difficult to explain, but I’m going to try to overcome that with pictures.
Pixels, texels, and fragments
These three things can get pretty confusing during the coming article, so I wanted to clear a few things up.
You’ve probably heard of pixel and fragment shaders, and observed that they’re basically the same thing. Well, they are. Technically, I believe, the correct term is ‘fragment shader’. A pixel (‘picture element’) is a single tiny rectangle of colour on a computer screen, whereas a fragment is also depth information and a few other things. A fragment might never become a pixel, because it might fail a depth test or whatever. The wikipedia page on fragments is a bit helpful. Either way, it’s good to know the difference, but this isn’t the most important distinction.
The distinction between a pixel and a texel is more important, especially if you’re a pixel artist. In a sense, I suppose, you could be described as a texel artist – texel means ‘texture element’ – basically, it’s one of the coloured rectangles that make up a texture. This is relevant because sometimes you draw a texture to the screen at a size other than its native size – in fact, in 3D games this is almost always what happens. That’s what filtering is for – bilinear filtering, for example, is a maxification filter – a way of zooming in on a texture – without it appearing blocky.
Per texel lighting
This is probably nothing new to artists who are used to working with computer graphics. The point is, when you’re viewing pixel art such that it is deliberately blocky (using nearest neighbour filtering (aka point filtering)), each texel is made up of many pixels. That is, each pixel in the source art is blown out to be many pixels on the monitor. This is the important thing to understand, because when a fragment shader operates on this artwork, it’s doing a lighting calculation once per pixel, NOT once per texel.
The result of this is that, even with the diffuse/normal/everything maps set to nearest neighbour filtering, you end up with colour variation within a texel. Almost always, this is imperceptible and doesn’t matter. However, when combined with cel shading, you can run into trouble. Here’s an example of what I’m talking about:
Because I have been staring at this kind of thing for a while now, it’s hard for me to tell how obvious the difference between these two images is. If it’s not immediately apparent to you, the point is that the image on the right has diagonal lines – discontinuities in the lighting level – running across texels. The image on the left could just be pixel art with static lighting – the image on the right definitely isn’t. This problem becomes less clear if the lighting is changing rapidly, but more clear if the light source is moving, but slowly. To me, and I daresay to various pixel purists out there, this is a problem worth fixing.
As you can see from the screenshot above, this has been solved in the Sprite Lamp shader. However, Sprite Lamp’s preview window makes this problem easier to solve than it is in the general case. I’m still working out the details of solving the general case, although I’ll document them carefully in the Unity shader so it can be reproduced elsewhere.
Until then, though, I have a question for anyone reading – would this feature be important to you? As mentioned, to me the visual problems are obvious, but I can understand that for many they wouldn’t be. Of course, it’s not relevant for high res art, and it’s mostly not relevant for low res art either unless there’s cel shading involved. Even then, arguably the problem being solved is fairly subtle. I’m curious to hear your thoughts.
My first attempt at fixing this would be to render at at a smaller size and then enlarge it using nearest neighbour filtering. As I’m sure you’ve thought about this, I wonder: What’s the benefit of fixing this in the shader? (I could see some reasons: rotating pixels, differently sized pixels, but I’d be interesting to hear your take)
Basically, I want to have the option for people whose art falls into the edge cases you mentioned (the need for rotating at a higher resolution, and for having game entities that scale at different rates). You’re right though – if you want your game to fully look lower res across the board, rendering at a lower resolution and scaling up is the simplest option.
Incidentally, for those that take this option, the per-texel shader thing will definitely be optional!
It would be nice to have this fixed. In our game, Hive Jump, we’re zoomed in on some lower-res art (tiles being 16x16px), so this fix will impact our visual style and help clean it up. (We do plan on using cel-shading as well).
Cool. Glad it’s not just me. π
This is the type of thing that would annoy me to no end, and would probably keep me from ever releasing anything. So yeah, please fix it π
I still want to render at full resolution for more detailed object positioning, but within a given sprite, I’d like to apply the lighting per-texel to keep the blocky look, so having this built into the shader would be great. It’s definitely something I’ve noticed and been trying to find a fix for.