Stochastic "image-from-grain" film simulation pipeline

Hi LGG community. I want to preface this with I am an engineer, i have learnt a lot about colour science, grading, etc, but in the end i understand maths, physics, and statistics. My field of research is in hardware accelerated stochastic rendering and I have used my relevant skill set to develop a full image-from-grain film simulation pipeline, fully simulating the grain itself, the chemical process in the emulsion process, the path traced light diffusion that causes halation, you name it I am simulating it.
I want this thread to serve as a jumping off point for potential further avenues of testing, development, and use case. I want to present to you the work if have done, the findings, and the results, as well as get some feedback from those of you with much more knowledge on how film is used (and abused) by film makers to achieve their vision as while the maths might be sound and match expectation, it is the feeling, the mood, the texture that film brings that artists love, and as an engineer I cannot fully ensure those needs are met.

I will break down the results in batches. Starting with the grain itself, and ending with the full look and feel of the pipeline. I will be using Kodak Vision3 500T as it's immense level of chemical engineering excellence provides a great deal of opportunity to demonstrate capabilities of stochastic rendering. I will also demonstrate the very new Kodak Verita 200D specifically for daylight 5200K scenes as these types of scenes really show what the 200D was made for vs the 3200K tungsten balanced 500T. I have Portra 800 and Gold 200 as well but those are generally used for Photography, I simulated them as "stress tests" of my engine essentially, to push the physics and see what happens.

This is by no means a completed project, it is a work in progress and i merely wish to share with you guys the state in which it is in, gather some feedback, and perhaps develop it towards an actual product if it is something people might want.

Rendering fundamentals:

Now to begin, a brief description of the engine and what stochastic rendering really is.

Most digital film emulation looks at a digital pixel and asks, "How do we make this look like film?" relying on static color mapping and 2D procedural noise or scanned overlays for grain.

My engine asks, "What physically happens when light energy hits this specific chemical medium?"

Stochastic rendering, at its core, is about probability and physical randomness. Real film isn't a uniform grid of pixels; it's a chaotic, three-dimensional suspension of silver halide crystals of varying sizes distributed through layers of gelatin. In my pipeline, the grain isn't an effect applied over the image at the end—it is the physical structure through which the image is actually formed. The engine generates these randomised crystal distributions using stochastic models.

Instead of simple RGB mapping, the engine calculates how scene-linear light interacts with this probabilistic structure. The simulated light energy scatters, and the crystals react and form dye clouds based on the physical sensitometry of the film stocks. The halation you see isn’t a radial blur node; it is the calculated byproduct of light energy physically bypassing the grain, hitting the anti-halation backing, and scattering back up into the emulsion layers. This distinction is important as RGB light has already undergone pre-processing by the digital camera the image was taken from, colour space transforms, etc. To properly simulate film's reaction, you have to try to being it back to what physical film would see.

Because the engine calculates the image from physical principles, the behaviors we associate with film—density-dependent grain structure, subtractive dye crosstalk, and the way saturated colours twist in the shoulder—aren't faked. They emerge naturally from the physics of the simulation.

I will start by showing you the raw stochastic grain structure before we move onto the dye layers.

1787131829196.jpeg

pictured above is a zoomed in image of a dark area in a frame. The grain structures are formed from 16mm Vision3 500T with forced under exposure to ensure visible, crisp grain (dye bleed is forced off).

Moving on to the dye layers:

Once the physical grain sites are exposed, the engine models the actual chromogenic chemistry of the ECN-2 development process.

In real color negative stock, metallic silver is completely bleached away, leaving behind microscopic dye clouds formed by oxidized developer reacting with color couplers in the three primary emulsion layers:

  • Cyan-forming layer (Red-sensitive)
  • Magenta-forming layer (Green-sensitive)
  • Yellow-forming layer (Blue-sensitive)
Instead of applying standard RGB curves or arbitrary color twists, the dye formation is governed directly by the stock's published sensitometric data—calculating the density response (D) against log exposure (log E) across the toe, straight-line, and shoulder regions for each individual layer.

Subtractive Density & Unwanted Absorptions​

The important difference between additive digital RGB and physical film comes down to how real-world dyes transmit light.

Real photographic dyes are not mathematically pure optical filters:

  • Cyan dyes have secondary unwanted absorptions in the green and blue wavelengths.
  • Magenta dyes have minor secondary absorptions in the blue wavelengths.
Most digital workflows try to mimic this using static 3x3 crosstalk matrices or 3D LUTs. In this pipeline, the final transmitted image is calculated by physically passing light through the accumulated subtractive dye densities.

Because the densities stack subtractively, the complex color interactions you see in real film—such as saturation compressing in the highlights as layers approach D_max, and hue shifts across high dynamic range ramps—emerge organically from the chemical curves rather than manual grading approximations.

Below are 3 images taken from the Arri Alex 35 sample footage. One shows an underexposure of 3 stop, one a neutral exposure, and one an over exposure of 3 stops. This test demonstrates the dye depletion simulation showing how as exposure drops, the contrast flattens as the image falls into the toe, and conversely as exposure climbs, the dyes become "depleted", and each channel hits it's D_max at slightly different rates showing varying levels of de-saturation as they climb into their respective shoulders. Pictured from -3 up to +3 stops.

Note: grain is reduced for visibility.

Screenshot 2026-08-19 at 12.38.56.jpegScreenshot 2026-08-19 at 12.39.40.jpegScreenshot 2026-08-19 at 12.38.28.jpeg

Continuing on to "lighting" behavior:

This is a more complicated task, to balance remaining "true to life" and "looks good" for outdoor daylight scenes Auto normalising the midpoints provides a better image out of the box, but defeats the true look of 500T's cool blue cast in daylight scenes. So I use a toggle for auto balancing and not, with it off one has to either manually go set up a Chroma Adaption node, or if they WANT the cool blue cast, they can keep it.

Here i want to demonstrate the difference between 500T and Verita 200D. The 500T has a very wide open, roughly 14 stop, dynamic range, while the 200D is heavily constrained, opting to go for the nostalgic mid century look ,boosting contrast and crushing shadows. Another interesting effect of this is that on skin tones. I don't know quite enough to fully explain it, admittedly, but to the best of my knowledge as the skin tones near the toe due to exposure, they also get crushed down. I did not adjust the exposure between the 500T and 200D, i merely dropped it by 4 stops to as to (crudely) bring the dynamic range to about 13 max (Arri Alexa 35 has 17 stops in the sample footage i am using if i am not mistaken)

Screenshot 2026-08-19 at 14.17.36.jpegScreenshot 2026-08-19 at 14.24.58.jpegScreenshot 2026-08-19 at 14.24.27.jpeg

Above pictured is, in order, Direct Arri Raw to Rec.709 though CST, Vision3 500T, Verita 200D. The two film images are being auto balanced, which si the equivalent of putting the relevant filter over the lens when shooting. The compressed JPEGs might make it slightly difficult to see but the 500T is very willing to open up those midtones and stretch them into high brightness, while the 200D is compressing them down into the toe and crushing information while it goes. Every variable can be tweaked and meddled with so if the grading work is terrible i apologise, i very much suck at grading, the images are straight out of the engine. The 200D's skin tones can easily be fixed by slightly pushing exposure, over exposing the sky while retaining mid tone colours. The reason the corrective filters have to be applied by the maths is that to properly expose film two things need to be known, 1) the balance illuminant that Kodak or the relevant film manufacturer builds into their film chemistry, and 2) the Scene illuminant. Now if it were real life, the light hitting the film would have the real life correct scene illuminant, but because this was FIRST captured on a camera (in this isntance an ALEXA 35) and then converted to RGB with white balance already applied for 0.18 neutral grey, that scene information is lost entirely. So that begs the question, how do you properly expose the camera if you dont know the scene illuminant? You dont. I have to tell the engine what the illuminant is or let it auto balance. The way i am doing the maths for the RGB->Light Spectrum is losely based on the paper "A Low-Dimensional Function Space forEfficient Spectral Upsampling" with some custom modifications. Without being told, the engine has to auto white balance the neutral back and hence the images above are relatively similar.
Spatial Light Transport and Halation:

Now that we have established the stochastic grain structure, the dye chemistry, and the spectral response, the final major physical component to address is spatial light transport—specifically, what happens when high-intensity lighting interacts with the physical depth of the emulsion stack.

In many digital workflows, halation is approached as a localized spatial composite, typically by blurring the red channel around high-exposure areas. For this engine, I wanted to see what happens when halation is treated strictly as a physical light transport phenomenon calculated at the grain level.

When calculating the interaction of high-intensity light (like a bare practical bulb or a blown-out window) with the physical film plane, photons do not just stop at their exact X,Y coordinate. A percentage of high-energy photons penetrate completely through the top blue-sensitive and green-sensitive layers, pass through the red-sensitive layer, and strike the film base.

Cinema negative stocks like Vision3 500T and Verita 200D utilize a carbon Rem-Jet backing specifically to absorb this excess light. However, at extreme exposures, the backing cannot absorb 100% of the energy.

The engine simulates the path of these unabsorbed photons as they strike the backing and scatter back up into the film plane. Because the red-sensitive (cyan dye-forming) layer sits at the very bottom of the emulsion stack, directly against the base, it naturally absorbs the vast majority of this scattered bounce light.

By calculating this diffusion stochastically based on light transport rather than applying a spatial blur, two specific lighting characteristics emerge organically in the render:

  1. Energy-Dependent Propagation: The halation does not have a fixed radius or a linear fall-off. The distance the scatter propagates across the film plane is tied directly to the calculated physical energy of the light source. A +6 EV highlight will cause a physically wider photon scatter than a +3 EV highlight, dynamically wrapping around high-contrast edges.
  2. Micro-Contrast Decay: Physical bounce light doesn't just create a red glow; it scatters into adjacent, otherwise unexposed silver halide crystals. This eats into local micro-contrast and organically lifts the density of the shadows immediately surrounding a bright light source, accurately reproducing the optical "bloom" that alters edge contrast in physical photography.

There is plenty more to this, and I am continuing to work further and further into the simulation, expanding the default stocks, and properly simulating what I dub the "SUPER" stock, which is just a stock you can change however you want, whatever properties you wish to change are made available. This is a work in progress and feedback, questions, request for specific scene demos, whatever is welcome and encoraged. I am still very much a computer engineer and lack a lot of artisitc knowledge and experience that cannot be gained from color sciense textbooks or research papers. If i made any mistakes or have misunderstood a fundemental concept pelase let me know, I am still but a student.

If you would like me to demonstrate any specific phenomena, practices (pushing the film, bleach bypass, etc.) please let me know and i will reply ASAP.
 
Note: I wrote the post over a couple of days due to time constraints so i forgot to add the comparisons between tabular grains and "perfect" grains, 16, 35, and 65mm film, etc. If this is something anybody would be interested feel free to let me know and i will include those comparisons.
 
@Jordan Wright Very exciting and interesting stuff!

I'd love to pick your brain! How are you processing the material in terms of software? Is this being implemented in dedicated software, or a plugin? How fast is image processing? Are you normalizing input color primaries from the digital cameras when processing in scene linear, if so, what's your working space? Also, how are you calculating grain vs raster? Have you done any profiling or is all of your work based on film technical publications? What is your pipeline for adding gamma/contrast?

Thanks again for sharing your work, please feel free to DM me if you'd like to share anything privately!
 
@Jordan Wright Very exciting and interesting stuff!

I'd love to pick your brain! How are you processing the material in terms of software? Is this being implemented in dedicated software, or a plugin? How fast is image processing? Are you normalizing input color primaries from the digital cameras when processing in scene linear, if so, what's your working space? Also, how are you calculating grain vs raster? Have you done any profiling or is all of your work based on film technical publications? What is your pipeline for adding gamma/contrast?

Thanks again for sharing your work, please feel free to DM me if you'd like to share anything privately!
Hi Al, no problem at all.

The software stack is a C++ wrapper and dedicated hardware accelerated compute shader Optimized in Metal for Apple silicon, and GLSL for AMD and Nvidia, it runs as an OFXPlugin within DaVinci resolve.

In terms of speed it heavily depends on how powerful your GPU is, it is in the end a very heavy physics simulation so anywhere between 500ms to 3-5 seconds for 4K 65mm film per frame. Unfortunately this is unavoidable on modern computer architectures. a great way to speed it up would be a dedicated acceleration box using an FPGA but that is another beast all on it's own, but one I am very much considering out of pure interest.

For the normalisation, I use a few although I am shifting entirely into the ACEScg space as ACES has built hundreds of DIT matrices already so I need not hand code them. I output Cineon Film Log preferably but also have been experimenting with ACES in the output space. Obviously linear too.

The grain is independent of raster as 16, 35, 65mm, etc. stocks all have the same grain size and I work in a continuous mathematical space instead of pixel bound space. I.e. a certain stock might specify grain size of 5um, weather it is 16mm or 65mm the grain itself is the same size so relative to the raster they look smaller but in their actual "space" in which they live in the engine they are the same physical size (5um).

I haven't yet been able to do profiling work unfortunately, but I have been using films shot on the relevant stock and any sample footage/photos I can get my hands on to visually verify. I do plan on getting a roll of 500T and taking a look but I'm a bit strapped for cash as a student so that is a future endeavour. Right now it is statistically and mathematically verified, empirical verification is something I would need somebody with far more experience with film and grading to help me get.

In terms of output I currently have the negative, 2383, Cineon Film Log, and ACEScg as outputs that can be selected. This layer is essentially the ARRISCAN part, because it's all colour science at this point removing the film's tint and simulation a print stock is fairly mundane. Although it does also use a secondary exposure step if you wish to output to an actual print stock like 2383 hereby it simulates the exact same steps as the negative but using the negative and printer lights and all that.

I hope that explains, let me know if anything is unclear and I'd be happy to help!
 
Very good; I’d like to run some tests as well.
I’d like to offer a suggestion for distributing the plugin: mcnexus.app is something I created to help the independent developer community.
 
Hi. Very nice stuff. Would it be possible to test your plug-in?
Hi Luca, I am working on getting it into a nice state to be tested, right now it is still very much a physics simulator and as such the UI/UX is very bare bones and it requires a lot of tweaking to get a proper look. I will update the thread when it is in a stage whereby it can be tested for general purpose workflows.
 
Very good; I’d like to run some tests as well.
I’d like to offer a suggestion for distributing the plugin: mcnexus.app is something I created to help the independent developer community.
Hi Magno, if i end up producing this as a plugin for release I will definitely look to use mcnexus, thank you very much for the recommendation and bringing such a useful platform for us independent devs!

As far as testing goes it is currently still very raw maths and physics and their variables, I am working on ensuring statistical significance with real world film and at that point I will look at who the target market might be, then develop a UI for them.

I will update the thread when it is ina. general purpose testing state!
 
Super cool stuff!
I spent a few months trying to create a similar grain model and ran into the same ceiling: performance. It’s highly intensive to do this correctly and at the end of the day, most users wouldn’t be able to tell the difference between my grain model and other plugins like filmbox (which use overlays or other faster less accurate methods).

This caused me to abandon the project but nonetheless was fun to work on.
 
Nice write-up! Very interesting read and it sounds like you're creating a tool with a lot of potential.

In terms of speed it heavily depends on how powerful your GPU is, it is in the end a very heavy physics simulation so anywhere between 500ms to 3-5 seconds for 4K 65mm film per frame. Unfortunately this is unavoidable on modern computer architectures.

Your simulation sounds very intensive, but one thought is that many users don't require perfection. They just need a result that's "excellent enough" for their use case. Physically accurate film emulation is a powerful selling point, but if the rendering cost is too great, people might find it a non-starter. Since you're still in the middle of development, it might be worthwhile to build in ways to control the precision or resolution of your simulation so that each user can choose the right balance of physical accuracy and speed for their project.
 
Nice write-up! Very interesting read and it sounds like you're creating a tool with a lot of potential.



Your simulation sounds very intensive, but one thought is that many users don't require perfection. They just need a result that's "excellent enough" for their use case. Physically accurate film emulation is a powerful selling point, but if the rendering cost is too great, people might find it a non-starter. Since you're still in the middle of development, it might be worthwhile to build in ways to control the precision or resolution of your simulation so that each user can choose the right balance of physical accuracy and speed for their project.
Hi Jason,

Yeah it's a very pertinent issue I've been facing, I have since built in a few more physics based simulators for things like DIR coupler kinematics and such so what I've started doing is having options to chose between the different "levels" of simulation vs emulation options, so for example for light transfer and halation you can now chose between a ray traced based method and a holistic estimate method. I have also found some very cool optimisation such that my MacBook m4 pro can hit about 12 fps at UHD 35mm, roughly 5fps with IMax 75mm 15 perf.

One unfortunate reality is that because the image itself is formed from the grain, over scanning is basically a requirement and it is also where the vast majority of the performance goes. As with real life scanners such as the ARRISCAN, one needs to perform subpixel sampling and the resolution therein very much dictates the overall resultant look. In accordance with the Nyquist criteria, you need to sample at at least 2x the desired resolution because below that the crystals will begin to alias and create rather hard digital noise rather than the smooth organic look that is actually under the hood, and it is this very super sampling, or over scanning, that is the single biggest performance hit.

For some context scale, A true IMAX 15-perf uncompressed frame is about 590gb of raw data that needs to be compressed back down into 4k, so a lot of information needs to be packed super tightly and Nyquist unfortunately doesn't play favourites. I am, however, exploring some other rastering techniques that can provide a similar, albeit less accurate look.

And the final point on this is, as a student in computer engineering, the commercial world of filmmaking is quite outside my knowledge. I am unsure if this is even something your average producer would want if I am being honest. While it is certifiably more "realistic" than existing solutions on account of the physics based simulation rather than empirical emulation, those solutions give easy, initiative, beautiful looks out of the box. I am starting to lean more away from a "look generator" to a ground up physics based film simulator and the markets where those are valuable. My next steps are to try get some 50 or so frames scanned from digital into film, perform a statistical validation to prove my method falls within the variance seen in physical film, and at that point my goal is achieved. Who to, or even if I must, sell it to is yet unknown. I feel there could be some cool use cases as an alternative to a film out for those with a lower budget but still looking for a film out, VFX workflows could be cool too as you can export the metadata of the simulator and as it is an OFX plugin it can go straight into nuke, and with that metadata you can ensure your VFX is baked into the same physical silver halide crystals the original shot is baked into, making it all feel more organic, and I'm sure there might be other avenues.

TLDR; I'm working actively to make it more performant and give users the ability to chose their level of physical simulation. The end goal from my perspective is a true, ground up digital twin of film, as opposed to something that can spit out 500T perfectly colour graded etc in 10ms. I would like the system to be able to perfectly simulate 500T, and any other film stock existing or entirely boutique, and to provide value where it is needed. That last point I am yet to figure out but as a student project it is the issue I will have to look at next as designing for the user is also an important part of the engineering process. If you have any thoughts or ideas on use case or adaptations I can make to meet other use case requirements I would love to know, this knowledge sits firmly outside of my field unfortunately.

Thanks,
Jordan Wright
 
Nice write-up! Very interesting read and it sounds like you're creating a tool with a lot of potential.



Your simulation sounds very intensive, but one thought is that many users don't require perfection. They just need a result that's "excellent enough" for their use case. Physically accurate film emulation is a powerful selling point, but if the rendering cost is too great, people might find it a non-starter. Since you're still in the middle of development, it might be worthwhile to build in ways to control the precision or resolution of your simulation so that each user can choose the right balance of physical accuracy and speed for their project.
Maybe For VFX plates matching to film, or doing a " digital film out" in a fat ass render pass that will take an eternity could be a use case?



im on the side more of wanting stuff to look good and accuracy not that important, so I guess this tool isn't for that
 
Maybe For VFX plates matching to film, or doing a " digital film out" in a fat ass render pass that will take an eternity could be a use case?



im on the side more of wanting stuff to look good and accuracy not that important, so I guess this tool isn't for that
Another option I do have is to use my pipeline to automatically create LUTs for the "look" part which can respond better to real world conditions, essentially quantising the engine to provide a (mostly) physically accurate look while also being incredibly light, reserving the core engine for exactly as you said, "digital film out" and VFX pipelines. I will look into this...
 
Back
Top