c/3d/tex_demo.c · 14323 字节 · 下载原文
/* tex_demo.c —— **3D 贴图**(`ui_3d_obj_set_tex` `#620`)示例 + 判据。
* tex_demo.c — **3D texturing** (`ui_3d_obj_set_tex` `#620`): example and criteria.
*
* ## 这个程序演示什么
* ## What this program demonstrates
*
* 「在 2D 画布上画一张图 → 抓成图像句柄 → 贴到 3D 物体上」,也就是本仓 3D 里
* 从前**唯一**没实现的那个接口。纹理源与 2D 绘图共用同一条链:
* "Draw a picture on the 2D canvas -> grab it as an image handle -> map it onto a 3D object",
* i.e. the one 3D entry point that used to be the **only** unimplemented one. The texture source
* reuses the very same 2D chain:
*
* ui_2d_rect(...) 画图案(这里是一个棋盘)
* ui_2d_get_image(x,y,w,h) 抓成句柄 —— 像素是 **RGBA8 行优先**,与 GPU 纹理格式逐字节同构
* ui_3d_obj_set_tex(obj, h) 贴上去(传 0 = 取消贴图)
*
* ## 为什么判据必须用「uv 有变化」的网格
* ## Why the grid must have varying uv
*
* 这是本次最容易写错、也最难发现的一条:**uv 属性没接上时,GL 对"被禁用的顶点属性"
* 读的是常量 (0,0)** ⇒ 所有顶点都取 `texel(0,0)` ⇒ 物体变成**单一颜色**,而
* **编译、链接、运行全绿、一个错都不报**。
* This is the easiest thing to get wrong and the hardest to notice: when the uv attribute is not
* hooked up, GL reads a constant (0,0) for a disabled attribute => every vertex samples texel(0,0)
* => the object becomes a **single colour**, while compiling, linking and running all stay green.
*
* ⇒ 若网格 uv 全 0(`g3_all.c` 那份就是),"贴什么图"与"uv 坏了"**读出来一模一样**,
* 拿它写的判据恒假绿。本文件的方片**四角 uv 互不相同**,并且断言的是
* 「棋盘的两色**同时在**物体上出现」—— 这才分辨得出来。
* => With an all-zero uv grid the two situations read identically and the criterion is a false green.
* This quad has four **distinct** corner uvs and asserts that **both** checker colours appear
* on the object at once.
*
* ⚠ 另有一条同族的静默坑:**纹理的 v 方向**。`ui_2d_get_image` 抓下来的像素是**自上而下**的
* 行优先,而纹理坐标 v=0 对应**第一行**(图像顶部)。方片把"屏幕上方"映到 v=0,于是图案正立;
* 写反了图案会上下颠倒 —— 对**棋盘**看不出来(对称),所以这里另外把判据押在
* 「红/白**两色都出现**」上:颠倒不会改变这个结论,而"uv 没接上"会(只剩一色)。
* ⚠ A cousin trap: the **v direction**. Grabbed pixels are top-down row-major and v=0 is the first
* row, so mapping "screen up" to v=0 keeps the pattern upright. For a checkerboard a flip is
* invisible, so the criterion leans on "both colours appear", which a flip preserves but a
* broken uv attribute destroys.
*
* ## 跑法
* ## How to run
* dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll \
* third_party/vml/Examples/c/3d/tex_demo.c --screen 393x757
*
* 机器判读:逐行 `PASS …` / `FAIL …`,末行 `TEX_DEMO PASS|FAIL (fails=N)`。
* Machine-readable: one `PASS ...` / `FAIL ...` line each, ending with `TEX_DEMO PASS|FAIL (fails=N)`.
*/
#include <vml_ui.h>
#include <vml_3d.h>
#include <math.h>
#include <stdio.h>
/* ══ 记分 ══════════════════════════════════════════════════════════════════ */
/* == Scoring == */
int g_fails;
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); }
}
int rch(int c) { return (c >> 16) & 0xFF; }
int gch(int c) { return (c >> 8) & 0xFF; }
int bch(int c) { return c & 0xFF; }
/* ══ 网格装配(pos3 + nrm3 + uv2 = 8 个 float32 = 32 字节/顶点,紧随其后是 int32 索引)════
* == Mesh assembly (pos3 + nrm3 + uv2 = 8 float32 = 32 bytes per vertex, then int32 indices) == */
float MB[256];
int g_vn;
int g_in;
void mb_vertex(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; /* ← uv 就在这 8 个 float 的尾巴上 */
}
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、中心在原点)。**四角 uv 互不相同** —— 见文件头的说明,
* 这是整份判据能不能响的前提。v=0 对应图像**第一行**(屏幕上方)。
* A quad facing +Z (side 2, centred at the origin). The four corner uvs are **all distinct**;
* v=0 is the first image row (screen up). */
void gen_quad(void) {
mb_vertex(0, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
mb_vertex(1, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
mb_vertex(2, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
mb_vertex(3, -1.0f, 1.0f, 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;
}
/* ══ 像素取样:在物体**自己报出来的**屏幕位置周围抽稀取点 ══════════════════════
* == Pixel sampling: sparse probes around the screen position the object itself reports ==
*
* ⚠ 两个约束,都是实测踩出来的:
* ① **不能按屏幕比例猜物体在哪儿** —— 物体的覆盖范围随相机/宽高比变;用
* `ui_3d_obj_get_screen` 报的那个点做中心才稳(619 段也是这么取的)。
* ② **必须抽稀** —— `ui_3d_viewport_get_pixel` 是**逐点一趟线程往返**,实测量过
* 6561 次 = 33 秒。400 次是可接受的量级。
* ⚠ Two constraints, both measured: do not guess the object's screen extent from proportions
* (it moves with camera/aspect - centre on what `ui_3d_obj_get_screen` reports), and do **not**
* read densely (`ui_3d_viewport_get_pixel` is one thread round-trip per point; 6561 points
* measured 33 seconds). 400 probes is fine.
*/
int sx(int sc) { return sc & 0xFFFF; }
int sy(int sc) { return (sc >> 16) & 0xFFFF; }
/* 数"明显偏红"的采样点。**通道强不对称**是关键:红与白在 R 上分不开,要靠 G/B 低。 */
/* Counts samples that are clearly red. Strongly asymmetric channels are the point. */
int count_red(int vp, int cx, int cy, int rad) {
int i; int j; int n; n = 0;
for (j = -10; j < 10; j = j + 1)
for (i = -10; i < 10; i = i + 1) {
int c = ui_3d_viewport_get_pixel(vp, cx + i * rad / 10, cy + j * rad / 10);
if (rch(c) > 200 && gch(c) < 60 && bch(c) < 60) n = n + 1;
}
return n;
}
/* 数"明显偏白"的采样点(三个通道都高)。 */
int count_white(int vp, int cx, int cy, int rad) {
int i; int j; int n; n = 0;
for (j = -10; j < 10; j = j + 1)
for (i = -10; i < 10; i = i + 1) {
int c = ui_3d_viewport_get_pixel(vp, cx + i * rad / 10, cy + j * rad / 10);
if (rch(c) > 200 && gch(c) > 200 && bch(c) > 200) n = n + 1;
}
return n;
}
int main(void) {
int w; int h; int vp; int mesh; int obj;
int img_check; int img_red;
int r; int i; int j; int sc; int px; int py; int rad;
int red_a; int white_a; int red_b; int white_b; int red_c;
int col_r = 0xFFD02020; /* 棋盘的"红"格 */
int col_w = 0xFFFFFFFF; /* 棋盘的"白"格 */
g_fails = 0;
ui_win_open_ex("3D 贴图示例", 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("TEX_DEMO FAIL (fails=99)\n"); return 99; }
/* ── ① 在 2D 画布上画一张 8×8 的棋盘(每格 4 像素 = 32×32)───────────────
* ① Draw an 8x8 checkerboard on the 2D canvas (4 px per cell = 32x32)
*
* ⚠ 图案**必须两色且通道强不对称**:纯灰阶的两色图抓不出"红蓝被换"这个错,
* 而"红蓝被换"正是纹理上传最容易犯的错(RGBA 序 vs 0xAARRGGBB)。
* The two colours must be strongly channel-asymmetric; a greyscale pair cannot catch a
* red/blue swap, which is the classic texturing mistake. */
for (j = 0; j < 8; j = j + 1)
for (i = 0; i < 8; i = i + 1) {
int c = col_r;
if ((i + j) % 2 == 0) c = col_w;
ui_2d_rect(4 + i * 4, 4 + j * 4, 4, 4, c, 1, 0, 0);
}
ui_2d_present();
img_check = ui_2d_get_image(4, 4, 32, 32);
ck("get_image(checker) -> handle", img_check > 0, img_check, 0);
/* ── ② 3D:视口 + 相机 + 方片 + 贴图 ─────────────────────────────────── */
/* ② 3D: viewport + camera + quad + texture */
vp = ui_3d_viewport_create(0, 0, w, h, 0);
ck("viewport -> handle", vp > 0, vp, 0);
ui_3d_viewport_set_camera(vp, 0.0f, 0.0f, 3.0f, 0.0f, 0.0f, 0.0f);
ui_3d_viewport_set_fov(vp, 60.0f, 0.1f, 100.0f);
ui_3d_viewport_set_bg(vp, 0xFF101018); /* 深色背景:与红/白都不撞 */
ui_3d_viewport_set_light(vp, 0.0f, 0.0f, 1.0f, 0xFFFFFFFF, 0.35f);
gen_quad();
mesh = ui_3d_mesh_create(mb_ptr(), g_vn, g_in);
ck("mesh_create -> handle", mesh > 0, mesh, 0);
obj = ui_3d_obj_create(vp, mesh);
ck("obj_create -> handle", obj > 0, obj, 0);
/* 物体色**白**(乘性 tint 的中性元),并开**自发光**:
* 自发光让片元直接用基色、不乘光照 ⇒ 读回来的就是纹理的原色 ⇒ 判据阈值可以卡严。
* White object colour (the neutral element of a multiplicative tint) plus **emissive**, so the
* fragment takes the base colour directly and the read-back is the texture's own colour. */
ui_3d_obj_set_color(obj, 0xFFFFFFFF);
ui_3d_obj_set_emissive(obj, 1);
sc = ui_3d_obj_get_screen(obj);
px = sx(sc); py = sy(sc);
rad = w / 12; /* 取样半径:方片远大于它 */
printf("OBJSCREEN=%d,%d rad=%d\n", px, py, rad);
/* ── ③ 贴上去之前:物体是**纯白**的(此时 "红点" 一个都不该有)──────────── */
/* ③ Before texturing the object is plain white (no red probe should hit) */
ui_2d_present();
red_a = count_red(vp, px, py, rad);
ck("no-tex: no red", red_a == 0, red_a, 0);
/* ── ④ 贴棋盘 ⇒ 物体上**同时**出现红与白 ─────────────────────────────── */
/* ④ Map the checkerboard: **both** colours must appear on the object at once */
r = ui_3d_obj_set_tex(obj, img_check);
ck("obj_set_tex(checker) rc", r == 0, r, 0);
ui_2d_present();
red_a = count_red(vp, px, py, rad);
white_a = count_white(vp, px, py, rad);
ck("tex(checker): red cells present", red_a > 0, red_a, 0);
ck("tex(checker): white cells present", white_a > 0, white_a, 0);
/* ⚠ **这一条才是能响的那条**:uv 若没接上(全取 texel(0,0)),物体只会剩**一种**颜色,
* 上面两条里必有一条变 0。只断言"两色都出现"而不看"是否真的分了格"是不行的 ——
* 所以再加一条:红与白在**同一片区域里**同时可观测,即两者都非零且都不到 400。
* The loud one: with a broken uv attribute the object collapses to a single colour and one of
* the two counts above goes to zero. Both being non-zero *and* neither saturating means the
* checker actually landed across the quad. */
ck("tex(checker): both colours coexist (not one flat colour)",
red_a > 0 && white_a > 0 && red_a < 400 && white_a < 400, red_a, white_a);
/* ── ⑤ 换成**纯红**图 ⇒ 白点全部消失(证明采样真的跟着源走)───────────── */
/* ⑤ Swap in a **solid red** image: every white probe must vanish */
ui_2d_rect(4, 4, 32, 32, col_r, 1, 0, 0); /* 整块盖成纯红 */
ui_2d_present();
img_red = ui_2d_get_image(4, 4, 32, 32);
ck("get_image(solid red) -> handle", img_red > 0, img_red, 0);
r = ui_3d_obj_set_tex(obj, img_red);
ck("obj_set_tex(red) rc", r == 0, r, 0);
ui_2d_present();
red_b = count_red(vp, px, py, rad);
white_b = count_white(vp, px, py, rad);
ck("tex(red): red everywhere", red_b > 0, red_b, 0);
ck("tex(red): no white left (source really swapped)", white_b == 0, white_b, 0);
/* ── ⑥ 取消贴图(传 0)⇒ 回到物体的**纯白** ───────────────────────────── */
/* ⑥ Detach the texture (pass 0): back to the object's plain white */
r = ui_3d_obj_set_tex(obj, 0);
ck("obj_set_tex(0) rc", r == 0, r, 0);
ui_2d_present();
red_c = count_red(vp, px, py, rad);
ck("untint: back to plain colour (no red)", red_c == 0, red_c, 0);
/* ── ⑦ 错误路径:不存在的物体 / 不存在的图像都必须报负数 ────────────────── */
/* ⑦ Error paths: a bogus object handle and a bogus image handle must both fail */
r = ui_3d_obj_set_tex(9999, img_red);
ck("bad object rejected", r < 0, r, 0);
r = ui_3d_obj_set_tex(obj, 999999);
ck("bad image rejected", r < 0, r, 0);
printf("TEX_DEMO %s (fails=%d)\n", g_fails == 0 ? "PASS" : "FAIL", g_fails);
return g_fails;
}