3d物体变透明 cocos creator 您所在的位置:网站首页 3dmax让物体透明 3d物体变透明 cocos creator

3d物体变透明 cocos creator

#3d物体变透明 cocos creator| 来源: 网络整理| 查看: 265

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的更新,代码如下:

private _updateUBO (view: RenderView) { this._descriptorSet.update(); const root = legacyCC.director.root; const camera = view.camera; const scene = camera.scene!; const mainLight = scene.mainLight; const ambient = this.ambient; const fog = this.fog; const fv = this._globalUBO; const device = this.device; const shadingWidth = Math.floor(device.width); const shadingHeight = Math.floor(device.height); // update UBOGlobal fv[UBOGlobal.TIME_OFFSET] = root.cumulativeTime; fv[UBOGlobal.TIME_OFFSET + 1] = root.frameTime; fv[UBOGlobal.TIME_OFFSET + 2] = legacyCC.director.getTotalFrames(); fv[UBOGlobal.SCREEN_SIZE_OFFSET] = device.width; fv[UBOGlobal.SCREEN_SIZE_OFFSET + 1] = device.height; fv[UBOGlobal.SCREEN_SIZE_OFFSET + 2] = 1.0 / fv[UBOGlobal.SCREEN_SIZE_OFFSET]; fv[UBOGlobal.SCREEN_SIZE_OFFSET + 3] = 1.0 / fv[UBOGlobal.SCREEN_SIZE_OFFSET + 1]; fv[UBOGlobal.SCREEN_SCALE_OFFSET] = camera.width / shadingWidth * this.shadingScale; fv[UBOGlobal.SCREEN_SCALE_OFFSET + 1] = camera.height / shadingHeight * this.shadingScale; fv[UBOGlobal.SCREEN_SCALE_OFFSET + 2] = 1.0 / fv[UBOGlobal.SCREEN_SCALE_OFFSET]; fv[UBOGlobal.SCREEN_SCALE_OFFSET + 3] = 1.0 / fv[UBOGlobal.SCREEN_SCALE_OFFSET + 1]; fv[UBOGlobal.NATIVE_SIZE_OFFSET] = shadingWidth; fv[UBOGlobal.NATIVE_SIZE_OFFSET + 1] = shadingHeight; fv[UBOGlobal.NATIVE_SIZE_OFFSET + 2] = 1.0 / fv[UBOGlobal.NATIVE_SIZE_OFFSET]; fv[UBOGlobal.NATIVE_SIZE_OFFSET + 3] = 1.0 / fv[UBOGlobal.NATIVE_SIZE_OFFSET + 1]; Mat4.toArray(fv, camera.matView, UBOGlobal.MAT_VIEW_OFFSET); Mat4.toArray(fv, camera.node.worldMatrix, UBOGlobal.MAT_VIEW_INV_OFFSET); Mat4.toArray(fv, camera.matProj, UBOGlobal.MAT_PROJ_OFFSET); Mat4.toArray(fv, camera.matProjInv, UBOGlobal.MAT_PROJ_INV_OFFSET); Mat4.toArray(fv, camera.matViewProj, UBOGlobal.MAT_VIEW_PROJ_OFFSET); Mat4.toArray(fv, camera.matViewProjInv, UBOGlobal.MAT_VIEW_PROJ_INV_OFFSET); Vec3.toArray(fv, camera.position, UBOGlobal.CAMERA_POS_OFFSET); let projectionSignY = device.screenSpaceSignY; if (view.window.hasOffScreenAttachments) { projectionSignY *= device.UVSpaceSignY; // need flipping if drawing on render targets } fv[UBOGlobal.CAMERA_POS_OFFSET + 3] = projectionSignY; const exposure = camera.exposure; fv[UBOGlobal.EXPOSURE_OFFSET] = exposure; fv[UBOGlobal.EXPOSURE_OFFSET + 1] = 1.0 / exposure; fv[UBOGlobal.EXPOSURE_OFFSET + 2] = this._isHDR ? 1.0 : 0.0; fv[UBOGlobal.EXPOSURE_OFFSET + 3] = this._fpScale / exposure; if (mainLight) { Vec3.toArray(fv, mainLight.direction, UBOGlobal.MAIN_LIT_DIR_OFFSET); Vec3.toArray(fv, mainLight.color, UBOGlobal.MAIN_LIT_COLOR_OFFSET); if (mainLight.useColorTemperature) { const colorTempRGB = mainLight.colorTemperatureRGB; fv[UBOGlobal.MAIN_LIT_COLOR_OFFSET] *= colorTempRGB.x; fv[UBOGlobal.MAIN_LIT_COLOR_OFFSET + 1] *= colorTempRGB.y; fv[UBOGlobal.MAIN_LIT_COLOR_OFFSET + 2] *= colorTempRGB.z; } if (this._isHDR) { fv[UBOGlobal.MAIN_LIT_COLOR_OFFSET + 3] = mainLight.illuminance * this._fpScale; } else { fv[UBOGlobal.MAIN_LIT_COLOR_OFFSET + 3] = mainLight.illuminance * exposure; } } else { Vec3.toArray(fv, Vec3.UNIT_Z, UBOGlobal.MAIN_LIT_DIR_OFFSET); Vec4.toArray(fv, Vec4.ZERO, UBOGlobal.MAIN_LIT_COLOR_OFFSET); } const skyColor = ambient.colorArray; if (this._isHDR) { skyColor[3] = ambient.skyIllum * this._fpScale; } else { skyColor[3] = ambient.skyIllum * exposure; } fv.set(skyColor, UBOGlobal.AMBIENT_SKY_OFFSET); fv.set(ambient.albedoArray, UBOGlobal.AMBIENT_GROUND_OFFSET); if (fog.enabled) { fv.set(fog.colorArray, UBOGlobal.GLOBAL_FOG_COLOR_OFFSET); fv[UBOGlobal.GLOBAL_FOG_BASE_OFFSET] = fog.fogStart; fv[UBOGlobal.GLOBAL_FOG_BASE_OFFSET + 1] = fog.fogEnd; fv[UBOGlobal.GLOBAL_FOG_BASE_OFFSET + 2] = fog.fogDensity; fv[UBOGlobal.GLOBAL_FOG_ADD_OFFSET] = fog.fogTop; fv[UBOGlobal.GLOBAL_FOG_ADD_OFFSET + 1] = fog.fogRange; fv[UBOGlobal.GLOBAL_FOG_ADD_OFFSET + 2] = fog.fogAtten; }

里面更新了两个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命令的调用。 其它命令的执行看这里:

UI渲染流程

可以看到在构造函数里监听了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 实验室设备网 版权所有