eduwin 3D 渲染教程

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


目录

  1. 3D 渲染的基本概念
  2. eduwin 的 D3D 封装
  3. 构建第一个 3D 程序
  4. 进一步阅读

1. 3D 渲染的基本概念

1.1 坐标系

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

  y
  |
  |___ x
 /
z

D3D11 使用左手系:伸出左手,食指朝上(Y),拇指朝右(X),中指朝前(Z)。Z 正方向指向屏幕内部。

1.2 顶点

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

在 eduwin 中,顶点包含位置 + 法线 + UV + 顶点色:

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 约定一致:

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)

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

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

视图矩阵 (View Matrix)

把世界坐标变换到相机坐标系(以相机为原点)。

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),实现近大远小的透视效果。

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_yY 方向视场角(弧度)π/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 文档

2.3 相机封装

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

用法:

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

窗口 resize 时改 aspect:

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

2.4 网格(Mesh = Geometry + Material + Transform)

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

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,绑定材质,绘制几何体。 可直接读写字段:

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

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

// 初始化(一次)
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 完整代码

#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] CreateDeviceD3D11 设备创建失败
[main] D3D11 初始化失败dx_init 返回 0

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


4. 进一步阅读