scam_perspective.c (9085B)
1 /* Copyright (C) 2021-2023 |Méso|Star> (contact@meso-star.com) 2 * 3 * This program is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 3 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 15 16 #include "scam_c.h" 17 #include "scam_log.h" 18 19 #include <rsys/double3.h> 20 #include <rsys/double33.h> 21 #include <rsys/math.h> 22 23 #include <math.h> 24 25 /******************************************************************************* 26 * Helper functions 27 ******************************************************************************/ 28 static res_T 29 check_perspective_args 30 (struct scam* cam, 31 const struct scam_perspective_args* args) 32 { 33 if(!args) return RES_BAD_ARG; 34 35 /* Invalid aspect ratio */ 36 if(args->aspect_ratio <= 0) { 37 log_err(cam,"perspective camera: invalid aspect ratio: %g\n", 38 args->aspect_ratio); 39 return RES_BAD_ARG; 40 } 41 42 /* Invalid lens radius */ 43 if(args->lens_radius < 0) { 44 log_err(cam,"perspective camera: invalid negative lens radius: %g\n", 45 args->lens_radius); 46 return RES_BAD_ARG; 47 } 48 49 /* Invalid focal distance */ 50 if(args->lens_radius > 0 && args->focal_distance < 0) { 51 log_err(cam, "perspective camera: invalid negative focal distance: %g\n", 52 args->focal_distance); 53 return RES_BAD_ARG; 54 } 55 56 /* Invalid field of view */ 57 if(args->field_of_view <= 0 || args->field_of_view >= PI) { 58 log_err(cam, "perspective camera: invalid vertical field of view: %g\n", 59 args->field_of_view); 60 return RES_BAD_ARG; 61 } 62 63 return RES_OK; 64 } 65 66 static res_T 67 setup_perspective(struct scam* cam, const struct scam_perspective_args* args) 68 { 69 double hfov; /* Horizotal field of view */ 70 double half_vfov; /* (Vertical vield of view) / 2 */ 71 double tan_half_vfov; 72 double x[3], y[3], z[3]; 73 res_T res = RES_OK; 74 ASSERT(cam && args && cam->type == SCAM_PERSPECTIVE); 75 76 cam->param.persp = PERSPECTIVE_DEFAULT; 77 78 if(d3_normalize(z, d3_sub(z, args->target, args->position)) <= 0 79 || d3_normalize(x, d3_cross(x, z, args->up)) <= 0 80 || d3_normalize(y, d3_cross(y, z, x)) <= 0) { 81 log_err(cam, 82 "perspective camera: invalid point of view:\n" 83 " position = %g %g %g\n" 84 " target = %g %g %g\n" 85 " up = %g %g %g\n", 86 SPLIT3(args->position), SPLIT3(args->target), SPLIT3(args->up)); 87 res = RES_BAD_ARG; 88 goto error; 89 } 90 91 half_vfov = args->field_of_view*0.5; 92 tan_half_vfov = tan(half_vfov); 93 94 cam->param.persp.rcp_tan_half_fov = 1.0/tan_half_vfov; 95 cam->param.persp.aspect_ratio = args->aspect_ratio; 96 cam->param.persp.lens_radius = args->lens_radius; 97 cam->param.persp.focal_distance = args->focal_distance; 98 99 d3_set(cam->param.persp.position, args->position); 100 101 d3_muld(cam->param.persp.screen2world+0, x, args->aspect_ratio); 102 d3_set (cam->param.persp.screen2world+3, y); 103 d3_muld(cam->param.persp.screen2world+6, z, cam->param.persp.rcp_tan_half_fov); 104 105 d3_set(cam->param.persp.camera2world+0, x); 106 d3_set(cam->param.persp.camera2world+3, y); 107 d3_set(cam->param.persp.camera2world+6, z); 108 109 /* Compute the solid angle of the camera */ 110 hfov = 2*atan(args->aspect_ratio * tan_half_vfov); 111 cam->param.persp.solid_angle = hfov * 2*sin(half_vfov); 112 113 exit: 114 return res; 115 error: 116 goto exit; 117 } 118 119 static INLINE void 120 pinhole_generate_ray 121 (const struct scam* cam, 122 const struct scam_sample* sample, 123 struct scam_ray* ray) 124 { 125 double x[3], y[3], z[3], len; 126 double pos[3]; 127 (void)len; 128 129 ASSERT(cam && sample && ray); 130 ASSERT(cam->param.persp.lens_radius == 0); 131 ASSERT(cam->type == SCAM_PERSPECTIVE); 132 ASSERT(0 <= sample->film[0] && sample->film[0] < 1); 133 ASSERT(0 <= sample->film[1] && sample->film[1] < 1); 134 135 /* Transform the sampled position in screen space */ 136 pos[0] = sample->film[0]*2-1; 137 pos[1] = sample->film[1]*2-1; 138 pos[2] = 1; 139 140 /* Transform the sampled position in world space. Note that no translation is 141 * performed to directly obtain the (un-normalized) ray direction. */ 142 d3_muld(x, cam->param.persp.screen2world+0, pos[0]); 143 d3_muld(y, cam->param.persp.screen2world+3, pos[1]); 144 d3_set (z, cam->param.persp.screen2world+6); 145 d3_add(ray->dir, x, y); 146 d3_add(ray->dir, ray->dir, z); 147 len = d3_normalize(ray->dir, ray->dir); 148 ASSERT(len >= 1.e-6); 149 150 /* Setup the ray origin */ 151 d3_set(ray->org, cam->param.persp.position); 152 } 153 154 static INLINE void 155 thin_lens_generate_ray 156 (const struct scam* cam, 157 const struct scam_sample* sample, 158 struct scam_ray* ray) 159 { 160 double focus_pt[3]; 161 double dir[3]; 162 double theta; 163 double len; 164 double t; 165 double r; 166 (void)len; 167 168 ASSERT(cam && sample && ray); 169 ASSERT(cam->param.persp.lens_radius > 0); 170 ASSERT(cam->type == SCAM_PERSPECTIVE); 171 ASSERT(0 <= sample->film[0] && sample->film[0] < 1); 172 ASSERT(0 <= sample->film[1] && sample->film[1] < 1); 173 ASSERT(0 <= sample->lens[0] && sample->lens[0] < 1); 174 ASSERT(0 <= sample->lens[1] && sample->lens[1] < 1); 175 176 /* Transform the sampled position in screen space and use it as the 177 * (un-normalized) direction starting from the lens center and intersecting 178 * the sample */ 179 dir[0] = sample->film[0]*2-1; 180 dir[1] = sample->film[1]*2-1; 181 dir[2] = 1; 182 183 /* Transform the sampled direction in camera space */ 184 dir[0] = dir[0] * cam->param.persp.aspect_ratio; 185 dir[1] = dir[1]; 186 dir[2] = dir[2] * cam->param.persp.rcp_tan_half_fov; 187 len = d3_normalize(dir, dir); 188 ASSERT(len >= 1.e-6); 189 190 /* find the focus point by intersecting dir with the focus plane */ 191 t = cam->param.persp.focal_distance / dir[2]; 192 focus_pt[0] = /* null ray origin + */ t*dir[0]; 193 focus_pt[1] = /* null ray origin + */ t*dir[1]; 194 focus_pt[2] = /* null ray origin + */ t*dir[2]; 195 196 /* Uniformly sample a position onto the lens in camera space */ 197 theta = 2 * PI * sample->lens[0]; 198 r = cam->param.persp.lens_radius * sqrt(sample->lens[1]); 199 ray->org[0] = r * cos(theta); 200 ray->org[1] = r * sin(theta); 201 ray->org[2] = 0; 202 203 /* Compute the ray direction in camera space */ 204 d3_sub(ray->dir, focus_pt, ray->org); 205 len = d3_normalize(ray->dir, ray->dir); 206 ASSERT(len >= 1.e-6); 207 208 /* Transform the ray from camera space to world space */ 209 d33_muld3(ray->dir, cam->param.persp.camera2world, ray->dir); 210 d33_muld3(ray->org, cam->param.persp.camera2world, ray->org); 211 d3_add(ray->org, cam->param.persp.position, ray->org); 212 } 213 214 /******************************************************************************* 215 * Exported functions 216 ******************************************************************************/ 217 SCAM_API res_T 218 scam_create_perspective 219 (struct logger* logger, /* NULL <=> use builtin logger */ 220 struct mem_allocator* allocator, /* NULL <=> use default allocator */ 221 const int verbose, /* Verbosity level */ 222 struct scam_perspective_args* args, 223 struct scam** out_cam) 224 { 225 struct scam* cam = NULL; 226 res_T res = RES_OK; 227 228 if(!args || !out_cam) { 229 res = RES_BAD_ARG; 230 goto error; 231 } 232 res = camera_create(logger, allocator, verbose, SCAM_PERSPECTIVE, &cam); 233 if(res != RES_OK) goto error; 234 res = check_perspective_args(cam, args); 235 if(res != RES_OK) goto error; 236 res = setup_perspective(cam, args); 237 if(res != RES_OK) goto error; 238 239 exit: 240 if(out_cam) *out_cam = cam; 241 return res; 242 error: 243 if(cam) { 244 SCAM(ref_put(cam)); 245 cam = NULL; 246 } 247 goto exit; 248 } 249 250 res_T 251 scam_perspective_get_solid_angle(const struct scam* camera, double* solid_angle) 252 { 253 if(!camera || camera->type != SCAM_PERSPECTIVE || !solid_angle) 254 return RES_BAD_ARG; 255 256 *solid_angle = camera->param.persp.solid_angle; 257 return RES_OK; 258 } 259 260 res_T 261 scam_focal_length_to_field_of_view 262 (const double lens_radius, 263 const double focal_length, 264 double* field_of_view) 265 { 266 if(lens_radius < 0 || focal_length <= 0 || !field_of_view) 267 return RES_BAD_ARG; 268 *field_of_view = 2 * atan(lens_radius /focal_length); 269 return RES_OK; 270 } 271 272 res_T 273 scam_field_of_view_to_focal_length 274 (const double lens_radius, 275 const double field_of_view, 276 double* focal_length) 277 { 278 if(lens_radius < 0 || field_of_view <= 0 || field_of_view >= PI || !focal_length) 279 return RES_BAD_ARG; 280 *focal_length = lens_radius / tan(field_of_view*0.5); 281 return RES_OK; 282 } 283 284 /******************************************************************************* 285 * Local function 286 ******************************************************************************/ 287 void 288 perspective_generate_ray 289 (const struct scam* cam, 290 const struct scam_sample* sample, 291 struct scam_ray* ray) 292 { 293 ASSERT(cam && cam->type == SCAM_PERSPECTIVE); 294 if(cam->param.persp.lens_radius == 0) { 295 pinhole_generate_ray(cam, sample, ray); 296 } else { 297 thin_lens_generate_ray(cam, sample, ray); 298 } 299 }