c/3d/skybox_radius.c · 9904 字节 · 下载原文
/* skybox_radius.c —— **天空盒半径 180 = 可见世界半径**,两个方向各一条判据。
* skybox_radius.c - **the skybox radius of 180 is the visible world radius**; one criterion per direction.
*
* 存在的理由:天空盒不是"无限远的背景",而是**真的放在世界里**的一个半径 180 的盒子(本仓没有"无限远"机制)。
* Why this exists: the skybox is not an infinitely far background; it is a real box of radius 180 placed in the
* world (this repo has no "infinity" mechanism).
*
* 盒子**以相机为中心**(见 `Vml3dRetained.RenderViewport`),于是它与深度测试一起把"看得见的世界"钉成一个球。
* The box is **centred on the camera** (see `Vml3dRetained.RenderViewport`), so together with depth testing it pins the visible world to a ball.
* ⚠ **距相机 180 以内的几何看得见、180 以外的被后壁挡住**。
* ⚠ **Geometry within 180 of the camera shows; anything farther is hidden by the far wall.**
*
* ⚠ 在加这条判据之前,**两个方向都没有人守**:`landscape.c` 的注释写着"一切都在 180 以内",但没有任何一条判据会因为它变红。
* ⚠ Before this file, **neither direction was guarded**: `landscape.c` comments that everything stays within 180, but no criterion went red when it changed.
* 半径被改错时(太大 ⇒ 远处不再被挡;太小 ⇒ 近处被吃掉)**一声不响**。
* A wrong radius (too large hides nothing, too small eats the near geometry) went completely silent.
*
* ⚠ 注释里那句"半径必须小于 zfar 减去相机距离"是**旧话**(盒子已改为跟随相机,`zfar` 不再是约束)。
* ⚠ The old note "the radius must be less than zfar minus the camera distance" is **stale**: the box now
* follows the camera, so `zfar` is no longer the binding constraint.
*
* 机器判读:逐行 `PASS …` / `FAIL …`,末行 `SKYBOX_RADIUS PASS|FAIL (fails=N)`。
* Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `SKYBOX_RADIUS PASS|FAIL (fails=N)`.
*/
#include <vml_ui.h>
#include <vml_3d.h>
#include <math.h>
#include <stdio.h>
int g_fails;
int rch(int c) { return (c >> 16) & 0xFF; }
int gch(int c) { return (c >> 8) & 0xFF; }
int bch(int c) { return c & 0xFF; }
int argb(int r, int g, int b) { return -16777216 + r * 65536 + g * 256 + b; }
/* 通过 / 失败各打一行。
* One PASS / FAIL line each. */
void ck(char* tag, int cond, int a, int b) {
if (cond) { printf("PASS %s %d %d\n", tag, a, b); }
else { g_fails = g_fails + 1; printf("FAIL %s %d %d\n", tag, a, b); }
}
/* 一格颜色是不是那个 RGB(忽略 alpha)。
* Is a colour channel triple that RGB (alpha ignored)? */
int is_rgb(int c, int r, int g, int b) { return rch(c) == r && gch(c) == g && bch(c) == b; }
/* ══ 网格缓冲:一张 60x60 的方片,法线 +Z(正对相机),自发光靠调用方设 ══════════ */
/* == Mesh buffer: one 60x60 quad, normal +Z (facing the camera); emissive is set by the caller == */
float MB[512];
int g_vn; int g_in;
void mb_vertex_uv(int i, float px, float py, float pz,
float nx, float ny, float nz, float u, float v) {
int b = i * 8;
MB[b + 0] = px; MB[b + 1] = py; MB[b + 2] = pz;
MB[b + 3] = nx; MB[b + 4] = ny; MB[b + 5] = nz;
MB[b + 6] = u; MB[b + 7] = v;
}
/* ⚠ 索引必须**等全部顶点写完再写**(落笔在 `g_vn*8+k`,即"紧跟顶点之后")。
* ⚠ Indices must be written **after every vertex is in place** (they land at `g_vn*8+k`, right after the vertices). */
void mb_index(int k, int v) { int* ip; ip = (int*)&MB[0]; ip[g_vn * 8 + k] = v; }
int mb_ptr(void) { return (int)&MB[0]; }
void gen_quad(float half) {
g_vn = 0; g_in = 0;
mb_vertex_uv(0, 0 - half, 0 - half, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
mb_vertex_uv(1, half, 0 - half, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
mb_vertex_uv(2, half, half, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
mb_vertex_uv(3, 0 - half, half, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
g_vn = 4;
mb_index(0, 0); mb_index(1, 1); mb_index(2, 2);
mb_index(3, 0); mb_index(4, 2); mb_index(5, 3);
g_in = 6;
}
/* 盒子半径(世界单位)—— 与宿主 `Vml3dRetained.SkyboxRadius` **必须一致**,下面两条探针距离围着它取。
* Box radius in world units; it **must match** the host's `Vml3dRetained.SkyboxRadius`. The two probe
* distances below are chosen around it. */
#define SKY_RADIUS 180.0f
int main(void) {
int w; int h; int vp; int mesh; int obj; int img;
int r; int c; int c_near; int c_far;
g_fails = 0;
ui_win_open_ex("天空盒半径 / Skybox radius", 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD);
w = ui_scr_get_w_ex(ui_scr_get_orientation(), VML_CHROME_NONE);
h = ui_scr_get_h_ex(ui_scr_get_orientation(), VML_CHROME_NONE);
if (w <= 0) w = 393;
if (h <= 0) h = 757;
/* 一张**纯蓝**的等距圆柱图:任何方向采到的都是同一个色 ⇒ 与"红方片"一眼分得开。
* A **solid blue** equirectangular image: every direction samples the same colour, so it is
* unmistakable against the red quad. */
ui_2d_clear(0);
ui_2d_rect(0, 0, 32, 16, argb(30, 30, 200), 1, 0, 0);
ui_2d_present();
img = ui_2d_get_image(0, 0, 32, 16);
ck("skybox image -> handle", img > 0, img, 0);
if (img <= 0) { printf("SKYBOX_RADIUS FAIL (fails=1)\n"); return 1; }
vp = ui_3d_viewport_create(0, 0, 256, 256, 0);
ck("viewport -> handle", vp > 0, vp, 0);
if (vp <= 0) { printf("SKYBOX_RADIUS FAIL (fails=1)\n"); return 1; }
/* 相机在原点、看向 -Z:探针方片就摆在 -Z 上,距离 = |z| —— 读数好解释。
* The camera sits at the origin looking down -Z, so the probe quad sits on -Z and its distance is |z|,
* which keeps the readings easy to read. */
ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f - 1.0f);
ui_3d_viewport_set_fov(vp, 60.0f, 1.0f, 8000.0f);
ui_3d_viewport_set_bg(vp, argb(10, 10, 10));
ui_2d_clear(0);
gen_quad(30.0f);
ck("mesh buffer fits", g_vn * 8 + g_in <= 512, g_vn * 8 + g_in, 512);
mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
ck("mesh -> handle", mesh > 0, mesh, 0);
obj = ui_3d_obj_create(vp, mesh);
ck("obj -> handle", obj > 0, obj, 0);
/* 自发光 ⇒ 读回来的就是那个橙色(255,0,0)那一族,与光照方向无关。
* Emissive, so the read-back is that red family regardless of the light direction. */
ui_3d_obj_set_emissive(obj, 1);
ui_3d_obj_set_color(obj, argb(255, 0, 0));
/* ── 基线:没有天空盒时,远近两个距离都看得见(深色方片)──────────────────────
* -- Baseline: with no skybox, both the near and the far quad are visible (red) -- */
ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 0.55f));
c_near = ui_3d_viewport_get_pixel(vp, 128, 128);
ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 2.2f));
c_far = ui_3d_viewport_get_pixel(vp, 128, 128);
ck("no skybox -> near quad visible", is_rgb(c_near, 255, 0, 0), c_near, argb(255, 0, 0));
ck("no skybox -> far quad visible", is_rgb(c_far, 255, 0, 0), c_far, argb(255, 0, 0));
/* ── 开天空盒 ────────────────────────────────────────────────────────────
* -- Turn the skybox on -- */
r = ui_3d_viewport_set_skybox(vp, img);
ck("set_skybox rc", r == 0, r, 0);
/* 角落(天上、没有几何)应当是天空盒那个蓝 —— 先证明**盒子真的在画**。
* The corner (sky, no geometry) must be the skybox blue; this first proves **the box is really drawn**.
* 少了这一条,"半径外被挡住"会以"整个视口都是清屏色"的样子假绿。
* Without it, "outside is occluded" would go green spuriously as "the whole viewport is the clear colour". */
c = ui_3d_viewport_get_pixel(vp, 4, 4);
ck("skybox drew something (corner is sky)", is_rgb(c, 30, 30, 200), c, argb(30, 30, 200));
/* ① 半径**以内**的东西看得见:盒子的前壁在相机前面,方片在它之前 ⇒ 赢深度测试。
* ① Geometry **within** the radius shows: the box front wall is in front of the camera and the quad is
* ahead of it, so it wins the depth test. */
ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 0.55f));
c_near = ui_3d_viewport_get_pixel(vp, 128, 128);
ck("skybox: inside radius still visible", is_rgb(c_near, 255, 0, 0), c_near, argb(255, 0, 0));
/* ② 半径**以外**的东西被后壁挡住:方片在盒子之外 ⇒ 读到的是天空盒的颜色,不是红。
* ② Geometry **beyond** the radius is hidden by the far wall: the quad lies outside the box, so the
* read-back is the skybox colour, not red. */
ui_3d_obj_set_pos(obj, 0.0f, 0.0f, 0.0f - (SKY_RADIUS * 2.2f));
c_far = ui_3d_viewport_get_pixel(vp, 128, 128);
ck("skybox: outside radius is occluded", is_rgb(c_far, 30, 30, 200), c_far, argb(30, 30, 200));
/* ── 反向:取消天空盒 ⇒ 远处那个方片**逐位**回到基线(老程序不受影响那条保证)──
* -- Reverse: clearing the skybox brings the far quad **bit-identically** back to the baseline (the
* "old programs are unaffected" guarantee) -- */
r = ui_3d_viewport_set_skybox(vp, 0);
ck("clear skybox rc", r == 0, r, 0);
c = ui_3d_viewport_get_pixel(vp, 128, 128);
ck("skybox off -> far quad visible again (bit-identical)", c == argb(255, 0, 0), c, argb(255, 0, 0));
printf("SKYBOX_RADIUS %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
return g_fails;
}