For me, two different options have been available how to enable the motion into a scene. For the
animation, it is crucial to have a time-frame set. My first attemt was to save the
animation-length (exposure-time) on the scene and then calculate all the motions with an uniform
sampled time between 0 and the specified time. With this approach I had to realize that it is
not that easy to calculate and expand the bounding boxes for all the moving shapes correctly
because the exposure time was not available during this stage. After rethinking my approach, I
decided to store the complete motion on each moving object (e.g. if the ball is moving 4 units
on x, the translation is stored as Vector3f(4.0,0.0,0.0)). This simplified the
procedure about finding the maximal bounding box in which the objects lies during the complete
motion.
Another important thinking was that I want to be able to move the camera as well the objects to be rendered. Of course the moving/rotation of the camera can also be obtained by moving/rotating all the rendered objects in the other way. However, this is not the state of the art and it is simpler to only animate the camera instead of all the other objects. (Additionally: by only moving the camera, no bounding box at all needs to be adjusted/extended. This is only needed when objects move its positions.)
My key idea is to only enable the motion in the integrator. For each object which is moving, a new motion-class can be added. All the properties which are specified as currently in the scene-xml will be taken as start-attributes for this object. In the motion class it can then be specified which property is changed (position, rotation, size, albedo, ...). For the purpose of my final image, I want to apply this motion-blur on a sphere which is rolling on the billiard table. Therefore, I need to implement only the translation as well the rotation. (Also it is enough to implement the motions on the sphere- and camera-class. The same approach can the applied ot other classes as needed).
Following properties will be available for the scene to add the motion-blur:
enableMotionBlurmotionSamplingByFramesampleCount-Property of the
scene-sampler. Then for each frame one ray is generated per pixel. To render with this
sampling-method, set this property to true. The other sampling method is to completely
uniformly sample the time between \([0,1]\) for each ray. Then the property can be set to
false or it can be left out. (In my experience for larger motions the uniformly-sampling
achieves faster results with less samples. However, for only small motions one can give the
frame-sampling a try. Then the sample-count does not need to be that high to render a smooth
motion)
Each object (shape, camera) will have the option to append several instances of the new class "motion" (of course with some restrictions also captured here). This will roughly be structured as described following:
enum typeTranslation
Vector3f directionRotation
Vector3f axisVector3f rotationOriginfloat angleHere is the main logic of the motions. Each motion-type is implemented in another class. These
all inherits from class Motion;. The main part of this class is the method
Eigen::Affine3f Translation::sample(float time). This returns a
transformation-matrix for the given motion at given time. This transformation-matrix can then be
applied to the vertices in the scene to translate at the correct target location.
This class extends the objects in the scene with the motions read from the xml-file. This motions are then stored with the correct motion-class to be sampled from during the render time. For the current version, it is only possible to specify one animation per motion-type. Also the order is always: rotation before translation.
The ray class is extended by a time-property (float). This is 0 in case the
motion-blur is disabled. Otherwise it might be any value between 0 and 1 (uniformly sampled).
This sampling process is applied when the primary-rays are generated through the renderer in the
sample-method of the camera. When the camera is sampled, it is also checked if there exists a
motion (currently only translations-supported) for the camera. If so, the origin of the ray is
adjusted to the correct camera-center after the translation. (Important: when only the camera is
translated, no bounding-box of any object needs to be adjusted/extended)
The tree build with the bounding-boxes to speed up the intersection-check is build after reading the scene and before rendering it. It is also build only once and my intention was that it is not good to rebuild this tree for each sampled time. So my idea was to adjust the bounding-box of the elements accordingly before the tree is built.
Before I adjusted the bounding-boxes, the behaviour was quite different between the meshes and the sphere. Because the mesh is saved with each triangle itself, each triangle has a bounding-box on it's own. So by only moving the sphere-mesh a little as an motion, it was not visible anymore. The sphere-class itself has only one big bounding box around the sphere. So only by moving it to far the effect of this became visible.
The bounding boxes can be adjusted in the void activate() const-method. When this
method gets invoked, the complete XML-file has been read and the motions are all available
through the Moveable-class. For the mesh it can be then iterated through all the triangles and
compute the position of it after the motion at time 1. Then the bounding box can be extended
correspondingly. The sphere has a pretty equal approach but it can be done much simpler. The
current bounding-box is taken and the min- and max-point of it is transformed to the final
stage. Then again the min and the max between all possible combinations is taken to expand the
bounding box.
Remark: I know that maybe it would be also needed to compute some stages in between to expand
the bounding-box correctly. My approach could not render a sphere correctly which is rotating
360 degrees around another origin than itself because the bounding-box would not be expanded at
all. However, for my approach for this billiard-table as well the idea that the motion blur is
not capturing that large motions, this should be sufficient for now.
In the corresponding function bool rayIntersect(uint32_t index, const Ray3f &ray, float &u,
float &v, float &t) the object has to be transformed before calculating anything else.
For the sphere it is enough to transform the center-point. The rest of the calculations in this
method is based only on this point and the radius. (scaling is not yet implemented in th
motion-blur-classes, if so => only the radius has to be scaled appropriately) For the mesh it
works quite similar. But instead of only transforming the center-point, the vertices of the
currently checked triangle needs to be transformed.
To work completely correctly, the same transformations needs to be done in the void
setHitInformation(uint32_t index, const Ray3f &ray, Intersection & its)-method. Otherwise
the intersection might not match the hit-information.
As mentioned in the proposal, my idea was to render a picture independently at time 0 and one at time 1 (start and end). Then I compare the rendered picture with the motion blur and can check if all positions are rendered in between. For this purpose, I used the cube which is spinning around itself in the center. The two top pictures are at the start and end time respectively. The bottom left picture is then a rendered image with motion blur sampled by frame and only with 3 frames. So one can see the start, end and the middle-step. The bottom right picture is also sampled with only 3 samples per pixel, but for each ray another time.
With these tests I found out that I used the wrong time-steps for the frame-sampling. As a
result, no ray was sent with the time 1.0, so never rendering the final position in the motion
blur. This could be fixed by dividing the time not by m_sampler->getSampleCount(),
but by m_sampler->getSampleCount() - 1
By comparing the different start, end and motion-blurred images, I think that it renders quite good and more importantly, correct pictures.