eduwin 3D 渲染教程
本文从零讲解 3D 渲染的基础概念,然后步进到 eduwin D3D 封装的使用。
目录
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_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 文档
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] CreateDevice | D3D11 设备创建失败 |
[main] D3D11 初始化失败 | dx_init 返回 0 |
运行时查看 log.txt,无内容 = 一切正常。