├── .gitignore
├── LICENSE
├── Mini3D.sln
├── Mini3D.vcxproj
├── Mini3D.vcxproj.filters
├── README.en.md
├── README.md
├── images
├── donation.png
├── mini_0.png
├── mini_1.png
├── mini_2.png
├── mini_3.png
└── mini_4.png
└── mini3d.c
/.gitignore:
--------------------------------------------------------------------------------
1 | # Object files
2 | *.o
3 | *.ko
4 | *.obj
5 | *.elf
6 |
7 | # Precompiled Headers
8 | *.gch
9 | *.pch
10 |
11 | # Libraries
12 | *.lib
13 | *.a
14 | *.la
15 | *.lo
16 |
17 | # Shared objects (inc. Windows DLLs)
18 | *.dll
19 | *.so
20 | *.so.*
21 | *.dylib
22 |
23 | # Executables
24 | *.exe
25 | *.out
26 | *.app
27 | *.i*86
28 | *.x86_64
29 | *.hex
30 |
31 | # Debug files
32 | *.dSYM/
33 |
34 | /.vscode/*
35 | *.jpg
36 | *.jpeg
37 | *.png
38 | *.bmp
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Linwei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Mini3D.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual Studio 2010
4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mini3D", "Mini3D.vcxproj", "{433E9031-0101-4ADF-A6DA-283375FD1898}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Win32 = Debug|Win32
9 | Release|Win32 = Release|Win32
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {433E9031-0101-4ADF-A6DA-283375FD1898}.Debug|Win32.ActiveCfg = Debug|Win32
13 | {433E9031-0101-4ADF-A6DA-283375FD1898}.Debug|Win32.Build.0 = Debug|Win32
14 | {433E9031-0101-4ADF-A6DA-283375FD1898}.Release|Win32.ActiveCfg = Release|Win32
15 | {433E9031-0101-4ADF-A6DA-283375FD1898}.Release|Win32.Build.0 = Release|Win32
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/Mini3D.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 |
14 | {433E9031-0101-4ADF-A6DA-283375FD1898}
15 | Win32Proj
16 | Mini3D
17 |
18 |
19 |
20 | Application
21 | true
22 | Unicode
23 |
24 |
25 | Application
26 | false
27 | true
28 | Unicode
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | true
42 |
43 |
44 | false
45 |
46 |
47 |
48 |
49 |
50 | Level3
51 | Disabled
52 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
53 |
54 |
55 | Console
56 | true
57 |
58 |
59 |
60 |
61 | Level3
62 |
63 |
64 | MaxSpeed
65 | true
66 | true
67 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
68 |
69 |
70 | Console
71 | true
72 | true
73 | true
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/Mini3D.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.en.md:
--------------------------------------------------------------------------------
1 | # mini3d
2 |
3 | 3D software rendering tutorial, without any performance optimization, mainly explains how to write a fixed pipeline software renderer. Although the main code is only 700 lines, it is small but complete.
4 |
5 | ## Features
6 |
7 | * Single File: The source code consists of only one file, mini3d.c, which implements all the functionality. It is easy to read and understand.
8 | * Independent Compilation: There is no third-party library dependency and no complex project directory.
9 | * Model Standard: Uses the standard D3D coordinate model, left-handed system with WORLD/VIEW/PROJECTION matrices.
10 | * Clipping Implementation: Implements simple CVV (Canonical View Volume) clipping.
11 | * Texture Support: Supports textures up to a maximum of 1024 x 1024.
12 | * Depth Buffering: Uses a depth buffer to determine the order of image rendering.
13 | * Perspective Texture Mapping: Implements perspective texture mapping and perspective color filling.
14 | * Edge Calculation: Accurately calculates polygon edge coverage.
15 | * Simplified Implementation: The rendering engine consists of only 700 lines of code, with clear modules and a prominent main structure.
16 | * Detailed Comments: The primary code is well-documented with detailed comments.
17 |
18 | ## Compile
19 |
20 | * mingw:
21 | gcc -O3 mini3d.c -o mini3d.exe -lgdi32
22 | * msvc:
23 | cl -O2 -nologo mini3d.c
24 | * Compiled version:
25 | [https://github.com/skywind3000/mini3d/releases](https://github.com/skywind3000/mini3d/releases)
26 |
27 | ## Demonstration
28 |
29 | Texture Mapping:RENDER_STATE_TEXTURE
30 | 
31 |
32 | Color Filling:RENDER_STATE_COLOR
33 | 
34 |
35 | Wireframe Rendering:RENDER_STATE_WIREFRAME
36 | 
37 |
38 | Added Lighting and Bicubic Interpolation (screenshot of lighting effect added by a friend to Mini3D)
39 | 
40 |
41 | ## Basic TO-DO:
42 |
43 | * Add backface culling
44 | * Implement simple lighting
45 | * Provide more rendering modes
46 | * Implement texture sampling with bicubic interpolation
47 |
48 | ## Advanced TO-DO:
49 |
50 | * Derive and prove all geometric knowledge used in the program
51 | * Optimize vertex computation performance
52 | * Optimize draw_scanline performance
53 | * Load textures from BMP/TGA files
54 | * Load BSP scenes and implement roaming.
55 |
56 | ## Explanation of principles
57 |
58 | - [Wei Yixiao: How do OpenGL and DirectX determine the position of pixels only based on vertices?](https://skywind.me/blog/archives/2594)
59 | - [Wei Yixiao: How does the computer access the graphics card at a low level?](https://skywind.me/blog/archives/1774)
60 |
61 | ## Related Projects
62 |
63 | - [RenderHelp](https://github.com/skywind3000/RenderHelp):Another implementation of a streamlined software renderer with support for programmable rendering pipelines, for more details see https://skywind.me/blog/archives/2589
64 |
65 | ## Welcome to donate:
66 |
67 | 
68 |
69 | Your donation is the greatest recognition for this tutorial. You are welcome to use Alipay to scan the QR code above to make a donation. The donated funds will be used to improve the tutorial documentation and illustrations, as well as to help me write more interesting tutorials.
70 |
71 |
72 |
73 | ## Welcome to follow us:
74 |
75 | blog: https://skywind.me/blog
76 |
77 | zhihu: https://www.zhihu.com/people/skywind3000
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini3d
2 |
3 | 3D软件渲染教程,并没有任何性能优化,主要向人说明如何写一个固定管线的软件渲染器。虽然主体代码只有 700行,但是麻雀虽小,五脏俱全。
4 |
5 | 【[README in English](README.en.md)】
6 |
7 | ## 特性
8 |
9 | * 单个文件:源代码只有一个 mini3d.c,单个文件实现所有内容,容易阅读。
10 | * 独立编译:没有任何第三方库依赖,没有复杂的工程目录。
11 | * 模型标准:标准 D3D 坐标模型,左手系加 WORLD / VIEW / PROJECTION 三矩阵
12 | * 实现裁剪:简单 CVV 裁剪
13 | * 纹理支持:最大支持 1024 x 1024 的纹理
14 | * 深度缓存:使用深度缓存判断图像前后
15 | * 透视贴图:透视纹理映射以及透视色彩填充
16 | * 边缘计算:精确的多边形边缘覆盖计算
17 | * 实现精简:渲染引擎只有 700行,模块清晰,主干突出。
18 | * 详细注释:主要代码详细注释
19 |
20 | ## 编译
21 |
22 | * mingw:
23 | gcc -O3 mini3d.c -o mini3d.exe -lgdi32
24 | * msvc:
25 | cl -O2 -nologo mini3d.c
26 | * 已编译版本:
27 | [https://github.com/skywind3000/mini3d/releases](https://github.com/skywind3000/mini3d/releases)
28 |
29 | ## 演示
30 |
31 | 纹理填充:RENDER_STATE_TEXTURE
32 | 
33 |
34 | 色彩填充:RENDER_STATE_COLOR
35 | 
36 |
37 | 线框绘制:RENDER_STATE_WIREFRAME
38 | 
39 |
40 | 增加光照和二次线性插值(朋友给 Mini3D 增加的光照效果截图)
41 | 
42 |
43 | ## 基础作业
44 |
45 | * 增加背面剔除
46 | * 增加简单光照
47 | * 提供更多渲染模式
48 | * 实现二次线性差值的纹理读取
49 |
50 | ## 进阶作业
51 |
52 | * 推导并证明程序中用到的所有几何知识
53 | * 优化顶点计算性能
54 | * 优化 draw_scanline 性能
55 | * 从 BMP/TGA 文件加载纹理
56 | * 载入 BSP 场景并实现漫游
57 |
58 | ## 原理讲解
59 |
60 | - [韦易笑:OpenGL 和 DirectX 是如何在只知道顶点的情况下得出像素位置的?](https://skywind.me/blog/archives/2594)
61 | - [韦易笑:计算机底层是如何访问显卡的?](https://skywind.me/blog/archives/1774)
62 |
63 | ## 相关项目
64 |
65 | - [RenderHelp](https://github.com/skywind3000/RenderHelp):另外一个支持可编程渲染管线的精简软渲染器实现,详细见 [介绍](https://skywind.me/blog/archives/2589)。
66 |
67 | ## 欢迎捐赠
68 |
69 | 
70 |
71 | 您的捐助是对该教程的最大肯定,欢迎使用支付宝手扫描上面的二维码,进行捐赠。捐赠款项将用于完善教程文档和图例,以及帮助我写出更多有意思的教程来。
72 |
73 |
74 |
75 | 欢迎关注
76 |
77 | blog: https://skywind.me/blog
78 |
79 | zhihu: https://www.zhihu.com/people/skywind3000
80 |
--------------------------------------------------------------------------------
/images/donation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skywind3000/mini3d/4b3e3af226691db920ece1dc58425c4a85c68997/images/donation.png
--------------------------------------------------------------------------------
/images/mini_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skywind3000/mini3d/4b3e3af226691db920ece1dc58425c4a85c68997/images/mini_0.png
--------------------------------------------------------------------------------
/images/mini_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skywind3000/mini3d/4b3e3af226691db920ece1dc58425c4a85c68997/images/mini_1.png
--------------------------------------------------------------------------------
/images/mini_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skywind3000/mini3d/4b3e3af226691db920ece1dc58425c4a85c68997/images/mini_2.png
--------------------------------------------------------------------------------
/images/mini_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skywind3000/mini3d/4b3e3af226691db920ece1dc58425c4a85c68997/images/mini_3.png
--------------------------------------------------------------------------------
/images/mini_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skywind3000/mini3d/4b3e3af226691db920ece1dc58425c4a85c68997/images/mini_4.png
--------------------------------------------------------------------------------
/mini3d.c:
--------------------------------------------------------------------------------
1 | //=====================================================================
2 | //
3 | // mini3d.c - Mini Software Render All-In-One
4 | //
5 | // build:
6 | // mingw: gcc -O3 mini3d.c -o mini3d.exe -lgdi32
7 | // msvc: cl -O2 -nologo mini3d.c
8 | //
9 | // history:
10 | // 2007.7.01 skywind create this file as a tutorial
11 | // 2007.7.02 skywind implementate texture and color render
12 | // 2008.3.15 skywind fixed a trapezoid issue
13 | // 2015.8.09 skywind rewrite with more comment
14 | // 2015.8.12 skywind adjust interfaces for clearity
15 | //
16 | //=====================================================================
17 | #include
18 | #include
19 | #include
20 | #include
21 |
22 | #include
23 | #include
24 |
25 | typedef unsigned int IUINT32;
26 |
27 | //=====================================================================
28 | // 数学库:此部分应该不用详解,熟悉 D3D 矩阵变换即可
29 | //=====================================================================
30 | typedef struct { float m[4][4]; } matrix_t;
31 | typedef struct { float x, y, z, w; } vector_t;
32 | typedef vector_t point_t;
33 |
34 | int CMID(int x, int min, int max) { return (x < min)? min : ((x > max)? max : x); }
35 |
36 | // 计算插值:t 为 [0, 1] 之间的数值
37 | float interp(float x1, float x2, float t) { return x1 + (x2 - x1) * t; }
38 |
39 | // | v |
40 | float vector_length(const vector_t *v) {
41 | float sq = v->x * v->x + v->y * v->y + v->z * v->z;
42 | return (float)sqrt(sq);
43 | }
44 |
45 | // z = x + y
46 | void vector_add(vector_t *z, const vector_t *x, const vector_t *y) {
47 | z->x = x->x + y->x;
48 | z->y = x->y + y->y;
49 | z->z = x->z + y->z;
50 | z->w = 1.0;
51 | }
52 |
53 | // z = x - y
54 | void vector_sub(vector_t *z, const vector_t *x, const vector_t *y) {
55 | z->x = x->x - y->x;
56 | z->y = x->y - y->y;
57 | z->z = x->z - y->z;
58 | z->w = 1.0;
59 | }
60 |
61 | // 矢量点乘
62 | float vector_dotproduct(const vector_t *x, const vector_t *y) {
63 | return x->x * y->x + x->y * y->y + x->z * y->z;
64 | }
65 |
66 | // 矢量叉乘
67 | void vector_crossproduct(vector_t *z, const vector_t *x, const vector_t *y) {
68 | float m1, m2, m3;
69 | m1 = x->y * y->z - x->z * y->y;
70 | m2 = x->z * y->x - x->x * y->z;
71 | m3 = x->x * y->y - x->y * y->x;
72 | z->x = m1;
73 | z->y = m2;
74 | z->z = m3;
75 | z->w = 1.0f;
76 | }
77 |
78 | // 矢量插值,t取值 [0, 1]
79 | void vector_interp(vector_t *z, const vector_t *x1, const vector_t *x2, float t) {
80 | z->x = interp(x1->x, x2->x, t);
81 | z->y = interp(x1->y, x2->y, t);
82 | z->z = interp(x1->z, x2->z, t);
83 | z->w = 1.0f;
84 | }
85 |
86 | // 矢量归一化
87 | void vector_normalize(vector_t *v) {
88 | float length = vector_length(v);
89 | if (length != 0.0f) {
90 | float inv = 1.0f / length;
91 | v->x *= inv;
92 | v->y *= inv;
93 | v->z *= inv;
94 | }
95 | }
96 |
97 | // c = a + b
98 | void matrix_add(matrix_t *c, const matrix_t *a, const matrix_t *b) {
99 | int i, j;
100 | for (i = 0; i < 4; i++) {
101 | for (j = 0; j < 4; j++)
102 | c->m[i][j] = a->m[i][j] + b->m[i][j];
103 | }
104 | }
105 |
106 | // c = a - b
107 | void matrix_sub(matrix_t *c, const matrix_t *a, const matrix_t *b) {
108 | int i, j;
109 | for (i = 0; i < 4; i++) {
110 | for (j = 0; j < 4; j++)
111 | c->m[i][j] = a->m[i][j] - b->m[i][j];
112 | }
113 | }
114 |
115 | // c = a * b
116 | void matrix_mul(matrix_t *c, const matrix_t *a, const matrix_t *b) {
117 | matrix_t z;
118 | int i, j;
119 | for (i = 0; i < 4; i++) {
120 | for (j = 0; j < 4; j++) {
121 | z.m[j][i] = (a->m[j][0] * b->m[0][i]) +
122 | (a->m[j][1] * b->m[1][i]) +
123 | (a->m[j][2] * b->m[2][i]) +
124 | (a->m[j][3] * b->m[3][i]);
125 | }
126 | }
127 | c[0] = z;
128 | }
129 |
130 | // c = a * f
131 | void matrix_scale(matrix_t *c, const matrix_t *a, float f) {
132 | int i, j;
133 | for (i = 0; i < 4; i++) {
134 | for (j = 0; j < 4; j++)
135 | c->m[i][j] = a->m[i][j] * f;
136 | }
137 | }
138 |
139 | // y = x * m
140 | void matrix_apply(vector_t *y, const vector_t *x, const matrix_t *m) {
141 | float X = x->x, Y = x->y, Z = x->z, W = x->w;
142 | y->x = X * m->m[0][0] + Y * m->m[1][0] + Z * m->m[2][0] + W * m->m[3][0];
143 | y->y = X * m->m[0][1] + Y * m->m[1][1] + Z * m->m[2][1] + W * m->m[3][1];
144 | y->z = X * m->m[0][2] + Y * m->m[1][2] + Z * m->m[2][2] + W * m->m[3][2];
145 | y->w = X * m->m[0][3] + Y * m->m[1][3] + Z * m->m[2][3] + W * m->m[3][3];
146 | }
147 |
148 | void matrix_set_identity(matrix_t *m) {
149 | m->m[0][0] = m->m[1][1] = m->m[2][2] = m->m[3][3] = 1.0f;
150 | m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
151 | m->m[1][0] = m->m[1][2] = m->m[1][3] = 0.0f;
152 | m->m[2][0] = m->m[2][1] = m->m[2][3] = 0.0f;
153 | m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
154 | }
155 |
156 | void matrix_set_zero(matrix_t *m) {
157 | m->m[0][0] = m->m[0][1] = m->m[0][2] = m->m[0][3] = 0.0f;
158 | m->m[1][0] = m->m[1][1] = m->m[1][2] = m->m[1][3] = 0.0f;
159 | m->m[2][0] = m->m[2][1] = m->m[2][2] = m->m[2][3] = 0.0f;
160 | m->m[3][0] = m->m[3][1] = m->m[3][2] = m->m[3][3] = 0.0f;
161 | }
162 |
163 | // 平移变换
164 | void matrix_set_translate(matrix_t *m, float x, float y, float z) {
165 | matrix_set_identity(m);
166 | m->m[3][0] = x;
167 | m->m[3][1] = y;
168 | m->m[3][2] = z;
169 | }
170 |
171 | // 缩放变换
172 | void matrix_set_scale(matrix_t *m, float x, float y, float z) {
173 | matrix_set_identity(m);
174 | m->m[0][0] = x;
175 | m->m[1][1] = y;
176 | m->m[2][2] = z;
177 | }
178 |
179 | // 旋转矩阵
180 | void matrix_set_rotate(matrix_t *m, float x, float y, float z, float theta) {
181 | float qsin = (float)sin(theta * 0.5f);
182 | float qcos = (float)cos(theta * 0.5f);
183 | vector_t vec = { x, y, z, 1.0f };
184 | float w = qcos;
185 | vector_normalize(&vec);
186 | x = vec.x * qsin;
187 | y = vec.y * qsin;
188 | z = vec.z * qsin;
189 | m->m[0][0] = 1 - 2 * y * y - 2 * z * z;
190 | m->m[1][0] = 2 * x * y - 2 * w * z;
191 | m->m[2][0] = 2 * x * z + 2 * w * y;
192 | m->m[0][1] = 2 * x * y + 2 * w * z;
193 | m->m[1][1] = 1 - 2 * x * x - 2 * z * z;
194 | m->m[2][1] = 2 * y * z - 2 * w * x;
195 | m->m[0][2] = 2 * x * z - 2 * w * y;
196 | m->m[1][2] = 2 * y * z + 2 * w * x;
197 | m->m[2][2] = 1 - 2 * x * x - 2 * y * y;
198 | m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
199 | m->m[3][0] = m->m[3][1] = m->m[3][2] = 0.0f;
200 | m->m[3][3] = 1.0f;
201 | }
202 |
203 | // 设置摄像机
204 | void matrix_set_lookat(matrix_t *m, const vector_t *eye, const vector_t *at, const vector_t *up) {
205 | vector_t xaxis, yaxis, zaxis;
206 |
207 | vector_sub(&zaxis, at, eye);
208 | vector_normalize(&zaxis);
209 | vector_crossproduct(&xaxis, up, &zaxis);
210 | vector_normalize(&xaxis);
211 | vector_crossproduct(&yaxis, &zaxis, &xaxis);
212 |
213 | m->m[0][0] = xaxis.x;
214 | m->m[1][0] = xaxis.y;
215 | m->m[2][0] = xaxis.z;
216 | m->m[3][0] = -vector_dotproduct(&xaxis, eye);
217 |
218 | m->m[0][1] = yaxis.x;
219 | m->m[1][1] = yaxis.y;
220 | m->m[2][1] = yaxis.z;
221 | m->m[3][1] = -vector_dotproduct(&yaxis, eye);
222 |
223 | m->m[0][2] = zaxis.x;
224 | m->m[1][2] = zaxis.y;
225 | m->m[2][2] = zaxis.z;
226 | m->m[3][2] = -vector_dotproduct(&zaxis, eye);
227 |
228 | m->m[0][3] = m->m[1][3] = m->m[2][3] = 0.0f;
229 | m->m[3][3] = 1.0f;
230 | }
231 |
232 | // D3DXMatrixPerspectiveFovLH
233 | void matrix_set_perspective(matrix_t *m, float fovy, float aspect, float zn, float zf) {
234 | float fax = 1.0f / (float)tan(fovy * 0.5f);
235 | matrix_set_zero(m);
236 | m->m[0][0] = (float)(fax / aspect);
237 | m->m[1][1] = (float)(fax);
238 | m->m[2][2] = zf / (zf - zn);
239 | m->m[3][2] = - zn * zf / (zf - zn);
240 | m->m[2][3] = 1;
241 | }
242 |
243 |
244 | //=====================================================================
245 | // 坐标变换
246 | //=====================================================================
247 | typedef struct {
248 | matrix_t world; // 世界坐标变换
249 | matrix_t view; // 摄影机坐标变换
250 | matrix_t projection; // 投影变换
251 | matrix_t transform; // transform = world * view * projection
252 | float w, h; // 屏幕大小
253 | } transform_t;
254 |
255 |
256 | // 矩阵更新,计算 transform = world * view * projection
257 | void transform_update(transform_t *ts) {
258 | matrix_t m;
259 | matrix_mul(&m, &ts->world, &ts->view);
260 | matrix_mul(&ts->transform, &m, &ts->projection);
261 | }
262 |
263 | // 初始化,设置屏幕长宽
264 | void transform_init(transform_t *ts, int width, int height) {
265 | float aspect = (float)width / ((float)height);
266 | matrix_set_identity(&ts->world);
267 | matrix_set_identity(&ts->view);
268 | matrix_set_perspective(&ts->projection, 3.1415926f * 0.5f, aspect, 1.0f, 500.0f);
269 | ts->w = (float)width;
270 | ts->h = (float)height;
271 | transform_update(ts);
272 | }
273 |
274 | // 将矢量 x 进行 project
275 | void transform_apply(const transform_t *ts, vector_t *y, const vector_t *x) {
276 | matrix_apply(y, x, &ts->transform);
277 | }
278 |
279 | // 检查齐次坐标同 cvv 的边界用于视锥裁剪
280 | int transform_check_cvv(const vector_t *v) {
281 | float w = v->w;
282 | int check = 0;
283 | if (v->z < 0.0f) check |= 1;
284 | if (v->z > w) check |= 2;
285 | if (v->x < -w) check |= 4;
286 | if (v->x > w) check |= 8;
287 | if (v->y < -w) check |= 16;
288 | if (v->y > w) check |= 32;
289 | return check;
290 | }
291 |
292 | // 归一化,得到屏幕坐标
293 | void transform_homogenize(const transform_t *ts, vector_t *y, const vector_t *x) {
294 | float rhw = 1.0f / x->w;
295 | y->x = (x->x * rhw + 1.0f) * ts->w * 0.5f;
296 | y->y = (1.0f - x->y * rhw) * ts->h * 0.5f;
297 | y->z = x->z * rhw;
298 | y->w = 1.0f;
299 | }
300 |
301 |
302 | //=====================================================================
303 | // 几何计算:顶点、扫描线、边缘、矩形、步长计算
304 | //=====================================================================
305 | typedef struct { float r, g, b; } color_t;
306 | typedef struct { float u, v; } texcoord_t;
307 | typedef struct { point_t pos; texcoord_t tc; color_t color; float rhw; } vertex_t;
308 |
309 | typedef struct { vertex_t v, v1, v2; } edge_t;
310 | typedef struct { float top, bottom; edge_t left, right; } trapezoid_t;
311 | typedef struct { vertex_t v, step; int x, y, w; } scanline_t;
312 |
313 |
314 | void vertex_rhw_init(vertex_t *v) {
315 | float rhw = 1.0f / v->pos.w;
316 | v->rhw = rhw;
317 | v->tc.u *= rhw;
318 | v->tc.v *= rhw;
319 | v->color.r *= rhw;
320 | v->color.g *= rhw;
321 | v->color.b *= rhw;
322 | }
323 |
324 | void vertex_interp(vertex_t *y, const vertex_t *x1, const vertex_t *x2, float t) {
325 | vector_interp(&y->pos, &x1->pos, &x2->pos, t);
326 | y->tc.u = interp(x1->tc.u, x2->tc.u, t);
327 | y->tc.v = interp(x1->tc.v, x2->tc.v, t);
328 | y->color.r = interp(x1->color.r, x2->color.r, t);
329 | y->color.g = interp(x1->color.g, x2->color.g, t);
330 | y->color.b = interp(x1->color.b, x2->color.b, t);
331 | y->rhw = interp(x1->rhw, x2->rhw, t);
332 | }
333 |
334 | void vertex_division(vertex_t *y, const vertex_t *x1, const vertex_t *x2, float w) {
335 | float inv = 1.0f / w;
336 | y->pos.x = (x2->pos.x - x1->pos.x) * inv;
337 | y->pos.y = (x2->pos.y - x1->pos.y) * inv;
338 | y->pos.z = (x2->pos.z - x1->pos.z) * inv;
339 | y->pos.w = (x2->pos.w - x1->pos.w) * inv;
340 | y->tc.u = (x2->tc.u - x1->tc.u) * inv;
341 | y->tc.v = (x2->tc.v - x1->tc.v) * inv;
342 | y->color.r = (x2->color.r - x1->color.r) * inv;
343 | y->color.g = (x2->color.g - x1->color.g) * inv;
344 | y->color.b = (x2->color.b - x1->color.b) * inv;
345 | y->rhw = (x2->rhw - x1->rhw) * inv;
346 | }
347 |
348 | void vertex_add(vertex_t *y, const vertex_t *x) {
349 | y->pos.x += x->pos.x;
350 | y->pos.y += x->pos.y;
351 | y->pos.z += x->pos.z;
352 | y->pos.w += x->pos.w;
353 | y->rhw += x->rhw;
354 | y->tc.u += x->tc.u;
355 | y->tc.v += x->tc.v;
356 | y->color.r += x->color.r;
357 | y->color.g += x->color.g;
358 | y->color.b += x->color.b;
359 | }
360 |
361 | // 根据三角形生成 0-2 个梯形,并且返回合法梯形的数量
362 | int trapezoid_init_triangle(trapezoid_t *trap, const vertex_t *p1,
363 | const vertex_t *p2, const vertex_t *p3) {
364 | const vertex_t *p;
365 | float k, x;
366 |
367 | if (p1->pos.y > p2->pos.y) p = p1, p1 = p2, p2 = p;
368 | if (p1->pos.y > p3->pos.y) p = p1, p1 = p3, p3 = p;
369 | if (p2->pos.y > p3->pos.y) p = p2, p2 = p3, p3 = p;
370 | if (p1->pos.y == p2->pos.y && p1->pos.y == p3->pos.y) return 0;
371 | if (p1->pos.x == p2->pos.x && p1->pos.x == p3->pos.x) return 0;
372 |
373 | if (p1->pos.y == p2->pos.y) { // triangle down
374 | if (p1->pos.x > p2->pos.x) p = p1, p1 = p2, p2 = p;
375 | trap[0].top = p1->pos.y;
376 | trap[0].bottom = p3->pos.y;
377 | trap[0].left.v1 = *p1;
378 | trap[0].left.v2 = *p3;
379 | trap[0].right.v1 = *p2;
380 | trap[0].right.v2 = *p3;
381 | return (trap[0].top < trap[0].bottom)? 1 : 0;
382 | }
383 |
384 | if (p2->pos.y == p3->pos.y) { // triangle up
385 | if (p2->pos.x > p3->pos.x) p = p2, p2 = p3, p3 = p;
386 | trap[0].top = p1->pos.y;
387 | trap[0].bottom = p3->pos.y;
388 | trap[0].left.v1 = *p1;
389 | trap[0].left.v2 = *p2;
390 | trap[0].right.v1 = *p1;
391 | trap[0].right.v2 = *p3;
392 | return (trap[0].top < trap[0].bottom)? 1 : 0;
393 | }
394 |
395 | trap[0].top = p1->pos.y;
396 | trap[0].bottom = p2->pos.y;
397 | trap[1].top = p2->pos.y;
398 | trap[1].bottom = p3->pos.y;
399 |
400 | k = (p3->pos.y - p1->pos.y) / (p2->pos.y - p1->pos.y);
401 | x = p1->pos.x + (p2->pos.x - p1->pos.x) * k;
402 |
403 | if (x <= p3->pos.x) { // triangle left
404 | trap[0].left.v1 = *p1;
405 | trap[0].left.v2 = *p2;
406 | trap[0].right.v1 = *p1;
407 | trap[0].right.v2 = *p3;
408 | trap[1].left.v1 = *p2;
409 | trap[1].left.v2 = *p3;
410 | trap[1].right.v1 = *p1;
411 | trap[1].right.v2 = *p3;
412 | } else { // triangle right
413 | trap[0].left.v1 = *p1;
414 | trap[0].left.v2 = *p3;
415 | trap[0].right.v1 = *p1;
416 | trap[0].right.v2 = *p2;
417 | trap[1].left.v1 = *p1;
418 | trap[1].left.v2 = *p3;
419 | trap[1].right.v1 = *p2;
420 | trap[1].right.v2 = *p3;
421 | }
422 |
423 | return 2;
424 | }
425 |
426 | // 按照 Y 坐标计算出左右两条边纵坐标等于 Y 的顶点
427 | void trapezoid_edge_interp(trapezoid_t *trap, float y) {
428 | float s1 = trap->left.v2.pos.y - trap->left.v1.pos.y;
429 | float s2 = trap->right.v2.pos.y - trap->right.v1.pos.y;
430 | float t1 = (y - trap->left.v1.pos.y) / s1;
431 | float t2 = (y - trap->right.v1.pos.y) / s2;
432 | vertex_interp(&trap->left.v, &trap->left.v1, &trap->left.v2, t1);
433 | vertex_interp(&trap->right.v, &trap->right.v1, &trap->right.v2, t2);
434 | }
435 |
436 | // 根据左右两边的端点,初始化计算出扫描线的起点和步长
437 | void trapezoid_init_scan_line(const trapezoid_t *trap, scanline_t *scanline, int y) {
438 | float width = trap->right.v.pos.x - trap->left.v.pos.x;
439 | scanline->x = (int)(trap->left.v.pos.x + 0.5f);
440 | scanline->w = (int)(trap->right.v.pos.x + 0.5f) - scanline->x;
441 | scanline->y = y;
442 | scanline->v = trap->left.v;
443 | if (trap->left.v.pos.x >= trap->right.v.pos.x) scanline->w = 0;
444 | vertex_division(&scanline->step, &trap->left.v, &trap->right.v, width);
445 | }
446 |
447 |
448 | //=====================================================================
449 | // 渲染设备
450 | //=====================================================================
451 | typedef struct {
452 | transform_t transform; // 坐标变换器
453 | int width; // 窗口宽度
454 | int height; // 窗口高度
455 | IUINT32 **framebuffer; // 像素缓存:framebuffer[y] 代表第 y行
456 | float **zbuffer; // 深度缓存:zbuffer[y] 为第 y行指针
457 | IUINT32 **texture; // 纹理:同样是每行索引
458 | int tex_width; // 纹理宽度
459 | int tex_height; // 纹理高度
460 | float max_u; // 纹理最大宽度:tex_width - 1
461 | float max_v; // 纹理最大高度:tex_height - 1
462 | int render_state; // 渲染状态
463 | IUINT32 background; // 背景颜色
464 | IUINT32 foreground; // 线框颜色
465 | } device_t;
466 |
467 | #define RENDER_STATE_WIREFRAME 1 // 渲染线框
468 | #define RENDER_STATE_TEXTURE 2 // 渲染纹理
469 | #define RENDER_STATE_COLOR 4 // 渲染颜色
470 |
471 | // 设备初始化,fb为外部帧缓存,非 NULL 将引用外部帧缓存(每行 4字节对齐)
472 | void device_init(device_t *device, int width, int height, void *fb) {
473 | int need = sizeof(void*) * (height * 2 + 1024) + width * height * 8;
474 | char *ptr = (char*)malloc(need + 64);
475 | char *framebuf, *zbuf;
476 | int j;
477 | assert(ptr);
478 | device->framebuffer = (IUINT32**)ptr;
479 | device->zbuffer = (float**)(ptr + sizeof(void*) * height);
480 | ptr += sizeof(void*) * height * 2;
481 | device->texture = (IUINT32**)ptr;
482 | ptr += sizeof(void*) * 1024;
483 | framebuf = (char*)ptr;
484 | zbuf = (char*)ptr + width * height * 4;
485 | ptr += width * height * 8;
486 | if (fb != NULL) framebuf = (char*)fb;
487 | for (j = 0; j < height; j++) {
488 | device->framebuffer[j] = (IUINT32*)(framebuf + width * 4 * j);
489 | device->zbuffer[j] = (float*)(zbuf + width * 4 * j);
490 | }
491 | device->texture[0] = (IUINT32*)ptr;
492 | device->texture[1] = (IUINT32*)(ptr + 16);
493 | memset(device->texture[0], 0, 64);
494 | device->tex_width = 2;
495 | device->tex_height = 2;
496 | device->max_u = 1.0f;
497 | device->max_v = 1.0f;
498 | device->width = width;
499 | device->height = height;
500 | device->background = 0xc0c0c0;
501 | device->foreground = 0;
502 | transform_init(&device->transform, width, height);
503 | device->render_state = RENDER_STATE_WIREFRAME;
504 | }
505 |
506 | // 删除设备
507 | void device_destroy(device_t *device) {
508 | if (device->framebuffer)
509 | free(device->framebuffer);
510 | device->framebuffer = NULL;
511 | device->zbuffer = NULL;
512 | device->texture = NULL;
513 | }
514 |
515 | // 设置当前纹理
516 | void device_set_texture(device_t *device, void *bits, long pitch, int w, int h) {
517 | char *ptr = (char*)bits;
518 | int j;
519 | assert(w <= 1024 && h <= 1024);
520 | for (j = 0; j < h; ptr += pitch, j++) // 重新计算每行纹理的指针
521 | device->texture[j] = (IUINT32*)ptr;
522 | device->tex_width = w;
523 | device->tex_height = h;
524 | device->max_u = (float)(w - 1);
525 | device->max_v = (float)(h - 1);
526 | }
527 |
528 | // 清空 framebuffer 和 zbuffer
529 | void device_clear(device_t *device, int mode) {
530 | int y, x, height = device->height;
531 | for (y = 0; y < device->height; y++) {
532 | IUINT32 *dst = device->framebuffer[y];
533 | IUINT32 cc = (height - 1 - y) * 230 / (height - 1);
534 | cc = (cc << 16) | (cc << 8) | cc;
535 | if (mode == 0) cc = device->background;
536 | for (x = device->width; x > 0; dst++, x--) dst[0] = cc;
537 | }
538 | for (y = 0; y < device->height; y++) {
539 | float *dst = device->zbuffer[y];
540 | for (x = device->width; x > 0; dst++, x--) dst[0] = 0.0f;
541 | }
542 | }
543 |
544 | // 画点
545 | void device_pixel(device_t *device, int x, int y, IUINT32 color) {
546 | if (((IUINT32)x) < (IUINT32)device->width && ((IUINT32)y) < (IUINT32)device->height) {
547 | device->framebuffer[y][x] = color;
548 | }
549 | }
550 |
551 | // 绘制线段
552 | void device_draw_line(device_t *device, int x1, int y1, int x2, int y2, IUINT32 c) {
553 | int x, y, rem = 0;
554 | if (x1 == x2 && y1 == y2) {
555 | device_pixel(device, x1, y1, c);
556 | } else if (x1 == x2) {
557 | int inc = (y1 <= y2)? 1 : -1;
558 | for (y = y1; y != y2; y += inc) device_pixel(device, x1, y, c);
559 | device_pixel(device, x2, y2, c);
560 | } else if (y1 == y2) {
561 | int inc = (x1 <= x2)? 1 : -1;
562 | for (x = x1; x != x2; x += inc) device_pixel(device, x, y1, c);
563 | device_pixel(device, x2, y2, c);
564 | } else {
565 | int dx = (x1 < x2)? x2 - x1 : x1 - x2;
566 | int dy = (y1 < y2)? y2 - y1 : y1 - y2;
567 | if (dx >= dy) {
568 | if (x2 < x1) x = x1, y = y1, x1 = x2, y1 = y2, x2 = x, y2 = y;
569 | for (x = x1, y = y1; x <= x2; x++) {
570 | device_pixel(device, x, y, c);
571 | rem += dy;
572 | if (rem >= dx) {
573 | rem -= dx;
574 | y += (y2 >= y1)? 1 : -1;
575 | device_pixel(device, x, y, c);
576 | }
577 | }
578 | device_pixel(device, x2, y2, c);
579 | } else {
580 | if (y2 < y1) x = x1, y = y1, x1 = x2, y1 = y2, x2 = x, y2 = y;
581 | for (x = x1, y = y1; y <= y2; y++) {
582 | device_pixel(device, x, y, c);
583 | rem += dx;
584 | if (rem >= dy) {
585 | rem -= dy;
586 | x += (x2 >= x1)? 1 : -1;
587 | device_pixel(device, x, y, c);
588 | }
589 | }
590 | device_pixel(device, x2, y2, c);
591 | }
592 | }
593 | }
594 |
595 | // 根据坐标读取纹理
596 | IUINT32 device_texture_read(const device_t *device, float u, float v) {
597 | int x, y;
598 | u = u * device->max_u;
599 | v = v * device->max_v;
600 | x = (int)(u + 0.5f);
601 | y = (int)(v + 0.5f);
602 | x = CMID(x, 0, device->tex_width - 1);
603 | y = CMID(y, 0, device->tex_height - 1);
604 | return device->texture[y][x];
605 | }
606 |
607 |
608 | //=====================================================================
609 | // 渲染实现
610 | //=====================================================================
611 |
612 | // 绘制扫描线
613 | void device_draw_scanline(device_t *device, scanline_t *scanline) {
614 | IUINT32 *framebuffer = device->framebuffer[scanline->y];
615 | float *zbuffer = device->zbuffer[scanline->y];
616 | int x = scanline->x;
617 | int w = scanline->w;
618 | int width = device->width;
619 | int render_state = device->render_state;
620 | for (; w > 0; x++, w--) {
621 | if (x >= 0 && x < width) {
622 | float rhw = scanline->v.rhw;
623 | if (rhw >= zbuffer[x]) {
624 | float w = 1.0f / rhw;
625 | zbuffer[x] = rhw;
626 | if (render_state & RENDER_STATE_COLOR) {
627 | float r = scanline->v.color.r * w;
628 | float g = scanline->v.color.g * w;
629 | float b = scanline->v.color.b * w;
630 | int R = (int)(r * 255.0f);
631 | int G = (int)(g * 255.0f);
632 | int B = (int)(b * 255.0f);
633 | R = CMID(R, 0, 255);
634 | G = CMID(G, 0, 255);
635 | B = CMID(B, 0, 255);
636 | framebuffer[x] = (R << 16) | (G << 8) | (B);
637 | }
638 | if (render_state & RENDER_STATE_TEXTURE) {
639 | float u = scanline->v.tc.u * w;
640 | float v = scanline->v.tc.v * w;
641 | IUINT32 cc = device_texture_read(device, u, v);
642 | framebuffer[x] = cc;
643 | }
644 | }
645 | }
646 | vertex_add(&scanline->v, &scanline->step);
647 | if (x >= width) break;
648 | }
649 | }
650 |
651 | // 主渲染函数
652 | void device_render_trap(device_t *device, trapezoid_t *trap) {
653 | scanline_t scanline;
654 | int j, top, bottom;
655 | top = (int)(trap->top + 0.5f);
656 | bottom = (int)(trap->bottom + 0.5f);
657 | for (j = top; j < bottom; j++) {
658 | if (j >= 0 && j < device->height) {
659 | trapezoid_edge_interp(trap, (float)j + 0.5f);
660 | trapezoid_init_scan_line(trap, &scanline, j);
661 | device_draw_scanline(device, &scanline);
662 | }
663 | if (j >= device->height) break;
664 | }
665 | }
666 |
667 | // 根据 render_state 绘制原始三角形
668 | void device_draw_primitive(device_t *device, const vertex_t *v1,
669 | const vertex_t *v2, const vertex_t *v3) {
670 | point_t p1, p2, p3, c1, c2, c3;
671 | int render_state = device->render_state;
672 |
673 | // 按照 Transform 变化
674 | transform_apply(&device->transform, &c1, &v1->pos);
675 | transform_apply(&device->transform, &c2, &v2->pos);
676 | transform_apply(&device->transform, &c3, &v3->pos);
677 |
678 | // 裁剪,注意此处可以完善为具体判断几个点在 cvv内以及同cvv相交平面的坐标比例
679 | // 进行进一步精细裁剪,将一个分解为几个完全处在 cvv内的三角形
680 | if (transform_check_cvv(&c1) != 0) return;
681 | if (transform_check_cvv(&c2) != 0) return;
682 | if (transform_check_cvv(&c3) != 0) return;
683 |
684 | // 归一化
685 | transform_homogenize(&device->transform, &p1, &c1);
686 | transform_homogenize(&device->transform, &p2, &c2);
687 | transform_homogenize(&device->transform, &p3, &c3);
688 |
689 | // 纹理或者色彩绘制
690 | if (render_state & (RENDER_STATE_TEXTURE | RENDER_STATE_COLOR)) {
691 | vertex_t t1 = *v1, t2 = *v2, t3 = *v3;
692 | trapezoid_t traps[2];
693 | int n;
694 |
695 | t1.pos = p1;
696 | t2.pos = p2;
697 | t3.pos = p3;
698 | t1.pos.w = c1.w;
699 | t2.pos.w = c2.w;
700 | t3.pos.w = c3.w;
701 |
702 | vertex_rhw_init(&t1); // 初始化 w
703 | vertex_rhw_init(&t2); // 初始化 w
704 | vertex_rhw_init(&t3); // 初始化 w
705 |
706 | // 拆分三角形为0-2个梯形,并且返回可用梯形数量
707 | n = trapezoid_init_triangle(traps, &t1, &t2, &t3);
708 |
709 | if (n >= 1) device_render_trap(device, &traps[0]);
710 | if (n >= 2) device_render_trap(device, &traps[1]);
711 | }
712 |
713 | if (render_state & RENDER_STATE_WIREFRAME) { // 线框绘制
714 | device_draw_line(device, (int)p1.x, (int)p1.y, (int)p2.x, (int)p2.y, device->foreground);
715 | device_draw_line(device, (int)p1.x, (int)p1.y, (int)p3.x, (int)p3.y, device->foreground);
716 | device_draw_line(device, (int)p3.x, (int)p3.y, (int)p2.x, (int)p2.y, device->foreground);
717 | }
718 | }
719 |
720 |
721 | //=====================================================================
722 | // Win32 窗口及图形绘制:为 device 提供一个 DibSection 的 FB
723 | //=====================================================================
724 | int screen_w, screen_h, screen_exit = 0;
725 | int screen_mx = 0, screen_my = 0, screen_mb = 0;
726 | int screen_keys[512]; // 当前键盘按下状态
727 | static HWND screen_handle = NULL; // 主窗口 HWND
728 | static HDC screen_dc = NULL; // 配套的 HDC
729 | static HBITMAP screen_hb = NULL; // DIB
730 | static HBITMAP screen_ob = NULL; // 老的 BITMAP
731 | unsigned char *screen_fb = NULL; // frame buffer
732 | long screen_pitch = 0;
733 |
734 | int screen_init(int w, int h, const TCHAR *title); // 屏幕初始化
735 | int screen_close(void); // 关闭屏幕
736 | void screen_dispatch(void); // 处理消息
737 | void screen_update(void); // 显示 FrameBuffer
738 |
739 | // win32 event handler
740 | static LRESULT screen_events(HWND, UINT, WPARAM, LPARAM);
741 |
742 | #ifdef _MSC_VER
743 | #pragma comment(lib, "gdi32.lib")
744 | #pragma comment(lib, "user32.lib")
745 | #endif
746 |
747 | // 初始化窗口并设置标题
748 | int screen_init(int w, int h, const TCHAR *title) {
749 | WNDCLASS wc = { CS_BYTEALIGNCLIENT, (WNDPROC)screen_events, 0, 0, 0,
750 | NULL, NULL, NULL, NULL, _T("SCREEN3.1415926") };
751 | BITMAPINFO bi = { { sizeof(BITMAPINFOHEADER), w, -h, 1, 32, BI_RGB,
752 | w * h * 4, 0, 0, 0, 0 } };
753 | RECT rect = { 0, 0, w, h };
754 | int wx, wy, sx, sy;
755 | LPVOID ptr;
756 | HDC hDC;
757 |
758 | screen_close();
759 |
760 | wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
761 | wc.hInstance = GetModuleHandle(NULL);
762 | wc.hCursor = LoadCursor(NULL, IDC_ARROW);
763 | if (!RegisterClass(&wc)) return -1;
764 |
765 | screen_handle = CreateWindow(_T("SCREEN3.1415926"), title,
766 | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
767 | 0, 0, 0, 0, NULL, NULL, wc.hInstance, NULL);
768 | if (screen_handle == NULL) return -2;
769 |
770 | screen_exit = 0;
771 | hDC = GetDC(screen_handle);
772 | screen_dc = CreateCompatibleDC(hDC);
773 | ReleaseDC(screen_handle, hDC);
774 |
775 | screen_hb = CreateDIBSection(screen_dc, &bi, DIB_RGB_COLORS, &ptr, 0, 0);
776 | if (screen_hb == NULL) return -3;
777 |
778 | screen_ob = (HBITMAP)SelectObject(screen_dc, screen_hb);
779 | screen_fb = (unsigned char*)ptr;
780 | screen_w = w;
781 | screen_h = h;
782 | screen_pitch = w * 4;
783 |
784 | AdjustWindowRect(&rect, GetWindowLong(screen_handle, GWL_STYLE), 0);
785 | wx = rect.right - rect.left;
786 | wy = rect.bottom - rect.top;
787 | sx = (GetSystemMetrics(SM_CXSCREEN) - wx) / 2;
788 | sy = (GetSystemMetrics(SM_CYSCREEN) - wy) / 2;
789 | if (sy < 0) sy = 0;
790 | SetWindowPos(screen_handle, NULL, sx, sy, wx, wy, (SWP_NOCOPYBITS | SWP_NOZORDER | SWP_SHOWWINDOW));
791 | SetForegroundWindow(screen_handle);
792 |
793 | ShowWindow(screen_handle, SW_NORMAL);
794 | screen_dispatch();
795 |
796 | memset(screen_keys, 0, sizeof(int) * 512);
797 | memset(screen_fb, 0, w * h * 4);
798 |
799 | return 0;
800 | }
801 |
802 | int screen_close(void) {
803 | if (screen_dc) {
804 | if (screen_ob) {
805 | SelectObject(screen_dc, screen_ob);
806 | screen_ob = NULL;
807 | }
808 | DeleteDC(screen_dc);
809 | screen_dc = NULL;
810 | }
811 | if (screen_hb) {
812 | DeleteObject(screen_hb);
813 | screen_hb = NULL;
814 | }
815 | if (screen_handle) {
816 | CloseWindow(screen_handle);
817 | screen_handle = NULL;
818 | }
819 | return 0;
820 | }
821 |
822 | static LRESULT screen_events(HWND hWnd, UINT msg,
823 | WPARAM wParam, LPARAM lParam) {
824 | switch (msg) {
825 | case WM_CLOSE: screen_exit = 1; break;
826 | case WM_KEYDOWN: screen_keys[wParam & 511] = 1; break;
827 | case WM_KEYUP: screen_keys[wParam & 511] = 0; break;
828 | default: return DefWindowProc(hWnd, msg, wParam, lParam);
829 | }
830 | return 0;
831 | }
832 |
833 | void screen_dispatch(void) {
834 | MSG msg;
835 | while (1) {
836 | if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) break;
837 | if (!GetMessage(&msg, NULL, 0, 0)) break;
838 | DispatchMessage(&msg);
839 | }
840 | }
841 |
842 | void screen_update(void) {
843 | HDC hDC = GetDC(screen_handle);
844 | BitBlt(hDC, 0, 0, screen_w, screen_h, screen_dc, 0, 0, SRCCOPY);
845 | ReleaseDC(screen_handle, hDC);
846 | screen_dispatch();
847 | }
848 |
849 |
850 | //=====================================================================
851 | // 主程序
852 | //=====================================================================
853 | vertex_t mesh[8] = {
854 | { { -1, -1, 1, 1 }, { 0, 0 }, { 1.0f, 0.2f, 0.2f }, 1 },
855 | { { 1, -1, 1, 1 }, { 0, 1 }, { 0.2f, 1.0f, 0.2f }, 1 },
856 | { { 1, 1, 1, 1 }, { 1, 1 }, { 0.2f, 0.2f, 1.0f }, 1 },
857 | { { -1, 1, 1, 1 }, { 1, 0 }, { 1.0f, 0.2f, 1.0f }, 1 },
858 | { { -1, -1, -1, 1 }, { 0, 0 }, { 1.0f, 1.0f, 0.2f }, 1 },
859 | { { 1, -1, -1, 1 }, { 0, 1 }, { 0.2f, 1.0f, 1.0f }, 1 },
860 | { { 1, 1, -1, 1 }, { 1, 1 }, { 1.0f, 0.3f, 0.3f }, 1 },
861 | { { -1, 1, -1, 1 }, { 1, 0 }, { 0.2f, 1.0f, 0.3f }, 1 },
862 | };
863 |
864 | void draw_plane(device_t *device, int a, int b, int c, int d) {
865 | vertex_t p1 = mesh[a], p2 = mesh[b], p3 = mesh[c], p4 = mesh[d];
866 | p1.tc.u = 0, p1.tc.v = 0, p2.tc.u = 0, p2.tc.v = 1;
867 | p3.tc.u = 1, p3.tc.v = 1, p4.tc.u = 1, p4.tc.v = 0;
868 | device_draw_primitive(device, &p1, &p2, &p3);
869 | device_draw_primitive(device, &p3, &p4, &p1);
870 | }
871 |
872 | void draw_box(device_t *device, float theta) {
873 | matrix_t m;
874 | matrix_set_rotate(&m, -1, -0.5, 1, theta);
875 | device->transform.world = m;
876 | transform_update(&device->transform);
877 | draw_plane(device, 0, 1, 2, 3);
878 | draw_plane(device, 7, 6, 5, 4);
879 | draw_plane(device, 0, 4, 5, 1);
880 | draw_plane(device, 1, 5, 6, 2);
881 | draw_plane(device, 2, 6, 7, 3);
882 | draw_plane(device, 3, 7, 4, 0);
883 | }
884 |
885 | void camera_at_zero(device_t *device, float x, float y, float z) {
886 | point_t eye = { x, y, z, 1 }, at = { 0, 0, 0, 1 }, up = { 0, 0, 1, 1 };
887 | matrix_set_lookat(&device->transform.view, &eye, &at, &up);
888 | transform_update(&device->transform);
889 | }
890 |
891 | void init_texture(device_t *device) {
892 | static IUINT32 texture[256][256];
893 | int i, j;
894 | for (j = 0; j < 256; j++) {
895 | for (i = 0; i < 256; i++) {
896 | int x = i / 32, y = j / 32;
897 | texture[j][i] = ((x + y) & 1)? 0xffffff : 0x3fbcef;
898 | }
899 | }
900 | device_set_texture(device, texture, 256 * 4, 256, 256);
901 | }
902 |
903 | int main(void)
904 | {
905 | device_t device;
906 | int states[] = { RENDER_STATE_TEXTURE, RENDER_STATE_COLOR, RENDER_STATE_WIREFRAME };
907 | int indicator = 0;
908 | int kbhit = 0;
909 | float alpha = 1;
910 | float pos = 3.5;
911 |
912 | TCHAR *title = _T("Mini3d (software render tutorial) - ")
913 | _T("Left/Right: rotation, Up/Down: forward/backward, Space: switch state");
914 |
915 | if (screen_init(800, 600, title))
916 | return -1;
917 |
918 | device_init(&device, 800, 600, screen_fb);
919 | camera_at_zero(&device, 3, 0, 0);
920 |
921 | init_texture(&device);
922 | device.render_state = RENDER_STATE_TEXTURE;
923 |
924 | while (screen_exit == 0 && screen_keys[VK_ESCAPE] == 0) {
925 | screen_dispatch();
926 | device_clear(&device, 1);
927 | camera_at_zero(&device, pos, 0, 0);
928 |
929 | if (screen_keys[VK_UP]) pos -= 0.01f;
930 | if (screen_keys[VK_DOWN]) pos += 0.01f;
931 | if (screen_keys[VK_LEFT]) alpha += 0.01f;
932 | if (screen_keys[VK_RIGHT]) alpha -= 0.01f;
933 |
934 | if (screen_keys[VK_SPACE]) {
935 | if (kbhit == 0) {
936 | kbhit = 1;
937 | if (++indicator >= 3) indicator = 0;
938 | device.render_state = states[indicator];
939 | }
940 | } else {
941 | kbhit = 0;
942 | }
943 |
944 | draw_box(&device, alpha);
945 | screen_update();
946 | Sleep(1);
947 | }
948 | return 0;
949 | }
950 |
951 |
--------------------------------------------------------------------------------