# eduwin 3D 渲染教程

本文从零讲解 3D 渲染的基础概念，然后步进到 eduwin D3D 封装的使用。

---

## 目录

1. [3D 渲染的基本概念](#1-3d-渲染的基本概念)
2. [eduwin 的 D3D 封装](#2-eduwin-的-d3d-封装)
3. [构建第一个 3D 程序](#3-构建第一个-3d-程序)
4. [进一步阅读](#4-进一步阅读)

---

## 1. 3D 渲染的基本概念

### 1.1 坐标系

3D 渲染使用**三维坐标系**来表示空间中的位置。每个点用 `(x, y, z)` 三个坐标表示。

```
  y
  |
  |___ x
 /
z
```

D3D11 使用**左手系**：伸出左手，食指朝上（Y），拇指朝右（X），中指朝前（Z）。Z 正方向指向屏幕内部。

### 1.2 顶点

**顶点**是 3D 模型的基本单元。一个顶点包含位置信息，还可以包含颜色、法线、纹理坐标等属性。

在 eduwin 中，顶点包含位置 + 法线 + UV + 顶点色：

```c
typedef struct {
    float x, y, z;       // 位置坐标
    float nx, ny, nz;    // 法线
    float u, v;          // 纹理坐标
    float r, g, b, a;    // 顶点色 (红绿蓝透明度，范围 0~1)
} dx_vertex;
```

### 1.3 三角形和索引

三个顶点构成一个**三角形**（3D 渲染的最小图元）。一堆三角形拼起来就是 3D 模型。

**索引**的作用是复用顶点。例如一个矩形需要 2 个三角形（6 个顶点），但如果用索引，只需 4 个顶点 + 6 个索引：

```
顶点: [A, B, C, D]
索引: [0,1,2,  0,2,3]  →  三角形1: A-B-C, 三角形2: A-C-D
```

eduwin 使用 16 位无符号整数索引。

### 1.4 变换矩阵

3D 中的位置变换通过 **4×4 矩阵** 计算。最基本的变换有三种：

| 变换                   | 作用     | 示例                        |
| ---------------------- | -------- | --------------------------- |
| **平移 (Translation)** | 移动位置 | 把物体从原点移到 (10, 0, 0) |
| **旋转 (Rotation)**    | 绕轴旋转 | 物体绕 Y 轴转 45°           |
| **缩放 (Scale)**       | 放大缩小 | 把物体放大 2 倍             |

矩阵可以**相乘组合**：先旋转再平移 = 旋转矩阵 × 平移矩阵。

eduwin 使用 **row-major** 矩阵存储，与 `D3DMATRIX` 和 HLSL 的 `row_major float4x4` 约定一致：

```c
typedef struct { float m[16]; } dx_mat4;
// m[row*4+col] — m[0]=_11, m[1]=_12, m[4]=_21, m[12]=_41
```

### 1.5 世界 → 视图 → 投影

3D 渲染的核心管线需要**三个矩阵**：

```
顶点位置 → 世界矩阵 → 视图矩阵 → 投影矩阵 → 屏幕坐标
         (模型摆哪)  (相机看哪)  (透视效果)
```

#### 世界矩阵 (World Matrix)

把模型从**模型坐标系**（原点在模型中心）变换到**世界坐标系**（模型在场景中的位置）。

```c
// 物体在原点绕 Y 轴旋转
dx_mat4 world;
dx_mat4_rotation_y(&world, angle);
dx_set_world(world);
```

#### 视图矩阵 (View Matrix)

把世界坐标变换到**相机坐标系**（以相机为原点）。

```c
float eye[] = {0, 3, -5};  // 相机位置
float at[]  = {0, 0, 0};   // 观察目标
float up[]  = {0, 1, 0};   // 上方向

dx_mat4 view;
dx_mat4_look_at_lh(&view, eye, at, up);
dx_set_view(view);
```

| 参数名 | 含义           | 示例值                  |
| ------ | -------------- | ----------------------- |
| `eye`  | 相机坐标       | `{0, 3, -5}` 即头顶稍后 |
| `at`   | 观察目标点     | `{0, 0, 0}` 看向原点    |
| `up`   | 世界"向上"方向 | `{0, 1, 0}` Y 轴向上    |

#### 投影矩阵 (Projection Matrix)

把 3D 坐标变换到 **2D 屏幕**（Clip Space），实现**近大远小**的透视效果。

```c
dx_mat4 proj;
dx_mat4_perspective_fov_lh(&proj,
    DX_PI/3,           // FOV: 60°
    800.0f/600.0f,     // Aspect: 宽/高
    0.1f,              // Near: 近裁剪面
    100.0f);           // Far: 远裁剪面
dx_set_projection(proj);
```

| 参数名   | 含义                 | 典型值            |
| -------- | -------------------- | ----------------- |
| `fov_y`  | Y 方向视场角（弧度） | `π/3`（60°）      |
| `aspect` | 屏幕宽高比           | `800/600 = 1.333` |
| `zn`     | 近裁剪面             | `0.1f`            |
| `zf`     | 远裁剪面             | `100.0f`          |

### 1.6 渲染管线

```
顶点输入 ─→ 顶点着色器 ─→ 光栅化 ─→ 像素着色器 ─→ 输出合并
              (VS)                 (PS)
```

eduwin 内嵌的 HLSL 顶点着色器用世界/视图/投影三个矩阵变换顶点位置，像素着色器计算顶点色 + 光照结果。

---

## 2. eduwin 的 D3D 封装

### 2.1 主要函数一览

| 函数                                           | 用途               |
| ---------------------------------------------- | ------------------ |
| `dx_init(hwnd, w, h)`                          | 初始化 D3D11       |
| `dx_shutdown()`                                | 销毁所有资源       |
| `dx_begin(r,g,b,a)`                            | 开始一帧           |
| `dx_end()`                                     | 结束一帧，交换缓冲 |
| `dx_set_world(m)`                              | 设置世界矩阵       |
| `dx_set_view(m)`                               | 设置视图矩阵       |
| `dx_set_projection(m)`                         | 设置投影矩阵       |
| `dx_create_geometry(v,c,i,c)`                 | 创建自定义几何体    |
| `dx_create_box(w,h,d, r,g,b,a)`                | 创建立方体         |
| `dx_create_sphere(r, r,g,b,a)`                 | 创建球体           |
| `dx_create_cylinder(r,h, r,g,b,a)`             | 创建圆柱体         |
| `dx_create_pyramid(w,h,d, r,g,b,a)`            | 创建棱锥           |
| `dx_create_plane(w,h, r,g,b,a)`                | 创建平面           |
| `dx_draw_geometry(g,c,s,b)`                    | 绘制几何体         |
| `dx_free_geometry(g)`                          | 释放几何体         |
| `dx_set_ambient_light(r,g,b,i)`               | 设置环境光         |
| `dx_set_directional_light(dx,dy,dz, r,g,b,i)`  | 设置方向光         |
| `dx_set_point_light(px,py,pz, r,g,b,i, range)` | 设置点光源         |

### 2.2 矩阵辅助函数

| 函数                                      | 用途                 |
| ----------------------------------------- | -------------------- |
| `dx_mat4_identity(&m)`                    | 单位矩阵             |
| `dx_mat4_mul(&o,&a,&b)`                   | 矩阵乘法 `o = a × b` |
| `dx_mat4_rotation_x(&m, a)`               | 绕 X 轴旋转          |
| `dx_mat4_rotation_y(&m, a)`               | 绕 Y 轴旋转          |
| `dx_mat4_rotation_z(&m, a)`               | 绕 Z 轴旋转          |
| `dx_mat4_translation(&m, x,y,z)`          | 平移                 |
| `dx_mat4_look_at_lh(&m, e,a,u)`           | 观察矩阵（左手系）   |
| `dx_mat4_perspective_fov_lh(&m, f,a,n,f)` | 透视投影（左手系）   |

> 更多细节见 [eduwin D3D API 文档](/content/docs/api/eduwin-d3d-api.md)

### 2.3 相机封装

```c
void dx_camera_init(dx_camera *cam, float px, float py, float pz, float aspect);
void dx_camera_update(dx_camera *cam);
```

用法：

```c
dx_camera cam;
dx_camera_init(&cam, 0, 3, -5, 800.0f / 600.0f);  // 位置 + 宽高比
dx_camera_update(&cam);                             // 每帧更新
```

窗口 resize 时改 aspect：

```c
cam.aspect = 1024.0f / 768.0f;
dx_camera_update(&cam);
```

### 2.4 网格（Mesh = Geometry + Material + Transform）

`dx_mesh` 把几何体 + 材质 + 变换打包，隐藏矩阵操作，仿 Three.js Mesh：

```c
typedef struct {
    dx_geometry *geometry;  /* 顶点数据 */
    dx_material *material;  /* 材质（可 NULL） */
    float position[3];      /* 平移 */
    float rotation[3];      /* 旋转（XYZ 欧拉角） */
    float scale[3];         /* 缩放 */
} dx_mesh;

dx_mesh* dx_create_mesh(dx_geometry *geo, dx_material *mat);
void     dx_mesh_set_position(dx_mesh *m, float x, float y, float z);
void     dx_mesh_set_rotation(dx_mesh *m, float x, float y, float z);
void     dx_mesh_set_scale(dx_mesh *m, float x, float y, float z);
void     dx_mesh_draw(dx_mesh *m);
void     dx_free_mesh(dx_mesh *m);
```

`dx_mesh_draw` 内部自动计算 `world = S × Rx × Ry × Rz × T`，绑定材质，绘制几何体。
可直接读写字段：

```c
mesh->position[0] = 2;    /* X 位置 */
mesh->rotation[1] = 0.5f; /* Y 旋转 */
mesh->scale[0] = 2.0f;    /* 水平拉长 2 倍 */
```

位置是固有属性，初始化设一次，每帧只改变化的旋转：

```c
// 初始化（一次）
dx_mesh *obj = dx_create_mesh(geo, NULL);
dx_mesh_set_position(obj, 0, 0.5f, 0);

// 每帧
obj->rotation[1] = angle;
dx_mesh_draw(obj);
```

---

## 3. 构建第一个 3D 程序

下面以 `samples/d3d_light_demo/` 为蓝本，一步步构建一个带光照的 3D 场景。

### 3.1 完整代码

```c
#include "eduwin.h"
#include <math.h>

static dx_mesh *g_box, *g_ground;
static dx_camera g_cam;
static float g_light_angle = 0;

static void on_frame(void)
{
    float px, py, pz;

    if (g_box) g_box->rotation[1] += 0.01f;      /* 立方体自转 */

    /* 点光源绕立方体旋转 */
    g_light_angle += 0.015f;
    px = 2.5f * cosf(g_light_angle);
    pz = 2.5f * sinf(g_light_angle);
    py = 1.5f;

    dx_camera_update(&g_cam);
    dx_begin(0.12f, 0.12f, 0.2f, 1);     /* 深蓝背景 */

    /* 设置光照 */
    dx_set_ambient_light(0.4f, 0.4f, 0.4f, 1);
    dx_set_directional_light(0.5f, -0.707f, 0.5f, 1, 1, 1, 1);
    dx_set_point_light(px, py, pz, 1, 0.6f, 0.1f, 0.8f, 5);

    dx_mesh_draw(g_ground);               /* 地面 */
    dx_mesh_draw(g_box);                  /* 立方体 */

    dx_end();
}

int main(void)
{
    if (init_app() != 0) { log_error(L"init失败"); return 1; }
    HWND win = create_window(L"3D 光照演示", 800, 600);
    if (!win) return 1;
    show_window(win);
    if (!dx_init(win, 800, 600)) return 1;

    /* 创建物体 */
    g_box    = dx_create_mesh(dx_create_box(1.2f, 1.2f, 1.2f, 0.85f, 0.15f, 0.15f, 1), NULL);
    g_ground = dx_create_mesh(dx_create_plane(8, 8, 0.5f, 0.5f, 0.5f, 1), NULL);
    dx_mesh_set_position(g_box,    0, 0, 0);
    dx_mesh_set_position(g_ground, 0, -0.8f, 0);

    dx_camera_init(&g_cam, 0, 1, -3.5f, 800.0f / 600.0f);

    run_app_frame(on_frame);
    dx_shutdown();
    return 0;
}
```

### 3.2 运行效果

运行后你会看到一个灰色地面 + 一个红色立方体在旋转。一个暖色（橙红）点光源围绕立方体旋转，立方体表面光影随之变化，同时有一个方向光从固定方向照射。

### 3.3 关键点回顾

| 知识点     | 说明                                                                 |
| ---------- | -------------------------------------------------------------------- |
| 几何体创建 | `dx_create_box` / `dx_create_plane` 自动生成顶点                     |
| 对象系统   | `dx_create_mesh` + `dx_mesh_set_position` + `mesh->rotation[1]` |
| 光照       | `dx_set_ambient_light` + `dx_set_directional_light` + `dx_set_point_light` |
| 相机       | `dx_camera_init` + 每帧 `dx_camera_update`                           |
| 帧循环     | `run_app_frame(on_frame)` 非阻塞消息循环                             |

### 3.4 调试技巧

eduwin D3D 的日志通过 `log_error` 写入 exe 同目录的 `log.txt`：

| 日志内容                  | 含义               |
| ------------------------- | ------------------ |
| `[d3d] VS: ...`           | 顶点着色器编译错误 |
| `[d3d] PS: ...`           | 像素着色器编译错误 |
| `[d3d] CreateDevice`      | D3D11 设备创建失败 |
| `[main] D3D11 初始化失败` | dx_init 返回 0     |

运行时查看 `log.txt`，无内容 = 一切正常。

---

## 4. 进一步阅读

- [eduwin D3D API 文档](/content/docs/api/eduwin-d3d-api.md) — 函数参考
- [samples/d3d_light_demo — 动态光照演示](/content/docs/samples/d3d_light_demo.md)
- [samples/d3d_shape_demo — 基本体形状展示](/content/docs/samples/d3d_shape_demo.md)
- [samples/d3d_material_demo — 纹理材质演示](/content/docs/samples/d3d_material_demo.md)
