← 都来码 / Dolaima← 例程索引c/3d/blend_demo.c原文

c/3d/blend_demo.c · 13132 字节 · 下载原文

/* blend_demo.c —— **透明**(`ui_3d_obj_set_blend` `#641`)专项示例 + 判据。
 * blend_demo.c — **transparency** (`ui_3d_obj_set_blend` `#641`): focused example and criteria.
 *
 * 一块透明的蓝色板挡在一块不透明的红板前面。
 * A transparent blue quad in front of an opaque red quad.
 *
 * 开透明之前,中心像素是前板自己的蓝;开透明之后**后面的红透出来**,红通道升上来。
 * Before blending the centre pixel is the front quad's own blue; after blending the red behind shows through.
 *
 * 这就是「真的混合了」,而不是「画了个暗一点的不透明块」。
 * That is "it really mixed", not "it drew a slightly darker opaque block".
 *
 * 透明度取自 `#619` `ui_3d_obj_set_color` 那个 argb 的 **alpha 通道** —— 本号没有 alpha 形参。
 * The opacity comes from the **alpha channel** of the `#619` `ui_3d_obj_set_color` argb; this number has no alpha parameter.
 *
 * 所以「alpha 越大、透出来的红越少」那条判据同时钉住了两件事:混合确实发生、且来源就是它。
 * So "larger alpha => less red showing through" pins two things at once: blending happens, and this is its source.
 *
 * 透明物体走第二趟、按离相机远近**远→近**排序;不透明趟按创建顺序、一个字节不动。
 * Blended objects go in a second pass sorted far-to-near; the opaque pass keeps creation order untouched.
 *
 * ⚠ 排序粒度是物体中心(不是逐片元)⇒ 大体积互相穿插的透明物仍会排错,本例两块平行板不碰这个限制。
 * ⚠ Sorting is per object centre, not per fragment, so large interpenetrating transparent objects can still sort wrong.
 *
 * 跑法:
 * How to run:
 *   dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll \
 *          third_party/vml/Examples/c/3d/blend_demo.c --screen 393x757
 *
 * 机器判读:逐行 `PASS …` / `FAIL …`,末行 `BLEND_DEMO PASS|FAIL (fails=N)`。
 * Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `BLEND_DEMO PASS|FAIL (fails=N)`.
 */

#include <vml_ui.h>
#include <vml_3d.h>
#include <math.h>
#include <stdio.h>
#include <file.h>

/* ══ 记分 ══════════════════════════════════════════════════════════════════ */
/* == Scoring == */

int g_fails;

/* 判据除了打 stdout,还写一份**结果文件**。
 * Besides stdout, every criterion is also written to a **result file**.
 *
 * ⚠ 手机端跑开窗程序时,命令行页会被绘图页顶掉,而驱动"看到窗口就算跑完" ⇒ 只抓屏会漏掉判据正文。
 * ⚠ On a phone a window-opening program hides the command-line page and the harness counts "window appeared" as done, so scraping the screen misses the criteria.
 *
 * 结果文件落在 cwd(手机上是工作区根),`adb pull` 直接取得到。
 * The file lands in cwd (the workspace root on a phone) and is directly pullable with `adb pull`. */

char RLINE[192];

/* ⚠ 句柄 **0 是合法值**(实测 `fopen` 成功返回 0)⇒ 判"开没开成"必须用**负值**,
 * ⚠ Handle **0 is valid** (measured: a successful `fopen` returns 0), so "did it open" must test for a **negative** value.
 * 写成 `<= 0` 会把成功当失败,一条都不写 —— 这个坑第一次就踩了。
 * Writing `<= 0` treats success as failure and writes nothing, which is exactly what happened the first time. */

int g_rpt = -2;   /* -2 = 还没试过,>= 0 = 已打开,< 0 = 开不了 */

void rpt_line(char* s) {
    int n;
    if (g_rpt == 0 - 2) g_rpt = fopen("_3d_blend_result.txt", 1);
    if (g_rpt < 0) return;
    n = 0;
    while (s[n] != 0) n = n + 1;
    fwrite(g_rpt, s, n);
}

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); }
    sprintf(RLINE, "%s %s %d %d\n", cond ? "PASS" : "FAIL", tag, a, b);
    rpt_line(RLINE);
}

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; }
int argba(int a, int r, int g, int b) { return a * 16777216 + r * 65536 + g * 256 + b; }

/* ══ 网格装配 ══════════════════════════════════════════════════════════════ */
/* == Mesh assembly == */

/* 顶点格式:pos3 + nrm3 + uv2 = 8 个 float32 = 32 字节,索引 int32 紧随其后。
 * Vertex layout: pos3 + nrm3 + uv2 = 8 float32 = 32 bytes, then int32 indices. */

/* ⚠ 数组必须装得下「顶点数 × 8 + 索引数」个 float —— 算错了不报错。
 * ⚠ The array must hold (vertexCount * 8 + indexCount) floats; getting it wrong reports nothing. */

/* 症状是「网格建出来了、mesh_create 返回句柄、但一个像素都不画」(见 light_demo.c 的实测记录)。
 * The symptom is "the mesh is created and handles come back, but not a single pixel is drawn" (see light_demo.c). */

float MB[256];
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;
}
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]; }

/* 一个正对 +Z 的方片(边长 2×half、中心在原点),四角 uv 互不相同。
 * A quad facing +Z (side 2*half, centred at the origin) with four distinct corner uvs. */

void gen_quad(float half) {
    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;
}

int main(void) {
    int w; int h; int vp; int mesh;
    int o_back; int o_front;
    int r; int px; int py; int sc;
    int off_a; int on_a; int off_b;
    int a_low; int a_high;
    int lang;

    g_fails = 0;
    lang = ui_scr_get_language();

    ui_win_open_ex(lang == 0 ? "3D 透明示例" : "3D Blend Demo",
                   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);
    printf("SCR=%dx%d\n", w, h);
    if (w < 64 || h < 64) { printf("BLEND_DEMO FAIL (fails=99)\n"); return 99; }

    vp = ui_3d_viewport_create(0, 0, w, h, 0);
    ck("viewport -> handle", vp > 0, vp, 0);

    /* 相机退到 z=8 看向原点。
     * Camera at z=8 looking at the origin. */

    /* ⚠ set_fov 是**竖直**张角,而竖屏宽高比只有 ~0.52 ⇒ 水平视野只有竖直的一半多。
     * ⚠ set_fov is a **vertical** angle and a portrait screen is only ~0.52 as wide as it is tall. */

    /* 所以方片按竖屏算尺寸:half=3 会横向出屏,但中心点仍稳稳落在板上,正是判据要的。
     * So the quad is sized for portrait: half=3 overflows sideways while the centre stays on the quad. */

    ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 8.0f, 0.0f, 0.0f, 0.0f);
    ui_3d_viewport_set_fov(vp, 55.0f, 0.1f, 100.0f);
    ui_3d_viewport_set_bg(vp, argb(16, 20, 24));
    ui_3d_viewport_set_light(vp, 0.0f, 0.0f, 1.0f, argb(255, 255, 255), 0.2f);

    /* ⚠⚠ 3D 视口是**透过 2D 层**看的 ⇒ 2D 层必须是透明的,否则窗口开了也是一片黑。
     * ⚠⚠ The 3D viewport shows **through** the 2D layer, so that layer must be transparent or the window opens black. */

    ui_2d_clear(0);

    gen_quad(3.0f);
    mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
    ck("mesh_create -> handle", mesh > 0, mesh, 0);

    o_back = ui_3d_obj_create(vp, mesh);
    ui_3d_obj_set_pos(o_back, 0.0f, 0.0f, 0 - 2.0f);
    ui_3d_obj_set_color(o_back, argb(255, 0, 0));

    /* 自发光 ⇒ 片元直接输出基色、不乘光照 ⇒ 读回来的就是红/蓝本身,判据阈值可以卡严。
     * Emissive makes the fragment output the base colour directly, so read-back is the raw red/blue. */

    ui_3d_obj_set_emissive(o_back, 1);

    o_front = ui_3d_obj_create(vp, mesh);
    ui_3d_obj_set_pos(o_front, 0.0f, 0.0f, 0.0f);
    ui_3d_obj_set_color(o_front, argb(0, 0, 255));
    ui_3d_obj_set_emissive(o_front, 1);
    ck("two objects created", o_back > 0 && o_front > 0, o_back, o_front);

    /* 自证:两块板真的在屏幕里 —— 「物体在画面外」不报错、也不崩,只能直接量。
     * Self-check: the quads really are on screen; "object off screen" neither errors nor crashes. */

    sc = ui_3d_obj_get_screen(o_front);
    px = sc & 0xFFFF; py = (sc >> 16) & 0xFFFF;
    printf("FRONTSCREEN=%d,%d\n", px, py);
    ck("front quad centre on screen", px > 0 && px < w && py > 0 && py < h, px, py);

    /* ── ① 不透明基线:中心就是前板自己的蓝 ───────────────────────────────── */
    /* ① Opaque baseline: the centre is the front quad's own blue */
    ui_2d_present();
    off_a = ui_3d_viewport_get_pixel(vp, px, py);
    printf("OPAQUE centre r=%d g=%d b=%d\n", rch(off_a), gch(off_a), bch(off_a));
    ck("opaque: centre is blue", bch(off_a) > 200 && rch(off_a) < 48, rch(off_a), bch(off_a));

    /* ── ② 开透明(alpha = 128 ⇒ 约 50% 混合)⇒ 后面的红透出来 ─────────────── */
    /* ② Turn blending on (alpha = 128 => about 50%): the red behind shows through */
    r = ui_3d_obj_set_blend(o_front, 1);
    ck("set_blend(1) rc", r == 0, r, 0);
    ui_3d_obj_set_color(o_front, argba(128, 0, 0, 255));
    ui_2d_present();
    on_a = ui_3d_viewport_get_pixel(vp, px, py);
    printf("BLENDED centre r=%d g=%d b=%d\n", rch(on_a), gch(on_a), bch(on_a));

    /* ⚠ 这一条才是能响的那条:把 set_blend 实现成「什么都不做」⇒ 与基线逐位相同 ⇒ 红。
     * ⚠ This is the loud one: implement set_blend as a no-op and it is bit-identical to baseline. */

    ck("blended: red behind shows through", rch(on_a) > 64, rch(on_a), rch(off_a));
    ck("blended: no longer pure blue", bch(on_a) < bch(off_a), bch(on_a), bch(off_a));

    /* 而且它确实画了东西 —— 否则「两次都空」也会假绿。
     * And the pixel really was drawn, otherwise "empty both times" would pass falsely. */

    ck("blended pixel not background", gch(on_a) < 200 || rch(on_a) > 8, gch(on_a), rch(on_a));

    /* ── ③ 透明度来源 = argb 的 alpha:alpha 越大、透出来的红越少 ───────────── */
    /* ③ Opacity source is the argb alpha: more alpha means less of the red behind */
    ui_3d_obj_set_color(o_front, argba(32, 0, 0, 255));
    ui_2d_present();
    a_low = ui_3d_viewport_get_pixel(vp, px, py);
    ui_3d_obj_set_color(o_front, argba(224, 0, 0, 255));
    ui_2d_present();
    a_high = ui_3d_viewport_get_pixel(vp, px, py);
    printf("ALPHA low32 r=%d  high224 r=%d\n", rch(a_low), rch(a_high));
    ck("opacity comes from the argb alpha", rch(a_low) > rch(a_high), rch(a_low), rch(a_high));

    /* ── ④ 反证:关掉透明 ⇒ 必须逐位回到不透明基线 ─────────────────────────── */
    /* ④ Reverse: switch blending off and it must return bit-identically to the baseline */
    r = ui_3d_obj_set_blend(o_front, 0);
    ck("set_blend(0) rc", r == 0, r, 0);
    ui_3d_obj_set_color(o_front, argb(0, 0, 255));
    ui_2d_present();
    off_b = ui_3d_viewport_get_pixel(vp, px, py);
    ck("blend off -> identical to baseline", off_b == off_a, off_b, off_a);

    /* ── ⑤ 错误路径:不存在的物体句柄必须报负数 ───────────────────────────── */
    /* ⑤ Error path: a bogus object handle must fail with a negative code */
    r = ui_3d_obj_set_blend(9999, 1);
    ck("bad object rejected", r < 0, r, 0);

    /* 收尾:把画面恢复成「透明开着」的观感,供 --frames 出图。
     * Final: restore the blended look so the exported frame shows the feature. */

    ui_3d_obj_set_blend(o_front, 1);
    ui_3d_obj_set_color(o_front, argba(140, 40, 90, 255));
    ui_2d_present();

    sprintf(RLINE, "BLEND_DEMO %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
    rpt_line(RLINE);
    printf("BLEND_DEMO %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
    if (g_rpt >= 0) fclose(g_rpt);
    return g_fails;
}