|
Stencil Buffer可用作计数器和分支逻辑器. DepthBuffer可用作大小比较器 GL_EYE_PLANE可以执行当前ModelView矩阵的逆矩阵,并保存下来使用.
右键点击目标点 1.无论人物行进方向如何变化,镜头方向不变,并保持对准人物. 2.人物行进方向变化时,保持镜头锁定到人物后方,呈第3人称视角. 3.智能方式,人物行进方向变化时,镜头适时地缓慢地变换到第3人称视角. 4.点击行进不进行寻路,直线行走,遇障碍停下.
我的blog里有许多此类笔记,主要是记录写程序时犯下的错误.有相当多的错误,我都是一犯再犯,而且每次都因这种小错耽误时间而骂自己是猪,所以很有必要记录下来,以备查找.作程序的要懂得积累经验,并书面化,抽象化.
渲染API容易出错,根源于其本质是个状态机,作一个渲染时,几十上百个状态都必须设置正确,才能得到正确的图像,这就很容易出点小差错. 1.GLSL的vec4有3种分量形式xyzw/rgba/stpq, 注意到的r分量是指颜色r分量, 习惯上的纹理坐标分量strq的r由于和颜色r冲突,改作p.
2.Global Amibient也要乘以材质Material.
3.HLSL没有shadow2DProj函数, GLSL的shadow2DProj返回的是深度比较的结果1.0或0.0的4元组,不是深度值!且要记住,shadow2DProj受到固定流水中纹理GL_TEXTURE_COMPARE_MODE/GL_TEXTURE_COMPARE_FUNC的影响,要使用shadow2DProj必须打开深度纹理比较模式.
4.HLSL允许如vec4向下强转为vec3这种隐式转换. GLSL语法上不允许,但实践上N卡出warning仍可以编译运行,A卡直接报错无法运行.
5.DirectX的shader调试状态和硬件实际运行状态的结果有很大区别,不能全信.
6.设置 glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); 在shader中, A卡的glFrontMaterial.diffuse不会跟踪设置为当前顶点色,而N卡正确.
7.GLSL: N卡可以使用vec3 temp = { 1.0, 0.0, 1.0}; 这种形式的构造, A卡报错. HLSL: 都可以使用float3 temp = { 1.0, 0.0, 1.0};形式.
8.HLSL的mul接受mul(vec, matrix)或mul(matrix, vec),要注意通常HLSL要依DirectX计算(V * M)使用mul(vec, matrix)的形式. 特别需要小心的是,vec如果是float3,前后行列不等,违反HLSL规范,但shader编译也不报错,直接当成float4(vec, 0)处理,而不是当成float4(vec, 1).即mul(float3, matrix)中的float3被当成向量,而不是顶点.
9.不要在一行中写过于复杂的表达式, N卡没问题, A卡很烂,通常都编译不过.例如: http://www.ozone3d.net/blogs/lab/?p=38
10.GLSL可以只有PixelShader, 没有Vertex Shader, HLSL不支持. vs_3_0 shader executed in hardware vertex processing mode can only be paired with at least a ps_3_0 shader. 11.GLSL的1.2版本开始支持mat4x3这种non-square矩阵,要注意4x3是4列3行,而不是4行3列. References: http://jegx.ozone3d.net/index.php?entry=entry070529-094845
Shadow Map实现测试,RenderCraft图形底层, 兼容OpenGL和DirectX两种实现,其中OpenGL可以使用固定管线或Shader兼容实现. DirectX的ShadowMap要比OpenGL晦涩而艰难的多,特别是Nvidia和ATI对Depth Texture的实现内部实现不统一. Shadow Map是个丑陋的技术方案. 最近写些Demo,放上来,主要是为了测试我的RenderCraft 3D渲染底层的对各机器的兼容性,以及它们在OpenGL和DirectX渲染是否一致, 在固定流水线和可编程流水线间的兼容性等等, 这样我到每处有计算机的地方都下载下来运行测试一下. 每个Demo先写OpenGL的实现,同时设计好接口,因为基础图形学算法是一致的,移植到DirectX就是实现一下DX接口或相应的shader. http://www.cnitblog.com/Files/linghuye/ShadowMapDemo.rar
投影矩阵(Project Matrix)本意是将不规则的视图体(View Volume)变换成一个单位立方体,但投射到屏幕时,Z被丢弃了,所以造成了2D投影的假象.注意这是错觉,实际概念应该是投射到单位立方体空间,是空间投射的概念.
需要注意的是,OpenGL最终投射到单位立方体[-1,-1,-1], [1, 1, 1],而DirectX则不是,它的投影矩阵将Z投射到[0, 1].
丢弃掉Z后,就得到[-1,-1],[1,1]的平面投射范围,为计算投射到的窗口屏幕坐标,将X,Y的值域,乘0.5,加0.5,映射到[0,1]值域,再分别乘窗口屏幕宽或高,才得到最终的窗口屏幕坐标.
经过OpenGL或DirectX的投射矩阵变换后的顶点,称作在Clip Space空间内,即单位立方体空间内. 为得到最终屏幕窗口坐标,将上述变换使用矩阵表达为: 0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 1 为得到OpenGL的投射立方体的矩阵为: 0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1 为得到DirectX的投射立方体的矩阵为: 0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 1
投影矩阵前的顶点w总是为1, 正射投影矩阵后的顶点w还是为1, 但透视投影矩阵后的顶点w开始不为1.
阴影体实现测试,RenderCraft图形底层, 兼容OpenGL和DirectX两种实现.
1.算法的本质是如何判断一个像素点代表的3D空间点是否在一个多面体内,用的是经典的直线穿越次数判断算法,即判断一个像素3D空间点是否在阴影体内,在于判断从视点到该点的连线穿越阴影体面的次数,算法映射到硬件模拟上,利用Stencil Buffer作计数器. 2.要保证算法的Robust,肯定要用ZFail算法,ZPass是靠不住的.使用shader实现后zpass优化也是没有必要的. 3.兼容支持Two side stencil的显卡,Nvidia是GL_EXT_stencil_two_side扩展, ATI是GL_ATI_separate_stencil扩展. 4.调试中最容易出现的问题是,出现半个阴影区,或阴影交叠处没有阴影,这是由于Shadow Volume没有封住Cap导致. 5.封Cap的问题: zfail只需要封住远平面.
对于shadow volume的frontface(既面对视点的这一面),如果depth test 的结果是pass,说明shadow volume的该front face是在场景物体之前,视线要到达场景物体必然需要穿过该face. 反之如果depth test 的结果是fail,说明shadow volume的该front face是在场景物体之后.
而对于shadow volume的back face(远离视点的一侧),如果depth test的结果是pass,说明shadow volume的该back face是在场景物体之前,视线要到达场景物体必然需要穿过该face. 反之如果depth test 的结果是fail,说明shadow volume的该back face是在场景物体之后.
The rendering of a vector (w = 0.0) in clip space would be semi-infinite.
http://www.cnitblog.com/Files/linghuye/ShadowVolumeDemo.rar
Reference: ShaderX2
本Demo测试RenderCraft图形底层对OpenGL和DirectX的兼容性 OpenGL可以使用固定流水线和Shader两种方式渲染. DirectX下只能用Shader渲染方式.
纹理投射注意点: 1.基本概念为使用投射纹理作第2遍渲染,利用纹理坐标自动生成机制(或Shader), 使用投射矩阵从顶点坐标计算投射纹理坐标. 2.注意投射纹理必须使用CLAMP模式,并且不能使用MIPMAP,否则出现边框纹理带Bug. 3.固定流水线不能直接解决投射纹理背向作用问题,使用Shader可以判断w值正负解决.
http://www.cnitblog.com/Files/linghuye/ProjectTextureDemo.rar
1.镜头不对: 物体不在镜头范围内,检查视图矩阵,世界矩阵,投影矩阵. 2.颜色全黑: 打开光照情况下,MATERIAL全为0, 或,在没有打开光照情况下,颜色值为0,造成全黑.检查当前Material和顶点颜色和纹理颜色. 3.深度不对: 被已有的深度遮住了.检查当前深度,直接在此渲染前加Clear深度为1.0作测试. 4.CullMode设置不对: 所有三角形被当作背面剔除了,设为CULL_NONE测试. 5.3D物件的纹理坐标统统大于1.0,且此时纹理寻址方式为BORDER, 且BORDER被设为黑色,画面全黑. 6.深度被Clear为0.0,且打开深度测试,绝对什么都画不上,上述3的特例. 7.COLORWRITE被DISABLE,一级白痴错误. 8.没有设置或没有正确设置Viewport,常发生于OpenGL. 9.OpenGL,使用DrawArray或glDrawElements时,没有打开glEnableClientState(GL_VERTEX_ARRAY),此时渲染语句失败,但没有任何错误返回. 10.DirectX使用XYZRHW顶点模式渲染2D图像时,顶点Z值取0且没有关闭ZBUFFERWRITE,导致情况6. 11.BeginScene和EndScene中崩溃,但被全局异常函数捕捉到,但异常处理函数没有EndScene,导致EndScene永远不会被调用,结果BeginScene也失败,没有调用BeginScene,没有画面,画面都没更新. 12.物件太小,而世界坐标体系太大,如设置平面模式glOrtho(0, 1024, 768, 0, 0, 1024); 而物体的坐标都在1.0f以下,物体就太小看不见了. 13.物体被ZNear和ZFar裁剪掉了,如glOrtho(0, cx, cy, 0, 0, 1); 在离镜头1单位之外的物体全不可见.
对同一个连接连续调用多次WSASend是安全的,不需要等到前一个WSASend的操作完成,数据将按调用WSASend的调用顺序发送(当然,前提是没有人笨到用多线程对一个同一个连接连续调用多次WSASend).
但对同一个连接连续调用多次WSASend是一种不稳定的方案,因为可能出现WSAENOBUFS错误(实现总是如此丑陋),对于服务器而言,可能出现就是必然爆发. 由于没有机制可以量化多少个WSASend之后才会出现该错误,所以只能等出现了错误,再做错误处理,这样的逻辑很危险.
实践证明,多个WSASend不会提高发送速率,但会提高CPU占用率和WSAENOBUFS出现的几率,因为每个WSASend都要有一个完成处理流程,得不断地查问那么多的WSASend完成了没有,并作出处理.
对一个连接保持同时只有一个WSASend在运作,发送速率不会慢. 因为服务器不是服务于一个socket,而是服务于上千个socket,不要把IOCP下单个socket的得失看得太重.
一个连接保持同时只有一个WSASend是没错的,当然,也不要像我一样笨到每次WSASend只发送十几个字节数据,然后去责怪单次WSASend的低能,要知道WSASend接收的是一个缓冲区数组. 可以但不能浪费,不要走极端.
当SO_SNDBUF不为0, WSASend的数据被拷贝到底层缓冲区(AFD.sys)等待发送,出现WSAENOBUFS表示缓冲区不足. 当SO_SNDBUF为0, WSASend的数据被内核直接锁定,不可交换页面,这种资源是稀缺的,出现WSAENOBUFS表示物理页面数不足.
线程是个好东西,但越少越好.IOCP已经开了CPU*2个线程.
理论很完美,实现却要面对丑陋的现实.以上结论仅供参考.
1.当处理数据文件时,一般总是写比读来得简单,因为写文件时我们有完全的控制,读时却不能确保碰上什么样的数据,我们需要保证健壮的程序能优雅地处理各种各样的错误数据.
2. Entry { title = "Tecgraf", org = "Computer Graphics Technology Group, PUC-Rio", url = "http://www.tecgraf.puc-rio.br/", contact = "Waldemar Celes", }; 语法含义是Entry函数调用,传入参数为指定的table.
3.a.Metatables allow us to change the behavior of a table. b.Each table in Lua may have its own metatable. c.Lua always create new tables without metatables. d.use setmetatable to set or change the metatable of any table e.Any table can be the metatable of any other table. f.A group of related tables may share a common meta. g.A table can be its own metatable. h.__add: + __mul: * __sub: - __div: / __unm: 取负 __pow: 次方 __concat: .. __eq: equality __lt: less than __le: less or equal __tostring __metatable __index __newindex
i:If the first value has a metatable with an __add field, Lua uses this value as the metamethod, independently of the second value; (2) otherwise, if the second value has a metatable with an __add field, Lua uses this value as the metamethod; (3) otherwise, Lua raises an error. 4.将light user data作为指针使用,并在脚本环境中维护一个以lightuserdata为key的table,value为对应的lua对象,则有,key为C++对象,value为lua对象. 5.userdata本质为一段内存区,在脚本中可作为单一参数进行传递.可为之定义脚本函数以操作该内存,一簇的函数形成一个对象概念,一个内存对象概念. 6.userdata可以有metatable,意味着其可以完成完整的C++对象语义. 检查userdata的metatable(luaL_checkudata)以确保该userdata是所期望的内存对象. 7.Remember that a:size() is equivalent to a.size(a) . Therefore, we have to arrange for the expression a.size to return our getsize function. The key mechanism here is the __index metamethod. For tables, this metamethod is called whenever Lua cannot find a value for a given key. For userdata, it is called in every access, because userdata have no keys at all. 8.设置metatable的__index为get方法,__newindex设为set方法,以实现[]的语法语义. 9.设置metatable的__index为metatable自身,并定义get,set,size函数,以实现C++对象语法语义
|