Frame: refine critical lens regions
This commit is contained in:
1 parent
48dcf4e707
commit
4c575fa8b5
7 files changed
+92
-10
No files matched your search
@@ -147,13 +147,15 @@ production refinement threshold.
|
||||
Adaptive refinement is disabled by default (`--refine-max-level 0`), so the
|
||||
existing coarse-mesh renders remain unchanged. When enabled, its defaults are
|
||||
an absolute direction error of `1e-3` degrees, relative error `0.1`, minimum
|
||||
long edge `0.5` pixels, and minimum area `0.25` pixel-squared. Each value can
|
||||
be overridden independently:
|
||||
long edge `0.5` pixels, minimum area `0.25` pixel-squared, and a provisional
|
||||
minimum discrete-Jacobian magnitude of `1e-3`. Each value can be overridden
|
||||
independently:
|
||||
|
||||
```text
|
||||
--refine-max-level N
|
||||
--refine-angle-abs-deg D
|
||||
--refine-angle-rel R
|
||||
--refine-jacobian-min J
|
||||
--refine-min-edge-pixels P
|
||||
--refine-min-area-pixels2 A
|
||||
```
|
||||
@@ -171,8 +173,20 @@ prevent selecting a leaf already at or below the requested image-plane
|
||||
long-edge and area scales.
|
||||
Triangles whose three vertices disagree between capture and escape are split
|
||||
independently of the direction-error thresholds, allowing the mesh to follow a
|
||||
shadow boundary. This first implementation deliberately does not evaluate
|
||||
orientation or Jacobian criteria.
|
||||
shadow boundary.
|
||||
|
||||
Independently of the midpoint geometry test, an all-escaped triangle also
|
||||
computes the discrete lens Jacobian
|
||||
`J = Omega_source / Omega_image`. Both signed solid angles use
|
||||
`2 atan2(dot(a, cross(b,c)), 1 + dot(a,b) + dot(b,c) + dot(c,a))`, with the
|
||||
ordered camera directions for `Omega_image` and their traced infinity
|
||||
directions for `Omega_source`. `abs(J) < --refine-jacobian-min` requests a
|
||||
split near a critical curve. The signed-area calculation retains the sign of
|
||||
`J`, but sign changes across adjacent triangles are not currently a refinement
|
||||
trigger; this makes it possible to evaluate the `abs(J)` criterion separately.
|
||||
The `1e-3` default is deliberately provisional and should be tuned with the
|
||||
small Schwarzschild refinement diagnostic before being treated as a production
|
||||
threshold.
|
||||
|
||||
For a short Schwarzschild diagnostic that permits at most one actual split
|
||||
generation, for example:
|
||||
@@ -181,7 +195,8 @@ generation, for example:
|
||||
./build/schwarzschild_sky --catalog assets/sky_grid_5deg.csv \
|
||||
--width 48 --height 48 --coarse-cell-pixels 24 --fov-deg 40 \
|
||||
--refine-max-level 1 --refine-angle-abs-deg 0.001 \
|
||||
--refine-angle-rel 0.001 --refine-min-edge-pixels 1 \
|
||||
--refine-angle-rel 0.001 --refine-jacobian-min 0.001 \
|
||||
--refine-min-edge-pixels 1 \
|
||||
--refine-min-area-pixels2 1 --draw-mesh \
|
||||
--output output/imgs/schwarzschild_refinement.png
|
||||
```
|
||||
|
||||
+35
@@ -335,6 +335,37 @@ static double direction_angle(const double a[3], const double b[3]) {
|
||||
return acos(product);
|
||||
}
|
||||
|
||||
static double spherical_signed_area(const double a[3], const double b[3],
|
||||
const double c[3]) {
|
||||
double b_cross_c[3];
|
||||
cross(b, c, b_cross_c);
|
||||
return 2.0 * atan2(dot(a, b_cross_c),
|
||||
1.0 + dot(a, b) + dot(b, c) + dot(c, a));
|
||||
}
|
||||
|
||||
/* Returns whether the discrete source/image solid-angle ratio is available.
|
||||
* Captured and degenerate image triangles have no reliable value. */
|
||||
static int discrete_jacobian(const FrameLensMesh *mesh,
|
||||
const LensTriangle *triangle, double *value) {
|
||||
const LensVertex *a = &mesh->vertices[triangle->vertex[0]];
|
||||
const LensVertex *b = &mesh->vertices[triangle->vertex[1]];
|
||||
const LensVertex *c = &mesh->vertices[triangle->vertex[2]];
|
||||
if (a->status != RAY_ENDPOINT_ESCAPED || b->status != RAY_ENDPOINT_ESCAPED ||
|
||||
c->status != RAY_ENDPOINT_ESCAPED)
|
||||
return 0;
|
||||
const double image_area = spherical_signed_area(
|
||||
a->camera_direction, b->camera_direction, c->camera_direction);
|
||||
if (!isfinite(image_area) || fabs(image_area) <= 1e-15)
|
||||
return 0;
|
||||
const double source_area = spherical_signed_area(
|
||||
a->n_infinity, b->n_infinity, c->n_infinity);
|
||||
const double jacobian = source_area / image_area;
|
||||
if (!isfinite(jacobian))
|
||||
return 0;
|
||||
*value = jacobian;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int probe_requires_split(const FrameLensMesh *mesh,
|
||||
const LensTriangle *triangle,
|
||||
unsigned int side,
|
||||
@@ -441,6 +472,10 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
||||
if (allowed[i]) {
|
||||
const unsigned int side = longest_side(mesh, triangle);
|
||||
requested[3 * i + side] = probe_requires_split(mesh, triangle, side, config);
|
||||
double jacobian;
|
||||
if (discrete_jacobian(mesh, triangle, &jacobian) &&
|
||||
fabs(jacobian) < config->jacobian_minimum)
|
||||
requested[3 * i + side] = 1;
|
||||
}
|
||||
}
|
||||
qsort(edges, edge_count, sizeof *edges, compare_mesh_edge);
|
||||
|
||||
+7
-3
@@ -28,11 +28,15 @@ typedef struct {
|
||||
unsigned int max_level;
|
||||
double angle_absolute_rad;
|
||||
double angle_relative;
|
||||
double jacobian_minimum;
|
||||
double min_edge_pixels;
|
||||
double min_area_pixels2;
|
||||
} RefinementConfig;
|
||||
|
||||
typedef enum { FRAME_SAMPLE_VERTEX, FRAME_SAMPLE_PROBE } FrameSampleKind;
|
||||
typedef enum {
|
||||
FRAME_SAMPLE_VERTEX,
|
||||
FRAME_SAMPLE_PROBE
|
||||
} FrameSampleKind;
|
||||
|
||||
typedef struct {
|
||||
FrameSampleKind kind;
|
||||
@@ -74,8 +78,8 @@ int frame_lens_mesh_build_coarse(FrameLensMesh *mesh, int width, int height,
|
||||
int frame_lens_mesh_trace(FrameLensMesh *mesh, const SpacetimeSource *spacetime,
|
||||
const ObserverState *observer,
|
||||
const GeodesicTraceConfig *trace);
|
||||
/* Builds exactly one generation of requests. Probe results must be installed
|
||||
* only after the caller has completed the generation's tracing sweep. */
|
||||
/* Builds one generation of vertex/probe ray requests. Results are installed
|
||||
* only after the caller has completed the generation's sweep. */
|
||||
int frame_lens_mesh_prepare_generation(FrameLensMesh *mesh,
|
||||
const RefinementConfig *config);
|
||||
const FrameSample *frame_lens_mesh_samples(const FrameLensMesh *mesh,
|
||||
|
||||
@@ -154,6 +154,7 @@ static int parse_args(int argc, char **argv, Settings *s,
|
||||
.refinement = {.angle_absolute_rad =
|
||||
1e-3 * 3.14159265358979323846 / 180.0,
|
||||
.angle_relative = 0.1,
|
||||
.jacobian_minimum = 1e-3,
|
||||
.min_edge_pixels = 0.5,
|
||||
.min_area_pixels2 = 0.25}};
|
||||
*write_path = NULL;
|
||||
@@ -181,6 +182,8 @@ static int parse_args(int argc, char **argv, Settings *s,
|
||||
s->refinement.angle_absolute_rad *= 3.14159265358979323846 / 180.0;
|
||||
} else if (!strcmp(argv[i], "--refine-angle-rel") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->refinement.angle_relative)) {
|
||||
} else if (!strcmp(argv[i], "--refine-jacobian-min") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->refinement.jacobian_minimum)) {
|
||||
} else if (!strcmp(argv[i], "--refine-min-edge-pixels") && i + 1 < argc &&
|
||||
!parse_positive(argv[++i], &s->refinement.min_edge_pixels)) {
|
||||
} else if (!strcmp(argv[i], "--refine-min-area-pixels2") && i + 1 < argc &&
|
||||
@@ -528,6 +531,7 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
"[--coarse-cell-pixels N] [--refine-max-level N "
|
||||
"--refine-angle-abs-deg D --refine-angle-rel R "
|
||||
"--refine-jacobian-min J "
|
||||
"--refine-min-edge-pixels P --refine-min-area-pixels2 A] "
|
||||
"[--draw-mesh] [--write-catalog PATH] "
|
||||
"[--catalog-load-workers N] "
|
||||
|
||||
+24
-1
@@ -164,9 +164,10 @@ int main(void) {
|
||||
* shared diagonal probe must produce one stable midpoint and conforming
|
||||
* children only after its endpoint has been installed. */
|
||||
FrameLensMesh adaptive_mesh = {0};
|
||||
const RefinementConfig refine = {.max_level = 1,
|
||||
RefinementConfig refine = {.max_level = 1,
|
||||
.angle_absolute_rad = 1e-4,
|
||||
.angle_relative = 1e-4,
|
||||
.jacobian_minimum = 1e-3,
|
||||
.min_edge_pixels = 1.0,
|
||||
.min_area_pixels2 = 1.0};
|
||||
if (frame_lens_mesh_build_coarse(&adaptive_mesh, width, height, 100, 30.0))
|
||||
@@ -192,6 +193,28 @@ int main(void) {
|
||||
goto done;
|
||||
}
|
||||
frame_lens_mesh_destroy(&adaptive_mesh);
|
||||
/* A zero discrete Jacobian is an independent fold/critical-band trigger:
|
||||
* the probe below agrees exactly with linear endpoint interpolation, so the
|
||||
* direction-error criterion cannot request this split. */
|
||||
if (frame_lens_mesh_build_coarse(&adaptive_mesh, width, height, 100, 30.0))
|
||||
goto done;
|
||||
for (size_t i = 0; i < adaptive_mesh.vertex_count; ++i) {
|
||||
adaptive_mesh.vertices[i].traced = 1;
|
||||
adaptive_mesh.vertices[i].status = RAY_ENDPOINT_ESCAPED;
|
||||
adaptive_mesh.vertices[i].n_infinity[0] = 1.0;
|
||||
}
|
||||
const RayEndpoint flat_probe = {.n_infinity = {1.0, 0.0, 0.0},
|
||||
.frequency_ratio = 1.0,
|
||||
.status = RAY_ENDPOINT_ESCAPED};
|
||||
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 1 ||
|
||||
frame_lens_mesh_install_sample(&adaptive_mesh, 0, &flat_probe) ||
|
||||
frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 1 ||
|
||||
adaptive_mesh.vertex_count != 5 || adaptive_mesh.triangle_count != 4) {
|
||||
fputs("adaptive discrete-Jacobian split regression failed\n", stderr);
|
||||
frame_lens_mesh_destroy(&adaptive_mesh);
|
||||
goto done;
|
||||
}
|
||||
frame_lens_mesh_destroy(&adaptive_mesh);
|
||||
result = 0;
|
||||
done:
|
||||
frame_lens_mesh_destroy(&mesh);
|
||||
|
||||
@@ -64,6 +64,7 @@ int main(void) {
|
||||
const RefinementConfig refinement = {.max_level = 1,
|
||||
.angle_absolute_rad = 1e-5,
|
||||
.angle_relative = 1e-5,
|
||||
.jacobian_minimum = 1e-3,
|
||||
.min_edge_pixels = 1.0,
|
||||
.min_area_pixels2 = 1.0};
|
||||
if (frame_lens_mesh_build_coarse(&mesh, 48, 48, 24, 40.0) ||
|
||||
|
||||
@@ -88,7 +88,7 @@ SampleRequest -> RayPool (SoA, inactive / active / terminated)
|
||||
|
||||
## Phase D:补全多 pass adaptive mesh 与 out-of-core 准备(未开始)
|
||||
|
||||
1. `FrameLensMesh` 采用 append-only stable vertex ID、generation-local request/probe 队列、triangle level 与“缺失 endpoint”状态。每 pass 的 probe 只放在最长 image-plane 边中点;完整 sweep 后才安装 endpoint 并改变拓扑。细分要求同时超过绝对与相对 `n_infinity` 方向误差阈值,并受最大层数、最小长边和最小面积约束;capture/escape 不一致强制细分。首版不计算 orientation 或 Jacobian。
|
||||
1. `FrameLensMesh` 采用 append-only stable vertex ID、generation-local request/probe 队列、triangle level 与“缺失 endpoint”状态。每 pass 的 probe 只放在最长 image-plane 边中点;完整 sweep 后才安装 endpoint 并改变拓扑。细分保留同时超过绝对与相对 `n_infinity` 方向误差阈值的几何判据,并受最大层数、最小长边和最小面积约束;capture/escape 不一致强制细分。另以三顶点的有向球面面积比 `J = Omega_source / Omega_image` 独立检测临界带:当前仅 `abs(J)` 小于阈值时细分;相邻三角形的 `J` 异号细分暂不启用,留待验证 `abs(J)` 的覆盖能力后再决定。
|
||||
2. 以迭代 queue(可按 frame/root tile 并行、线程本地 request buffer 后 sort/deduplicate)替代递归 task。每一个 pass 完整执行 Phase C sweep;只有所有 frame 都无新请求才进行 catalog splat。
|
||||
3. 在 movie 生命周期中及时释放已完成的 RayPool、临时 request 和单帧 HDR;保留最终 mesh/endpoints,或在渲染 PNG 后按明确策略释放,避免视频时无界增长。
|
||||
4. 为 nmesh 预留并实现 source-side slab overlap / temporal ghost-slice 契约、可配置 memory budget、slab coverage 日志和线程本地 `MetricWorkspace` ownership。此 phase 不重采样为 Cartesian grid,也不假定相邻时间 slice 的 AMR tree 相同。
|
||||
|
||||
Reference in new issue
Block a user