Vulkan with wxWidgets

Alexander Overvoorde wrote a good tutorial for programming Vulkan; this is available at vulkan-tutorial.com. In the tutorial, he used GLFW as the windowing system. This is fine if your application uses one main window with no adornments such as menus and status bars, and no additional windows.

I have modified the Hello Triangle example to use wxWidgets. This corresponds to the code up to the end of the Drawing a triangle section of the tutorial. The source code of my wxWidgets version is available on GitHub.

Here is a description of the changes:

  1. HelloTriangleApplication code is split among VulkanTutorialApp, VulkanWindow, and VulkanCanvas. The vast majority of the code is placed in VulkanCanvas. Specifically, the code in HelloTriangleApplication::run is split into two parts:
    • The line:
      mainloop()

      is handled automatically by VulkanTutorialApp, and to some extent by VulkanWindow.

    • The functionality in initWindow and initVulkan are performed in the VulkanCanvas constructor.
  2. The HelloTriangleApplication::drawFrame code is placed in VulkanCanvas::OnPaint.
  3. The HelloTriangleApplication::onWindowResized code is placed in VulkanCanvas::OnResize. wxWindow::RefreshRect must be called to ensure that the canvas is redrawn every time the window is resized. Calling wxWindow::Refresh or wxWindow::Updateonly causes a redraw to occur when the window grows in size, not when shrinking is size.
  4. Two changes were made regarding exceptions:
    • In the tutorial code, runtime_error exceptions are thrown whenever Vulkan errors are encountered. These are replaced with a new exception type called VulkanException which provides information about the actual Vulkan error that was encountered.
    • The tutorial code simply outputs text messages to std::cout. Because the wxVulkanTutorial code is Windows-based, there is no console to output the messages to, so they are output to a wxMessageBox. This was discussed previously in the post C++ Exceptions and wxWidgets.
  5. The tutorial code contains a templated class called VDeleter. This class automatically handles the destruction of various Vulkan handles. I have not included this class in wxVulkanTutorial. Instead, I destroy the handles in the VulkanCanvas destructor.

Have fun with it.

ChaosExplorer Wrapup

This is the final post in the ChaosExplorer series of posts. ChaosExplorer was created to explore a number of chaotic systems and to create the displays for these systems in fragment shaders using OpenGL and GLSL. Four different fractal formulae were used to generate fractals and their corresponding Julia Sets. From those standpoints, I would call ChaosExplorer a success. The program is not commercial grade; there are many changes that should be made to ChaosExplorer.

  1. There are many more fractal equations that could be used. In fact, Fractal Formulas Researched lists more than 1000 different fractal formulae. There are some duplicates in the list, but still more formulae that you are likely to investigate.When choosing a formula from that list, remember that z0 = 0, so make sure that z1 is not always also 0 or you will simply get a black display. Also, make sure that for formulae containing both a numerator and a denominator, that the denominator does not also calculate out to 0 or you will simply get a solid orange display.
  2. Additional fractals without Julia Sets include:

    Each of these can be generated using GLSL.

  3. ChaosExplorer currently displays different colours based solely on the number of iterations, so there is a step difference between each colour. The colour instead could be chosen based on the value of z after a constant number of iterations. This would potentially produce a much smoother transition between colours.
  4. ChaosExplorer could be refactored to place additional functionality in the panel base classes. It may be possible to entirely eliminate the individual derived panels in all cases except the MultibrotPanel.
  5. There are a few places in the code where magic numbers are used. These may be refactored to use defined constants.

Those are just a few ways that ChaosExplorer could be improved. Since my reasons for creating this program have been met, I will leave those modifications to you.

(z^3+1)/(1+cz^2)

The Fractal dropdown menu in the ChaosExplorer program contains 4 menu items:

  • z^p + c
  • (z^3+1)/(1+cz^2)
  • c*e^z, and
  • z^3-z^2+z+c

Each of these menu items displays the fractal (z0 = 0.0 + 0.0i, varying c over the complex plane). Selecting the first menu item displays the Mandelbrot Set; selecting any of the others displays the fractal for the indicated formula. This post specifically shows the fractal and Julia Sets for the second menu item, whose formula is:

zn+1 = (zn3 + 1) / (1 + czn2)

Here is the fractal display. This is for the region -10.0 ≤ x ≤ 10.0, -10.0 ≤ y ≤ 10.0:

Fractal2

And here are 4 Julia Set displays for points in this fractal:

Julia21

Julia22

Julia23

Julia24

As you can see, the fractal and Julia Sets are very different from the Mandelbrot/Multibrot fractals and Julia Sets.

Julia Sets

Julia Sets are another set of images that can be created by iterating a dynamic equation. If you want all of the ugly mathematics, see the Wikipedia entry on Julia Set. If you take the time to understand all of it, you are a better person than me. For a better explanation, I suggest seeing Julia and Mandelbrot Sets, and Understanding Julia and Mandelbrot Sets.

That’s all fine and good, but how do we create a Julia Set mathematically? Certainly not by attempting to follow the Wikipedia entry! For a Julia Set that corresponds to the Mandelbrot set, we use the same equation, but generate the plot for a different plane. Remember the equation:

zn+1 = znp + c

For the Mandelbrot Set, z0 = 0 + 0i and p = 2. We set c to be each point in the section of the complex plane that we were looking at. For the Julia Set, we set c to a single value in the complex plane, then set z0 to each point in the section of the complex plane we are looking at, and iterate. For every point, c, inside the Mandelbrot Set, there is a connected Julia Set; for each point outside of the Mandelbrot Set, the Julia Set is disconnected.

The most interesting images are generated from points outside the Mandelbrot Set or just inside it. This image is generated for a point near the centre of the secondary bulb:

MJulia1

and this is image is generated for a point inside one of the small bulbs off the main bulb:

MJulia2

This image is generated for a point just outside the Mandelbrot set and slightly about the x-axis:

MJulia3

Here are a couple of images created from the tendrils of the Mandelbrot Set:

MJulia4

MJulia5

and finally, an image from a short distance outside the Mandelbrot Set:

MJulia6

Each value, c, produces a different image. To generate an image for a point in the Mandelbrot Set using the ChaosExplorer program, place the mouse cursor over the point, click the right mouse button, and select the Julia Set menu item. The image is generated in a panel in a new notebook tab. You can also zoom in on the image by selecting an area to zoom, and then selecting the Draw From Selection menu item. The source code and a wiki are available on GitHub.

This is the fragment shader for generating these Julia Sets:

    std::string fragmentSource =
        "#version 330 core\n"
        "uniform vec2 c;"
        "uniform vec2 p;"
        "uniform vec2 ul;"
        "uniform vec2 lr;"
        "uniform vec2 viewDimensions;"
        "uniform vec4 color[50];"
        "out vec4 OutColor;"
        "vec2 iPower(vec2 vec, vec2 p)"
        "{"
        "    float r = sqrt(vec.x * vec.x + vec.y * vec.y);"
        "    if(r == 0.0f) {"
        "        return vec2(0.0f, 0.0f);"
        "    }"
        "    float theta = vec.y != 0 ? atan(vec.y, vec.x) : (vec.x < 0 ? 3.14159265f : 0.0f);"
        "    float imt = -p.y * theta;"
        "    float rpowr = pow(r, p.x);"
        "    float angle = p.x * theta + p.y * log(r);"
        "    vec2 powr;"
        "    powr.x = rpowr * exp(imt) * cos(angle);"
        "    powr.y = rpowr * exp(imt) * sin(angle);"
        "    return powr;"
        "}"
        ""
        "void main()"
        "{"
        "    float x = ul.x + (lr.x - ul.x) * gl_FragCoord.x / (viewDimensions.x - 1);"
        "    float y = lr.y + (ul.y - lr.y) * gl_FragCoord.y / (viewDimensions.y - 1);"
        "    vec2 z = vec2(x, y);"
        "    int i = 0;"
        "    while(z.x * z.x + z.y * z.y < 4.0f && i < 200) {"
        "        z = iPower(z, p) + c;"
        "        ++i;"
        "    }"
        "	 OutColor = i == 200 ? vec4(0.0f, 0.0f, 0.0f, 1.0f) : "
        "        color[i%50 - 1];"
        "}";

If you compare this to the fragment shader for the Multibrot Sets included in the post Mandelbrot Set, you will see few changes.

Summary

This post discussed Julia Sets and showed images of some Julia Sets generated from several points inside and outside the Mandelbrot Set. The fragment shader that is used to generate the images was also shown.

Multibrot Sets

The last two posts have discussed Chaotic Systems and Fractals, and the Mandelbrot Set. As stated in the second of these posts, the Mandelbrot Set is a specialization of the Multibrot Set. These sets are created by iterating zn+1 = znp + c where z, p, and c are complex numbers. c is the coordinate, in the complex plane, of the point being calculated. Points inside the set are bounded as n approaches infinity, and points outside the set are unbounded (tend to infinity) as n approaches infinity. More information is provided in the post on the Mandelbrot Set and in the ChaosExplorer program’s wiki on GitHub.

For the Mandelbrot Set, p = 2. This post will look at other values of p, including both real and complex values, and also the effects of varying the real and imaginary portions of Z0.

The first menu item in the context menu of MultibrotPanel is a submenu containing menu items that set p to integer values between 2 and 10. The next four images show the resulting images for the values 2, 3, 4, and 10.

Mandelbrot

Multibrot3

Multibrot4

Multibrot10

Note that the number of secondary bulbs (the second largest bulbs) in each image is equal to p – 1 where p is the integer power in the iterated equation.

The Animate Real Powers menu item changes the power value in steps of 0.01 from 1.0 to 10.0. Here is a video showing the display when you select this menu item:

The Animate Imaginary Powers menu item changes the imaginary portion of power in steps of 0.01i from -1.0i to +1.0i. Here is a video showing the display when you select this menu item starting from the display of Power = 3:

The Animate Z0 Real menu item changes the real portion of z0 from -1.0 to +1.0 in increments of 0.01. Here is a video of the result with Power = 6:

And finally, the Animate z0 Imaginary menu item changes the imaginary portion of z0 from -1.0i to +1.0i in increments of 0.01i. Here is a video of the result with Power = 3:

This ends the exploration of the Mandelbrot and Multibrot Sets. In the next post, we will look at Julia Sets related to the Mandelbrot Set.