3d物体变透明 cocos creator | 您所在的位置:网站首页 › 3dmax让物体透明 › 3d物体变透明 cocos creator |
Cocos Creator 3D源码简析
为什么要看源码
了解引擎背后的实现。api文档没有说清楚的地方,就可以直接自己看源码一探究竟了。引擎有bug反馈到论坛可能需要过好几天才能看到pr。自己动手就可以丰衣足食了。引擎的实现是建立在通用的目标之上的,面对一些极端情况下性能会表现不佳。此时源码在手你就可以做一些定制优化了。
Cocos的核心:Node
Node的api文档:https://docs.cocos.com/creator3d/api/zh/classes/scene_graph.node-1.html 现在我们对这些方法进行归纳梳理 https://app.yinxiang.com/shard/s36/nl/30731409/50e284a4-91c3-4332-a645-4727cb2613cb Node提供的能力大致划分成:父子节点层级关系的管理、组件的管理、坐标变换transform、事件的管理和注册能力、其它。 引擎的主循环director.ts 场景结构图 渲染代码 我们来看一下camera.update(); 可以看到这里主要是更新视图矩阵和投影矩阵 接下来看看 camera.scene!.update(stamp); 第一部分光源的更新,可以看到有平行光、点光、聚光灯,基本就是更新这些光源的世界坐标、朝向、包围盒等信息。 第二部分是模型的更新。model.updateTransform(stamp);中更新了变换矩阵和包围盒。 来详细看一下model.updateUBOs(stamp); 可以看到是在更新模型的世界矩阵,但是这里分了两种情况:情况1是模型开启了GPU instance所做的处理。情况2是没有开启,然后这里的两个矩阵会被传到shader的UBO当中。shader中对应如下定义: 关于什么是UBO可以看这里https://learnopengl-cn.readthedocs.io/zh/latest/04 Advanced OpenGL/08 Advanced GLSL/#uniform 接下来终于到了真正的渲染的地方 this._pipeline.render(this._views); 这里就是渲染管线对所有视图进行渲染的地方。其中每一个视图对应一个相机的视野。 下图是原始的renderpipeline的render函数。代码很简洁,渲染管线顺序遍历视图,每个视图里面又顺序遍历渲染流。 forwardpipeline继承renderpipeline,并重写了render函数。可以看到在每一个RenderView渲染执之前都有一个场景剪裁的过程。 好,我们来看看sceneCulling里干了些啥。 export function sceneCulling (pipeline: ForwardPipeline, view: RenderView) { const camera = view.camera; const scene = camera.scene!; const renderObjects = pipeline.renderObjects; roPool.freeArray(renderObjects); renderObjects.length = 0; const shadowObjects = pipeline.shadowObjects; shadowPool.freeArray(shadowObjects); shadowObjects.length = 0; const mainLight = scene.mainLight; const shadows = pipeline.shadows; const shadowSphere = shadows.sphere; shadowSphere.center.set(0.0, 0.0, 0.0); shadowSphere.radius = 0.01; if (mainLight) { mainLight.update(); if (shadows.type === ShadowType.Planar) { shadows.updateDirLight(mainLight); } } if (pipeline.skybox.enabled && pipeline.skybox.model && (camera.clearFlag & SKYBOX_FLAG)) { renderObjects.push(getRenderObject(pipeline.skybox.model, camera)); } const models = scene.models; for (let i = 0; i const vis = view.visibility & Layers.BitMask.UI_2D; if (vis) { if ((model.node && (view.visibility === model.node.layer)) || view.visibility === model.visFlags) { renderObjects.push(getRenderObject(model, camera)); } } else { if (model.node && ((view.visibility & model.node.layer) === model.node.layer) || (view.visibility & model.visFlags)) { // shadow render Object if (model.castShadow) { sphere.mergeAABB(shadowSphere, shadowSphere, model.worldBounds!); shadowObjects.push(getCastShadowRenderObject(model, camera)); } // frustum culling if (model.worldBounds && !intersect.aabb_frustum(model.worldBounds, camera.frustum)) { continue; } renderObjects.push(getRenderObject(model, camera)); } } } } if (shadows.type === ShadowType.Planar) { shadows.updateShadowList(scene, camera.frustum, (camera.visibility & Layers.BitMask.DEFAULT) !== 0); } }里面其实就是在遍历所有的模型,如果模型的包围盒跟当前相机的视锥体相交,就会被丢到renderObjects这个数组里。另外如果模型进行了投影会被丢到shadowObjects数组中。可以看到这个判定是在视锥剪裁之前进行的。这是因为模型本身可能不在视锥体内,但它的投影却可能遮挡视锥体内的模型。在这里也进行了阴影包围球shadowSphere的更新。不过遗憾的是目前v1.2.0版本投影范围并没有根据这个阴影包围球进行投影,而是跟平行光的位置进行了锁定。具体可见如下代码: 现在我们先来捋一下渲染管线、渲染视图、渲染流程之间的关系。 目前引擎只内置了一个渲染管线forward-pipeline,也就是前向渲染管线。 然后前向渲染管线里面有三个渲染流程。 forward-pipeline ShadowFlow 阴影贴图绘制流程ForwardFlow 前向渲染流程。UIFlow UI渲染流程。这里可以看到不仅渲染管线里有渲染流程,渲染视图里也有渲染流程?? 揭开谜底的时候到了 原来渲染视图是有选择的执行渲染管线中的渲染流程!每一个渲染视图对应一个相机,相机分两类:UI相机、3D场景相机。UI相机所对应的渲染视图只有一个渲染流程UIFlow,3D场景相机有两个渲染流程ShadowFlow、ForwardFlow。如下图: 接下来看看渲染流程RenderFlow里干了啥 RenderFlow中的render函数 UIFlow中重写后的render函数 ForwardFlow中重写后的render函数 好吧,可以看到这些flow里面又分了一些阶段。这里应该是为了灵活性和可扩展性。实际上目前为止所有的RenderFlow都只有一个RenderStage:ForwardFlow只有一个ForwardStage、ShadowFlow只有一个ShadowStage、UIFlow只有一个UIStage。 不过在执行flow的这些stage的render之前都进行了渲染管线UBO的更新,代码如下: 里面更新了两个UBO(Uniform Buffer Object):阴影的UBO、全局UBO。对应shader中的定义如下图: 我们继续往下走,看看RenderFlow中的RenderStage是个什么样。 ForwardStage 前向渲染阶段 可以看到几个渲染队列: _batchedQueue 动态合批队列_instancedQueue 开启了GPU Instance的队列_additiveLightQueue 光照相关的队列?不透明物体渲染队列,队列的排序是FRONT_TO_BACK。透明物体渲染队列,排序方式是BACK_TO_FRONT。排序函数的实现如下:![]() 其中depth的计算是根据物体中心点在相机前方向投影的距离,代码如下: 接着看ForwardStage的render部分的实现,代码以及注释如下图: 接下来看一看RenderQueue的recordCommandBuffer做了些什么 加一下注释 public recordCommandBuffer (device: GFXDevice, renderPass: GFXRenderPass, cmdBuff: GFXCommandBuffer) { for (let i = 0; i subModel, passIdx } = this.queue.array[i]; const { inputAssembler, handle: hSubModel } = subModel; const hPass = SubModelPool.get(hSubModel, SubModelView.PASS_0 + passIdx) as PassHandle; const shader = ShaderPool.get(SubModelPool.get(hSubModel, SubModelView.SHADER_0 + passIdx) as ShaderHandle); const pso = PipelineStateManager.getOrCreatePipelineState(device, hPass, shader, renderPass, inputAssembler) // 渲染管线状态的设置,例如:混合模式、模板剪裁、深度测试、正反面的剔除等。 cmdBuff.bindPipelineState(pso); // uniform的更新 cmdBuff.bindDescriptorSet(SetIndex.MATERIAL, DSPool.get(PassPool.get(hPass, PassView.DESCRIPTOR_SET))); cmdBuff.bindDescriptorSet(SetIndex.LOCAL, DSPool.get(SubModelPool.get(hSubModel, SubModelView.DESCRIPTOR_SET))); // 顶点数据装配器的绑定 cmdBuff.bindInputAssembler(inputAssembler); // 绘制命令真正执行的地方 cmdBuff.draw(inputAssembler); } }接来下看一看cmdBuff.draw(inputAssembler); 进入WebGL2CmdFuncDraw 在这里我们才真正看到WebGL命令的调用。 其它命令的执行看这里: 可以看到在构造函数里监听了Director.EVENT_BEFORE_DRAW 可以看到事件是在渲染管线开始渲染之前发出的。接来下看看update里的实现 可以看到_renderScreens()其实就是遍历所有的画布,再递归遍历画布所有子节点并调用子节点的渲染组件的updateAssembler。 接着看render.updateAssembler(this);这里以sprite为render继续解析后续的流程。 autoMergeBatches 最后剩下的this.render(); 可以看到:UI里的每一个批次都会被构造成一个只会被UI相机看到的模型Model放到场景中。而UI的自动合批无非就是将相同渲染条件的顶点数据集中放到一个buff中,注意这里填充的顶点坐标需要是世界坐标系下。 更多文章 个人博客: https://blog.csdn.net/u014560101 公众号: |
CopyRight 2018-2019 实验室设备网 版权所有 |