Archive

Archive for August, 2016

256字节3D程序是如何实现3D引擎的呢?

August 26th, 2016 No comments

网上有很多 256 个字节实现图形渲染的 “引擎”,他们的原理是什么呢?

  1. 全都不是基于正统3D引擎的多边形绘制,而是基于少数特定情况的简化版光线跟踪算法
  2. 只能渲染特定几种物体,并不能渲染通用物体。
  3. 无资源或者少资源(基本靠生成),重复
  4. 16位代码,COM格式的可执行(没有PE头,代码数据和栈都在一个段内,指针只有两字节)
  5. 尽可能用汇编来写

你自己花点时间也能做出来,

具体解释一下:

简化版的 raycasting,实现起来的代码量比通用的多边形绘制方法至少 N个量级。

基本的光线跟踪,在 320×200 的解析度下,从摄像机中心射出 320×200条光线,屏幕上每个点对应一条光线,首先碰撞到的物体的位置颜色,就是屏幕上这个点的颜色:

可以描述为下面这段代码:

for (int y = 0; y < 200; y++) {
    for (int x = 0; x < 320; x++) {
        DWORD color = RayCasting(x, y); 
        DrawPixel(x, y, color);
    }
}

其中函数 RayCasting(x, y) 就是计算从视点开始穿过屏幕上 (x, y)这个点的射线。

所谓简化版的光线跟踪,是只需要实现特定物体,以及针对特定条件,比如早年游戏里面用的最多的实时光线跟踪绘制地形高度图的(比如三角洲特种部队,xxx直升飞机):

比如云风 2002年写过的文章:3D地表生成及渲染 (VOXEL)
实现上述效果的地形渲染,只需要 200多行 C 代码
使用标准三角形渲染这样的地形(软件渲染),代码少说也上千行了,使用标准的光线跟踪少说也要 500行左右。

Read more…

Categories: 图形编程 Tags:

Vim 异步运行 Shell 指令的插件 – AsyncRun

August 24th, 2016 2 comments

自制另一个新的 Vim 8.0 专用异步插件:asyncrun.vim,它可以让你在 Vim 里面异步运行各种 Shell 指令并且把结果实时输出到 Quickfix,需要 Vim 7.4.1829 以上版本。

安装方法

到插件首页 https://github.com/skywind3000/asyncrun.vim 下载项目,并拷贝 asyncrun.vim 到你的 ~/.vim/plugin。或者使用 Vundle 指向 skywind3000/asyncrun.vim 来自动更新。

基本教程

使用 gcc 异步编译当前文件:

:AsyncRun gcc % -o %<
:AsyncRun g++ -O3 % -o %< -lpthread 

该命令会在后台运行 gcc 并且把输出实时显示在 Quickfix 窗口,宏 % 代表当前文件名,%< 代表没有扩展名的文件名。

异步运行 make:

:AsyncRun make
:AsyncRun make -f makefile

异步调用 grep:

:AsyncRun! grep -R word . 
:AsyncRun! grep -R <cword> .

默认 :AsyncRun 运行命令后,输出添加到 Quickfix时 Quickfix 会自动滚动到最下面那一行,使用感叹号修饰符以后,可以避免 Quickfix 自动滚动。同时 <cword> 代表当前光标下面的单词。

编译 go项目:

:AsyncRun go build %:p:h

%:p:h 代表当前文件的目录

查询 man page,异步 git push ,以及把设置 F7异步编译当前文件:

:AsyncRun! man -S 3:2:1 <cword> 
:AsyncRun git push origin master
:noremap <F7> :AsyncRun gcc % -o %< <cr> 

使用手册

AsyncRun – Run shell command:

:AsyncRun{!} [cmd] ...

后台运行命令并且实时输出到 quickfix 窗口,如果有感叹号修饰符,quickfix 窗口的自动滚动将会禁止。

命令参数以空格分割,接受下面这些以 ‘%‘, ‘#‘ or ‘<‘ 开头的替换宏:

%:p     - File name of current buffer with full path
%:t     - File name of current buffer without path
%:p:h   - File path of current buffer without file name
%:e     - File extension of current buffer
%:t:r   - File name of current buffer without path and extension
%       - File name relativize to current directory
%:h:.   - File path relativize to current directory
<cwd>   - Current working directory
<cword> - Current word under cursor
<cfile> - Current file name under cursor

运行一个命令前,环境变量也会做如下设置:

$VIM_FILEPATH  - File name of current buffer with full path
$VIM_FILENAME  - File name of current buffer without path
$VIM_FILEDIR   - Full path of current buffer without the file name
$VIM_FILEEXT   - File extension of current buffer
$VIM_FILENOEXT - File name of current buffer without path and extension
$VIM_CWD       - Current directory
$VIM_RELDIR    - File path relativize to current directory
$VIM_RELNAME   - File name relativize to current directory 
$VIM_CWORD     - Current word under cursor
$VIM_CFILE     - Current filename under cursor
$VIM_GUI       - Is running under gui ?
$VIM_VERSION   - Value of v:version
$VIM_MODE      - Execute via 0:!, 1:makeprg, 2:system()
$VIM_COLUMNS   - How many columns in vim's screen
$VIM_LINES     - How many lines in vim's screen

AsyncStop – Stop the running job:

:AsyncStop{!}

停止后台任务(使用 TERM信号),如果有感叹号修饰,则使用 KILL 信号结束

基本设置:

g:asyncrun_exit - 字符串,如果不为空那么任务结束时会被执行(VimScript)
g:asyncrun_bell - 如果非零的话,任务结束后会响铃(终端输出控制符 \a)
g:asyncrun_mode - 0:异步(需要 vim 7.4.1829) 1:同步 2:直接运行

全局变量:

g:asyncrun_code   - 退出码
g:asyncrun_status - 状态 'running', 'success' or 'failure'

如果你喜欢的话请为我投一票:
http://www.vim.org/scripts/script.php?script_id=5431

Categories: 随笔 Tags:

3D 图形光栅化的透视校正问题

August 18th, 2016 1 comment

写了文章《如何写一个软件渲染器》以后,不少网友希望进一步解释背后的数学公式,询问以及自己加一个 phong 光照该如何加,本文将对透视纹理映射的插值原理做一个简单的解释,希望能帮助到大家:

透视纹理绘制发生在最后阶段,坐标已经完成projection,剔除,裁剪了,然后顶点/w,开始批量绘制扫描线之前,这时候开始计算纹理的位置。

使用w还是用z,关系不大,早年的3d引擎,直接/z的,只是后面标准化了以后,发现w更好用,可以同时表示透视投影和正交投影。同时顶点经过标准的mvp矩阵运算后,w和z是承线性关系的,方便对z/w做 [0,1] 的cvv裁剪。你可以理解成w就是另外一个z。以前屏幕坐标:

x' = x / z * d + A
y' = y / z * d + A

现在是

x' = x / w * d + A
y' = y / w * d + A

然后绘制纹理前,你需要先证明屏幕上两个点之间,1/w 承线性关系,即屏幕上两个点X1′, X2’之间任意取一点X3’,他们的(1/w)值的变化比例相同,即在 t 取任意值有:

x3' = x1' + (x2' - x1') * t
(1 / w3) = (1 / w1) + ((1 / w2)  - (1 / w1)) * t

再根据他们在同一个平面上,证明屏幕上两个点之间,u/w, v/w 承线性关系,即 t 取任意值有:

x3' = x1' + (x2' - x1') * t
(u3 / w3) = (u1 / w1) + ((u2 / w2) - (u1 / w1)) * t
(v3 / w3) = (v1 / w1) + ((v2 / w2) - (v1 / w1)) * t

具体到代码里面的做法就是三角形的三个顶点/w以后,u和v也同时/w,然后把w换成自己的倒数:w = 1 / w,及把顶点数据:

(x, y, z, w) + (u, v)

变换成:

(x / w, y / w, z / w, 1 / w) + (u / w, v / w)

然后用 1/w, u/w, v/w进行屏幕空间插值,具体绘制某个点的时候,先从1/w求倒得到w,然后乘以 u/w, v/w得到 u, v,就可以了。

更进一步,可以证明,所有在三维空间里同x,y,z成线性关系的变量,不管是纹理坐标,顶点色或者法向还是其他,他们在屏幕空间里的插值规则都可以通过:插值前先/w,插值后要用时再 * w得到具体值,然后我们把这类三维空间里同x,y,z成线性关系的变量统进行统一的批量处理,和OpenGL的 attribute,varying处理方法相同。

相关阅读:
如何写一个软件渲染器
还原被摄像机透视的纹理

Categories: 图形编程 Tags:

如何用 OpenGL 封装一个 2D 引擎?

August 11th, 2016 No comments

如何正确的使用 OpenGL “封装一个2D引擎” ?以下几个步骤:

1. 别用什么 glBegin/glEnd,至少写兼容GLES2代码,不然手机上跑不起来。
2. 用两个三角形的纹理拼凑出一个2D的图块出来,不是搞啥每个点自己画。
3. 2D图像库基本就是要把显示对象树给做出来就得了。
4. 每个显示对象除了自己外还有很多儿子节点。
5. 每个显示对象有一个变换矩阵,用来设置位置和角度还有缩放,最后是节点的显示效果。
6. 渲染的时候需要从远到近排序,并尽量归并相同效果(fs)及纹理。
7. 把常用纹理管理起来,提供资源加载,可以换进换出,提供类 LRU的机制。
8. 在此基础上提供一些动画(精灵)和场景控制的api,提供显示字体,即可。

最后推荐两个现成的轻量级2D引擎供阅读:

StarEngine:GitHub – StarEngine/engine: Crossplatform C++11 2D Game Engine for Desktop and Mobile games

EJoy2D:GitHub – ejoy/ejoy2d: A 2D Graphics Engine for Mobile Game

就是这样。

Categories: 图形编程 Tags:
Wordpress Social Share Plugin powered by Ultimatelysocial