前言

UE4开发中, 通常用到的MeshStaticMesh,SkeletalMesh,ProceduralMesh等等, 他们对应都有相应的渲染组件如UStaticMeshComponent, UProceduralMeshComponent, 本质上这些Mesh组件都继承了UPrimitiveComponent, UPrimitiveComponent通过FPrimitiveSceneProxy渲染代理负责将特定的Mesh的渲染数据(VertexBuffer, IndexBuffer, Material)从游戏线程送往渲染线程. 有时候为了定制某种特殊的Mesh渲染, 我们得自定义新的PrimitiveComponent。(当然UProceduralMeshComponent往往满足了定制新的Mesh需求, 但有时候为了进一步的性能或者进行特殊的MeshPass得定制PrimitiveComponent)。

自定义金字塔 PrimitiveComponent

下面我就以一个八面体为例子介绍自定义PrimitiveComponent

创建渲染代理

创建的渲染代理包含VertexBuffer, IndexBuffer, Material相关数据

class FPyramidSceneProxy final : public FPrimitiveSceneProxy
{
public:SIZE_T GetTypeHash() const override{static size_t UniquePointer;return reinterpret_cast<size_t>(&UniquePointer);}FPyramidSceneProxy(const UPyramidComponent* InComponent);virtual ~FPyramidSceneProxy(){VertexBuffers.PositionVertexBuffer.ReleaseResource();VertexBuffers.StaticMeshVertexBuffer.ReleaseResource();VertexBuffers.ColorVertexBuffer.ReleaseResource();VertexFactory.ReleaseResource();IndexBuffer.ReleaseResource();}virtual void GetDynamicMeshElements(const TArray<const FSceneView *>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const override;virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const;void BuildPyramidMesh(const FPyramidMesh* PyramidMeshData, TArray<FDynamicMeshVertex>& OutVertices, TArray<uint32>& OutIndices);void SetMeshData_RenderThread(FPyramidMesh* PyramidMeshData);uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); }virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); }private:/** Vertex buffer for this section */FStaticMeshVertexBuffers VertexBuffers;/** Index buffer for this section */FDynamicMeshIndexBuffer32 IndexBuffer;/** Vertex factory for this section */FLocalVertexFactory VertexFactory;FColor PyramidColor;UMaterialInterface* Material;FMaterialRelevance MaterialRelevance;
};

初始化VertexBuffer, IndexBuffer, Material

FPyramidSceneProxy::FPyramidSceneProxy(const UPyramidComponent* InComponent): FPrimitiveSceneProxy(InComponent), VertexFactory(GetScene().GetFeatureLevel(), "FPyramidSceneProxy"), PyramidColor(InComponent->ShapeColor), MaterialRelevance(InComponent->GetMaterialRelevance(GetScene().GetFeatureLevel()))
{// Setup VertexData And IndexDataTArray<FDynamicMeshVertex> Vertices;TArray<uint32> Indices;BuildPyramidMesh(&InComponent->PyramidMesh, Vertices, Indices);//VertexFactory Bind vertexBufferVertexBuffers.InitFromDynamicVertex(&VertexFactory, Vertices, 1);IndexBuffer.Indices = Indices;// Enqueue initialization of render resourceBeginInitResource(&VertexBuffers.PositionVertexBuffer);BeginInitResource(&VertexBuffers.StaticMeshVertexBuffer);BeginInitResource(&VertexBuffers.ColorVertexBuffer);BeginInitResource(&IndexBuffer);BeginInitResource(&VertexFactory);Material = nullptr == InComponent->Material ? UMaterial::GetDefaultMaterial(MD_Surface) : InComponent->Material;
}

覆盖GetDynamicMeshElements, 进行MeshBatch的提交

Mesh的顶点数据, 索引数据, 顶点工厂,材质Proxy等

void FPyramidSceneProxy::GetDynamicMeshElements(const TArray<const FSceneView *>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const
{QUICK_SCOPE_CYCLE_COUNTER(STAT_PyramidSceneProxy_GetDynamicMeshElements);// Set up wireframe material (if needed)const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;FColoredMaterialRenderProxy* WireframeMaterialInstance = NULL;if (bWireframe){WireframeMaterialInstance = new FColoredMaterialRenderProxy(GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : NULL,FLinearColor(0, 0.5f, 1.f));Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);}FMaterialRenderProxy* MaterialProxy = bWireframe ? WireframeMaterialInstance : Material->GetRenderProxy();for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex){if (VisibilityMap & (1 << ViewIndex)){const FSceneView* View = Views[ViewIndex];//Draw MeshFMeshBatch& Mesh = Collector.AllocateMesh();FMeshBatchElement& BatchElement = Mesh.Elements[0];BatchElement.IndexBuffer = &IndexBuffer;Mesh.bWireframe = bWireframe;//VertexFactory Bind vertexBufferMesh.VertexFactory = &VertexFactory;Mesh.MaterialRenderProxy = MaterialProxy;bool bHasPrecomputedVolumetricLightmap;FMatrix PreviousLocalToWorld;int32 SingleCaptureIndex;bool bOutputVelocity;GetScene().GetPrimitiveUniformShaderParameters_RenderThread(GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity);FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource<FDynamicPrimitiveUniformBuffer>();DynamicPrimitiveUniformBuffer.Set(GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity);BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer;BatchElement.FirstIndex = 0;BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;BatchElement.MinVertexIndex = 0;BatchElement.MaxVertexIndex = VertexBuffers.PositionVertexBuffer.GetNumVertices() - 1;Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();Mesh.Type = PT_TriangleList;Mesh.DepthPriorityGroup = SDPG_World;Mesh.bCanApplyViewModeOverrides = false;Collector.AddMesh(ViewIndex, Mesh);}}
}

覆盖GetViewRelevance,设置Mesh渲染状态信息

FPrimitiveViewRelevance FPyramidSceneProxy::GetViewRelevance(const FSceneView* View) const
{FPrimitiveViewRelevance Result;Result.bDrawRelevance = IsShown(View);Result.bShadowRelevance = IsShadowCast(View);Result.bDynamicRelevance = true;Result.bRenderInMainPass = ShouldRenderInMainPass();Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();Result.bRenderCustomDepth = ShouldRenderCustomDepth();Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow;MaterialRelevance.SetPrimitiveViewRelevance(Result);Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass;return Result;
}

创建八面体的PrimitiveComponent

UCLASS(meta = (BlueprintSpawnableComponent), ClassGroup = Rendering)
class CUSTOMRENDERCOMPONENT_API UPyramidComponent : public UPrimitiveComponent
{GENERATED_BODY()public:virtual FPrimitiveSceneProxy* CreateSceneProxy() override;//~ Begin UActorComponent Interface.virtual void OnRegister() override;private://~ Begin USceneComponent Interface.virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;//~ Begin USceneComponent Interface.#if WITH_EDITORvoid PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif // WITH_EDITORvoid RecreateMeshData();void UpdateLocalBounds();public:void RecreateMesh();void SendMeshDataToRenderThread();UFUNCTION(BlueprintCallable)void SetCustomMaterial(UMaterialInterface* InMaterial);/** Accesses the scene relevance information for the materials applied to the mesh. Valid from game thread only. */FMaterialRelevance GetMaterialRelevance(ERHIFeatureLevel::Type InFeatureLevel) const;virtual void GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials = false) const override;UFUNCTION(BlueprintCallable)void SetPyramidHeight(float NewPyramidHeight);UFUNCTION(BlueprintCallable)void SetPyramidBottomSize(FVector2D NewSize);private:/** Local space bounds of mesh */UPROPERTY()FBoxSphereBounds LocalBounds;UPROPERTY(EditAnywhere)float PyramidHeight = 4.0f;UPROPERTY(EditAnywhere)FVector2D PyramidBottomSize = FVector2D(4.0f, 4.0f);public:/** Color used to draw the shape. */UPROPERTY(EditAnywhere)FColor ShapeColor;UPROPERTY()FPyramidMesh PyramidMesh;UPROPERTY(EditAnywhere)UMaterialInterface* Material;
};

八面体构建Mesh数据函数

void UPyramidComponent::RecreateMeshData()
{PyramidMesh.Reset();//Vertex PosFVector HeightPos = FVector(0.0f, 0.0f, PyramidHeight);FVector LowPos = FVector(0.0f, 0.0f, -PyramidHeight);float BoxSizeX = PyramidBottomSize.X / 2.0f;float BoxSizeY = PyramidBottomSize.Y / 2.0f;FVector RightTopPos = FVector(BoxSizeX, BoxSizeY, 0.0f);FVector RightDownPos = FVector(-BoxSizeX, BoxSizeY, 0.0f);FVector LeftTopPos = FVector(BoxSizeX, -BoxSizeY, 0.0f);FVector LeftDownPos = FVector(-BoxSizeX, -BoxSizeY, 0.0f);FVector FaceNormal1 = FVector::CrossProduct(RightTopPos - HeightPos, HeightPos - LeftTopPos);FaceNormal1.Normalize();FVector FaceNormal2 = FVector::CrossProduct(LeftTopPos - HeightPos, HeightPos - LeftDownPos);FaceNormal2.Normalize();FVector FaceNormal3 = FVector::CrossProduct(LeftDownPos - HeightPos, HeightPos - RightDownPos);FaceNormal3.Normalize();FVector FaceNormal4 = FVector::CrossProduct(RightDownPos - HeightPos, HeightPos - RightTopPos);FaceNormal4.Normalize();FVector FaceNormal5 = FVector::CrossProduct(LeftTopPos - LowPos, LowPos - RightTopPos);FaceNormal5.Normalize();FVector FaceNormal6 = FVector::CrossProduct(LeftDownPos - LowPos, LowPos - LeftTopPos);FaceNormal6.Normalize();FVector FaceNormal7 = FVector::CrossProduct(RightDownPos - LowPos, LowPos - LeftDownPos);FaceNormal7.Normalize();FVector FaceNormal8 = FVector::CrossProduct(RightTopPos - LowPos, LowPos - RightDownPos);FaceNormal8.Normalize();//Add Vertex//UpPyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal1));PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal1));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal1));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal2));PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal2));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal2));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal3));PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal3));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal3));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal4));PyramidMesh.VertexArray.Add(FCustomMeshVertex(HeightPos, FaceNormal4));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal4));//DownPyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal5));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal5));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal5));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftTopPos, FaceNormal6));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal6));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal6));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LeftDownPos, FaceNormal7));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal7));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal7));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightDownPos, FaceNormal8));PyramidMesh.VertexArray.Add(FCustomMeshVertex(LowPos, FaceNormal8));PyramidMesh.VertexArray.Add(FCustomMeshVertex(RightTopPos, FaceNormal8));for (int32 Index = 0; Index < 24; ++Index){PyramidMesh.IndexArray.Add(Index);}UpdateLocalBounds();
}

覆盖创建渲染代理函数

FPrimitiveSceneProxy* UPyramidComponent::CreateSceneProxy()
{return new FPyramidSceneProxy(this);
}

覆盖构建包围体函数

Mesh视椎剔除的包围盒是通过CalcBounds函数传递的, 每次构建新的Mesh数据的时候记得调用UpdateLocalBounds 和 MarkRenderTransformDirty 更新剔除包围盒的大小

FBoxSphereBounds UPyramidComponent::CalcBounds(const FTransform& LocalToWorld) const
{FBoxSphereBounds Ret(LocalBounds.TransformBy(LocalToWorld));Ret.BoxExtent *= BoundsScale;Ret.SphereRadius *= BoundsScale;return Ret;
}void UPyramidComponent::UpdateLocalBounds()
{FBox LocalBox(ForceInit);for(auto& MeshVertex : PyramidMesh.VertexArray){LocalBox += MeshVertex.Position;}LocalBounds = LocalBox.IsValid ? FBoxSphereBounds(LocalBox) : FBoxSphereBounds(FVector(0, 0, 0), FVector(0, 0, 0), 0); // fallback to reset box sphere bounds// Update global boundsUpdateBounds();// Need to send to render threadMarkRenderTransformDirty();
}

UPyramidComponent更新数据给FPrimitiveSceneProxy

当你设置新的八面体高度,相应的顶点数据都会发生改变, 这时候更新数据有两种办法:

(1)进行渲染脏标记, 引起渲染Component再次调佣CreateSceneProxy,创建新的FPrimitiveSceneProxy,重新根据Component的数据构建新的VertexBuffer, IndexBuffer等等。不过这样重复销毁资源和创建资源不太好。设置材质新的时候就是进行了脏标记

(2)另外一种方式是Component通过渲染指令把要进行的顶点缓存更新。 具体是在GameThread直接通过ENQUEUE_RENDER_COMMAND把构建的数据变化传递到渲染线程的FPrimitiveSceneProxy,这种比较适合在频繁改变VertexBuffer, IndexBuffer参数的Mesh.

void UPyramidComponent::SendMeshDataToRenderThread()
{FPyramidMesh* NewPyramidMesh = new FPyramidMesh;*NewPyramidMesh = PyramidMesh;// Enqueue command to set vertex data to renderthreadFPyramidSceneProxy* PyramidSceneProxy = (FPyramidSceneProxy*)SceneProxy;if (PyramidSceneProxy){ENQUEUE_RENDER_COMMAND(FPyramidMeshData)([PyramidSceneProxy, NewPyramidMesh](FRHICommandListImmediate& RHICmdList){PyramidSceneProxy->SetMeshData_RenderThread(NewPyramidMesh);});}
}void FPyramidSceneProxy::SetMeshData_RenderThread(FPyramidMesh* NewPyramidMeshData)
{check(IsInRenderingThread());//FCustomVertex To FDynMeshVertexildPyramidMesh(NewPyramidMeshData, Vertices, Indices);TArray<FDynamicMeshVertex> Vertices;TArray<uint32> Indices;BuildPyramidMesh(NewPyramidMeshData, Vertices, Indices);for (int32 Index = 0; Index < Vertices.Num(); Index++){const FDynamicMeshVertex& Vertex = Vertices[Index];VertexBuffers.PositionVertexBuffer.VertexPosition(Index) = Vertex.Position;VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(Index, Vertex.TangentX.ToFVector(), Vertex.GetTangentY(), Vertex.TangentZ.ToFVector());VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(Index, 0, Vertex.TextureCoordinate[0]);VertexBuffers.ColorVertexBuffer.VertexColor(Index) = Vertex.Color;}{auto& VertexBuffer = VertexBuffers.PositionVertexBuffer;void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);}{auto& VertexBuffer = VertexBuffers.ColorVertexBuffer;void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetNumVertices() * VertexBuffer.GetStride(), RLM_WriteOnly);FMemory::Memcpy(VertexBufferData, VertexBuffer.GetVertexData(), VertexBuffer.GetNumVertices() * VertexBuffer.GetStride());RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);}{auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTangentSize(), RLM_WriteOnly);FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTangentData(), VertexBuffer.GetTangentSize());RHIUnlockVertexBuffer(VertexBuffer.TangentsVertexBuffer.VertexBufferRHI);}{auto& VertexBuffer = VertexBuffers.StaticMeshVertexBuffer;void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI, 0, VertexBuffer.GetTexCoordSize(), RLM_WriteOnly);FMemory::Memcpy(VertexBufferData, VertexBuffer.GetTexCoordData(), VertexBuffer.GetTexCoordSize());RHIUnlockVertexBuffer(VertexBuffer.TexCoordVertexBuffer.VertexBufferRHI);}if (NewPyramidMeshData){delete NewPyramidMeshData;NewPyramidMeshData = nullptr;}
}

结果显示

这里比较注意的设置材质的时候得进行 MarkRenderStateDirtyMarkRenderStateDirty会使得UPyramidComponent创建新的FPrimitiveSceneProxy。

八面体渲染组件的源码链接

CustomRenderComponent.zip-其他文档类资源-CSDN下载

参考资料

【1】CableComponent.h 和 ProceduralMeshComponent.h

【2】https://medium.com/@lordned/unreal-engine-4-rendering-part-2-shaders-and-vertex-data-80317e1ae5f3

(UE4 4.27)自定义PrimitiveComponent相关推荐

  1. UE4:27启动虚幻引擎的步骤崩溃课程4

    家庭商店全部350+教程 教程主题▾ 搜索关于联系 UE4:27启动虚幻引擎的步骤崩溃课程4 类别:虚幻引擎4 2015年9月21日 分享 此贴/页: 在分享 鸣叫 全文教程和注释: 虚幻引擎4是一款 ...

  2. UE4 4.27 修改Mobile Forward管线支持Cluster多光源剔除

    前言 最近闲着没事想熟悉下UE4的图形模块,自己想寻找个需求,目前UE4 4.27的 Mobile Forward是不支持多光源的,默认是最大支持到4栈点光源. 于是,我萌发了一点想法,通过改造UE的 ...

  3. 【UE4】实现自定义框选

    要在UE4中实现自定义框选功能,首先我们来分析一下顶顶一框选插件需要些什么模块? 绘制模块 显示模块 计算模块 嗯,大概分这么三个模块,好,现在我们一个个模块来分析实现.首先分析实现一下显示模块. 提 ...

  4. thinkphp3.2.3 找不到自定义模型_Orion HTC VIVE高性价比动作捕捉,虚拟直播 支持UE4.25 导入自定义模型...

    Orion HTC VIVE动作捕捉系统核心技术展示 一:原UE4实时插件只支持4.22,经过厂长重构代码,现支持最新的UE4.25.3版本啦,包括支持后续的UE4以及UE5的更新. 二:Orion支 ...

  5. ue4当中材质自定义uv和多套uv

    首先是多套uv 多套uv可以通过fbx方式导入到ue4中(obj只能一套) 在texcoord中index里面切换,从0开始为第一套uv ue4当中还支持自定义uv的 这个功能是用来优化效率的,特别带 ...

  6. UE4 EQS C++自定义节点编写

    目录 决策对象 Generator Context Test 基础应用就不说了,直接看官方文档. 这篇主要讲怎么在C++层定义Generator.Test和Context. 决策对象 EQS的作用是做 ...

  7. UE4 4.27像素流公网布置教程

    前言: 网上4.26与5.0以上版本像素流部署比较多,但是4.27资料就很少,而且很多坑,鉴于这些原因,加上最近项目需求,打算把近期研究的东西分享给大家. 为了不浪费大家时间,这里把最终效果先描述出来 ...

  8. 【UE4 005】自定义人物角色(Charactor) 替换小白人

    本篇讲解UE4 新手开发者如何自定义一个角色模型,网上相关资料很少,这里分享给大家供参考. 本文主要知识点: Animation Blueprint(动画蓝图)的使用 Blend Space(速度控制 ...

  9. (2017.9.27) 自定义列表项 list-style 使用心得

    今天给某公司做招聘专页.早上完成设计图,下午开始排版.页面套用了我之前做的某人才局的招聘页面,导航栏.banner 很快就出来了.这次内容里我有些地方用了列表,当然要用 <ul> < ...

最新文章

  1. 博士毕业后,去哪儿?
  2. YOLO 卷积层代码学习
  3. display: none;、visibility: hidden、opacity=0区别总结
  4. TP5 封装通用的微信服务类
  5. #11 压缩与解压缩
  6. rust面向对象_面向初学者的Rust操作员综合教程
  7. NOI.AC-积木【堆】
  8. mysql日期时间操作函数详解
  9. iosselect简单使用(三级联动)
  10. CSS选择器速记笔记
  11. 修改mysql限制ip_MySQL 修改账号的IP限制条件
  12. 异常详细信息: System.UnauthorizedAccessException: 对路径的访问被拒绝。
  13. 一个困扰我很久的问题
  14. 连接服务器打印机文件名无效,excel服务器-办公室常识——共享服务器文件报错,共享打印机无法打印的解决办法(解决word、excel不能打印))...
  15. android 微信分享小程序 图片显示不全
  16. FreeCAD快速开始
  17. 阿里系盒子英菲克i6八核 科学使用 笔记 (2015年12月26日成功)
  18. 窗口函数深度探索(二):控制窗口大小(UNBOUNDED、CURRENT、PRECEDING、FOLLOWING)
  19. MAF-YOLO: Multi-modal attention fusion based YOLO forpedestrian detection
  20. java emoji表情 乱码_java 微信昵称带有emoji 表情乱码

热门文章

  1. PMP项目管理师考试---准备与学习过程经验分享
  2. CISSP-D8-软件开发安全
  3. 电路布线-----问题详解
  4. 整数分解(java)
  5. android studio抛出,Android Studio升级到3.0,抛出Aapt2Exception异常
  6. C++ accumulate()函数
  7. 算法模板:归并排序【沈七】
  8. Cesium深入浅出之图层管理器
  9. java IO编程详解
  10. 案例分析:FIFA2018球员数据分析