3d 游戏系统的渲染机制是复杂的,这里只是以简洁的方式基于stage3d描述个人对于3d游戏渲染系统设计的看法和实践
游戏系统的性能在游戏系统或产品中占据绝对主导地位,我们的游戏相关的产品除了关注其可玩性,画面,更关注其性能。性能也可以说是前两者得以全面发挥的基石。游戏产品和别的软件产品的一个明显的区别是运算量大,也就是对同一硬件或者环境下,同一时间段内其能量消耗更大,所以我们要尽可能的提升程序本身的性能,实际就是提升能量利用率(好吧,这是另外一个话题,以后再说吧)。提升性能的法则之一,就是尽量少的尽量有效的使用资源,这里讲的资源包含:系统外部静态资源(A),逻辑对象(B),内存/显存资源(C),这三大部分。这里B是关联和管理A与C的核心,也是我们程序系统直接"操作使用"的部分。B这个逻辑对象的产生,运行(运算),销毁,都会消耗能量,也就是会"消耗"系统的性能, 而且这个过程也是直接影响了A和C的性能。所以一个高效的渲染系统,必然有一个高效的程序系统来高效的运作逻辑对象的生命周期。因此,总的来讲,有一个大家都熟悉的总原则:能不创建就不创建,能不运行就不运行,能不销毁就不销毁。但是,但是,在我们的系统运行中,怎么会没有很多的创建和销毁呢?更不可能没有大量的运行!但是,但是,我们还得解决问题,构建力所能及的高效系统。所以,低耦合度便是一种解决问题的策略。低耦合度是让各个子系统,模块,对象,资源之间合理的降低耦合度,甚至如果有需要,在某些地方尽量低的降低耦合度。合理的低耦合度可以达到的作用是: 1.尽量在需要的时候才创建对应的属性,对象,资源等 2.尽量在需要的时候才运行逻辑对象相关的算法 3.尽量在需要的时候才销毁相关的对象活资源做好上面这三条的目的是: 能不创建就不创建,能不运行就不运行,能不销毁就不销毁。哈哈哈,似乎说了废话。但是你的系统是不是做到了这个设计目标?镜头转到 stage3d 身上.用stage3d as3 来设计渲染引擎有无数方式,现存的相关引擎也一大把。在这里就假定从无到有的我在来一遍。个人在这个系统建立的时候把这个系统所要实现的东西分为: 1.属性集合,例如各种将要传递给GPU的buffer,几何数据,动画,色彩,灯光等对象,我认为他们都是一个基本的数据集合,为的是向shader传递参数。这些对象不会和其他资源有直接的关联,这些对象大部分都被显示对象持有,作为显示对象的属性而存在。 2.显示对象,显示对象是被渲染逻辑run()和render()的对象,持有属性集合(但是属性集合不会持有显示对象),显示对象会作为参数传递给shader。 3.shader,我这里讲的shader是stage3d中的用于将各种必须的数据传递给显卡的管理对象,这类对象是渲染的关键。这类对象绝大部分情况下只从显示对象获取纯粹的数据,小部分情况是自带的数据例如某个shader节点会带有贴图数据,当然shader自带的数据只会存在于 shader 的某几个节点上。这里一个完整的shader是由若干个 shader node 组合而成的, 统一组合的shader只会构建一次AGAL代码, 每一个 shader node 自己会决定是否需要使用数据,使用什么样的数据。以上是大体的设计规划。在这个规划里,属性集可以轻易共享或组合,也可以轻易的和目标对象脱离。在循环执行的流程当中,只执行必要的部分,其余的部分只会在创建或销毁的过程执行各一遍。这里以漫反射灯光为例子做一个简要的介绍和展示,来说明上述的规划。在漫反射的情况下,灯光大体分为: 1.定向光(平行光) 2.光强不会发生变化作用距离无限远的点光源 3.光强会发生变化且有作用半径的点光源 4.环境光在我的灯光系统中,若干个不同类型的灯光组成一个灯光组,这个灯光组表示了这个显示对象被哪些灯光共同作用,在一个灯光组中只有一个环境光灯光对象。以下代码创建一个 由三个灯光和一个环境光组成的灯光组 LightGroup对象: public static function createLightGroupB():LightGroup { // var groups:LightGroup = new LightGroup(); // 初始化环境光,为亮度很低的蓝色 groups.ambient.color.setColorEle(0.0, 0.0, 0.3); groups.ambient.strength = 1.0; // 光强会发生变化且有作用半径的点光源 var lightSpot:SpotLight = null; lightSpot = new SpotLight(); lightSpot.radius = 300; lightSpot.strength = 8; lightSpot.color.setColorEle(0.5,0,0); lightSpot.setPosByXYZ(55,-30,55); lightSpot.update(); groups.addLight(lightSpot); // 光强会发生变化且有作用半径的点光源 lightSpot = new SpotLight(); lightSpot.radius = 35; lightSpot.strength = 4; lightSpot.color.setColorEle(0.0,0.5,0); lightSpot.setPosByXYZ(-55,-30,-55); lightSpot.update(); groups.addLight(lightSpot); // 平行光 var lightDirec:DirecLight = null; lightDirec = new DirecLight(); lightDirec.strength = 0.5; lightDirec.setDirecByEndXYZ(100, 100, 100); lightDirec.color.setRGBFromUint(0xff00ff); lightDirec.update(); groups.addLight(lightDirec); groups.update(); return groups; }以下代码是一个显示对象使用上面创建的灯光组: // 创建一个薄板(高度很小的盒子) var cubeM:Cube = createACube(1, 100, "test_cube"); cubeM.lightGroup = createLightGroupB(); // getRandTex()函数的作用是随机获取一张贴图 cubeM.material = ShaderBuilder.createMaterial(getRandTex(), true); cubeM.sy = 0.2; EFRenderLayer.addToRoleLayer(cubeM);请见flash示例(加载贴图可能会有点慢):在这个示例中 中间不动的薄板 就是使用上面创建的灯光组 其他的显示对象包括天空盒使用的都是随机产生的10个点光源和一个环境光组成的一个灯光组 如果看不合适请刷新页面, 每次刷新页面会看到不同的贴图效果 flash支持鼠标左键点击拖动和滚轮操作 此文是抛砖引玉, 期待更多的系统设计思想或规划,敬请多喷。