接下来讲解多边形,多边形在3D模型中类似于骨架的作用,搭好了骨架,才能把纹理包裹上去
NDS的3D引擎支持2种多边形的绘制,这2种多边形又有2种绘制模式,绘制多边形的方式是通过依次设置顶点坐标完成
关于顶点坐标的设置顺序,不同的模式有不同的顺序,下面也将介绍
此外为了更逼真地模拟出3D模型,NDS的3D引擎支持顶点着色,顶点着色一般分2步骤,第一步是设置顶点颜色,第二步是设置光照参数,第二步是可以省略的
接下来的颜色混合将由3D引擎完成,本文也会介绍光照对于顶点着色的实际影响,此外,顶点颜色对于纹理混合的影响,没意外的话,将在纹理贴图这一章介绍
多边形参数设置
引用
复制内容到剪贴板
代码:
40004A4h - Cmd 29h - POLYGON_ATTR - 设置多边形参数 (W)
0-3 分别对应4道光线的启用flag (0为禁用,1为启用)
4-5 多边形模式(0=Modulation,1=Decal,2=Toon/Highlight Shading,3=Shadow)
6 多边形背面是否渲染(0为隐藏,1为渲染) //线段总是被渲染(因为没有正面/背面)
7 多边形正面是否渲染(0为隐藏,1为渲染)
8-10 无用
11 Depth-value for Translucent Polygons (0=Keep Old, 1=Set New Depth)
12 远截面横穿多边形时是否渲染 (0=隐藏, 1=渲染/裁剪)
13 1-Dot polygons的深度比深度阈值大时是否隐藏 (0=隐藏, 1=依旧渲染)
14 深度测试,跟之前绘制的多边形采用何种方式比较 (0=小于, 1=等于) (经常用0)
15 雾化启用 (0=禁用, 1=启用)
16-20 Alpha通道(0=线框,1..30=透明,31=非透明、固实)
21-23 无用
24-29 多边形ID(00h..3Fh,用于透明/阴影/勾边)
30-31 无用
参数一个个分析过去
首先是0-3的灯光控制flag,1bit控制一个
用这个可以控制多边形接受到的光线的数量,关于4道光线的具体控制以及对顶点着色的影响将在下文提到
4-5的多边形模式,这4个模式影响的是纹理混合,在纹理篇中介绍
6 7的话,则是控制多边形正面跟背面是否渲染,关于正面跟背面的判定,我也不清楚,desmume里找不到相关的代码
11的话,我没有翻译,毕竟3D方面我也是半吊子,呵呵,这个flag,我的理解是对于透明状态的多边形是否要重定义Depth-value
12的话,远截面在上一章有介绍,就不多说了,如何判定是否横穿,可以通过判断多边形跟远截面是否有相交的部分
13的话,先解释一下1-Dot polygons,1-Dot polygons就是非常小或者距离非常远的多边形,在屏幕上经常以一个点出现
NDS的3D引擎提供一个I/O寄存器,允许你设置深度阈值,1-Dot polygons的深度大于这个值(即意离camera更远),将自动隐藏
设置深度阈值的I/O寄存器是0x4000610(DISP_1DOT_DEPTH),bit13的作用就是选择是否开启这个功能
14的话,是深度测试,在opengl里又叫DepthFunc,深度测试的作用就是与之前绘制的多边形的深度进行比较,大于则隐藏,这里提供2种比较判定,一种小于一种是等于
GBATEK提到,设置为等于,可能会出现数值误差导致的绘图错误,存在不可知的结果,所以一般设为0
15的话,雾化启用,由于雾化属于3D特效,这个系列的教程就不讲解了,其实GBATEK这部分写得很少,想研究也比较费力
16-20的话,阿尔法通道,阿尔法通道是用来表示多边形的透明程度的
24-29的话,各种3D特效内用来表示优先级的东西吧,不讲解
此外,关于多边形属性,NO$GBA debugger的3D查看器有提供完整的细节信息显示
注意polygon_attr,后面包括了多边形属性的细节,这里表示灯光0开启,多边形模式为Modulation,正面渲染,开启雾化效果,阿尔法的值为0x1f(不透明)
SDK内相关的API为
复制内容到剪贴板
代码:
void G3_PolygonAttr(int light,GXPolygonMode polyMode,GXCull cullMode,int polygonID,int alpha,int misc);
cullMode就是上文提到的正背面是否渲染的设置参数,0同时不显示,1显示正面,2显示背面,3全部显示
misc是个5bit的flags,对应多边形属性的bit11到bit15
设置顶点颜色
为多边形的顶点上色,如果有开启光照特效的话,顶点的颜色将受光照特效的影响而改变,具体见下文
复制内容到剪贴板
代码:
4000480h - Cmd 20h - COLOR - Directly Set Vertex Color (W)
Parameter 1, Bit 0-4 Red
Parameter 1, Bit 5-9 Green
Parameter 1, Bit 10-14 Blue
Parameter 1, Bit 15-31 Not used
这个不用我翻译了吧,这个寄存器允许你直接设置顶点颜色的RGB值,注意,如果RGB555要转换成RGB666的话,必须乘以2然后加1,不能只是乘以2
这一小节的内容可能太短,我补个简单的hack实例,rom是空之探险队汉化版,目标是修改开始菜单对话框的顶点颜色
先跟踪顶点颜色,发现,它来自0x22A8A5C,这里存储了顶点颜色RGB的3个值,将其中一个修改成0,我将R修改为0,它变成下面这样
对于纹理混合结果产生了很大的影响
另外,0x22A8A5F存储多边形的阿尔法通道,可以通过修改它将对话框透明化
SDK内对应的API为
复制内容到剪贴板
代码:
void G3_Color(GXRgb color);
绘制3D多边形
上文也说到了,多边形的绘制,依靠按顺序设置顶点坐标完成
DS的3D引擎支持2种多边形绘制,一种是三角形一种是四边形,每种多边形又有2种绘制模式,一种是分开的绘制方式,一种是连接在一起的绘制方式
如果要绘制线段,可以先将绘制模式设置为三角形,让其中2个顶点重合即可,此外要注意,绘制四边形的时候,不能绘制凹多边形
复制内容到剪贴板
代码:
分开绘制三角形 合并绘制三角形 绘制线段
v0 v2___v4____v6
|\ v3 /|\ |\ /\ v0 v1
| \ /\ v0( | \ | \ / \ ------
|__\ /__\ \|__\|__\/____\ v2
v1 v2 v4 v5 v1 v3 v5 v7
分开绘制四边形 合并绘制四边形 凹多边形
v0__v3 v0__v2____v4 v10__ v0__v3 v4
/ \ v4____v7 / \ |\ _____ / /v11 \/ |\
/ \ | \ / \ | |v6 v8| / /\ v5| \
/______\ |_____\ /______\___|_|_____|/ /__\ /___\
v1 v2 v5 v6 v1 v3 v5 v7 v9 v2 v1 v6 v7
绘制的顺序也体现在这里了
绘制多边形之前,得先设置多边形的绘制模式
引用
复制内容到剪贴板
代码:
4000500h - Cmd 40h - BEGIN_VTXS - 开始设置顶点 (W)
Parameter 1, Bit 0-1 Primitive Type (0..3见下)
Parameter 1, Bit 2-31 无用
参数是0到3,对应的就是分开绘制三角形 分开绘制四边形 合并绘制三角形 合并绘制四边形
SDK内对应的API
复制内容到剪贴板
代码:
void G3_Begin(GXBegin primitive);
设定顶点坐标的I/O寄存器
复制内容到剪贴板
代码:
400048Ch - Cmd 23h - VTX_16 - Set Vertex XYZ Coordinates (W)
4000490h - Cmd 24h - VTX_10 - Set Vertex XYZ Coordinates (W)
4000494h - Cmd 25h - VTX_XY - Set Vertex XY Coordinates (W)
4000498h - Cmd 26h - VTX_XZ - Set Vertex XZ Coordinates (W)
400049Ch - Cmd 27h - VTX_YZ - Set Vertex YZ Coordinates (W)
参数的设定如下,
VTX_16接收2个32bit的参数,其中,第一字节的bit0-bit15存储X坐标,bit16-bit31存储Y坐标,第二字节的bit0-bit15存储Z坐标
它们都是有符号的带12位小数的定点数
VTX_10接收1个32bit的参数,X Y Z各占10bit,分别为有符号的带6为小数的定点数
VTX_XY VTX_XZ VTX_YZ都接收一个32bit参数,X Y Z的数字格式同VTX_16
但只能设置X Y Z中的其中2个,另外一个将沿用最后一次被设置的值,为绘制某些图提供了方便
SDK内对应的API
复制内容到剪贴板
代码:
void G3_Vtx(fx16 x, fx16 y, fx16 z);
void G3_Vtx10(fx16 x, fx16 y, fx16 z);
void G3_VtxXY(fx16 x, fx16 y);
void G3_VtxXZ(fx16 x, fx16 z);
void G3_VtxYZ(fx16 y, fx16 z);
还有一个设置相对顶点坐标的I/O寄存器
复制内容到剪贴板
代码:
40004A0h - Cmd 28h - VTX_DIFF - Set Relative Vertex Coordinates (W)
接收一个32bit的参数,坐标的偏移量X Y Z分别以10bit存储,均为有符号带9位小数的定点数
将把上一次设置的顶点坐标的X Y Z与偏移量的X Y Z相加,得出新的顶点坐标,注意,因为这里设置的顶点坐标最终的结果是有符号带12位小数的定点数
所以3D引擎在计算的时候,会把小数的最后3位补0后再计算,因此,在设置参数的时候,可以先除以8后(即逻辑右移3位)再设置
SDK内对应的API为
复制内容到剪贴板
代码:
void G3_VtxDiff(fx16 x, fx16 y, fx16 z);
最后一个是顶点坐标绘制结束的I/O寄存器
复制内容到剪贴板
代码:
4000504h - Cmd 41h - END_VTXS - End of Vertex List (W)
根据GBATEK的说法,这个接口其实无用,DS硬件跟NO$GBA上这个操作无意义,但在官方的模拟器上似乎有用?(gbatek在这里也打了个问号,汗)
本来嘛,顶点坐标设置好以后,就可以交给3D引擎绘制了,不需要它也可以
SDK内对应的API为
复制内容到剪贴板
代码:
void G3_End();
看到这里,我想有的读者应该有疑问了,这里的顶点坐标的取值范围异常的小(最大的都不超过10),稍微大一点的3D模型都很难绘制
其实不用担心这个问题,除了将大的3D模型分割以外,还有一个方法
因为顶点坐标会乘以当前的position矩阵,换算到世界坐标系中,所以可以不断地修改position矩阵,以适应3D模型的绘制需要
再来个hack实例,通过修改顶点坐标调整空之探险队的对话框
通过跟踪可以发现对话框的顶点坐标都是通过计算间接生成的(也没必要一个个独立设置,因为对话框本来就是比较简单的图形)
所以要修改的话,只需要一步步逆回去,找到影响计算结果的数值即可(这里可能是对话框的宽高)
之前的hack实例没有说得很详细,一些略掉了,这里稍微详细点,也算填充下篇幅
在0x400048C(VTX_16)上下断点,发现它会断在函数sub_1FF8728,没猜错的话,这是对话框绘制函数
在函数一开始的地方改写为bx r14(让它不执行这个函数),发现对话框完全不显示了,判断正确
接下来分析这个函数的参数,这个函数只接收一个参数,是指向一个结构体的指针
在分析的时候,要一步步往C或者C++靠拢,因为基于C或者C++编写的游戏,必定有大量相关的特征
然后观察这个参数的变化,0x224D260 0x224D294 0x224D2C8 0x224D2FC...以一个线性的方式增长,每次加0x34,可以肯定这里存放了结构体数组
也可以初步断定,结构体的大小是0x34,并且这个结构体内定义了顶点坐标
因为我们只是需要修改顶点坐标调整对话框,所以结构体并不需要完全分析出来,当然,如果能完全分析,那可以做更多的hack
如何确认结构体里的顶点坐标呢,很简单,通过分析sub_1FF8728这个函数就行
在函数的开始,可以看到r0的值被赋值到r10上,后面的代码对于结构体的访问都是ldr* r* [r10,*]/str* r* [r10,*]这样的形式
利用这个特点可以很容易分析出结构体的具体结构
先运行到写入顶点坐标的代码
写入第一个顶点坐标的代码在0x1FF88FC上,接下来看一下R0是从哪来的,唔,跟R0相关的运算好多
最靠近的一条是mov r0,r0,r1,lsr 10h,这里把r1跟r0进行了合并,通过0x400048C的定义可以得知,这里的r0是Y坐标,r1是X坐标
先确认R0,盯着R0一步步往上确认,可以看到R5这个寄存器是R0的一部分,接下来看一下R5的来历,这个很好确认
在0x1FF87B0这里,读者要是觉得眼花,不妨把这段代码dump出成文本,用文本工具搜索R5
发现它是来自r2,往上确认r2的来源,是从r10+0x18这个位置上读取的,所以可以判定结构体的0x18处存储一个点的顶点坐标的Y(16位有符号变量)
观察可知结构体的0x16是同一个点的顶点坐标的X,0x1A后面的12字节存储另外3个顶点坐标,接下来要看一下这几个顶点坐标是怎样生成的
下断点然后分析,这个过程我就不细写了
最后会来到0x22A8950,分别存储对话框的坐标与大小,修改后的效果图
嘛,这里插一句,这个对话框的坐标与大小跟显示在屏幕上一致,因此直接搜索也可以,哦呵呵呵呵~
从CVV转换到屏幕坐标
这段差点漏掉,现在我们来考虑一个问题,当一个点的坐标从模型坐标系变换到世界坐标系,再变换到camera坐标系,最后转到长宽高各为2的CVV空间里
首先这个变换过程,利用上篇文章提到的裁剪矩阵来说明就是
(x,y,z,1)*裁剪矩阵
因为裁剪矩阵本身就是position矩阵*投影矩阵的结果
如果记(xx,yy,zz,ww)=(x,y,z,1)*裁剪矩阵
则屏幕上的坐标为
复制内容到剪贴板
代码:
screen_x = (xx+ww)*viewport_width / (2*ww) + viewport_x1
screen_y = (yy+ww)*viewport_height / (2*ww) + viewport_y1
其中
viewport_width=viewport_x2-viewport_x1
viewport_height=viewport_y2-viewport_y1
关于x1 y1 x2 y2,直观点说,就是2个坐标,把ds的屏幕左下角作为原点形成的二维坐标系中的坐标
可以在no$gba debugger的I/O窗口的LCD 3D标签页确认到,老实说我找老半天才找到这个属性
这个公式的推导顺便解说一下
利用比例跟相似多边形的概念,很容易就能推导出来
首先得把坐标的ww转成1,也就是所有的分量除以ww,即(xx/ww,yy/ww,zz/ww,1)
根据比例跟相似多边形的概念,有如下关系式
复制内容到剪贴板
代码:
((xx/ww)-(-1))/2 = (screen_x-viewport_x1)/viewport_width
((yy/ww)-(-1))/2 = (screen_y-viewport_y1)/viewport_height
接着求出screen_x跟screen_y的值就行
[
本帖最后由 enler 于 2013-3-23 20:34 编辑 ]