Wednesday, 4 June 2014

Katana integration support

I decided to take a very brief detour in my plans and quickly prototype integrating Imagine into Katana 1.5 as a Render plugin...

Currently only very basic support for Disk Renders of subd and poly geometry, arbitrary transforms, default materials and physical sky light...

But as a back-burner project I'll possibly keep chipping away at it - would in theory allow slightly saner comparisons with production renderers...

Monday, 26 May 2014

Complex Shading and UDIM Texture Atlas support

I did some prototyping of two different shading implementations which allow vastly more flexible shading than Imagine's previous baked-BSDF approach. The two different methods I tested really only varied in how the memory for the dynamic BSDF components was allocated and used - both methods built the BSDF components after each geometry intersection (for non-shadow rays - in my final implementation it's also performed when Transparent shadows are enabled), either from constant values or textures. The first test method was using a memory arena to allocate the samples, and the second was allocating the memory on the stack within the integrator loops.

Fairly comprehensive benchmarking - using a worst-case scenario: allocating lots of different BSDFs all driven from image textures, and all controlled by quite convoluted and expensive branching logic - showed that between the two methods, in terms of speed, there was practically no difference. However, there was (as I expected) an overhead to doing this compared to the baked BSDF approach - generally around 4-9% overhead total render time. The extreme end of this I'm putting down to image texture evaluations (all images in the tests were memory-resident, so with texture paging and displacement the overhead could be even higher), and the lower end is probably the additional branching for controlling the BSDF creation now being called for every ray bounce instead of just once. Because of this, and the fact that there didn't seem to be any overhead in just having the stack based dynamic BSDF components in the integrator loops if I didn't use them, I decided to use this second approach which allowed me to still use the baked BSDF approach if the material definition was simple enough to allow it. This allows great flexibility but at the cost of some code complexity, but I think that's a worthwhile trade-off.

So now any float/Col3f material parameter can be driven by textures, and a decision is made per-material at pre-render time whether complex shading is needed on a per-material basis. If not, the material will pre-bake the BSDF as previously, and within the integrators, this baked BSDF is returned by the material shade() function and used.

If complex shading is needed, then the material can make use of the pointer to the stack-allocated BSDF memory which is passed in to the shade() function, allocate BSDF components as required using this memory and then return this pointer to the integrators. The base infrastructure is now in place for node-based shading networks - the GUI side of things for that is the main work required to complete this.

Based on this new functionality, I implemented a MixMaterial ability to mix or binary-switch materials based on a texture.

I also added UDIM texture atlas support with lazy on-demand reading of textures based on the UV coordinates.

Thursday, 1 May 2014

Progress and Future work

Since the last update, I've thoroughly refactored Imagine's image texture and file-reading infrastructure to now support Image textures at native bit depths (except for 16-bit uint support) instead of always converting them to floats as I did previously. I've basically reversed the way image loading and texture creation work - previously, image textures were always full float and the image classes were filled by the file readers - now, image classes are created at the most suitable or requested bit depth and interpretation (for single channel images) by the file readers, and suitable image texture classes are created based on these resultant images, which then do any relevant conversion in the texture lookup to return full float values for the integrators / BSDFs: for 8-bit uchar textures, a LUT is used to convert and linearise the values, so speed is not an issue, but for half float, casts have to be done, and unfortunately there's a small (but noticeable) overhead here, despite twice as many fitting into cachelines. I've tried doing any filtering / interpolation for the lookup before converting to full float, and then converting only a single half to a float afterwards, and this helps, but this isn't possible for HDR images used for environment lighting as the values are often quite high and can be near the limit of half, meaning you can't average them at that precision. But regardless, this change brings a huge reduction in memory allocation for textures, and it'd now be fairly easy to add texture paging to my texture caching infrastructure.

I've also finally made my DistributedPath integrator usable - I've been trying to duplicate how Arnold splits diffuse and glossy ray bounces for over a year now, and thanks to some diagrams in its documentation, it looked like they branch at every bounce, which was how I wrote my integrator. Doing this however resulted in a stupid amount of final rays that was ridiculously slow, and also made generating decent samples very difficult and expensive (pre-generating and re-using might have been an option). After playing with Arnold over the last six months and benchmarking it with various sample and depth settings, I'm very certain now that it only splits rays on the first bounce. So with this modification, my DistributedPath integrator is now very usable, and for scenes where geometry aliasing isn't an issue and no depth-of-field or motion blur is required, can speed up rendering to a particular noise level fairly significantly compared to pure path tracing: it's helpful when there's lots of indirect illumination, where the diffuse split multiplier can really help to reduce noise. However, you generally need to increase the number of light samples as well to compensate for the reduced number of camera samples sent out.

I'm currently in the middle of implementing a new and much more flexible shading system - Imagine's current one is pretty limited and basic, and basically just bakes down BSDF components to a container BSDF at render start, which is then always fixed for that material. This works very well (in terms of shading speed) for simple shading and non-varying mixes of materials, but makes more complex mixes and blends which are controlled by textures very difficult to code, sample and control, and also makes medium IOR transitions very complicated. I'm prototyping two different methods here to see what the overheads / limitations of each are.

In terms of future work, the task after the shading change is to seriously reduce Imagine's geometry memory footprint, in order to make it more competitive with other renderers. Thanks to Imagine's origin as a sandbox for learning OpenGL and 3D programming, its native GeometryInstance representation is very inefficient for source geometry, and the baked geometry (tessellated version of source geometry) representation is also pretty inefficient, due to OpenGL's requirement that you can only access vertex attributes uniformly, so triangle points, normals and uvs pretty much need to be gathered, leading to up to three times as many points, normals and uvs than the source geometry has. I've had a TriangleGeometryInstance for a while, which I used in order to be able to load the Lucy Stanford model on my laptop which stores pure triangles very efficiently (and doesn't do any OpenGL drawing), but I need to support polygons and Sub-ds correctly efficiently, so quite a bit of work is needed. I'd also like to look into changing the indexing size for geometry, so that for meshes with less than 65,535 vertices, I can index them with ushorts, instead of wasting space using uints - for low LOD representations of geometry, this might be quite useful.

Sunday, 2 March 2014

Volumetric Renders

I've spent a bit more time both getting some decent volumetrics source data (via Mantaflow for the fluid simulations and better self-created procedural clouds for the images below) and improving Imagine's Volumetrics rendering capabilities.

Below are two animations rendered of smoke fluid simulations:

Smoke filling a virtual tank from ImagineRender on Vimeo.

Smoke in Cornell Box from ImagineRender on Vimeo.

I've added importance sampling (not MIS yet - currently there's a separate integrator for anything with volumetrics in it) of mediums, so noise is reduced a bit, and I've optimised several things - calculating the transmission integration through the medium for lighting is now done with double the step distance than Camera and other GI rays use (I could probably do the same for diffuse GI rays in the future), and I've added data window extents to my voxelbuffer format, both of which together give significant speedups (the latter especially with trilinear filtering and sparse volume extent).

I've also added initial emission support, but the results are currently pretty noisy.

Wednesday, 12 February 2014


I've now got scene-wide homogeneous single-scattering and multiple-scattering of heterogeneous volumes rendering in Imagine.

I'm not doing importance sampling yet, so there's room for improvement there, and I'm not taking possible emission into account yet - when I do this I might try and get a black body shader working for the emission values.

The above image is a procedural pyroclastic cloud - for the moment I'm not going to spend too much time modelling volumes, as that's quite a task in itself, but I've implemented a dense grid and at some point, I'll try and implement a sparse grid for better memory-efficiency and storage. I could have used Field3D's versions, but the dense version is trivially simple anyway, and ignoring memory limits and paging in the sparse version, I don't foresee that being that difficult either, plus the dependency on HDF5 is more trouble than it's worth.

I was tempted to try to get OpenVDB integrated, but the Boost and Intel TBB dependencies put me off for the moment, but it seems a nice solution.

Above is a bound single-scattering medium in a Cornell Box, with the cliché spotlight.

For scene-wide mediums, there's an interesting limitation which in hindsight is obvious, but I hadn't thought of before: image-based (or environment) lighting doesn't really work. It definitely doesn't work when you set the light distance to infinity or large values, and while it's usable to a limited extent at much closer values, it's not really practical. So I'm not really sure how useful scene-wide mediums are in practice - in production, they seem to be mainly used for god-rays anyway, so...

Sunday, 26 January 2014

Curve Rendering for Fur and Hair

I've implemented Curve primitive ray intersection in Imagine based off Koji Nakamaru and Yoshio Ohno's 2002 paper "Ray Tracing For Curves Primitive". Basically, it involves projecting each curve's ControlPoint positions into orthographic ray-space, so that the main intersection test can be done as a curve width test in two dimensions down the ray, and then the depth t-test can be worked out.

For straight curve primitives, this is sufficient, but for actual curves with any curvature down the length, splitting the projected curve recursively and performing the intersection test on these split curves is necessary. The recursion level needed to ensure accurate intersection depends on the curvature of each curve.

This recursive splitting obviously has an effect on the performance of the algorithm, so while intersecting straight curves is fairly fast, for a curve that curves gently at around 45 degrees from the root to the tip, a recursive splitting depth of six is needed, which results in 32 recursive splits, and a total of 64 intersections on both the original curve and the recursively split curves.
Which is unfortunate, as to some extent it makes rendering non-straight curves unpractical for reasonable levels (+100,000) of curves.

For the moment, I'm setting the resultant geometric and shader normals from any intersection as facing back along the original camera ray, so that the normal always faces the camera. This is sufficient for very thin curves.

I've also implemented a set of Hair BSDFs for Diffuse and Specular, based on the 1989 Kajiya and Kay paper, which is commonly used. This is optimised for very thin curves, with no effective normal change across the curve, but with a tangent value which can be calculated from the intersection position on the ray.

For the moment, I'm storing curves in an acceleration structure, which works well for very short curves or longer curves which are axis-aligned, but for anything else (long curves going diagonally across dimensions) is bordering on ineffectual, as the resulting axis-aligned boundary boxes for each curve are extraordinarily large, with multiple curves often overlapping each other. I had hoped that spatial partitioning (with curve clipping to boundary boxes) would improve this considerably (it's fairly useful for triangles), but the improvement for using curve clipping with spatial partitioning is not anywhere near as good as I would have hoped (it provides a slight intersection speedup though compared to object partitioning).

So for longer hairs, I'm going to have to think about how to speed this up considerably, as currently rendering long curved curves is orders of magnitude more expensive than I would have liked. It's also going to either involve work to model and simulate hair strand interaction more, or import curves from elsewhere, as the method of generating hairs around meshes (importance sampling positions on each triangle for the root positions) and giving a random tilt or curve only really works with fur-type curves.

Monday, 13 January 2014

Translucent Material for Subsurface Scattering

I've created a translucent material type for Imagine which allows a more artist-friendly way of specifying the colour and transparency of translucent objects, without having to work out the very unintuitive (until you understand what's going on internally) absorption and scattering coefficients that control the medium interaction for scattering events and the resulting transmission.

I'm pretty certain it's not physically-accurate, but it seems to give pretty pleasing results, although I'm still not convinced of the correctness of my implementation, as it's very easy to produce extremely noise results.

I got a copy of Volume Rendering for Production for Christmas, so I'm going to be looking into heterogeneous volumes in the future.