|
3.20-3.31
1.阅读Torque,Ogre,OpenSceneGraph相应部分的源码,并作出比较和深入学习。 2.再次阅读RealtimeRendering,3D Game Engine Design相应部分。 3.www.openscenegraph.org,www.opensg.org,www.panda3d.etc.cmu.edu
3月底前完成通读RealtimeRendering的着色部分。
3.31 今天很高兴,Karsten Hiddemann告诉了我一项资源,让我的知识得到了极大的提升.
3.31-4.9 编写完成(8*8)*(16*16)*9的室外场景渲染,使用四叉树+ROAM多层纹理地形渲染,时候变化的天空渲染,基本水面渲染,数百个分布的Doodad物体模型渲染. 内存占用150M+,帧率<10,自此开始全面的优化工作,为期1周。
然后开始加入WMO室内场景,重构现有的SceneGraph,暴力法加入,然后开始1个月的优化和整个引擎的重构。
1.一套Skeleton由若干Bone组成,在数据存储上通常表现为Bone结构数组. 2.一个Bone表达一个空间Transform概念,在代码上本质归结为一个Transform Matrix. 3.一个Bone有两种概念上的变换: 1.自身的变换矩阵 2.与父骨骼整合后的变换矩阵. 4.一个Bone在数据文件中通常存储的是自身的变换矩阵,存储x,y,z的Translation,四元数Rotation,及x,y,z的Scale(或直接一个4*4矩阵). 5.一个Bone存储的是多个变换矩阵,对应多个关键帧. 6.某个动画序列给出在该动画中每个骨骼从哪一帧变换到哪一帧,以及变换的插值法(线性,Hermit,Bezier,Spline). 7.Bone数组之间呈N树关系,由唯一的root(ID=-1)向下遍历所有子节点(Bone结构标出自己的父节点ID). 8.每个骨骼对附属于它(与之关联)的模型顶点施加空间变换. 9.对模型顶点进行分组,一个组内的所有顶点附属于(受影响于)某个或某些Bone. 10.每个骨骼施加的空间变换是其自身空间变换与其父骨骼空间变换的累积,而其父骨骼的空间变换以次向上类推. 11.换而言之,即施加的空间变换为分支向上所有节点的自身距阵的乘积. 12.以数学标准而言 integrated = parentMatrix * matSelf; vOut = integrated * vIn(DirectX反数学标准). 13.骨骼相连处节点出现权重现象概念,即一个点同时受多个骨骼影响,此影响有权重数,都在8之顶点分组中定义. 14.在一个动画时刻,设帧N,向每个骨骼询问在帧N时的变换矩阵,每个骨骼推算帧N的前后相邻的关键帧,取出变换数据,作数学变换插值,得出变换矩阵.对受该骨骼影响的每个点施加该矩阵影响,得出此刻该点的位置.
Sample: // 泛化骨骼类型,假设仅有骨骼数据与应用相关. template<class bone_type> class CSkeleton { public: CSkeleton() { }
~CSkeleton() { }
public: void AddBone(bone_type* pBone) { m_vecBones.push_back(pBone); }
void FrameSkeletonTransformMatrixs(int nCurFrame) { matrix_type rootMat; rootMat.set_identity();
//从骨骼系统的根部开始递归算出各骨骼的变换距阵. const int rootID = -1; FrameChildBonesTransformMatrixs(rootMat, rootID, nCurFrame); }
matrix_type& GetSkeletonTransformMatrix(int nBoneID) { return m_vecBones[nBoneID]->IntegratedTransformMatrix(); }
protected: void FrameChildBonesTransformMatrixs(const matrix_type& parentMat, int nParentID, nCurFrame) { int nBonesCount = m_vecBones.size(); for(int id = 0; id < nBonesCount; id++) { //找出nParentID的所有子节点骨骼 bone_type* bone = m_vecBones[id]; if(bone->ParentID() == nParentID) { //计算骨骼的在分支上的完整的变换距阵. CalcuBoneTransformMatrix(bone, parentMat, nFrame); //递归 FrameChildBonesTransformMatrixs(bone->IntegratedTransformMatrix(), bone->BoneID(), nCurFrame); } } }
void CalcuBoneTransformMatrix(bone_type* bone, const matrix_type& parentMatrix, int nFrame) { matrix_type& mat = bone->IntegratedTransformMatrix();
//计算取出bone自有的变换距阵数据. bone->CalcuBoneOwnTransformMatrix(m_nCurFrame, mat);
//与父骨骼变换距阵累积. mat = parentMatrix * mat; }
public: typedef std::vector<bone_type*> CBoneVector; CBoneVector m_vecBones; };
/************************************************* ** 与CSkeleton合作的Bone所需实现的语义接口. *************************************************/ class CBoneBaseImpl { public: matrix_type m_transformMatrix;
public: CBoneBaseImpl() { }
~CBoneBaseImpl() { }
public: matrix_type& IntegratedTransformMatrix() { return m_transformMatrix; }
bool CalcuBoneOwnTransformMatrix(matrix_type& matRes) { }
int ParentID() { }
int BoneID() { } }; |
粒子系统是一种使用现有3D图象技术描绘某些自然复杂现象的机制.
粒子系统用于描绘如火焰,烟尘,水滴等此类随时间动态变化的由无数个并行的微小物质构成的复杂的自然物质. 注意其只是一个概念上的总称,一般的解决思路,并不是什么特别的技术.
其基本思路无非是将每个组成粒子描述为一个结构,并拥有若干具体自然现象中的物理参数变量,随时间的推移依照一定的物理公式定律进行参数演变,并将之描绘出来形成一个总体的画面(同时注意效率和内存消耗).
其参数主要为外观参数和行为参数,通常包括大小,颜色,速度,起始位置,寿命等.
其本质在于对物理模型的推演和自然现象的数学表达.
在概念上有个产生发射粒子的对象,而对象在经过一定周期后会自我消亡.
粒子系统的呈现目标是审美上的,无须严格遵循物理法则.
1.没有glDisable(GL_TEXTURE_2D),导致基本几何作图全部失败。 2.镜头位置没调好,导致所有物体不可见. 3.物体位置数据错误,导致物体大大超出镜头显示范围. 4.纹理没有GL_CLAMP导致边界拼接出现缝线. 5.纹理没有GL_REPEAT导致使用超出1.0纹理坐标技巧失败,显示单色插值. 6.没有glEnable(GL_TEXTURE_2D)导致全白纹理. 7.glEnable(GL_TEXTURE_2D)后没有使用纹理顶点导致如glColor效果的纯单色. 8.应该在作图循环设置灯光位置,因为The position is transformed by the modelview matrix when glLight is called (just as if it were a point), and it is stored in eye coordinates. 9.灯光位置应该是4个float,3个float程序不出错,但结果完全扭曲。 10.打开灯光却没设置顶点法线,到头一场空。 11.Window Class 没有CS_OWNDC,导致glDrawPixels失败,如在子窗口View上进行象素写入操作. 12.没有设置PIXELFORMATDESCRIPTOR的cAlphaBits,导致glReadPixels读取alpha通道数据失败,统统为0. 13.手动实现的多层纹理闪烁,出现线带,没有使用glPolygonOffset. 14.glDrawPixels格式GL_RGB或GL_BGR_EXT的数据时崩溃,且数据有效,没有使用glPixelStorei(GL_UNPACK_ALIGNMENT, 1);导致图像行默认4字节对齐,导致OpenGL数据读取越界崩溃.相应的要为glReadPixels设置glPixelStorei(GL_PACK_ALIGNMENT, 1); 15.glReadPixel读取被覆盖的OpenGL窗口部分的图像数据时,读出数据混乱无效,OpenGL Pixel Ownership检测导致,标准如此.使用PixelBuffer,或强制置前OpenGL窗口解决. 16.glReadPixels读取总是从FrameBuffer的左下往右上读取,毫无参数可设置改变方向,真是笨到家了. 载入图像一般还是从上到下比较容易理解. 17.使用glDepthMask(GL_FALSE)后不打开,glClear无法清除深度缓冲,导致现象:场景起始静止正常,一旦移动出现不规则的透明无渲染区域,深度缓存逻辑混乱. 18. gluPerspective(45.0f, (GLfloat)cxPixels/(GLfloat)cyPixels, 0.1f, 2000.0f);这里的0.1f将导致物体和地面的相交处闪烁,原因待查,至少取1.0f. 19.没有显示定义shiniess或shiniess为0,在shader中对gl_FrontMaterial.shininess使用pow计算,结果不可测,黑色条带闪烁,quite not understand. 20.纹理出现白色点状闪烁,没有Mipmap过滤导致! 21.任何初装的Windows系统,多好的机器,一开始对OpenGL只支持1.1,连多重纹理都没有,需要警惕这种机器,装着DX9,微软真恶心,::SwapBuffer直接崩溃,故需要检测显卡至少支持了1.2标准,否则要求更新显卡驱动程序. 22.3D和2D,透视和正射模式混合时,2D模式的渲染覆盖不了3D的渲染,原因深度测试失败, a.2D下渲染时所取点深度确实大于3D点的深度. b.glOrtho参数错误,导致2D渲染所得深度值错误,如zNear被取负数! c.使用glDisable(GL_DEPTH_TEST); 23.glDrawElements崩溃, a.glXXXPointer设置的缓冲错误,顶点索引越界引用顶点数组. b.当前glBindBufferARB(GL_ARRAY_BUFFER_ARB, buffer);硬件缓冲和内存缓冲混淆. 24.画面上出现方块形背景色,原因,渲染一次后,在glClear时glDepthMask没有打开,glClear没能clear掉depth,但clear掉颜色缓冲,depth值持续到下一次render,导致depth test没通过,颜色画不上. 25.树叶挡在物体前,树叶间隙或蛀洞本应该透明却呈现背景色.原因,没有启用Alpha过滤.一般不可能用几何形状勾勒出叶子的形状,只用较大的多边形包围叶子,用Alpha过滤掉空隙.此时不启用Alpha过滤,出现Alpha为0的三角面也被渲染,但得到的是背景图像,并且由于Alpha为0的三角面离视点近,后面的物体渲染无法通过深度测试补上透明的空隙区域. 26.atlgdi.h里有#pragma comment(lib, "opengl32.lib"),导致无源无故链接opengl32.lib失败.
http://realtimerendering.comhttp://www.geimetrictools.comhttp://astronomy.swin.edu.au/~pbourke/ http://www.opengl.org/ http://www.gamasutra.com/php-bin/article_display.php http://www.blizzhackers.com/index.php
(M*N)^T = (N^T) * (M^T) (M*N)^-1 = (N^-1) * (M^-1) (M^-1)^T = (M^T)^-1
E(h,p,r) = Rz(r)Rx(p)Ry(h)
orthonormal vectors:正交单位向量组 trace:矩阵主对角线元素之和 adjoint:伴随矩阵
1.右手规则: 以右手握住Z轴,当右手的4个手指(最下面的一节)从正向X轴以PI/2角度转向正向Y轴时(即手掌与拳面形成的直角),大拇指的的指向就是Z轴的正向.
2.无论相对左手坐标系DirectX或右手坐标系OpenGL,空间中某点作同种几何变换,结果值相等.即几何变换结果与坐标系统无关,几何变换的描述也与坐标系统无关.数学标准为右手坐标系.
3.无论DirectX的行主模式或OpenGL的列主模式(平移阵列处于行上还是列上),一个变换矩阵表达的变换是数学语义的,与距阵表达方式无关,用DirectX或OpenGL距阵作变换,结果值一定相等.即行主或列主矩阵描述同一个几何变换,则矩阵的数学语义必然相同.矩阵的数学语义以列主为标准,即平移阵列位于最后一列.
4.矩阵相乘算规则是:用左矩阵的每一行(第i行)的元素去依次乘以右矩阵的第一列(第j列)的对应元素,得到(i,j)元素.在遍历左矩阵每一行中,遍历右矩阵每一列。即左阵出行,右阵出列。 故,在OpenGL列主矩阵下,点是列向量表达,空间变换为M*V 在DirectX行主矩阵下,点是行向量表达,空间变换为V*M 当作空间连续变换时,OpenGL中最后调用的空间变换函数才是最先起作用的,DirectX中反之.
5.变换先作用于或后作用于顶点是客观观念,左乘右乘是实现手段,OpenGL右乘为先作用,DX左乘为先作用.
6.在顶点作用乘法式中,离顶点近的矩阵在世界坐标系上先作用于顶点,离顶点远的矩阵在自身坐标系上先作用于顶点. pre,post均相对顶点而言,以顶点为参照点.左手坐标系,顶点在左,右手坐标系,顶点在右.
7.Sample quaternion_type quat; quat.set_axis_angle(vec3_type(8.0f, -11.2f, -231.0f), 0.271f); quat.normalize4();
vec3_type vRc(23.3f, 17.4f, 43.9f); vec3_type vTrans(10.0f, 10.0f, 10.0f);
D3DXVECTOR3 vOut1(11.0f, 45.0f, 99.0f); vec3_type vOut2(11.0f, 45.0f, 99.0f);
// DirectX Transform /////////////////////////////////////////////////////////////////////////////// D3DXMATRIX mat; D3DXMatrixTransformation(&mat, NULL, NULL, NULL, (D3DXVECTOR3*)&vRc, (D3DXQUATERNION*)(&quat), (D3DXVECTOR3*)&vTrans);
D3DXVec3TransformCoord(&vOut1, &vOut1, &mat);
std::cout << vOut1.x << ", " << vOut1.y << ", " << vOut1.z << std::endl;
// Math Transform /////////////////////////////////////////////////////////////////////////////////////// matrix_type matRes, matRotate, matTrans1, matTrans2, matTrans3; matRotate.makeRotate(quat); matTrans1.makeTranslate(-vRc.x, -vRc.y, -vRc.z); matTrans2.makeTranslate(vRc); matTrans3.makeTranslate(vTrans);
matRes = matTrans3 * matTrans2 * matRotate * matTrans1; matRes.transformVector(vOut2);
std::cout << vOut2.x << ", " << vOut2.y << ", " << vOut2.z << std::endl;
matrix_type proj, modl; // 标准数学矩阵 glGetFloatv(GL_MODELVIEW_MATRIX, modl); glGetFloatv(GL_PROJECTION_MATRIX, proj); clip = modl * proj; // Note: (M * N)^T = (N^T) * (M^T) clip.transpose(); // OpenGL数组内存存储方式为列主,需要转置,得proj * modl最终矩阵.
// v1,v2为原始3D点 vec3_type v1(5.0f, 12.5f, -1.0f); vec3_type v2(33.0f, 41.0f, -1024.0f);
// v3,v4为经过终极矩阵变换后的齐次坐标 vec4_type v3 = clip.transformVertex4(v1); vec4_type v4 = clip.transformVertex4(v2);
// 作齐次计算后得: q(x/w, y/w, z/w),值域均为[-1, 1],canonical view colume. // 若-1.0f为近平面则,必有z/w = -1.0f, 若-1024.0为远平面则必有z/w = 1.0f,即near_z = -1.0f, far_z = 1.0f // 结果z轴与原有OpenGL的z轴方向相逆,又OpenGL以正值输入near,far.
// 将q直接映射到viewport屏幕象素上为 PixelX = (q.x + 1.0f) * viewport_width * 0.5f; PixelY = (q.y + 1.0f) * viewport_height * 0.5f;
书之理论,未经编码实不可信! Don't show me the theory, show me your code!
1.形式上都是2进制代码. 2.应用接口编程思想,三种形式进行接口交互,三种形式可以任意变化,最终产生不同形式组合的程序,Blizzard只有一个主程序,但开发一定是lib接口化. 3.使用预定义组成一个源码 #ifndef _LIB BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; }
extern "C" __declspec(dllexport) IUICraft* UICraftCreate() { return new CUICraft; } #else // Static Lib IUICraft* UICraftCreate() { return new CUICraft; } #endif//_LIB 对于动态DLL,采用动态加载,静态Lib使用extern手段,但严格遵循接口编程。 4.一个动态DLL可以同时向外展现内部C++ dllexport类, C++接口,COM组件对象以交给不同的最终用户使用。
1.使用多线程DLL运行时库,需要链接msvcr71.dll和msvcp71.dll,这两个文件合计828K,且需要随同发布. 2.但使用多线程静态运行时库链接,每个生成的可执行文件多出50K左右. 3.将原来的DLL功能模块静态联编,使得既不需要链接DLL运行时库,而静态联编的文件大小又最小. 4.A静态库关联B静态库编译时,并不链接B的实现,而由最终的执行程序编译链接B实现,即若执行程序C要链接A,就也必须手动链接B.但使用工程依赖设置可以避免这问题. 5.VC工程中库链接 a.在工程中加入lib文件 b.使用#pragma comment(lib,)指令 c.使用工程设置关联lib选项.
1.使用刚被删除的对象指针,由于该块内存仍然有效,不会发生页访问错误,程序继续正常运行,直到有对象分配到该块内存,错误爆发。使用构造分配内存,析构释放内存的类对象,容易有此问题,应避免该做法。特别,若内存处有stl容器,有可能会破坏整个stl的分配器,导致所有stl容器发生不可预测逻辑的行为。
2.VC SP5的MFCO42.dll与原VC版本有重大差别,使用SP5和不使用SP5的程序DLL若涉及该DLL,程序发生不可预测逻辑行为。
3.用新版本DLL替换旧版本DLL,出错。推论:新版本有错。 该推论不成立。客户端程序的隐藏错误可能影响DLL内对象的运行,其在旧DLL不会发作,而新DLL二进制Image变化,内存分配变动, 使得隐藏错误发挥效果,程序崩溃。
4.程序出错后,无正常逻辑可言,应先予排除所有次要或有干扰的原因,最后跟踪主要原因。
5.在调试时需要被try catch的异常崩出,Debug->Exceptiion->Stop Alway.
6.Release版,客户机运行崩溃。出call map,记下崩溃地址,从map中查找。
7.void Test() { try{ int* p = NULL; *p = 10; } catch(...) { printf("Error in Test"); } }
int main() { try { Test(); } catch(...) { printf("Error in main"); } }
Debug版Test出错,Release版main出错,即try catch不能有效地防止空指针崩溃.
8.pragma pack(push, 1)没有pop,再写一个pragma pack(push, 1),编译期结构对齐将完全混乱,得出的运行期的地址将和编译期推算完全不符,轻则疯狂,重则吐血。
9.全局模板函数,使用策略式参数,编译出的函数,永远都是最后一个被实例化的函数,而非根据模板参数的函数,吐血问候写编译器的那位。 struct A { void test() { int f = 0; } };
struct B { void test() { double g = 0.1; } };
template<typename mytype> void Test() { mytype k; k.test(); }
int main(int argc, char* argv[]) { Test<A>(); Test<B>(); printf("Hello World!\n"); return 0; } 调用的都是B::test() 10.换行的文本在Watch中显示没有换行,在tip中换行,导致判断错误. 11.编辑器换行符\后不能有tab制表符,负责文本解析混乱出错.
|