Modelling Arbitrary Real Lenses for Simulation

TL; DR: You can use this matlab code to model arbitrary lenses, and simulate them in Luxrender.

Full Post:

So you’ve got a shiny new lens, and you’re all like, how can I make a 3D model of it and do physically-based ray simulations with it? In essence, what you want to do is make an accurate 3D model of your lens, and then put it into a ray-tracing engine like luxrender and see what light/imaging effects you can achieve. What’s cool about ray-tracers is that you can simulate multiple lenses and their effect in fog. This is going to be critical for the lightsaber [click here].

We’ll break this down into 3 steps:

1. Identify the 1D functions that define the bottom and top curves of the lens
2. Build a 3D model (PLY) in Matlab
3. Import the model and simulate it

The functions that define the bottom and top curves of the lens:

The lens above has a top part which is spherical, and a bottom part which is parabolic. It’s possible to derive the equations for the lens by measuring the width (radius) and height of each curve.

Top curve:

$y = \sqrt{R^2 - x^2} - d$
$R = \frac{W^2}{2H} + \frac{H}{2}$
$d = R - H$

Bottom curve:

$y = a x^2$
$a = \frac{H}{W^2}$

Building the 3D model for the lens

In order to model the lens, we’ll use matlab to generate a PLY file. The PLY file is a format used for 3D models which can capture vertices, faces, normals, colors, and any other set of attributes. For this project, the PLY file will contain a set of points, and a set of faces. The matlab code below works by taking a single primitive (for example, a single line of parabola), and rotating 360 degrees, adding points and faces as it goes. The trickiest part is not creating duplicate vertices. This is done by saving all the verticies in a hash-map, and then performing a look-up on each new vertex. There’s probably a better way to do that.

Example of a single line of the parabola rotated twice, with new faces added:

The code works with any functions for the top and bottom parts of the lenses. So feel free to try it out and capture new lens shapes. Also feel free to alert me to bugs you find.

% This is a function which can be used to generate meshes for arbitrary lenses.
% The lenses are made of a top and bottom curve.
% The functions for the top and bottom curves can be edited below.
%
% Usage:
% create_lens_mesh('cool_lens',200,.032);
%
% This generates "cool_lens.ply" with radius .032 and approximately 2*200*200 faces
%
%
% Hisham Bedri, December 2015

function [] = create_lens_mesh(file_name, n, radius)

duplicate_map = containers.Map();

theta_list = linspace(0,2*pi,n);
x = r_list'*cos(theta_list(1));
y = r_list'*sin(theta_list(1));

vertex_list = [];
face_list = [];

last_primitive = bottom(x,y);
for tt = 2:length(theta_list)
if(1)
x = r_list'*cos(theta_list(tt));
y = r_list'*sin(theta_list(tt));
new_primitive = bottom(x,y);

[last_vertex_idx, vertex_list, duplicate_map] = update_vertex_list(last_primitive, vertex_list, duplicate_map);
[new_vertex_idx, vertex_list, duplicate_map] = update_vertex_list(new_primitive, vertex_list, duplicate_map);

for ii = 2:length(new_vertex_idx)
%face_list(end+1,:)=[last_vertex_idx(ii-1), last_vertex_idx(ii), new_vertex_idx(ii), new_vertex_idx(ii-1)];
face_list(end+1,:)=[last_vertex_idx(ii-1), last_vertex_idx(ii), new_vertex_idx(ii), new_vertex_idx(ii-1)]; %specify by the contour
end

last_primitive = new_primitive;
end
end

offset = max(vertex_list(:,3));

last_primitive = top(x,y,offset);
for tt = 2:length(theta_list)
if(1)
x = r_list'*cos(theta_list(tt));
y = r_list'*sin(theta_list(tt));
new_primitive = top(x,y,offset);

[last_vertex_idx, vertex_list, duplicate_map] = update_vertex_list(last_primitive, vertex_list, duplicate_map);
[new_vertex_idx, vertex_list, duplicate_map] = update_vertex_list(new_primitive, vertex_list, duplicate_map);

for ii = 2:length(new_vertex_idx)
%face_list(end+1,:)=[last_vertex_idx(ii-1), last_vertex_idx(ii), new_vertex_idx(ii), new_vertex_idx(ii-1)];
face_list(end+1,:)=[last_vertex_idx(ii-1), last_vertex_idx(ii), new_vertex_idx(ii), new_vertex_idx(ii-1)]; %specify by the contour
end

last_primitive = new_primitive;
end
end

save_ply(file_name, vertex_list,face_list);

end

function [new_vertex_idx, updated_vertex_list, duplicate_map] = update_vertex_list( vertices_to_add, vertex_list, duplicate_map)
new_vertex_list = [];

% if(size(vertex_list,1)>0)
% diff_vector = sqrt( (vertex_list(:,1) - new_point(1)).^2+...
% (vertex_list(:,2) - new_point(2)).^2+...
% (vertex_list(:,3) - new_point(3)).^2 );
% [dist, ind] = min(diff_vector(:));
% else
% dist = 10; %anything greater than 0
% end

key = sprintf('%012.8f %012.8f %012.8f',new_point(1),new_point(2),new_point(3));
if(duplicate_map.isKey(key))
new_vertex_idx(kk)=duplicate_map(key);
else
new_vertex_list(end+1,:) = new_point;
new_vertex_idx(kk) = size(vertex_list,1) + size(new_vertex_list,1);
end
end

updated_vertex_list = [vertex_list; new_vertex_list];
for kk = 1:size(new_vertex_list,1)
key = sprintf('%012.8f %012.8f %012.8f',new_vertex_list(kk,1),new_vertex_list(kk,2),new_vertex_list(kk,3));
duplicate_map(key)=size(vertex_list,1)+kk;
end

end

function lens_point_cloud = bottom(x,y)
H = .0169;
W = .032;
a = H/(W.^2);
%a = 1;
lens_height = a*x.^2 + a*y.^2;
lens_point_cloud = [x,y,lens_height];

end

function lens_point_cloud = top(x,y,offset)
W = .032;
H = .0078;
R = W^2 /(2*H) + H/2;
d = R - H;
%R = 1;
lens_height = sqrt( R^2 - x.^2 - y.^2 ) -d+ offset;
lens_point_cloud = [x,y,lens_height];
end

function [] = save_ply(file_name, vertex_list, face_list)

if(file_name(end-3:end)~='.ply')
file_name = [file_name, '.ply'];
end

fid = fopen(file_name,'w');
fprintf(fid,'ply \n');
fprintf(fid,'format ascii 1.0 \n');
fprintf(fid,'element vertex %d \n',size(vertex_list,1));
fprintf(fid,'property float x \n');
fprintf(fid,'property float y \n');
fprintf(fid,'property float z \n');
fprintf(fid,'element face %d \n',size(face_list,1));
fprintf(fid,'property list uchar int vertex_index \n');

for ii = 1:size(vertex_list,1)
fprintf(fid,'%f %f %f \n',vertex_list(ii,1),vertex_list(ii,2),vertex_list(ii,3));
end

for ii = 1:size(face_list,1)

%faces = unique(face_list(ii,:));
faces = unique_without_sorting(face_list(ii,:));

%if(length(faces)==2)
if(0)
fprintf(fid,'3 %d %d 101\n',faces(1),faces(2));
else

fprintf(fid,'%d ',length(faces));
for kk = 1:length(faces)
fprintf(fid,'%d ',faces(kk)-1);
end
fprintf(fid,' \n');
end
end

fclose(fid);
end

function unique_vector = unique_without_sorting(vector)
unique_vector = [];

if(size(vector,1)==1)
vector = vector';
end

for ii = 1:length(vector)
if(~ any(unique_vector == vector(ii)))
unique_vector = [unique_vector, vector(ii)];
end
end

end



Importing the model and Simulating it

To test out the lens, we can import the PLY into blender. We’ll need to make sure the normals are facing the right direction (edit mode -> mesh -> normals -> recalculate). Once the mesh is loaded into blender, we can turn on the Luxrender raytracer, add a light, a background (with checkerboard texture), a camera, and see what the lens looks like.

Here’s the mesh in blender:

Here’s a render of the lens face-on

Here’s a render of the lens rotated slightly:

Here’s a render of the lens in front of a monkey. What’s really cool about this lens is that the distortion towards the edges is much stronger than the distortion towards the middle. This is going to be important for the “heart” of the lightsaber.

If you’re interested, here is the matlab file, a blender file to test, and an example of the generated mesh:

3 thoughts on “Modelling Arbitrary Real Lenses for Simulation”

1. I do not have luxrender nor Matlab; but I do have Blender. When I try with Cycles, I get artefacts in the middle. Any suggestions?

Like

1. Hmmm, I can’t be sure without seeing/hearing more about the nature of the artifacts, but here are some suggestions:

1) I think Isospheres in Blender typically have the fewest triangles in their mesh at the center, so it’s the part of the approximation that’s the hardest to do. Try increasing the number of triangles in the isosphere or rotating it.

2) Do the artifacts look like you sprinkled fairy dust on the image (fireflies)? A lot of raytracing algorithms suffer from this. One solution is to spend more time rendering, or play with the cycles render settings (the menu with the camera). Alternatively, I’ve seen much fewer fireflies with luxrender (which is not a bad install btw).

Thanks for the comment, let me know if I can be of further help

Like