Frame: gate fold refinement by Jacobian
This commit is contained in:
1 parent
4c575fa8b5
commit
b95c6579bd
4 files changed
+65
-31
No files matched your search
@@ -180,13 +180,14 @@ computes the discrete lens Jacobian
|
|||||||
`J = Omega_source / Omega_image`. Both signed solid angles use
|
`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
|
`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
|
ordered camera directions for `Omega_image` and their traced infinity
|
||||||
directions for `Omega_source`. `abs(J) < --refine-jacobian-min` requests a
|
directions for `Omega_source`. A J-driven split requires **both** a shared
|
||||||
split near a critical curve. The signed-area calculation retains the sign of
|
image edge whose incident triangles have opposite nonzero signs of `J` and
|
||||||
`J`, but sign changes across adjacent triangles are not currently a refinement
|
`min(abs(J_left), abs(J_right)) < --refine-jacobian-min`. It then requests
|
||||||
trigger; this makes it possible to evaluate the `abs(J)` criterion separately.
|
that shared edge on both leaves. Thus `|J|` bounds the fold selection instead
|
||||||
The `1e-3` default is deliberately provisional and should be tuned with the
|
of widening it as a standalone critical-curve band. A negative sign is
|
||||||
small Schwarzschild refinement diagnostic before being treated as a production
|
physical parity and is retained. The `1e-3` default is deliberately
|
||||||
threshold.
|
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
|
For a short Schwarzschild diagnostic that permits at most one actual split
|
||||||
generation, for example:
|
generation, for example:
|
||||||
|
|||||||
+37
-12
@@ -344,9 +344,11 @@ static double spherical_signed_area(const double a[3], const double b[3],
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Returns whether the discrete source/image solid-angle ratio is available.
|
/* Returns whether the discrete source/image solid-angle ratio is available.
|
||||||
* Captured and degenerate image triangles have no reliable value. */
|
* A zero output parity is a valid, critical (zero-Jacobian) result; captured
|
||||||
|
* and degenerate image triangles have no reliable parity. */
|
||||||
static int discrete_jacobian(const FrameLensMesh *mesh,
|
static int discrete_jacobian(const FrameLensMesh *mesh,
|
||||||
const LensTriangle *triangle, double *value) {
|
const LensTriangle *triangle, double *value,
|
||||||
|
signed char *parity) {
|
||||||
const LensVertex *a = &mesh->vertices[triangle->vertex[0]];
|
const LensVertex *a = &mesh->vertices[triangle->vertex[0]];
|
||||||
const LensVertex *b = &mesh->vertices[triangle->vertex[1]];
|
const LensVertex *b = &mesh->vertices[triangle->vertex[1]];
|
||||||
const LensVertex *c = &mesh->vertices[triangle->vertex[2]];
|
const LensVertex *c = &mesh->vertices[triangle->vertex[2]];
|
||||||
@@ -363,6 +365,7 @@ static int discrete_jacobian(const FrameLensMesh *mesh,
|
|||||||
if (!isfinite(jacobian))
|
if (!isfinite(jacobian))
|
||||||
return 0;
|
return 0;
|
||||||
*value = jacobian;
|
*value = jacobian;
|
||||||
|
*parity = jacobian > 0.0 ? 1 : jacobian < 0.0 ? -1 : 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,8 +459,11 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
|||||||
MeshEdge *edges = calloc(edge_count, sizeof *edges);
|
MeshEdge *edges = calloc(edge_count, sizeof *edges);
|
||||||
unsigned char *requested = calloc(edge_count, sizeof *requested);
|
unsigned char *requested = calloc(edge_count, sizeof *requested);
|
||||||
unsigned char *allowed = calloc(mesh->triangle_count, sizeof *allowed);
|
unsigned char *allowed = calloc(mesh->triangle_count, sizeof *allowed);
|
||||||
if (edges == NULL || requested == NULL || allowed == NULL) {
|
signed char *parity = calloc(mesh->triangle_count, sizeof *parity);
|
||||||
free(edges); free(requested); free(allowed);
|
double *jacobians = calloc(mesh->triangle_count, sizeof *jacobians);
|
||||||
|
if (edges == NULL || requested == NULL || allowed == NULL || parity == NULL ||
|
||||||
|
jacobians == NULL) {
|
||||||
|
free(edges); free(requested); free(allowed); free(parity); free(jacobians);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < mesh->triangle_count; ++i) {
|
for (size_t i = 0; i < mesh->triangle_count; ++i) {
|
||||||
@@ -472,13 +478,32 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
|||||||
if (allowed[i]) {
|
if (allowed[i]) {
|
||||||
const unsigned int side = longest_side(mesh, triangle);
|
const unsigned int side = longest_side(mesh, triangle);
|
||||||
requested[3 * i + side] = probe_requires_split(mesh, triangle, side, config);
|
requested[3 * i + side] = probe_requires_split(mesh, triangle, side, config);
|
||||||
double jacobian;
|
(void)discrete_jacobian(mesh, triangle, &jacobians[i], &parity[i]);
|
||||||
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);
|
qsort(edges, edge_count, sizeof *edges, compare_mesh_edge);
|
||||||
|
/* A fold is selected only when its adjacent discrete parities disagree and
|
||||||
|
* at least one of those leaves is close enough to the critical curve. */
|
||||||
|
for (size_t first = 0; first < edge_count;) {
|
||||||
|
size_t last = first + 1;
|
||||||
|
while (last < edge_count && edges[last].a == edges[first].a &&
|
||||||
|
edges[last].b == edges[first].b)
|
||||||
|
++last;
|
||||||
|
if (last - first == 2) {
|
||||||
|
const size_t left = edges[first].triangle;
|
||||||
|
const size_t right = edges[first + 1].triangle;
|
||||||
|
if (parity[left] != 0 && parity[right] != 0 &&
|
||||||
|
parity[left] != parity[right] &&
|
||||||
|
fmin(fabs(jacobians[left]), fabs(jacobians[right])) <
|
||||||
|
config->jacobian_minimum) {
|
||||||
|
if (allowed[left] && allowed[right]) {
|
||||||
|
requested[3 * left + edges[first].side] = 1;
|
||||||
|
requested[3 * right + edges[first + 1].side] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first = last;
|
||||||
|
}
|
||||||
/* A requested interior edge is split by both incident leaves, preserving a
|
/* A requested interior edge is split by both incident leaves, preserving a
|
||||||
* conforming mesh. If either side has reached its geometric limit, reject
|
* conforming mesh. If either side has reached its geometric limit, reject
|
||||||
* the whole edge instead of introducing a T-junction. */
|
* the whole edge instead of introducing a T-junction. */
|
||||||
@@ -542,7 +567,7 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
|||||||
if (split_edges == 0) {
|
if (split_edges == 0) {
|
||||||
mesh->sample_count = 0;
|
mesh->sample_count = 0;
|
||||||
mesh->samples_include_probes = 0;
|
mesh->samples_include_probes = 0;
|
||||||
free(edges); free(requested); free(allowed);
|
free(edges); free(requested); free(allowed); free(parity); free(jacobians);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/* Allocate a single stable midpoint vertex for each requested edge group. */
|
/* Allocate a single stable midpoint vertex for each requested edge group. */
|
||||||
@@ -559,7 +584,7 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
|||||||
first = last;
|
first = last;
|
||||||
}
|
}
|
||||||
if (ensure_vertices(mesh, mesh->vertex_count + midpoint_count)) {
|
if (ensure_vertices(mesh, mesh->vertex_count + midpoint_count)) {
|
||||||
free(edges); free(requested); free(allowed);
|
free(edges); free(requested); free(allowed); free(parity); free(jacobians);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
size_t next_vertex = mesh->vertex_count;
|
size_t next_vertex = mesh->vertex_count;
|
||||||
@@ -586,7 +611,7 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
|||||||
const size_t old_count = mesh->triangle_count;
|
const size_t old_count = mesh->triangle_count;
|
||||||
LensTriangle *children = calloc(old_count * 4, sizeof *children);
|
LensTriangle *children = calloc(old_count * 4, sizeof *children);
|
||||||
if (children == NULL) {
|
if (children == NULL) {
|
||||||
free(edges); free(requested); free(allowed);
|
free(edges); free(requested); free(allowed); free(parity); free(jacobians);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
size_t child_count = 0;
|
size_t child_count = 0;
|
||||||
@@ -634,7 +659,7 @@ int frame_lens_mesh_finish_generation(FrameLensMesh *mesh,
|
|||||||
mesh->vertex_count = next_vertex;
|
mesh->vertex_count = next_vertex;
|
||||||
mesh->sample_count = 0;
|
mesh->sample_count = 0;
|
||||||
mesh->samples_include_probes = 0;
|
mesh->samples_include_probes = 0;
|
||||||
free(edges); free(requested); free(allowed);
|
free(edges); free(requested); free(allowed); free(parity); free(jacobians);
|
||||||
return (int)midpoint_count;
|
return (int)midpoint_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-11
@@ -193,24 +193,32 @@ int main(void) {
|
|||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
frame_lens_mesh_destroy(&adaptive_mesh);
|
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},
|
const RayEndpoint flat_probe = {.n_infinity = {1.0, 0.0, 0.0},
|
||||||
.frequency_ratio = 1.0,
|
.frequency_ratio = 1.0,
|
||||||
.status = RAY_ENDPOINT_ESCAPED};
|
.status = RAY_ENDPOINT_ESCAPED};
|
||||||
|
/* Opposite nonzero discrete-Jacobian signs on the two sides of the shared
|
||||||
|
* diagonal require a sufficiently small magnitude before requesting it. */
|
||||||
|
refine.jacobian_minimum = 10.0;
|
||||||
|
refine.angle_absolute_rad = 3.14159265358979323846;
|
||||||
|
refine.angle_relative = 1e6;
|
||||||
|
if (frame_lens_mesh_build_coarse(&adaptive_mesh, width, height, 100, 30.0))
|
||||||
|
goto done;
|
||||||
|
const double source_directions[4][3] = {
|
||||||
|
{1.0, 0.0, 0.0},
|
||||||
|
{sqrt(0.99), 0.0, 0.1},
|
||||||
|
{sqrt(0.99), 0.0, 0.1},
|
||||||
|
{sqrt(0.98), 0.1, 0.1}};
|
||||||
|
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;
|
||||||
|
memcpy(adaptive_mesh.vertices[i].n_infinity, source_directions[i],
|
||||||
|
sizeof source_directions[i]);
|
||||||
|
}
|
||||||
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 1 ||
|
if (frame_lens_mesh_prepare_generation(&adaptive_mesh, &refine) != 1 ||
|
||||||
frame_lens_mesh_install_sample(&adaptive_mesh, 0, &flat_probe) ||
|
frame_lens_mesh_install_sample(&adaptive_mesh, 0, &flat_probe) ||
|
||||||
frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 1 ||
|
frame_lens_mesh_finish_generation(&adaptive_mesh, &refine) != 1 ||
|
||||||
adaptive_mesh.vertex_count != 5 || adaptive_mesh.triangle_count != 4) {
|
adaptive_mesh.vertex_count != 5 || adaptive_mesh.triangle_count != 4) {
|
||||||
fputs("adaptive discrete-Jacobian split regression failed\n", stderr);
|
fputs("adaptive fold-parity split regression failed\n", stderr);
|
||||||
frame_lens_mesh_destroy(&adaptive_mesh);
|
frame_lens_mesh_destroy(&adaptive_mesh);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ SampleRequest -> RayPool (SoA, inactive / active / terminated)
|
|||||||
|
|
||||||
## Phase D:补全多 pass adaptive mesh 与 out-of-core 准备(未开始)
|
## 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 不一致强制细分。另以三顶点的有向球面面积比 `J = Omega_source / Omega_image` 独立检测临界带:当前仅 `abs(J)` 小于阈值时细分;相邻三角形的 `J` 异号细分暂不启用,留待验证 `abs(J)` 的覆盖能力后再决定。
|
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` 检测 fold:仅当共享边两侧非零 `J` 异号,且 `min(abs(J_left), abs(J_right))` 小于阈值时,细分该共享边的两叶;负 parity 本身保留而不剔除。
|
||||||
2. 以迭代 queue(可按 frame/root tile 并行、线程本地 request buffer 后 sort/deduplicate)替代递归 task。每一个 pass 完整执行 Phase C sweep;只有所有 frame 都无新请求才进行 catalog splat。
|
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 后按明确策略释放,避免视频时无界增长。
|
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 相同。
|
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