From b526b02e7be4eab7c9d4feac0ceb6f53f605ff2b Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Fri, 13 Dec 2024 20:03:53 -0500 Subject: [PATCH] More writing --- .../1-introduction/index.mdx | 34 ++++++--- .../2-transforms/index.mdx | 72 +++++++++++-------- .../3-log-density/index.mdx | 30 ++++++-- 3 files changed, 93 insertions(+), 43 deletions(-) diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx index c9ecc16..fc88bb5 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx @@ -170,11 +170,11 @@ Thus, by applying the functions in our system to "fixed points," we will find th However, this is all a bit vague, so let's work through an example.
- But if you want a bit more math... + If you want a bit more math first... - ...then it's worth mentioning some information I've skipped over. + ...then there are some details worth mentioning that I've glossed over so far. - First, the Hutchinson paper explains that the functions $F_i$ must be _contractive_ for this result to hold. + First, the Hutchinson paper requires that the functions $F_i$ be _contractive_ tor the solution set to exist. That is, applying the function to a point must bring it closer to other points. However, as the Fractal Flame algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will degenerate and produce a bad image. @@ -200,7 +200,7 @@ $$ ### The chaos game Next, how do we find the "fixed points" we mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)" -that will give us points in the solution set: +that will give us points in the solution set. $$ \begin{align*} @@ -213,7 +213,11 @@ $$ \end{align*} $$ -Let's turn this into code, one piece at a time. +:::note +In effect, the chaos game algorithm implements the "finite compositions of $F_{i_1..i_p}$ mentioned earlier. +::: + +Now, let's turn this into code, one piece at a time. First, the "bi-unit square" is the range $[-1, 1]$. We can : @@ -272,10 +276,13 @@ I think the paper has an error, so I'm choosing to plot the image in a way that's consistent with [`flam3` itself](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441). -## Weights +### Weights -Finally, we'll introduce a "weight" parameter ($w_i$) assigned to each function, which controls -how often that function is used: +Finally, we'll introduce a "weight" ($w_i$) for each function that controls how often we choose +that function during the chaos game relative to each other function. + +For Sierpinski's Gasket, we start with equal weighting, +but you can see how changing the function weights affects the image below: import randomChoiceSource from '!!raw-loader!../src/randomChoice' @@ -288,4 +295,13 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted"; import GasketWeighted from "./GasketWeighted"; import {SquareCanvas} from "../src/Canvas"; - \ No newline at end of file + + +## Summary + +Studying the foundations of fractal flames is challenging, +but we now have an understanding of both the mathematics +and implementation of iterated function systems. + +In the next post, we'll study the first innovation that fractal flames +bring: variations. \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx b/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx index 1979e4f..6dfa42f 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx @@ -6,20 +6,22 @@ authors: [bspeice] tags: [] --- -Now that we have a basic chaos game in place, it's time to spice things up. Transforms and variations create the +Now that we have a basic chaos game in place, it's time to spice things up. Variations create the shapes and patterns that fractal flames are known for. -:::note +:::info This post uses a set of [reference parameters](../params.flame) to demonstrate the fractal flame algorithm. If you're interested in tweaking the parameters, or generating your own art, [Apophysis](https://sourceforge.net/projects/apophysis/) can load that file and you can try tweaking things yourself! - -This post covers section 3 of the Fractal Flame Algorithm paper ::: -## Transforms and variations +## Variations + +:::note +This post covers section 3 for the Fractal Flame Algorithm paper +::: import CodeBlock from '@theme/CodeBlock' @@ -28,8 +30,8 @@ playing the chaos game leads to an image of Sierpinski's Gasket. Even though we the image it generates is exciting. But it's still not nearly as exciting as the images the Fractal Flame algorithm is known for. -This leads us to the first big innovation of the Fractal Flame algorithm: using non-linear functions -for the transforms. These functions are known as "variations": +This leads us to the first big innovation of the Fractal Flame algorithm: applying non-linear functions +after the affine transform has happened. These functions are called "variations": $$ F_i(x, y) = V_j(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i) @@ -39,10 +41,10 @@ import variationSource from '!!raw-loader!../src/variation' {variationSource} -Variations, labeled $V_j$ above, are functions just like transforms (we use $j$ to indicate a specific variation). -They take an input point $(x,y)$, and give an output point. However, the sky is the limit for what variation functions do in between -input to output. The Fractal Flame paper lists 49 different variation functions, -and the official `flam3` implementation supports [98 different functions](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c). +Just like transforms, variations ($V_j$) are functions that map $(x, y)$ coordinates to new coordinates. +However, the sky is the limit for what we can do in between input and output. +The Fractal Flame paper lists 49 different variation functions, +and the official `flam3` implementation supports [98 different variations](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c). To draw our reference image, we'll focus on four variations: @@ -59,16 +61,16 @@ import linearSrc from '!!raw-loader!../src/linear' {linearSrc} :::tip - -In a way, we've already been using this variation! The functions that define Sierpinski's Gasket +In a way, we've already been using this variation! The transforms that define Sierpinski's Gasket apply the affine coefficients to the input point, and use that as the output point. - ::: ### Julia (variation 13) -This variation is a good example of the non-linear functions the Fractal Flame Algorithm introduces. -It still receives an input point $(x, y)$, but does some crazy things with it: +This variation is a good example of the non-linear functions we can use. It uses both trigonometry +and probability to produce interesting shapes: + +TODO: Connection with the julia set? $$ \begin{align*} @@ -91,8 +93,8 @@ import juliaSrc from '!!raw-loader!../src/julia' ### Popcorn (variation 17) -Some variations rely on knowing the transform's affine coefficients; these are known as "dependent variations." -For the popcorn variation, we use the $c$ and $f$ coefficients: +Some variations rely on knowing their transform's affine coefficients; they're known as "dependent variations." +For the popcorn variation, we use $c$ and $f$: $$ V_{17}(x,y) = (x + c \cdot \text{sin}(\text{tan }3y), y + f \cdot \text{sin}(\text{tan }3x)) @@ -104,7 +106,7 @@ import popcornSrc from '!!raw-loader!../src/popcorn' ### PDJ (variation 24) -Some variations have extra parameters that the designer can choose; these are known as "parametric variations." +Some variations have extra parameters we can choose; these are known as "parametric variations." For the PDJ variation, there are four extra parameters we can choose: $$ @@ -132,14 +134,11 @@ import blendSource from "!!raw-loader!../src/blend"; {blendSource} -With that in place, we have enough to render a first full fractal flame. We'll use the same -chaos game as before, but use our new transforms and variations to produce a dramatically different image: +With that in place, we have enough to render a first fractal flame. We'll use the same +chaos game as before, but our new transforms and variations produce a dramatically different image: :::tip -This image is interactive! The sliders change the variation weights ($v_{ij}$ parameters) -so you can design your own image. - -Try using the sliders to find which parts of the image each transform controls. +Try using the variation weight sliders to figure out which parts of the image each transform controls. ::: import {SquareCanvas} from "../src/Canvas"; @@ -149,8 +148,8 @@ import FlameBlend from "./FlameBlend"; ## Post transforms -Post transforms introduce a second affine transform, this time _after_ variation blending. -We'll use introduce some new variables, but the post transform function should look familiar by now: +Next, we'll introduce a second affine transform, known as a post transform, that is applied _after_ variation blending. +We'll use some new variables, but the post transform function should look familiar: $$ \begin{align*} @@ -163,11 +162,11 @@ import postSource from '!!raw-loader!./post' {postSource} -The image below starts with the same initial transforms/variations as the previous fractal flame, +The image below uses the same transforms/variations as the previous fractal flame, but allows modifying the post-transform coefficients.
- If you want a challenge... + If you want to test your understanding... Challenge 1: What post-transform coefficients will give us the previous image? @@ -180,10 +179,23 @@ import FlamePost from "./FlamePost"; ## Final transforms +Our last step is to introduce a "final transform" ($F_{final}$) that is applied +regardless of which transform function we're using. It works just like a normal transform +(composition of affine transform, variation blend, and post transform), +but it doesn't change the chaos game state: + import chaosGameFinalSource from "!!raw-loader!./chaosGameFinal" {chaosGameFinalSource} import FlameFinal from "./FlameFinal"; - \ No newline at end of file + + +## Summary + +Variations are the fractal flame algorithm's first major innovation over previous IFS's. +Blending variation functions and post/final transforms allows us to generate interesting images. + +However, the images themselves are grainy and unappealing. In the next post, we'll clean up +the quality and add color. \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx b/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx index 5c435d7..924bbec 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx +++ b/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx @@ -11,8 +11,8 @@ to a specific pixel, and color in that pixel. This works well for simple function systems (like Sierpinski's Gasket), but more complex systems (like our reference parameters) produce grainy images. -Every time we "turn on" pixels that have already been enabled, we're wasting work. -Can we do something more intelligent with that information? +Every additional time we plot a pixel, we're wasting work. +Can we do something more intelligent instead? @@ -23,8 +23,12 @@ This post covers sections 4 and 5 of the Fractal Flame Algorithm paper ::: To start with, it's worth demonstrating how much work is actually "wasted." -We'll render the reference image again, but this time, set each pixel's transparency -based on how many times we encounter it in the chaos game: +Previously, pixels would be either transparent or opaque depending on whether +we encountered them while running the chaos game. + +We'll render the reference image again, but this time, keep track of how many times +we encounter each pixel during the chaos game. At the end, we'll "paint" our final image +by setting each pixel's transparency based on how frequently we encounter it: import CodeBlock from "@theme/CodeBlock"; @@ -40,6 +44,24 @@ import {paintLinear} from "./paintLinear"; ## Log display +Using a histogram to paint our image is definitely a quality improvement, +but it produces "ghostly" images. In our reference parameters, the outer circle +is preserved, but the interior appears to be missing! + +To fix this, we'll introduce the second major innovation of the fractal flame algorithm: tone mapping. +This technique is used in computer graphics to adjust for the fact that +people perceive brightness on a logarithmic scale. + +As a concrete example, high dynamic range (HDR) photography uses this technique to capture +nice images of scenes with very different brightness levels. If you want to take a picture of something dark, +you need a long exposure time. However, long exposures lead to bright spots that "wash out" and become nothing but white. +If we take multiple images using different exposure times, we can blend them together to create +a final image where everything is visible. + +TODO: HDR link? + +In fractal flames, this "tone map" is accomplished + import paintLogarithmicSource from "!!raw-loader!./paintLogarithmic" {paintLogarithmicSource}