├── .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 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_1.png) 31 | 32 | Color Filling:RENDER_STATE_COLOR 33 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_0.png) 34 | 35 | Wireframe Rendering:RENDER_STATE_WIREFRAME 36 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_2.png) 37 | 38 | Added Lighting and Bicubic Interpolation (screenshot of lighting effect added by a friend to Mini3D) 39 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_3.png) 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 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/donation.png) 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 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_1.png) 33 | 34 | 色彩填充:RENDER_STATE_COLOR 35 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_0.png) 36 | 37 | 线框绘制:RENDER_STATE_WIREFRAME 38 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_2.png) 39 | 40 | 增加光照和二次线性插值(朋友给 Mini3D 增加的光照效果截图) 41 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/mini_3.png) 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 | ![](https://raw.githubusercontent.com/skywind3000/mini3d/master/images/donation.png) 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 | --------------------------------------------------------------------------------