Fisheye Camera for Cycles

For sample images see:
http://www.dalaifelinto.com/?p=399 (equisolid)
http://www.dalaifelinto.com/?p=389 (equidistant)

The 'use_panorama' option is now part of a new Camera type: 'Panorama'.
Created two other panorama cameras:

- Equisolid: most of lens in the market simulate this lens - e.g. Nikon, Canon, ...)
             this works as a real lens up to an extent. The final result takes the
             sensor dimensions into account also.
             .:. to simulate a Nikon DX2S with a 10.5mm lens do:
                 sensor: 23.7 x 15.7
                 fisheye lens: 10.5
                 fisheye fov: 180
                 render dimensions: 4288 x 2848

- Equidistant: this is not a real lens model. Although the old equidistant lens simulate
               this lens. The result is always as a circular fisheye that takes the whole sensor
               (in other words, it doesn't take the sensor into consideration).
               This is perfect for fulldomes ;)

               For the UI we have 10 to 360 as soft values and 10 to 3600 as hard values (because we can).


Reference material:
http://www.hdrlabs.com/tutorials/downloads_files/HDRI%20for%20CGI.pdf
http://www.bobatkins.com/photography/technical/field_of_view.html

Note, this is not a real simulation of the light path through the lens.
The ideal solution would be this:
https://graphics.stanford.edu/wikis/cs348b-11/Assignment3
http://www.graphics.stanford.edu/papers/camera/


Thanks Brecht for the fix, suggestions and code review.
Kudos for the dome community for keeping me stimulated on the topic since 2009 ;)

Patch partly implemented during lab time at VisGraf, IMPA - Rio de Janeiro.
This commit is contained in:
Dalai Felinto 2012-05-04 16:20:51 +00:00
parent b6edcc4b33
commit d7fbe03a8a
18 changed files with 262 additions and 43 deletions

View File

@ -290,8 +290,22 @@ static void xml_read_camera(const XMLReadState& state, pugi::xml_node node)
cam->type = CAMERA_ORTHOGRAPHIC;
else if(xml_equal_string(node, "type", "perspective"))
cam->type = CAMERA_PERSPECTIVE;
else if(xml_equal_string(node, "type", "environment"))
cam->type = CAMERA_ENVIRONMENT;
else if(xml_equal_string(node, "type", "panorama"))
cam->type = CAMERA_PANORAMA;
if(xml_equal_string(node, "panorama_type", "equirectangular"))
cam->panorama_type = PANORAMA_EQUIRECTANGULAR;
else if(xml_equal_string(node, "panorama_type", "fisheye_equidistant"))
cam->panorama_type = PANORAMA_FISHEYE_EQUIDISTANT;
else if(xml_equal_string(node, "panorama_type", "fisheye_equisolid"))
cam->panorama_type = PANORAMA_FISHEYE_EQUISOLID;
xml_read_float(&cam->fisheye_fov, node, "fisheye_fov");
xml_read_float(&cam->fisheye_lens, node, "fisheye_lens");
xml_read_float(&cam->sensorwidth, node, "sensorwidth");
xml_read_float(&cam->sensorheight, node, "sensorheight");
cam->matrix = state.tfm;

View File

@ -54,3 +54,9 @@ aperture_types = (
('RADIUS', "Radius", "Directly change the size of the aperture"),
('FSTOP', "F/stop", "Change the size of the aperture by f/stops"),
)
panorama_types = (
('EQUIRECTANGULAR', "Equirectangular", "Render the scene with a spherical camera, also known as Lat Long panorama"),
('FISHEYE_EQUIDISTANT', "Fisheye Equidistant", "Ideal for fulldomes, ignore the sensor dimensions"),
('FISHEYE_EQUISOLID', "Fisheye Equisolid", "Similar to most fisheye modern lens, take sensor dimensions into consideration"),
)

View File

@ -278,6 +278,25 @@ class CyclesCameraSettings(bpy.types.PropertyGroup):
subtype='ANGLE',
default=0,
)
cls.panorama_type = EnumProperty(
name="Panorama Type",
description="Distortion to use for the calculation",
items=enums.panorama_types,
default='FISHEYE_EQUISOLID',
)
cls.fisheye_fov = FloatProperty(
name="Field of View",
description="Field of view for the fisheye lens",
min=0.1745, soft_max=2*math.pi, max=10.0*math.pi,
subtype='ANGLE',
default=math.pi,
)
cls.fisheye_lens = FloatProperty(
name="Fisheye Lens",
description="Lens focal length (mm))",
min=0.01, soft_max=15.0, max=100.0,
default=10.5,
)
@classmethod
def unregister(cls):

View File

@ -48,6 +48,10 @@ struct BlenderCamera {
float2 pixelaspect;
PanoramaType panorama_type;
float fisheye_fov;
float fisheye_lens;
enum { AUTO, HORIZONTAL, VERTICAL } sensor_fit;
float sensor_width;
float sensor_height;
@ -94,9 +98,37 @@ static void blender_camera_from_object(BlenderCamera *bcam, BL::Object b_ob)
bcam->nearclip = b_camera.clip_start();
bcam->farclip = b_camera.clip_end();
bcam->type = (b_camera.type() == BL::Camera::type_ORTHO)? CAMERA_ORTHOGRAPHIC: CAMERA_PERSPECTIVE;
if(bcam->type == CAMERA_PERSPECTIVE && b_camera.use_panorama())
bcam->type = CAMERA_ENVIRONMENT;
switch(b_camera.type())
{
case BL::Camera::type_ORTHO:
bcam->type = CAMERA_ORTHOGRAPHIC;
break;
case BL::Camera::type_PANO:
bcam->type = CAMERA_PANORAMA;
break;
case BL::Camera::type_PERSP:
default:
bcam->type = CAMERA_PERSPECTIVE;
break;
}
switch(RNA_enum_get(&ccamera, "panorama_type"))
{
case 1:
bcam->panorama_type = PANORAMA_FISHEYE_EQUIDISTANT;
break;
case 2:
bcam->panorama_type = PANORAMA_FISHEYE_EQUISOLID;
break;
case 0:
default:
bcam->panorama_type = PANORAMA_EQUIRECTANGULAR;
break;
}
bcam->fisheye_fov = RNA_float_get(&ccamera, "fisheye_fov");
bcam->fisheye_lens = RNA_float_get(&ccamera, "fisheye_lens");
bcam->ortho_scale = b_camera.ortho_scale();
bcam->lens = b_camera.lens();
@ -138,7 +170,7 @@ static Transform blender_camera_matrix(const Transform& tfm, CameraType type)
{
Transform result;
if(type == CAMERA_ENVIRONMENT) {
if(type == CAMERA_PANORAMA) {
/* make it so environment camera needs to be pointed in the direction
of the positive x-axis to match an environment texture, this way
it is looking at the center of the texture */
@ -172,6 +204,9 @@ static void blender_camera_sync(Camera *cam, BlenderCamera *bcam, int width, int
bool horizontal_fit;
float sensor_size;
cam->sensorwidth = bcam->sensor_width;
cam->sensorheight = bcam->sensor_height;
if(bcam->sensor_fit == BlenderCamera::AUTO) {
horizontal_fit = (xratio > yratio);
sensor_size = bcam->sensor_width;
@ -203,7 +238,7 @@ static void blender_camera_sync(Camera *cam, BlenderCamera *bcam, int width, int
aspectratio = bcam->ortho_scale/2.0f;
}
if(bcam->type == CAMERA_ENVIRONMENT) {
if(bcam->type == CAMERA_PANORAMA) {
/* set viewplane */
cam->left = 0.0f;
cam->right = 1.0f;
@ -240,6 +275,11 @@ static void blender_camera_sync(Camera *cam, BlenderCamera *bcam, int width, int
/* type */
cam->type = bcam->type;
/* panorama */
cam->panorama_type = bcam->panorama_type;
cam->fisheye_fov = bcam->fisheye_fov;
cam->fisheye_lens = bcam->fisheye_lens;
/* perspective */
cam->fov = 2.0f*atan((0.5f*sensor_size)/bcam->lens/aspectratio);
cam->focaldistance = bcam->focaldistance;

View File

@ -132,16 +132,40 @@ __device void camera_sample_orthographic(KernelGlobals *kg, float raster_x, floa
#endif
}
/* Environment Camera */
/* Panorama Camera */
__device void camera_sample_environment(KernelGlobals *kg, float raster_x, float raster_y, Ray *ray)
__device float3 panorama_to_direction(KernelGlobals *kg, float u, float v, Ray *ray)
{
switch (kernel_data.cam.panorama_type) {
case PANORAMA_EQUIRECTANGULAR:
return equirectangular_to_direction(u, v);
break;
case PANORAMA_FISHEYE_EQUIDISTANT:
return fisheye_to_direction(u, v, kernel_data.cam.fisheye_fov, ray);
break;
case PANORAMA_FISHEYE_EQUISOLID:
default:
return fisheye_equisolid_to_direction(u, v, kernel_data.cam.fisheye_lens, kernel_data.cam.fisheye_fov, kernel_data.cam.sensorwidth, kernel_data.cam.sensorheight, ray);
break;
}
}
__device void camera_sample_panorama(KernelGlobals *kg, float raster_x, float raster_y, Ray *ray)
{
Transform rastertocamera = kernel_data.cam.rastertocamera;
float3 Pcamera = transform_perspective(&rastertocamera, make_float3(raster_x, raster_y, 0.0f));
/* create ray form raster position */
ray->P = make_float3(0.0f, 0.0f, 0.0f);
ray->D = equirectangular_to_direction(Pcamera.x, Pcamera.y);
#ifdef __CAMERA_CLIPPING__
/* clipping */
ray->t = kernel_data.cam.cliplength;
#else
ray->t = FLT_MAX;
#endif
ray->D = panorama_to_direction(kg, Pcamera.x, Pcamera.y, ray);
/* transform ray from camera to world */
Transform cameratoworld = kernel_data.cam.cameratoworld;
@ -161,17 +185,11 @@ __device void camera_sample_environment(KernelGlobals *kg, float raster_x, float
ray->dP.dy = make_float3(0.0f, 0.0f, 0.0f);
Pcamera = transform_perspective(&rastertocamera, make_float3(raster_x + 1.0f, raster_y, 0.0f));
ray->dD.dx = normalize(transform_direction(&cameratoworld, equirectangular_to_direction(Pcamera.x, Pcamera.y))) - ray->D;
ray->dD.dx = normalize(transform_direction(&cameratoworld, panorama_to_direction(kg, Pcamera.x, Pcamera.y, ray))) - ray->D;
Pcamera = transform_perspective(&rastertocamera, make_float3(raster_x, raster_y + 1.0f, 0.0f));
ray->dD.dy = normalize(transform_direction(&cameratoworld, equirectangular_to_direction(Pcamera.x, Pcamera.y))) - ray->D;
#endif
ray->dD.dy = normalize(transform_direction(&cameratoworld, panorama_to_direction(kg, Pcamera.x, Pcamera.y, ray))) - ray->D;
#ifdef __CAMERA_CLIPPING__
/* clipping */
ray->t = kernel_data.cam.cliplength;
#else
ray->t = FLT_MAX;
#endif
}
@ -198,7 +216,7 @@ __device void camera_sample(KernelGlobals *kg, int x, int y, float filter_u, flo
else if(kernel_data.cam.type == CAMERA_ORTHOGRAPHIC)
camera_sample_orthographic(kg, raster_x, raster_y, ray);
else
camera_sample_environment(kg, raster_x, raster_y, ray);
camera_sample_panorama(kg, raster_x, raster_y, ray);
}
CCL_NAMESPACE_END

View File

@ -224,6 +224,57 @@ __device float3 equirectangular_to_direction(float u, float v)
cos(theta));
}
/* Fisheye <- Cartesian direction */
__device float3 fisheye_to_direction(float u, float v, float fov, Ray *ray)
{
u = (u - 0.5f) * 2.f;
v = (v - 0.5f) * 2.f;
float r = sqrt(u*u + v*v);
if (r > 1.0) {
ray->t = 0.f;
return make_float3(0.f,0.f,0.f);
}
float phi = acosf((r!=0.f)?u/r:0.f);
float theta = asinf(r) * (fov / M_PI_F);
if (v < 0.f) phi = -phi;
return make_float3(
cosf(theta),
-cosf(phi)*sinf(theta),
sinf(phi)*sinf(theta)
);
}
__device float3 fisheye_equisolid_to_direction(float u, float v, float lens, float fov, float width, float height, Ray *ray)
{
u = (u - 0.5f) * width;
v = (v - 0.5f) * height;
float rmax = 2.f * lens * sinf(fov * 0.5f);
float r = sqrt(u*u + v*v);
if (r > rmax) {
ray->t = 0.f;
return make_float3(0.f,0.f,0.f);
}
float phi = acosf((r!=0.f)?u/r:0.f);
float theta = 2.f * asinf(r/(2.f * lens));
if (v < 0.f) phi = -phi;
return make_float3(
cosf(theta),
-cosf(phi)*sinf(theta),
sinf(phi)*sinf(theta)
);
}
/* Mirror Ball <-> Cartesion direction */
__device float3 mirrorball_to_direction(float u, float v)

View File

@ -474,7 +474,12 @@ __device void kernel_path_trace(KernelGlobals *kg,
camera_sample(kg, x, y, filter_u, filter_v, lens_u, lens_v, time, &ray);
/* integrate */
float4 L = kernel_path_integrate(kg, &rng, sample, ray, buffer);
float4 L;
if (ray.t != 0.f)
L = kernel_path_integrate(kg, &rng, sample, ray, buffer);
else
L = make_float4(0.f, 0.f, 0.f, 0.f);
/* accumulate result in output buffer */
kernel_write_pass_float4(buffer, sample, L);

View File

@ -268,7 +268,15 @@ typedef enum LightType {
enum CameraType {
CAMERA_PERSPECTIVE,
CAMERA_ORTHOGRAPHIC,
CAMERA_ENVIRONMENT
CAMERA_PANORAMA
};
/* Panorama Type */
enum PanoramaType {
PANORAMA_EQUIRECTANGULAR,
PANORAMA_FISHEYE_EQUIDISTANT,
PANORAMA_FISHEYE_EQUISOLID
};
/* Differential */
@ -452,7 +460,11 @@ typedef struct ShaderData {
typedef struct KernelCamera {
/* type */
int type;
int pad1, pad2, pad3;
/* panorama */
int panorama_type;
float fisheye_fov;
float fisheye_lens;
/* matrices */
Transform cameratoworld;
@ -476,6 +488,11 @@ typedef struct KernelCamera {
float nearclip;
float cliplength;
/* sensor size */
float sensorwidth;
float sensorheight;
int pad1, pad2;
/* more matrices */
Transform screentoworld;
Transform rastertoworld;

View File

@ -39,8 +39,14 @@ Camera::Camera()
use_motion = false;
type = CAMERA_PERSPECTIVE;
panorama_type = PANORAMA_EQUIRECTANGULAR;
fisheye_fov = M_PI_F;
fisheye_lens = 10.5f;
fov = M_PI_F/4.0f;
sensorwidth = 0.036;
sensorheight = 0.024;
nearclip = 1e-5f;
farclip = 1e5f;
@ -181,6 +187,15 @@ void Camera::device_update(Device *device, DeviceScene *dscene, Scene *scene)
/* type */
kcam->type = type;
/* panorama */
kcam->panorama_type = panorama_type;
kcam->fisheye_fov = fisheye_fov;
kcam->fisheye_lens = fisheye_lens;
/* sensor size */
kcam->sensorwidth = sensorwidth;
kcam->sensorheight = sensorheight;
/* store differentials */
kcam->dx = float3_to_float4(dx);
kcam->dy = float3_to_float4(dy);
@ -208,6 +223,8 @@ bool Camera::modified(const Camera& cam)
(fov == cam.fov) &&
(nearclip == cam.nearclip) &&
(farclip == cam.farclip) &&
(sensorwidth == cam.sensorwidth) &&
(sensorheight == cam.sensorheight) &&
// modified for progressive render
// (width == cam.width) &&
// (height == cam.height) &&
@ -217,7 +234,10 @@ bool Camera::modified(const Camera& cam)
(top == cam.top) &&
(matrix == cam.matrix) &&
(motion == cam.motion) &&
(use_motion == cam.use_motion));
(use_motion == cam.use_motion) &&
(panorama_type == cam.panorama_type) &&
(fisheye_fov == cam.fisheye_fov) &&
(fisheye_lens == cam.fisheye_lens));
}
void Camera::tag_update()

View File

@ -50,6 +50,15 @@ public:
CameraType type;
float fov;
/* panorama */
PanoramaType panorama_type;
float fisheye_fov;
float fisheye_lens;
/* sensor */
float sensorwidth;
float sensorheight;
/* clipping */
float nearclip;
float farclip;

View File

@ -87,10 +87,16 @@ class DATA_PT_lens(CameraButtonsPanel, Panel):
elif cam.type == 'ORTHO':
col.prop(cam, "ortho_scale")
col = layout.column()
col.enabled = cam.type == 'PERSP'
col.prop(cam, "use_panorama")
elif cam.type == 'PANO':
if context.scene.render.engine == 'CYCLES':
ccam = cam.cycles
col.prop(ccam, "panorama_type", text="Type")
if ccam.panorama_type == 'FISHEYE_EQUIDISTANT':
col.prop(ccam, "fisheye_fov")
elif ccam.panorama_type == 'FISHEYE_EQUISOLID':
row = layout.row()
row.prop(ccam, "fisheye_lens", text="Lens")
row.prop(ccam, "fisheye_fov")
split = layout.split()

View File

@ -141,7 +141,7 @@ void object_camera_mode(RenderData *rd, Object *cam_ob)
if (cam_ob && cam_ob->type==OB_CAMERA) {
Camera *cam= cam_ob->data;
if (cam->type == CAM_ORTHO) rd->mode |= R_ORTHO;
if (cam->flag & CAM_PANORAMA) rd->mode |= R_PANORAMA;
if (cam->type == CAM_PANO) rd->mode |= R_PANORAMA;
}
}

View File

@ -138,7 +138,7 @@ UvCameraInfo *project_camera_info(Object *ob, float(*rotmat)[4], float winx, flo
UvCameraInfo uci;
Camera *camera = ob->data;
uci.do_pano = (camera->flag & CAM_PANORAMA);
uci.do_pano = (camera->type == CAM_PANO);
uci.do_persp = (camera->type == CAM_PERSP);
uci.camangle = focallength_to_fov(camera->lens, camera->sensor_x) / 2.0f;

View File

@ -7398,6 +7398,18 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
}
}
if (main->versionfile < 263 || (main->versionfile == 263 && main->subversionfile < 4))
{
Camera *cam;
for (cam = main->camera.first; cam; cam = cam->id.next) {
if (cam->flag & CAM_PANORAMA) {
cam->type = CAM_PANO;
cam->flag &= ~CAM_PANORAMA;
}
}
}
/* WATCH IT!!!: pointers from libdata have not been converted yet here! */
/* WATCH IT 2!: Userdef struct init has to be in editors/interface/resources.c! */

View File

@ -67,8 +67,10 @@ void CamerasExporter::operator()(Object *ob, Scene *sce)
Camera *cam = (Camera*)ob->data;
std::string cam_id(get_camera_id(ob));
std::string cam_name(id_name(cam));
if (cam->type == CAM_PERSP) {
switch (cam->type) {
case CAM_PANO:
case CAM_PERSP: {
COLLADASW::PerspectiveOptic persp(mSW);
persp.setXFov(RAD2DEGF(focallength_to_fov(cam->lens, cam->sensor_x)), "xfov");
persp.setAspectRatio((float)(sce->r.xsch)/(float)(sce->r.ysch), false, "aspect_ratio");
@ -76,8 +78,11 @@ void CamerasExporter::operator()(Object *ob, Scene *sce)
persp.setZNear(cam->clipsta, false, "znear");
COLLADASW::Camera ccam(mSW, &persp, cam_id, cam_name);
addCamera(ccam);
break;
}
else {
case CAM_ORTHO:
default:
{
COLLADASW::OrthographicOptic ortho(mSW);
ortho.setXMag(cam->ortho_scale, "xmag");
ortho.setAspectRatio((float)(sce->r.xsch)/(float)(sce->r.ysch), false, "aspect_ratio");
@ -85,5 +90,6 @@ void CamerasExporter::operator()(Object *ob, Scene *sce)
ortho.setZNear(cam->clipsta, false, "znear");
COLLADASW::Camera ccam(mSW, &ortho, cam_id, cam_name);
addCamera(ccam);
}
}
break;
}}
}

View File

@ -48,7 +48,7 @@ typedef struct Camera {
ID id;
struct AnimData *adt; /* animation data (must be immediately after id for utilities to use it) */
char type; /* CAM_PERSP or CAM_ORTHO */
char type; /* CAM_PERSP, CAM_ORTHO or CAM_PANO */
char dtx; /* draw type extra */
short flag;
float passepartalpha;
@ -75,6 +75,7 @@ typedef struct Camera {
/* type */
#define CAM_PERSP 0
#define CAM_ORTHO 1
#define CAM_PANO 2
/* dtx */
#define CAM_DTX_CENTER 1
@ -94,7 +95,7 @@ typedef struct Camera {
#define CAM_SHOWNAME 16
#define CAM_ANGLETOGGLE 32
#define CAM_DS_EXPAND 64
#define CAM_PANORAMA 128
#define CAM_PANORAMA 128 /* deprecated */
#define CAM_SHOWSENSOR 256
/* yafray: dof sampling switch */

View File

@ -97,6 +97,7 @@ void RNA_def_camera(BlenderRNA *brna)
static EnumPropertyItem prop_type_items[] = {
{CAM_PERSP, "PERSP", 0, "Perspective", ""},
{CAM_ORTHO, "ORTHO", 0, "Orthographic", ""},
{CAM_PANO, "PANO", 0, "Panoramic", ""},
{0, NULL, 0, NULL, NULL}};
static EnumPropertyItem prop_draw_type_extra_items[] = {
{CAM_DTX_CENTER, "CENTER", 0, "Center", ""},
@ -271,12 +272,6 @@ void RNA_def_camera(BlenderRNA *brna)
RNA_def_property_enum_items(prop, prop_lens_unit_items);
RNA_def_property_ui_text(prop, "Lens Unit", "Unit to edit lens in for the user interface");
prop = RNA_def_property(srna, "use_panorama", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", CAM_PANORAMA);
RNA_def_property_ui_text(prop, "Panorama",
"Render the scene with a cylindrical camera for pseudo-fisheye lens effects");
RNA_def_property_update(prop, NC_OBJECT|ND_DRAW, NULL);
/* pointers */
rna_def_animdata_common(srna);

View File

@ -193,7 +193,7 @@ static DerivedMesh *uvprojectModifier_do(UVProjectModifierData *umd,
if (projectors[i].ob->type == OB_CAMERA) {
cam = (Camera *)projectors[i].ob->data;
if (cam->flag & CAM_PANORAMA) {
if (cam->type == CAM_PANO) {
projectors[i].uci= project_camera_info(projectors[i].ob, NULL, aspx, aspy);
project_camera_info_scale(projectors[i].uci, scax, scay);
free_uci= 1;