Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multilens cameras #2914

Merged
merged 36 commits into from
Feb 28, 2022
Merged

Add multilens cameras #2914

merged 36 commits into from
Feb 28, 2022

Conversation

JaanWilli
Copy link
Contributor

@JaanWilli JaanWilli commented Aug 13, 2021

Summary

This PR contains the implementation of a physically-based multi-lens camera created as a bachelor thesis
supervised by @LZaw. It partially covers the topics of the issue "Physically-based camera" #1740 and consists of the complete code as well as test scenes.

  • The source code is located in src/appleseed/renderer/modeling/camera/multilenscamera.cpp and src/appleseed/renderer/modeling/camera/multilenscamera.h.
  • The test scenes are located in sandbox/tests/test scenes/multilens camera/.
  • Lens prescriptions to use with the model are located in sandbox/lenses/.

TODOs

It is currently a draft pull request, as there are some open tasks that will be completed once I submit the thesis:

  • Improve lens file handling
  • Make lens file path relative in test scenes
  • Debug & test connect_vertex()
  • Presumably some code revisions to comply with the styling conventions

Problem Description

All cameras currently available in appleseed are approximations of real models and sacrifice realistic camera phenomena for performance. There are however scenarios, where images as if shot on a real camera are desired, for example when combining real images with computer-generated footage.

Solution Description

To achieve photorealistic images, a simulation of a real lens with many lens elements is required. In literature, there are mainly two approaches for that, which I like to call "ray simulation" and "polynomial optics". Below, I list some characteristics of the two.

Ray simulation

  • What: Iterate through the list of lens elements and use Snell's law of refraction to compute the path of the ray.
  • Pro: Pretty straight-forward, reasonable computation overhead, fits well into the current implementation
  • Con: performance depends on lens complexity, intelligent sampling for lens points is required to not waste too many rays

Polynomial Optics

  • What: See lens as a function and polynomially approximate it
  • Pro: Evaluation of polynomial is fast and not dependent on lens
  • Con: Building the polynomial takes time, high approximation error can lead to bad quality, high polynomial degree can lead to high precomputations

In the end, my choice fell on ray simulation, more precisely the model described in "Realistic rendering of bokeh effect based on optical aberrations" with elements from "An Accurate and Practical Camera Lens Model for Rendering Realistic Lens Effects" and Physically Based Rendering: From Theory to Implementation. For more details on the the comparison and the choice, please see Chapter 3 of my thesis.

Implementation

As discussed, this PR extends the cameras by another one and is located in src/appleseed/renderer/modeling/camera. This PR contains the following changes:

  • Add MultiLensCamera that inherits from PerspectiveCamera. The most important parts of it are:
    • Read lens prescription files that the user specifies as a file path. They can either be self-specified or some are currently located in sandbox/lenses/.
    • Modify lens based on user-specified values (focal length, focus distance, f-number)
    • Compute entrance and exit pupil following the approach of "Realistic rendering of bokeh effect based on optical aberrations". This is a good tradeoff between precomputation and ray passage rate.
    • Algorithm to trace a ray through the lens
  • Add MultiLensCameraFactory to CameraFactoryRegistrar
  • Change void spawn_ray(..) to bool spawn_ray(..). This is required, as rays can be blocked by the aperture and should then not contribute to the illumination calculation. This change affects camera.h and all <name>camera.cpp files, as well as src/appleseed/renderer/kernel/rendering/generic/genericsamplerenderer.cpp and src/appleseed/renderer/kernel/rendering/scenepicker.cpp.
  • Move extract_focal_length(..) from PerspectiveCamera::on_render_begin(..) to all childen (pinhole, thinlens, fisheye). This is because MultiLensCamera has its own focal length extraction that allows for not specifying a focal length.

Results

The implemented model can achieve the following:

  • Variable focal length, focus, f-number and custom aperture shapes (like thin lens)
  • Spherical aberration
    image
  • Coma
    image
  • Field curvature
    image
  • Astigmatism
    image
  • Distortion
    image
  • Realistic bokeh with custom shapes (left: thin lens, right: multi-lens)
    image
    image
  • Natural vignetting
    image

It cannot produce:

  • Chromatic aberration: Would require a redesign of the camera and ray implementation, such that the ray carries a wavelength and the camera refracts the rays based on wavelength
  • Lens flare: Efficient solutions exist, but were out of scope for this thesis.

Performance

I spent quite some time analyzing the performance of the implementation in relation to pinhole and thin lens camera. For details, see Chapter 6.2 of my thesis. The main takeaways were:

  • The multi-lens camera is slower in all cases, but the overhead is constant. The more complex the scene, the less severe the overhead.
  • The overhead only depends on the complexity of the lens, the simpler the lens, the closer the rendering time to pinhole and thin lens.
  • The single exit pupil is usually enough to have a small ray failure rate. Custom aperture shapes, however, can block large portions of rays, leading to better rendering times, but also to under-exposed images.
  • A big factor that worsens the times are ray differentials, their inclusion triples the overhead. A toggle to switch off ray differentials is therefore included.

Conclusion

The implemented model is not a thin lens replacement, but offers advantages in some scenarios, especially when bokeh or vignetting is important. Although performance is a bit worse than pinhole and thin lens, the overhead is constant and therefore too significant in complex scenes. With simple scenes though, the multi-lens camera is probably not worthwhile to use.

@LZaw LZaw added the WIP ⚠ label Aug 13, 2021
Copy link
Member

@LZaw LZaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review.
I read all the code, but didn't test it yet.
Generally, it looks good. I think most things are minors.
Maybe it's worth it to have a LensCamera class (or so), which has all the extract_... functions in it, which are used by this camera as well as the thin lens camera (and possible future camera models with lenses).

<parameter name="film_dimensions" value="0.025 0.01875" />
<parameter name="focal_distance" value="1.0" />
<parameter name="focal_length" value="0.035" />
<parameter name="lens_file" value="C:\Users\willi\Documents\appleseed-dev\appleseed\sandbox\lenses\double-gauss-tronnier.txt" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repetition of your TODO: Change to relative paths.

// This software is released under the MIT license.
//
// Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
// Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2021

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f123f3b


typedef ImageImportanceSampler<Vector2d, float> ImageImportanceSamplerType;

class ImageSampler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is part of the thinlenscamera as well.

// Extract autofocus status.
m_autofocus_enabled = m_params.get_optional<bool>("autofocus_enabled", true);

m_derivatives_enabled = m_params.get_optional<bool>("derivatives_enabled", true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was only for testing purposes and can be removed again?

// Extract diaphragm configuration.
m_diaphragm_map_bound = build_diaphragm_importance_sampler(*project.get_scene());
extract_diaphragm_blade_count();
m_diaphragm_tilt_angle =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you move this out of a function?


for (auto lens_iter = m_lens_container.rbegin() + start_index; lens_iter != m_lens_container.rend(); ++lens_iter)
{
LensElement current_element = *lens_iter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Const reference

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 209c818

current_z -= current_element.thickness;

double t = 0; // Parameter, at which ray intersects element
Vector3d normal(0, 0, 0); // Normal vector
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f123f3b

return true;
}

bool trace_ray_from_film(Ray3d& ray, const int start_index = 0, const bool ignore_aperture_shape = false) const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two functions are almost alike. Is there a way to merge them, while still having readable code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They were originally merged, but I split them at some point as it kept confusing me. The problem is that the iteration direction and the index of refraction computation is different.

ray.m_org = transform.point_to_parent(ray.m_org);
ray.m_dir = normalize(transform.vector_to_parent(ray.m_dir));

if (m_derivatives_enabled && ndc.has_derivatives())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure if derivatives should have an enable/disable option.


void extract_lens_file()
{
if (has_param("lens_file"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens in the overall workflow, if the parameter doesn't exist? If you're sure it's always there, remove this check, if it may not exist, adapt the rest of the workflow.

@JaanWilli
Copy link
Contributor Author

Thanks for the first look!
Completed are all the syntax and style issues.
Still left are things that require a bit more thought and testing (memory/speed improvements, function locations, exception handling, ...)

@LZaw LZaw added PR | Squash This PR must be squashed when merged. and removed WIP ⚠ labels Feb 21, 2022
@JaanWilli JaanWilli marked this pull request as ready for review February 21, 2022 18:42
Copy link
Member

@LZaw LZaw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey,
Great work!
Since the CI is currently not working, I manually compiled this PR using Ubuntu with GCC-6, GCC-7 and Clang-8. All compilers didn't throw any warning or error under Debug or Release build.
On top of that, I ran the full testsuite, which resulted in 141 failures, which is exactly the number we had before (see here for the old log)

Thus, from my side, this PR is ready to go.

@LZaw LZaw changed the title Multilens camera Add multilens cameras Feb 28, 2022
@LZaw LZaw merged commit 8c894f2 into appleseedhq:master Feb 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR | Squash This PR must be squashed when merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants