前言

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. os.makedirs和os.mkdir 生成文件夹
  2. 发展通用智能,需要无监督学习
  3. js判断display隐藏显示
  4. 一些实用的Javascript代码
  5. win10:tensorflow学习笔记(2)
  6. 什么牌子的平板电脑好_台式电脑哪个牌子好
  7. 水星无线网dns服务器是什么,水星路由dns设置教程,-1
  8. 远程连接管理软件 v1.0
  9. 一种经典的网络游戏服务器架构
  10. Fingersmith---指匠情挑
  11. php mysql分页_PHP+Mysql实现分页
  12. c语言计算音频分贝函数,如果用c语言程序读取一段音频文件要如何做,具体要调用哪些函数,在网上搜索了半天,乱七八糟的,找不到什么有用的信息...
  13. 使用depends查看64位dll/exe
  14. 【渝粤教育】电大中专营销策划原理与实务 (3)作业 题库
  15. Encoder-decoder模型及Attention机制
  16. Windows桌面虚拟小猫Candy - From Neko
  17. java微信支付v3系列——6.微信支付查询订单API
  18. 我的一次创业经历--分享给希望创业的大学生们 .
  19. VLookup函数怎么用?详细解析
  20. 在股票技术指标里,EMA和SMA 的区别

热门文章

  1. 乐视汽车仅靠老贾的哽咽和激情是不够的
  2. 微机原理和计算机组成原理一样吗_计算机组成原理(指令)
  3. 使用功耗分析仪,对一款LORA低功耗温度传感器进行功耗评测,评估温度传感器的待机时长,供参考。
  4. Socket/Node/Die/Core/Processor/ 针对CPU封装的精细区分
  5. 深入浅出PaddlePaddle函数——paddle.Tensor
  6. python生成桌面路径(winreg)
  7. jieba分词算法总结
  8. exe4j将jar转exe时出现的ClassNotFoundException解决办法
  9. Android Binder设计与实现 - 设计篇
  10. 参数估计:最大似然估计MLE