c/3d/shadowmap_demo.c · 14225 字节 · 下载原文
/* shadowmap_demo.c —— `#654 ui_3d_viewport_set_shadow_map` 的例程判据。
* shadowmap_demo.c - the acceptance example for `#654 ui_3d_viewport_set_shadow_map`.
*
* 它要证明的不是"画面上多了块黑"(那 `#642` 平面投影阴影也能做到),而是**新的那一件事**:
* It has to prove is not "some black appeared" (the planar shadow `#642` does that too) but the **new
* capability**:
* 影子落在**一块抬高的平面**上 —— 老路(把物体沿光方向压到固定的 `y = 0`)**够不着**那里。
* the shadow lands on a **raised surface**, which the old path cannot reach (it flattens objects onto a
* fixed `y = 0` plane).
*
* 场景:一大块地面(y = 0)+ 一座台子(顶面 y = 90)+ 悬在台子上方的方块。
* Scene: a wide ground plane (y = 0) + a plateau (top face at y = 90) + a box floating above it.
*
* 判据(每条都要能响;反证见 `docs/VML3D对标浏览器.md` 的 D 段):
* Criteria (each must be able to go red):
* ① 没开阴影时,台子顶面在方块正下方是**亮的**;
* ① with no shadow at all, the plateau top under the caster is **lit**;
* ② 开 `#642`(平面投影)**够不着**台子顶面(那个像素逐位不变)—— 这就是"新老之分";
* ② with `#642` (the planar path) it **cannot reach** the raised top (that pixel stays bit-identical);
* ③ 开 `#654` 之后同一个像素**变暗**;
* ③ with `#654` that same pixel **darkens**;
* ④ 关掉 `#654` 之后**逐位还原**(老程序不受影响那条保证);
* ④ turning `#654` off restores it **bit-identically**;
* ⑤ 没有自阴影噪声(acne):台子上远离影子的取样点与"没开阴影"**逐位相同**;
* ⑤ no shadow acne: a plateau point far from the shadow stays **bit-identical** to the unshadowed frame;
* ⑥ **光转了,影子跟着转**:把主光换到另一边,新落点变暗、老落点变亮。
* ⑥ **the shadow follows the light**: move the key light and the new spot darkens while the old one lights up.
*
* 机器判读:逐行 `PASS …` / `FAIL …`,末行 `SHADOWMAP_DEMO PASS|FAIL (fails=N)`。
* Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `SHADOWMAP_DEMO 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 lum(int c) { return rch(c) + gch(c) + bch(c); }
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); }
}
/* ══ 网格缓冲:一张方片、一座台子、一个方块 ══════════════════════════════════════ */
/* == Mesh buffer: a ground quad, a plateau box, a cube == */
float MB[2048];
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`). */
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 add_face(float ax, float ay, float az, float bx, float by, float bz,
float cx, float cy, float cz, float dx, float dy, float dz,
float nx, float ny, float nz) {
int b;
b = g_vn;
mb_vertex_uv(b + 0, ax, ay, az, nx, ny, nz, 0.0f, 1.0f);
mb_vertex_uv(b + 1, bx, by, bz, nx, ny, nz, 1.0f, 1.0f);
mb_vertex_uv(b + 2, cx, cy, cz, nx, ny, nz, 1.0f, 0.0f);
mb_vertex_uv(b + 3, dx, dy, dz, nx, ny, nz, 0.0f, 0.0f);
g_vn = g_vn + 4;
}
void quad(float hx, float hz, float y, float nx, float ny, float nz) {
g_vn = 0; g_in = 0;
mb_vertex_uv(0, 0 - hx, y, 0 - hz, nx, ny, nz, 0.0f, 1.0f);
mb_vertex_uv(1, hx, y, 0 - hz, nx, ny, nz, 1.0f, 1.0f);
mb_vertex_uv(2, hx, y, hz, nx, ny, nz, 1.0f, 0.0f);
mb_vertex_uv(3, 0 - hx, y, hz, nx, ny, nz, 0.0f, 0.0f);
g_vn = 4;
g_in = 0;
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;
}
/* 长方体(半边长各自给):**六个面各四个顶点**,为的是每面一个常量法线。
* Box with three half extents: **four vertices per face** so each face gets a constant normal. */
void box(float hx, float hy, float hz) {
int f;
g_vn = 0; g_in = 0;
add_face(0-hx, 0-hy, hz, hx, 0-hy, hz, hx, hy, hz, 0-hx, hy, hz, 0, 0, 1);
add_face(hx, 0-hy, 0-hz, 0-hx, 0-hy, 0-hz, 0-hx, hy, 0-hz, hx, hy, 0-hz, 0, 0, 0-1);
add_face(0-hx, hy, hz, hx, hy, hz, hx, hy, 0-hz, 0-hx, hy, 0-hz, 0, 1, 0);
add_face(0-hx, 0-hy, 0-hz, hx, 0-hy, 0-hz, hx, 0-hy, hz, 0-hx, 0-hy, hz, 0, 0-1, 0);
add_face(hx, 0-hy, hz, hx, 0-hy, 0-hz, hx, hy, 0-hz, hx, hy, hz, 1, 0, 0);
add_face(0-hx, 0-hy, 0-hz, 0-hx, 0-hy, hz, 0-hx, hy, hz, 0-hx, hy, 0-hz, 0-1, 0, 0);
for (f = 0; f < 6; f = f + 1) {
int b = f * 4, k = f * 6;
mb_index(k+0, b+0); mb_index(k+1, b+1); mb_index(k+2, b+2);
mb_index(k+3, b+0); mb_index(k+4, b+2); mb_index(k+5, b+3);
}
g_in = 36;
}
/* ── 场景常量(判据里的期望值都是**按它们算出来的**,不是拍出来的)────────────── */
/* -- Scene constants; every expected value below is **derived** from them -- */
#define PLATEAU_HALF 120.0f /* 台子半宽(x/z)*/
/* plateau half width in x and z */
#define PLATEAU_TOP 90.0f /* 台子顶面的高度 y */
/* world y of the plateau's top face */
#define CASTER_Y 150.0f /* 方块中心的高度 */
/* world y of the caster's centre */
#define CASTER_HALF 25.0f
/* 影子落点:把方块中心沿**光线方向**投到台子顶面上。
* Shadow landing point: project the caster's centre onto the plateau top **along the light**. */
void shadow_point(float lx, float ly, float lz, float* outx, float* outz) {
float ln = sqrt(lx * lx + ly * ly + lz * lz);
float t = (CASTER_Y - PLATEAU_TOP) / (ly / ln);
*outx = 0.0f - (lx / ln) * t;
*outz = 0.0f - (lz / ln) * t;
}
/* 建一个极小的自发光标记物,拿它的屏幕坐标,然后**藏起来**(藏起来之后它既不画、也不投影)。
* Make a tiny emissive marker, read its screen position, then **hide it** (hidden means it is neither
* drawn nor a shadow caster). */
int marker_at(int vp, float x, float y, float z, int* px, int* py) {
int m; int o; int sc;
quad(1.5f, 1.5f, 0.0f, 0.0f, 1.0f, 0.0f);
m = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
o = ui_3d_obj_create(vp, m);
ui_3d_obj_set_pos(o, x, y, z);
ui_3d_obj_set_color(o, argb(255, 0, 255));
ui_3d_obj_set_emissive(o, 1);
sc = ui_3d_obj_get_screen(o);
*px = sc & 0xFFFF;
*py = (sc >> 16) & 0xFFFF;
ui_3d_obj_set_visible(o, 0);
return o;
}
int main(void) {
int w; int h; int vp; int mg; int mb; int mc;
int og; int ob; int oc; int r; int c;
int sx; int sy; int fx; int fy; int sx2; int sy2;
float px1; float pz1; float px2; float pz2;
int ground_off; int plateau_off; int far_off;
int plateau_on; int far_on; int ground_on;
g_fails = 0;
ui_win_open_ex("阴影贴图 / Shadow map", 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);
(void)w; (void)h;
vp = ui_3d_viewport_create(0, 0, 320, 320, 0);
ck("viewport -> handle", vp > 0, vp, 0);
if (vp <= 0) { printf("SHADOWMAP_DEMO FAIL (fails=1)\n"); return 1; }
/* 相机从 +z 上方斜看;影子朝 +z 落(在方块**靠相机这一侧**)⇒ 不会被方块自己挡住。
* The camera looks down from +z; the shadow lands toward +z (the camera side of the caster), so the
* caster never hides it. */
ui_3d_viewport_set_camera(vp, 0.0f, 420.0f, 420.0f, 0.0f, 60.0f, 0.0f);
ui_3d_viewport_set_fov(vp, 50.0f, 1.0f, 4000.0f);
ui_3d_viewport_set_bg(vp, argb(18, 20, 26));
ui_3d_viewport_set_light(vp, 0.3f, 1.0f, 0.0f - 0.4f, argb(255, 255, 255), 0.35f);
ui_2d_clear(0);
/* 地面(y = 0) */
/* Ground (y = 0) */
quad(260.0f, 260.0f, 0.0f, 0.0f, 1.0f, 0.0f);
mg = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
ck("ground mesh rc", mg > 0, mg, 0);
og = ui_3d_obj_create(vp, mg);
ui_3d_obj_set_color(og, argb(190, 190, 190));
/* 台子:半高 45 ⇒ 底面 y = 0、顶面 y = 90(正是判据要的那块**抬高的接收面**)。
* Plateau: half height 45, so its bottom is y = 0 and its top is y = 90 - the **raised receiver**. */
box(PLATEAU_HALF, PLATEAU_TOP * 0.5f, PLATEAU_HALF);
mb = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
ck("plateau mesh rc", mb > 0, mb, 0);
ob = ui_3d_obj_create(vp, mb);
ui_3d_obj_set_pos(ob, 0.0f, PLATEAU_TOP * 0.5f, 0.0f);
ui_3d_obj_set_color(ob, argb(200, 210, 205));
/* 悬在台子上方的方块(投影体) */
/* The box floating above the plateau (our caster). */
box(CASTER_HALF, CASTER_HALF, CASTER_HALF);
mc = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
ck("caster mesh rc", mc > 0, mc, 0);
oc = ui_3d_obj_create(vp, mc);
ui_3d_obj_set_pos(oc, 0.0f, CASTER_Y, 0.0f);
ui_3d_obj_set_color(oc, argb(230, 90, 60));
/* 三个取样点:影子落点(台子顶面)、台子上一个远离影子的点、地面上的一个点。
* Three sample points: the shadow spot (plateau top), a plateau point far from the shadow, and a
* point on the lower ground. */
shadow_point(0.3f, 1.0f, 0.0f - 0.4f, &px1, &pz1);
marker_at(vp, px1, PLATEAU_TOP + 0.5f, pz1, &sx, &sy);
marker_at(vp, 0.0f - 95.0f, PLATEAU_TOP + 0.5f, 0.0f - 95.0f, &fx, &fy);
printf("SHADOW_AT=%.1f,%.1f SCREEN=%d,%d FARSCREEN=%d,%d\n", px1, pz1, sx, sy, fx, fy);
/* ① 基线:没开任何阴影 */
/* (1) Baseline: no shadow at all. */
ground_off = ui_3d_viewport_get_pixel(vp, sx, sy);
plateau_off = ui_3d_viewport_get_pixel(vp, fx, fy);
printf("BASE shadowspot=%d plateaufar=%d\n", lum(ground_off), lum(plateau_off));
ck("1 baseline: receiver under the caster is lit",
lum(ground_off) > 300, lum(ground_off), 300);
/* ② 老路(`#642` 平面投影)**够不着**抬高了的接收面 —— 这就是"新老之分"。
* ② The old path (`#642`, planar) **cannot reach** the raised receiver - this is the whole
* difference between old and new. */
r = ui_3d_viewport_set_shadow(vp, 1);
ck("2 set planar shadow rc", r == 0, r, 0);
c = ui_3d_viewport_get_pixel(vp, sx, sy);
printf("PLANAR shadowspot=%d\n", lum(c));
ck("2 planar shadow does NOT reach the raised top", c == ground_off, c, ground_off);
ui_3d_viewport_set_shadow(vp, 0);
/* ③ 阴影贴图:同一个像素变暗 */
/* (3) Shadow map: the same pixel darkens. */
r = ui_3d_viewport_set_shadow_map(vp, 1);
ck("3 set shadow map rc", r == 0, r, 0);
ground_on = ui_3d_viewport_get_pixel(vp, sx, sy);
printf("SHADOWMAP shadowspot=%d\n", lum(ground_on));
ck("3 shadow map darkens the raised receiver",
lum(ground_on) < lum(ground_off) - 60, lum(ground_on), lum(ground_off));
/* ⑤ 没有自阴影噪声:远离影子的台面与"没开阴影"**逐位相同**
* ⑤ No acne: a plateau point far from the shadow stays **bit-identical** to the unshadowed frame */
far_on = ui_3d_viewport_get_pixel(vp, fx, fy);
ck("5 no shadow acne away from the caster", far_on == plateau_off, far_on, plateau_off);
/* ⑥ 光转了,影子跟着转:新落点变暗、老落点变亮 */
/* (6) Move the light: the new spot darkens and the old one lights up. */
shadow_point(0.0f - 0.3f, 1.0f, 0.0f - 0.4f, &px2, &pz2);
marker_at(vp, px2, PLATEAU_TOP + 0.5f, pz2, &sx2, &sy2);
printf("SHADOW2_AT=%.1f,%.1f SCREEN=%d,%d\n", px2, pz2, sx2, sy2);
ui_3d_viewport_set_light(vp, 0.0f - 0.3f, 1.0f, 0.0f - 0.4f, argb(255, 255, 255), 0.35f);
{
int old_spot; int new_spot;
old_spot = ui_3d_viewport_get_pixel(vp, sx, sy);
new_spot = ui_3d_viewport_get_pixel(vp, sx2, sy2);
printf("MOVED old=%d new=%d\n", lum(old_spot), lum(new_spot));
ck("6a old spot becomes lit again", lum(old_spot) > lum(ground_on) + 60, lum(old_spot), lum(ground_on));
ck("6b new spot is shadowed", lum(new_spot) < lum(ground_off) - 60, lum(new_spot), lum(ground_off));
}
/* 把光放回去(后面的④要靠"关掉之后逐位还原") */
/* Put the light back (step 4 relies on a bit-identical restore). */
ui_3d_viewport_set_light(vp, 0.3f, 1.0f, 0.0f - 0.4f, argb(255, 255, 255), 0.35f);
/* ④ 关掉 ⇒ **逐位**回到基线(老程序不受影响那条保证的落点) */
/* (4) Off => **bit-identical** back to the baseline. */
r = ui_3d_viewport_set_shadow_map(vp, 0);
ck("4 clear shadow map rc", r == 0, r, 0);
c = ui_3d_viewport_get_pixel(vp, sx, sy);
ck("4 shadow map off -> bit-identical restore", c == ground_off, c, ground_off);
/* 句柄校验:坏视口必须**失败**(负数),而不是静默成功 */
/* Handle validation: a bad viewport must **fail** (negative), never silently succeed. */
r = ui_3d_viewport_set_shadow_map(9999, 1);
ck("bad viewport rejected", r < 0, r, 0);
printf("SHADOWMAP_DEMO %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
return g_fails;
}