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].

20151213_002512

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:

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

Bottom curve:

parabolic_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:

partial

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);
 r_list = linspace(0,radius,n);
 x = r_list'*cos(theta_list(1));
 y = r_list'*sin(theta_list(1));

 vertex_list = [];
 face_list = [];
 
 
 %add the bottom faces:
 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
 
 
 
% %add the top faces:
 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 = [];
 new_vertex_idx = zeros(1,size(vertices_to_add,1));
 for kk = 1:size(vertices_to_add,1)
 
 new_point = vertices_to_add(kk,:);
% 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');
 fprintf(fid,'end_header \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:

blender_partial

Here’s a render of the lens face-on

lens_proof4

Here’s a render of the lens rotated slightly:

lens_proof3

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.

lens_proof2

 

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

matlab file: https://dl.dropboxusercontent.com/u/110033260/projects/blog_files/create_lens_mesh.m

blender test file: https://dl.dropboxusercontent.com/u/110033260/projects/blog_files/lens_test2.blend1

example lens mesh: https://dl.dropboxusercontent.com/u/110033260/projects/blog_files/cool.ply

 

Advertisements

3 thoughts on “Modelling Arbitrary Real Lenses for Simulation

    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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s