A while ago, artist/engineer/cool-dude Barak and I made a little "Venice inspired" VR scene, using Unreal Engine 4. It had an interesting art style that I still don't quite know how to describe. I think "Black-And-White" is closest, but colloquially that often means "shades of grey", and this style didn't use grey. Just pure black, and pure white. Here's an example of the style, not set in Italy this time:
We certainly weren't the first to work with this "black-and-white" look (go play Antichamber), but it does look kinda cool, if I do say so myself. We showed the original scene to some friends and family (it was actually made as a gift for Barak's then-fiancé), and somewhere along the line, somebody said a very inspiring sentence:
"Haha, it looks just like a coloring book."
Man, it kind of does, huh? And wouldn't that be really fun, a coloring book in VR? We thought so. A coloring book that you can stand inside of. The only problem with the idea is that someone has to actually make it, and that someone is me.
I started in UE4, because I already had our black-and-white style working there, and because it has an easy API for drawing into render targets, getting UV coordinates from hit tests, etc:
So I spent a few hours getting a basic prototype working. In short, I made a special actor which represents a mesh that can be drawn on. While the user is drawing, the system runs a line trace every frame from the end of their brush. If the object can be drawn on, and is the same object as last frame, the system draws a line from the old to the new point in the render target. Pretty simple, especially since UE4 has a project setting to enable computing UV coordinates from a hit result.
There are a few disadvantages though. For one, this only supports fully opaque brushes, with no anti-aliasing. It also just sweeps a square in order to form the line, so the endpoints are uh, pointy. The biggest issue though is that it draws the line in UV space, which is not continuous in screen (brush?) space. That means that if you try to draw over a UV seam, it takes the shortest path through the object UVs, which is not necessarily the shortest path in world space. It also means that if you use a brush that is much larger than the geometry, painted strokes can sort of spill over into unrelated parts of the object.
I thought about trying to partially correct for this by world projecting the textures, but I do want this system to support concave objects. For now, I elected to ignore the problem; Just don't draw the line if the UV-space length is too long. This doesn't really fix the issue, and also doesn't solve the spill-over problem for large brushes, but it does mitigate the first issue a bit. The real, robust solution is a bit complicated, and involves a couple extra render targets to project from screen (brush?) space back into UV space. A good subject for another post.
So as mentioned above, instead of stopping to solve these problems, I ignored them, pushing forward with a nice rounded brush by drawing my own custom geometry instead of just "lines". Because I'm specifying the vertices of the brush stroke, I get to also supply texture coordinates and colors. In practice, this means that both brush size and color can be nicely interpolated along a brush stroke:
It feels better already. I imagine the next steps in a good feeling solid brush will be some sort of spline interpolation - maybe a cubic spline based on some history of points. There's also the problem of tracking jitter and hand shakiness at a distance, which I've alleviated in the past with the help of some low-pass filtering. My plan is to try out different combinations of size/location interpolation and filtering, and see what sorts of brushes (pencil, fountain pen, solid marker, etc) I can come up with.
Oh and I'll leave you with two more images. One is a first-pass at soft brushes, and the other is a scribbled-on version of the scene above, using this little painting prototype:
I definitely plan to keep working with this project. It's a ton of fun so far, both to develop and to play with. Things may take a bit of a detour into more heavy graphics programming, while I figure out how to make this stuff scale to a whole scene. Render Targets in UE4 obfuscate a lot of what's actually going on in terms of memory, plus I can't get runtime mipmap generation to work without making some engine changes. Expect some more posts about this, especially since there's also a fair bit of work done on a custom shading model for the engine that should help improve the outline shader, among other things.
So stay tuned!