unreal4 修改顶点缓存 - CSDN
  • Unreal 入门-顶点变形

    千次阅读 2017-03-23 00:12:30
    Unreal 提供了两个用于3DsMax定点变形的插件。 在Epic Games\4.14\Engine\Extras\3dsMaxScripts目录下面 StaticMeshMorpher.ms 这里只支持两个变形目标。 复制Clone模型,制作过程渐变形状。 ...

    Unreal 提供了两个用于3DsMax定点变形的插件。

    在Epic Games\4.14\Engine\Extras\3dsMaxScripts目录下面

    StaticMeshMorpher.ms

    blob.png

    这里只支持两个变形目标。

    1. 复制Clone模型,制作过程渐变形状。

    2. 所有模型位置归0,Pick Game Mesh 拾取开始的模型,下面两份拾取渐变后的目标。

    3. 选择原始模型,导出。

    4. 导入UE4材质设置。

      blob.png

    5. PS: 此种定点变形方法是将定点变形信息存储UV 通道2和3中。

      


    VertexAnimationTools.ms 工具介绍见 

    https://docs.unrealengine.com/latest/CHN/Engine/Animation/Tools/VertexAnimationTool/index.html

    blob.png

    使用定点动画Key帧(ProcessAnimatedMeshes)的方式:

    1. 定点动画Key帧。

    2. 按下Process Animated Meshes,导出EXR和Normal贴图。

    3. 导出模型

    4. 贴图导入UE4,设置贴图

      Normal贴图:blob.png

      EXR贴图:blob.png

      材质中贴图转为Texture Object。

      blob.png

    5. PS: 定点信息保存在定点变形位置贴图和定点变形发现上。并且不能用在带骨骼模型上。


    使用模型序列(Sequence Painter):

    1. 复制Clone模型,制作每个序列的渐变形状。

    2. 所有模型位置归0,转为可编辑多边形。

    3. 依次根据序列播放选择模型,选择Paint Selection Sequence。

    4. 选中所有模型导出。

    5. UE4材质根据MS_SequencePainter_SequenceFlipbook 到 World Position Offset 即可。

      blob.png


    使用morpher 变形器:

    https://docs.unrealengine.com/latest/CHN/Engine/Content/FBX/MorphTargets/index.html

    步骤如下:

    1. 复制Clone模型,制作每个模型的变形后的结果

    2. 原模型加入变形器

    3. 变形器PickObjectFromScene 选择场景中变形后的对象。

    4. 导出变形目标

    5. PS: 此种方法只能做带骨骼的动画 加入曲线的变形器。


    展开全文
  • https://medium.com/@lordned/unreal-engine-4-rendering-part-2-shaders-and-vertex-data-80317e1ae5f3 翻译:yarswang 转载请保留   着色器与顶点工厂    本章我们来关注着色器与顶点工厂。Unreal使用一些...

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

    翻译:yarswang 转载请保留

     

    着色器与顶点工厂

     

        本章我们来关注着色器与顶点工厂。Unreal使用一些魔法来绑定C++的着色器表现与等价HLSL类,使用顶点工厂(Vertex Factory)来将顶点着色器的控制数据上传到GPU。从本章起,我们将使用Unreal的类名来讨论问题,让它们更容易被你自己查找。

        我们将专注于核心的着色器/顶点工厂涉及类。有很多处理内务的结构/方法如胶水一般地黏合了整个系统。我们不太可能去修改这些胶水,所以我们不会去谈他们来让事情变得更复杂。

     

    FLocalVertexFactory的顶点输出

     

    着色器

        Unreal中所有着色器都派生自一个基类 FShader.  Unreal有两个主要的着色器分类,FGlobalShader用于在只应存在一个实例的情况,以及FMaterialShader使用在关联材质的着色器上。FShader和FShaderResouce配对,后者跟踪与特定着色器相关联的GPU上的资源。如果FShader的已编译输出与已存在的输出相匹配,则可以在多个FShaders之间共享FShaderResource。

    FGlobalShader

        这是个很简单,并且有限(但是有效!)的用法。 当着色器类从FGlobalShader派生时,它将它们标记为Global重新编译组的一部分(这似乎意味着它们在引擎打开时不会重新编译!)。 只有一个全局着色器实例存在,这意味着你不能拥有每个实例的参数。 但是,您可以拥有全局参数。 示例:FLensDistortionUVGenerationShader,FBaseGPUSkinCacheCS(用于计算网格蒙皮的计算着色器)和FSimpleElementVS/ FSimpleElementPS。

      

    FMaterialShader和FMeshMaterialShader

        FMaterialShader和FMeshMaterialShader更加复杂。这两个类都允许多个实例,每个实例都与其自己的GPU资源副本相关联。 FMaterialShader添加了一个SetParameters函数,它允许着色器的C ++代码更改绑定HLSL参数的值。参数绑定是通过FShaderParameter / FShaderResourceParameter类完成的,并且可以在着色器的构造函数中完成,比如说FSimpleElementPS。在使用该着色器渲染某些内容之前调用SetParameters函数,并传递相当一部分信息(包括材质),这会为您提供大量信息来作为您计算要更改的参数的一部分。

        现在我们知道如何设置着色器范围的参数,我们可以看看FMeshMaterialShader。这增加了我们在绘制每个网格之前在着色器中设置参数的功能。大量着色器从该类派生,因为它是需要材质和顶点工厂参数的所有着色器的基类(根据文件中留下的注释)。这只需添加一个SetMesh函数,在绘制每个网格之前调用该函数,从而允许您修改GPU上的参数以适应特定的网格。示例:TDepthOnlyVS,TBasePassVS,TBasePassPS。

     

    将C++绑定到HLSL

            因此,现在我们知道FShaders是CPU上着色器的C++表示,我们需要知道如何将给定的FShader与其相应的HLSL代码相关联。这是我们第一个C++宏的用途:IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)。在解释每个参数之前,让我们看一下DepthRendering.cpp中的一个例子:

     

    IMPLEMENT_MATERIAL_SHADER_TYPE(,FDepthOnlyPS,TEXT("/Engine/Private/DepthOnlyPixelShader.usf"),TEXT("Main"),SF_Pixel);

     

        该宏将C++类FDepthOnlyPS绑定到位于/Engine/Private/DepthOnlyPixelShader.usf中的HLSL代码。具体而言,它与入口点“Main”以及SF_Pixel的频率相关联。我们现在拥有一个存在于(DepthOnlyPixelShader.usf)中HLSL文件的C++代码(FDepthOnlyPS)和该HLSL代码中要调用(Main)的函数之间的关联。Unreal使用术语“频率”来指定它的着色器类型 - 顶点,船体,域,几何,像素或计算。

        你会注意到这个实现忽略了第一个参数。因为这个特定的例子不是模板化的函数。在某些情况下,宏专门化了一个模板类,其中的模板类由另一个宏实例化,以创建特定的实现。例如为每种可能的照明类型创建一个变体。如果您好奇的看到了BasePassRendering.cpp的顶部的宏IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE......我们将在Base Pass文章中深入介绍它!

     

    综述

    FShader的实现是着色管道中的特定阶段,可以在使用之前修改其HLSL代码中的参数。Unreal使用宏将C++代码绑定到HLSL代码。从头开始实现着色非常简单,但将其集成到现有的延迟基本通道/阴影中将会复杂的多。

     

    缓存和编译环境

    在我们继续之前,有两个重要概念要介绍。在你修改材质后,Unreal会自动编译许多可能的着色器队列。这是一件好事,但会产生过多的无用着色器。这里介绍一下ShouldCache函数。

        如果“着色器”,“材质”和“顶点工厂”都同意应该缓存某队列,Unreal才会会创建着色器的这个特定队列。如果其中任何一个不同意,那么Unreal就会跳过创建该队列,这意味着在此队列可被绑定的情况下,你永远等不到结束。比如说,你不想去缓存一个需要SM5支持的着色器。如果您正在构建不支持它的平台,则没有理由编译和缓存它。

        ShouldCache函数是一个静态函数,可以在FShader,FMaterial或FVertexFactory类中实现。检查现有的使用情况将为您提供一个关于如何以及何时可以实现它的想法。

        第二个重要概念是在编译之前更改HLSL代码中的预处理器定义的能力。 FShader使用ModifyCompilationEnvironment(通过宏实现的静态函数),FMaterial使用SetupMaterialEnvironment,而FVertexFactory使用ModifyCompilationEnvironment。这些函数在着色器编译之前调用,并允许您修改HLSL预处理器定义。 FMaterial广泛使用该设置来根据材料内的设置设置着色模型相关的定义,以优化掉任何不必要的代码。

     

    FVertexFactory

        现在我们知道如何在渲染着色器之前修改着色器,首先需要知道如何将数据提取到GPU中!顶点工厂封装顶点数据源并可以链接到顶点着色器。如果你已经写出了自己的渲染代码,那么你可能会试图创建一个包含顶点可能需要的所有可能数据的类。 Unreal使用顶点工厂,它只允许您将实际需要的数据上传到顶点缓冲区。

        要了解顶点工厂,我们应该了解两个具体的例子。 FLocalVertexFactory和FGPUBaseSkinVertexFactory。FLocalVertexFactory用于许多地方,因为它提供了一种简单的方法来将显式顶点属性从本地变换到世界空间。静态网格使用这个,缆绳和过程网格也是如此。骨骼网格(需要更多数据)另一方面使用FGPUBaseSkinVertexFactory。再下来,我们看看与这两个顶点工厂匹配的着色器数据如何具有不同的数据。

     

    FPrimitiveSceneProxy

        那么Unreal如何知道哪个顶点工厂用于网格?通过FPrimitiveSceneProxy类! FPrimitiveSceneProxy是UPrimitiveComponent的呈现线程版本。它的目的是子类UPrimitiveComponent和FPrimitiveSceneProxy并创建特定的实现。

     

    FCableSceneProxy aFPrimitiveSceneProxy for the dynamic cable component.

        后退一下 - 虚幻有一个游戏线程和一个渲染线程,两者不应该触及属于另一个线程的数据(除非通过几个特定的​​同步宏)。为了解决这个问题,Unreal使用UPrimitiveComponent作为游戏线程,并通过重写CreateSceneProxy()函数来决定创建哪个FPrimitiveSceneProxy类。然后,FPrimitiveSceneProxy可以在适当的时候查询游戏线程,从游戏线程获取数据到渲染线程中,以便将其处理并放置在GPU上。

        这两个类通常成对出现,这里有两个很好的例子:UCableComponent / FCableSceneProxyUImagePlateFrustrumComponent /FImagePlateFrustrumSceneProxy。在FCableSceneProxy中,渲染线程查看UCableComponent中的数据并构建一个新的网格(计算位置,颜色等),然后与之前的FLocalVertexFactory相关联。 UImagePlateFrustrumComponent是整洁的,因为它根本没有顶点工厂!它只是使用渲染线程的回调来计算一些数据,然后使用该数据来绘制线条。没有着色器或顶点工厂与它关联,它只是使用GPU回调来调用一些即时模式样式的渲染函数。

     

    Binding C++ to HLSL

        目前为止,我们已经介绍了不同类型的顶点数据以及场景中的组件如何创建并存储这些数据(通过具有顶点工厂的场景代理)。现在我们需要知道如何在GPU上使用唯一的顶点数据,尤其是考虑到basepass只有一个顶点函数需要处理所有不同类型的传入数据!如果你猜“另一个C++宏”,答对了!

    IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename, bUsedWithmaterials, bSupportsStaticLighting,bSupportsDynamicLighting, bPrecisePrevWorldPos, bSupportsPositionOnly)

     

        这个宏允许我们将顶点工厂的C ++表示绑定到特定的HLSL文件。例:

    IMPLEMENT_VERTEX_FACTORY_TYPE(FLocalVertexFactory,”/Engine/Private/LocalVertexFactory.ush”,true,true,true,true,true);

        

        现在您会注意到一些有趣的内容,没有指定入口点(根本没有该入口)!我认为实际工作的方式非常精彩(虽然会让人感到困惑):Unreal会根据所使用的顶点工厂改变数据结构和函数调用的内容,同时重用相同的名称,以便通用代码工作。

        我们将看一个例子:BasePass顶点着色器将FVertexFactoryInput作为输入。 这个数据结构在LocalVertexFactory.ush中定义,具有特定的含义。 但是,GpuSkinVertexFactory.ush也定义了这个结构! 然后,根据包含哪个头,提供给顶点着色器的数据将发生变化。这种模式在其他领域中重复使用,并将在“Shader Architecture 着色器体系结构”文章中进行更深入的介绍。

    // Entry point for the base pass vertexshader. We can see that it takes a generic FVertexFactoryInput struct andoutputs a generic FBasePassVSOutput.

    void Main(FVertexFactoryInput Input, outFBasePassVSOutput Output)

    {

       // This is where the Vertex Shader would calculate things based on theInput and store them in the Output.

    }

    // LocalVertexFactory.ush implements theFVertexFactoryInput struct

    struct FVertexFactoryInput

    {

       float4 Position : ATTRIBUTE0;

       float3 TangentX : ATTRIBUTE1;

       float4 TangentZ : ATTRIBUTE2;

       

       float4 Color : ATTRIBUTE3;

       // etc…

    }

    // GpuSkinVertexFactory.ush alsoimplements the FVertexFactoryInput struct

    struct FVertexFactoryInput

    {

       float4 Position : ATTRIBUTE0;

       half3 TangentX : ATTRIBUTE1;

       half4 TangentZ : ATTRIBUTE2;

       uint4 BlendIndices : ATTRIBUTE3;

       uint4 BlendIndicesExtra : ATTRIBUTE14;

       // etc…

    }

     

    总结

        IMPLEMENT_MATERIAL_SHADER_TYPE宏定义了着色器的入口点,但顶点工厂确定了传入该顶点着色器的数据。着色器使用非特定变量名称(例如FVertexFactoryInput),这些名称对不同的顶点工厂有不同的含义。 UPrimitiveComponent / FPrimitiveSceneProxy一起工作,通过特定的数据布局从您的场景中获取数据并将其传输到GPU上。

    着色器管线的一个脚注

        Unreal具有“着色器管线”的概念,它在一个管线中一起处理多个着色器(顶点,像素),以便查看输入/输出并优化它们。它们在引擎中的三个地方使用:DepthRendering,MobileTranslucentRendering和VelocityRendering。 我不能很好地理解它们,但是如果您正在使用这三种系统中的任何一种,并且在阶段之间优化语义的问题,请调查IMPLEMENT_SHADERPIPELINE_TYPE_ *。

    啊,是的,不常用的VSHSDSGSPS 类型

     

     

    下章

        在下一篇文章中,我们将着眼于绘图策略是什么,绘图策略工厂是什么,以及Unreal如何实际告诉GPU绘制网格。

     

     

    展开全文
  • https://medium.com/@lordned/unreal-engine-4-rendering-part-4-the-deferred-shading-pipeline-389fc0175789 翻译:yarswang 转载请保留   延迟着色基本通道 在第3章,我们完成了对C++方面的研究,并最终准备...

    https://medium.com/@lordned/unreal-engine-4-rendering-part-4-the-deferred-shading-pipeline-389fc0175789

    翻译:yarswang 转载请保留

     

    延迟着色基本通道

    在第3章,我们完成了对C++方面的研究,并最终准备好了解GPU上的一切是如何工作的。 我们将深入研究顶点工厂如何控制公共基本顶点着色器着色器代码的输入,以及如何处理曲面细分(使用其附加的Hull和Domain阶段),以及触及材质图表如何结束 在你的HLSL代码中。

     

    在我们理解了这些部分是如何组合在一起之后,我们将逐步完成延迟通道并查看它经历的一些不同步骤。 这将让我们知道表面着色器在哪个部分运行,以及材料图中的修改内容实际上最终会做什么。

     

    顶点工厂再览

    回到第2章,我们简要讨论了顶点工厂如何去更改提供给顶点着色器的数据。Unreal最终明智地决定用提升学习复杂度的代价以减少代码重复。 我们将在我们的示例中使用LocalVertexFactory.ushBasePassVertexCommon.ush,并将GpuSkinVertexFactory.ush作为要比较的东西,因为它们都使用相同的顶点着色器。

     

    更改输入数据

    不同类型的网格最终将需要不同的数据来完成它们的工作,即:GPU蒙皮顶点需要比简单静态网格更多的数据。 Unreal使用FVertexFactory处理CPU端的这些差异,但在GPU方面它有点棘手。

     

    因为所有顶点工厂共享相同的顶点着色器(至少对于基础通道),所以它们使用通常命名的输入结构FVertexFactoryInput

     

    因为Unreal使用相同的顶点着色器,但每个顶点工厂包含不同的代码,Unreal重新定义了每个顶点工厂中的FVertexFactoryInput结构。此结构被唯一定义在GpuSkinVertexFactory.ushLandscapeVertexFactory.ushLocalVertexFactory.ush和其他一些文件中。显然包含所有这些文件是行不通的 - 而BasePassVertexCommon.ush引用了/Engine/Generated/VertexFactory.ush。 编译着色器时,这将设置为正确的顶点工厂,允许引擎知道要使用哪个FVertexFactoryInput实现。我们在第2章简要地谈到了使用宏来声明顶点工厂,你必须提供着色器文件 - 这就是原因。

     

    现在我们的基本通道顶点着色器的数据输入与我们上传的顶点数据的类型相匹配。下一个问题是不同的顶点工厂将需要在顶点着色器和像素着色器之间插入不同的数据。同样,BasePassVertexShader.usf调用泛型函数 - GetVertexFactoryIntermediatesVertexFactoryGetWorldPositionGetMaterialVertexParameters。如果我们在文件中执行另一个查找,我们会发现每个 *VertexFactory.ush 都按需而将这些函数独特定义。

     

    每个顶点工厂重新实现这个方法

     

    更改输出数据

    现在我们需要了解如何从Vertex Shader到Pixel Shader获取数据。不出所料,BasePassVertexShader.usf的输出是另一个通常命名的struct(FBasePassVSOutput),它的实现取决于顶点工厂。虽然这里有一点障碍 - 如果你启用了曲面细分,那么顶点着色器和像素着色器之间有两个阶段(Hull和Domain阶段),这些阶段需要的数据不同于只是顶点着色器到像素着色器的数据。

     

    Unreal的下一个技巧。他们使用#define来改变FBasePassVSOutput的含义,它可以定义为简单的FBasePassVSToPS结构,也可以为曲面细分定义为FBasePassVSToDS(此代码可以在BasePassVertexCommon.ush中找到)。这两种结构具有几乎相同的内容,除了Domain着色器版本添加了一些额外的变量。那么,我们需要的那些独特的每个顶点工厂中插值呢? Unreal通过创建FVertexFactoryInterpolantsVSToPSFBasePassInterpolantsVSToPS作为FBasePassVSOutput的成员来解决这个问题。惊喜! FVertexFactoryInterpolantsVSToPS在每个 *VertexFactory.ush 文件中定义,这意味着我们仍然在阶段之间传递正确的数据,即使我们停止在中间添加Hull/Domain阶段。 FBasePassInterpolantsVSToPS未重新定义,因为存储在此结构中的内容不依赖于特定顶点工厂特有的任何内容,包含VertexFog值,AmbientLightingVector等内容。

     

    Unreal的重定义技术抽象出基本传递顶点着色器中的大部分差异,允许公共代码不管曲面细分或特殊顶点工厂都能被使用。

     

    基本通道顶点渲染

    现在我们知道Unreal如何处理着色器阶段和曲面细分支持中的差异,我们将看看延迟渲染管道中每个着色器实际上做了什么。

     

    BasePassVertexShader.usf总体来说非常简单。在大多数情况下,顶点着色器只是计算和分配BasePassInterpolants和VertexFactoryInterpolants,虽然如何计算这些值会变得有点复杂 - 有很多特殊情况,他们选择只在某些预处理器定义下声明某些插值器和然后只分配匹配定义下的那些。

     

    例如,在顶点着色器的底部附近,我们可以看到定义#if WRITES_VELOCITY_TO_GBUFFER,它通过计算其位置最后一帧与该帧之间的差异来计算每个顶点的速度。一旦计算出来,它就会将它存储在BasePassInterpolants变量中,但是如果你查看那里,他们就会在匹配的#if WRITES_VELOCITY_TO_GBUFFER中包装该变量的声明。

     

    这意味着只有向GBuffer写入速度的着色器变体才会计算它 - 这有助于减少阶段之间传递的数据量,这意味着带宽减少,从而导致更快的着色器。

     

    您不必接触的Boilerplate代码!

     

    基本通道像素着色器

    这是事情开始变得相当复杂的地方,并且可能是每个人都被吓跑的地方。 深呼吸,假设预处理器检查中的大部分内容都不存在,我们将一起解决这个问题!

     

    材质图到HLSL

    当我们在Unreal中创建一个材质图时,Unreal会将您的节点网络转换为HLSL代码。 此代码由编译器插入到HLSL着色器中。 如果我们看一下MaterialTemplate.ush就会发现它包含许多没有实体的结构(比如FPixelMaterialInputs) - 相反它们只有一个 %s。 Unreal将其用作字符串格式,并将其替换为特定于材质图的代码。

     

    此文本替换不仅限于结构,MaterialTemplate.ush还包括几个没有实现的函数。 例如,half GetMaterialCustomData0half3 GetMaterialBaseColorhalf3 GetMaterialNormal都是根据您的素材图填写其内容的不同函数。 这允许你从像素着色器调用这些函数,并知道它将执行你在材质图中创建的计算,并将返回该像素的结果值。

     

    一些函数的内容由C++ 填充,其它的则具有实际定义

     

    “原始”变量

    遍历所有代码,您将找到对名为“Primitive”的变量的引用 - 在着色器文件中搜索它不会返回任何声明! 这是因为它实际上是通过一些宏魔法在C++ 端声明的。 在GPU上绘制每个Primitive变量之前,此宏会声明一个由渲染器设置的结构。

     

    它支持的完整变量列表可以在PrimitiveUniformShaderParameters.h中顶部的宏找到。它默认包括LocalToWorld, WorldToLocal, ObjectWorldPositionAndRadius, LightingChannelMask等。

     

    创建GBuffer

    延迟着色使用“GBuffer”(几何缓冲区)的概念,它是一系列渲染目标,用于存储关于几何的不同信息,例如世界法线,基色,粗糙度等。当照明计算确定最终的阴影时,Unreal会采样这些缓冲区。在它生效之前,虚幻通过几个步骤来创建和填充它。

     

    GBuffer的确切内容可能有所不同,根据您的项目设置,可以改变通道数量及其用途。一个常见的案例是5纹理GBuffer,从A到E:GBufferA.rgb = World Normal,使用PerObjectGBufferData填充alpha通道。 GBufferB.rgba = Metallic, Specular, Roughness, ShadingModelIDGBufferC.rgbBaseColor,使用GBufferAO填充alpha通道。 GBufferD专用于自定义数据,GBufferE用于预先计算的阴影因子。

     

    BasePassPixelShader.usf中,FPixelShaderInOut_MainPS函数充当像素着色器的入口点。由于有许多预处理器定义,此函数看起来相当复杂,不过大多数都是用样板代码填充的。 Unreal使用几种不同的方法来计算GBuffer所需的数据,具体取决于您启用的照明模型和功能。除非您需要更改某些样板代码,否则首个有意义的功能在函数的中间靠下的地方,着色器用来获取BaseColor, Metallic, Specular, MaterialAORoughness的值。它通过调用MaterialTemplate.ush中声明的函数来完成此操作,它们的实现由你的材质图定义。

     

    着色器缓存材质图调用的结果,以避免多次执行其函数。

     

    现在我们已经采样了一些数据通道,Unreal将针对某些着色模型修改其中一些数据通道。例如,如果您使用的是使用次表面散射(次表面,次表面轮廓,预整合蒙皮,双面叶子或布料)的着色模型,那么Unreal将根据对GetMaterialSubsurfaceData的调用来计算次表面颜色。如果照明模型不是其中之一,则使用默认值零。 “次表面颜色”值现在是进一步计算的一部分,但除非您使用的是着色模型,否则它将仅为零!

     

    计算次表面颜色后,如果在项目中启用了DBuffer,Unreal则允许DBuffer Decals修改GBuffer的结果。在做了一些数学运算后,Unreal将DBufferData应用于BaseColor, Metallic, Specular, Roughness, NormalSubsurface Color通道。

     

    在允许DBuffer Decals修改数据后, Unreal计算不透明度(使用材质图中的结果)并进行一些体积光照贴图计算。最后,它创建了FGBufferData结构,并将所有这些数据打包到其中,每个FGBufferData实例代表一个像素。

     

    设置GBuffer着色模型

    Unreal列表中的下一件事是让每个着色模型按照它认为合适的方式修改GBuffer。为了实现这一点,Unreal在ShadingModelMaterials.ush中有一个名为SetGBufferForShadingModel的函数。此函数采用我们的Opacity, BaseColor, Metallic, Specular, RoughnessSubsurface数据,并允许每个着色模型将数据分配给它想要的GBuffer结构。

     

    大多数着色模型只是简单地分配输入数据而不进行修改,但某些着色模型(例如与子表面相关的模型)将使用自定义数据通道将附加数据编码到GBuffer中。此函数的另一个重要功能是将ShadingModelID写入GBuffer。这是每像素存储的整数值,允许延迟传递查找每个像素稍后应使用的着色模型。

     

    这里需要注意的是,如果要使用GBuffer的CustomData通道,则需要修改BasePassCommon.ush,它具有WRITES_CUSTOMDATA_TO_GBUFFER的预处理器定义。如果您尝试使用GBuffer的CustomData部分而不确保在此处添加着色模型,它将被丢弃,您将不会获得任何值!

     

    单个模型使用三种不同的着色模型 - 头发,眼睛和皮肤。

     

    使用数据

    既然我们已经让每个照明模型选择他们将数据写入FGBufferData结构的方式,那么BasePassPixelShader将会做更多的样板代码和内部处理 - 计算每个像素的速度,进行次表面颜色变换,重写ForceFullyRough的粗糙度等。

     

    在这个样板代码之后,Unreal将获得预先计算的间接光照和天空光照数据(GetPrecomputedIndirectLightingAndSkyLight)并将其添加到GBuffer的DiffuseColor中。这里有相当一些与半透明前向着色,顶点雾化和调试相关的代码,我们最终来到了FGBufferData结构的末尾。 Unreal调用EncodeGBuffer(DeferredShadingCommon.ush),它接收FGBufferData结构并将其写入各种GBuffer纹理A-E。

     

    这包含了大部分的基本通道像素着色器的结尾部分。你会注意到此方法中没有提及光照或阴影。这是因为在延迟渲染器中,这些计算推迟到了!接下来我们来看看。

     

    综述

    基本通道像素着色器负责通过调用材质图生成的函数对各种PBR数据通道进行采样。此数据被打包到FGBufferData中,该数据被传递给基于各种着色模型修改数据的各种函数中。着色模型确定写入纹理的ShadingModelID,以选择稍后使用的着色模型。最后,FGBufferData中的数据被编码为多个渲染目标以供稍后使用。

     

    延迟光照像素着色器

    接下来我们将看看DeferredLightPixelShaders.usf,因为这是计算光对像素的影响的地方。为此,Unreal使用一个简单的顶点着色器来绘制适当的几何体来匹配每个光的可能影响范围,即:用于点光源的球体和用于聚光灯的锥体。这将在那些需要像素着色器上运行的像素上创建一个掩模,这使得填充较少像素的光照消耗更少。

     

    阴影和无阴影光照

    虚幻在多个阶段绘制光照。首先绘制不投射阴影光,然后绘制间接光照(通过光照投射体)。最后Unreal绘制所有投射阴影光。 Unreal使用类似的像素着色器进行投射阴影和非投射阴影光 - 它们之间的差异来自投投射阴影光的额外预处理步骤。对于每个灯光,Unreal会计算ScreenShadowMaskTexture,它是场景中阴影像素的屏幕空间表示。

     

    ScreenShadowMaskTexture用于一个包含一些球体的简单场景

                               

    为此,Unreal会渲染几何图形,这些几何图形与场景中每个对象的边界框以及场景中对象的几何表示体相匹配。 它不会重新渲染场景中的物体,而是对GBuffer进行采样,结合给定像素的深度,以查看它是否会出现投射光影。 听起来复杂吗? 别担心,确实如此。 好消息是,我们唯一需要的是每个阴影光计算处于阴影中的表面的屏幕空间表示体,这些数据后续会用到!

     

    基本通道像素着色器

    现在我们知道阴影光照创建了一个屏幕空间阴影纹理,我们可以回过头来看看基本通道像素着色器的工作原理。提醒一下,这是针对场景中的每个光照运行的,因此对于具有多个光照影响的任何对象的每个像素将计算多次。像素着色器可以非常简单,我们会对这个像素着色器调用的函数更感兴趣。

     

    void RadialPixelMain( float4 InScreenPosition, float4 SVPos, out float4 OutColor)
    {
    // Intermediate variables have been removed for brevity
    FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(ScreenUV);
    FDeferredLightData LightData = SetupLightDataForStandardDeferred();

    OutColor = GetDynamicLighting(WorldPosition, CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, GetPerPixelLightAttenuation(ScreenUV), Dither, Random);

    OutColor *= ComputeLightProfileMultiplier(WorldPosition, DeferredLightUniforms_LightPosition,     DeferredLightUniforms_NormalizedLightDirection);
    }

    只有几个功能,所以直接跳过每个的具体实现。GetScreenSpaceData从给定像素的GBuffer中检索信息。 SetupLightDataForStandardDeferred计算诸如光方向,光色,衰减等信息。最后,它调用GetDynamicLighting并传入我们到目前为止计算的所有数据 - 像素在哪里,GBuffer数据是什么,什么是Shading Model ID 使用,和我们的光信息。

     

    获得动态照明

    GetDynamicLighting函数(位于DeferredLightingCommon.ush)很长,看起来很复杂,但复杂因素很多是因为各种光照的不同设置。此函数计算初始值为1.0的SurfaceShadowSubsurfaceShadow变量 - 如果存在阴影,则该值变为小于1。这一点非常重要,因为我们稍后会将值与它相乘,所以现在只需知道更高的值表示更浅的阴影。

     

    如果启用了阴影,则GetShadowTerms会被调用。这使用之前的光衰减缓冲(称为ScreenShadowMaskTexture)来确定给定像素的阴影项。阴影数据可以来自大量不同的地方,(虚幻存储光功能+ z通道中的每个对象阴影,w中的每个对象次表面散射,x中的整个场景平行光阴影和y中整个场景平行光的表面散射,来自相应的GBuffer通道的静态阴影),GetShadowTerms将此信息写入我们之前的SurfaceShadowSubsurfaceShadow变量。

     

    现在我们已经确定了表面和次表面的阴影因子,我们计算了光衰减。衰减实际上是基于与光的距离的能量衰减,并且可以被修改以产生不同的效果,即:卡通渲染经常从计算中去除衰减,使得到光源的距离无关紧要。Unreal根据距离,光半径和衰减以及阴影项分别计算SurfaceAttenuationSubsurfaceAttenuation。阴影与衰减绑定,这意味着我们未来的计算仅考虑衰减强度。

     

    最后,我们计算了这个像素的表面着色。表面着色计算使用到了GBuffer, Surface Roughness, Area Light Specular, Light Direction, View Direction和Normal。Roughness由我们的GBuffer数据决定。Area Light Specular使用基于物理的渲染(基于我们的光数据和Roughness)来计算新的能量值,并可以修改Roughness和光矢量。

     

    标准着色模型在其计算中使用各种数据

     

    Surface Shading最终让我们有机会修改每个表面如何响应这些数据。这个函数位于ShadingModels.ush中,它只是一个很大的switch语句,用来查看我们之前写入GBuffer中的ShadingModel ID!许多照明模型共享标准着色功能,但一些更不寻常的着色模型使用自己的自定义实现。表面着色不考虑衰减,因此它仅涉及计算没有阴影的表面颜色。

     

    在Light Accumulator运行之前,不会考虑衰减(距离+阴影)。 Light Accumulator将表面照明和衰减考虑在内,并在将它们乘以光衰减值后正确地将表面和次表面照明相加。

     

    最后,动态光照函数返回Light Accumulator累积的总光照。实际上,这只是表面+次表面照明,但代码因次表面属性和调试选项而变得复杂。

     

    ComputeLightProfileMultiplier

    最后,DeferredLightPixelShader所做的最后一件事是将GetDynamicLighting计算的颜色乘以ComputeLightProfileMultiplier中的值。此函数允许使用1D IES光轮廓纹理。如果没有将IES光照配置文件用于该光照,则不会更改结果值。

     

    累积光

    因为BasePassPixelShaders是针对影响对象的每个光照执行的,所以Unreal会累积此灯光并将其存储在缓冲区中。在ResolveSceneColor步骤中稍后几步之前,此缓冲区甚至不会被绘制到屏幕上。这里之前计算了几个额外的东西,例如半透明物体(使用传统的前向渲染技术绘制),屏幕空间temporal anti aliasing和屏幕空间反射。

     

    综述

    对于每个光照,阴影数据在屏幕空间中计算,并结合静态阴影、次表面阴影和方向阴影,然后绘制每个光的近似几何图形,并绘制该光对每个像素的影响。表面着色基于GBuffer数据和着色模型计算,然后乘以光衰减。光衰减是光设置(距离,衰减等)和阴影采样的组合。每个表面阴影的输出累积在一起以产生最终的光值

     

    下一章

    我们已经介绍了有关渲染系统的大量背景信息以及不同部分如何组合在一起。 在下一篇文章中,我们将开始讨论着色器排列,并试图找到减少重新编译着色器所需时间的方法,这样您就可以花更少的时间等待,而且可以花更多的时间进行编码!

    展开全文
  • 我专栏的目录小IVan:专题概述及目录​zhuanlan.zhihu.com本节使用的是UE4.17,如果使用4.19或以上版本的话会有一些不同。UE4.19又把这些封装了一下。从原理上为了解释清楚我还是选择了比较原始的4.17版本。...

    我专栏的目录

    小IVan:专题概述及目录zhuanlan.zhihu.com图标

    本节使用的是UE4.17,如果使用4.19或以上版本的话会有一些不同。UE4.19又把这些封装了一下。从原理上为了解释清楚我还是选择了比较原始的4.17版本。后面的章节会把它改造成现在的版本。

    还是先上效果吧

    上篇文章分析了虚幻自带的CableComponent并且在unity中重现了CableComponent,这篇将开始着手自己进行图元汇编的编程。我们先做一个简单的,后续再慢慢做一些比较好玩的图元汇编相关的效果。

    v2-f2546f9f279eca809cd37e1a0a798085_b.gif
    这个GIF在网页上的表现优点奇怪,我这里看到有残影,其实是没有的哈
    v2-6070db4aa860f3d8855d1910602441b7_b.jpg

    首先我们先建一个新的插件

    v2-6ca02c9eb3811e5c18437d27538e07f6_b.jpg

    我给它命名为RayLine,你也可以给它叫个什么其他名字,然后在插件里加一个头文件和源文件

    v2-543ecd422637d4007849f83eea13ceb2_b.jpg

    然后在Build.cs文件里引入我们需要的模块

    v2-a8966dddef89062c484c0bfddc7812dc_b.jpg

    然后我们在RayBasicComponent.h中键入我们的新类型

    v2-aba530ce174b0f63556c108181bf4725_b.jpg

    我随便定义了一个结构体。

    然后我们来定义我们的组件类型

    v2-e5cb0442b1353fe4662b7777da3ae5ba_b.jpg

    我们的这个类型继承自UMeshComponent,然后有一个RAYLINE_API类导出宏,这个宏的名字必须和我们模块的名字保持一致,我们的模块就是我们的插件啦。

    然后我们声明了一个GENERATED_UCLASS_BODY(),这个宏会帮我们自动生成默认构造函数,我们这里如果再敲构造函数,编译器就会报错了,所以我们这里什么都不需要干,如果我们这里使用的是GENERATED_BODY()宏的话就需要写构造函数了。

    然后我声明了一个变量DebugSec并给他UPROPERTY宏,这样编辑器就能认识它了,它也会被纳入垃圾回收里。

    然后我们重写了virtual FPrimitiveSceneProxy* CreateSceneProxy() override函数,这个函数创建场景代理,场景代理的作用就是负责在渲染线程端把逻辑线程这边的数据压入渲染管线,下面我画个图感受一下:


    v2-beca70e98f1d5ad8ba1d86ddc803084f_b.jpg

    我们这里只需要管到把我们的模型加入渲染队列就可以了(红圈圈起来的部分),至于后面的事情就是引擎帮我们处理了,当然如果你不想使用引擎的渲染管线把后面一大片自己撸一套新机制出来也行。

    然后来到我们的RayBasicComponent.cpp

    我们先把需要的头文件包含进来

    v2-8b5e1c43c08447ce1b3b2dd62542759e_b.jpg

    然后定义我们的顶点缓冲区,注意这个和Shader篇的顶点着色器是两个东西哈。

    v2-14200508fdba3712dbbf50184600028b_b.jpg

    然后是我们的索引缓冲区

    v2-a6bb97172ba8d0012ef185b3090c5d3d_b.jpg

    然后是顶点输入布局

    v2-5afcd6f2116a504d0f179d885c9b2997_b.jpg

    然后是一个结构体,把它视作一个数据包,方便我们从逻辑层把数据们打包一起发送到渲染线程

    v2-1c5c671c443824f03ec85e22e486c47d_b.jpg

    然后有了这些资源后,我们就可以开始实现我们的场景代理了。

    v2-1f3823a260e4ed12e65e20a45f67b7b5_b.jpg

    我们先在我们的代理类中加入一些成员

    v2-1c86dcab94495069d2168e3c24f7db6b_b.jpg

    然后就是我们的构造函数了

    v2-268d2bce23d34f70368cec0dce7ffb14_b.jpg

    这里它派生自FPrimitiveSceneProxy,然后在构造函数里我们初始化了顶点缓存的顶点数量然后初始化了我们的顶点缓存,输入布局工厂,索引缓存

    这里的GetRequriedVertexCount函数我直接先返回几个值

    v2-2eb9aa0a2d217b7acb606848e2386250_b.jpg

    然后是析构函数,我们要自己控制释放资源。

    v2-3e84855cf251bba53858401c22ccb173_b.jpg

    然后是GetDynamicMeshElement函数,这个函数负责把模型数据加入到绘制队列,注意我们仅到此为止,后面真正的绘制都不是我们管的了。

    v2-36c13c45c914c7f02a4a4b9a485988ce_b.jpg

    这里需要注意一下:

    v2-b9dc693229707f969600bd91919385b5_b.jpg

    红线勾画的这里的树木要和顶点缓存和索引缓存的数目有关系了,一定要匹配,不如会报错。

    然后是以下四个函数

    v2-748f2132efa835c4b379693560a28c09_b.jpg

    因为我们这个是动态的模型,所以需要有一个函数负责在渲染线程接收逻辑层发送过来的数据

    v2-6c83410b1162ecd569af087fb50d4ea6_b.jpg

    然后是BuildMesh函数

    v2-7d9e3a9abf966ec1dcc44466fa47d006_b.jpg

    完成了场景代理,我们下一步就是开始实现URayBasicComponent部分

    先是构造函数

    v2-2756912a25bbae9230716ddf742482b2_b.jpg

    一定要把三个开关打开,这样才能调用ComponentTick这些函数来更新我们的组件。

    v2-991d2de8e164377086c0c824aed065fd_b.jpg

    然后是注册组件,注意最后的MarkRenderDynamicDataDirty();这个函数,这个函数会开启一个开关,让引擎每帧更新所有组件渲染状态的时候,会更新到我们的组件。

    然后是tickcomponent

    v2-257f78f20b688493105d4a1ae9454a8f_b.jpg

    这个MarkRenderDynamicDataDirty();函数如果被调用,为组件开启了渲染状态开关,那么引擎就会自己调用我们下面的两个函数了

    v2-57ad7fc10b279fb867b04aeb74760296_b.jpg

    这两个函数才是真正负责把逻辑线程的数据发送到渲染线程的。注意红线勾的那个函数就是我们自己在场景代理里自己定义的接收函数。

    v2-088d3b07311e37672a482a4ffcdb910d_b.jpg

    然后是创建场景代理,获取材质,构建包围盒的操作了。

    至此我们就通过一个简短的框架自己编辑了顶点缓存和索引缓存然后上传它并且渲染它,这只是个简单的框架,后面我们将慢慢基于此框架做更多有趣的效果。

    下面是完整代码:

    RayBasicComponent.h

    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/ObjectMacros.h"
    #include "Components/MeshComponent.h"
    #include "RayBasicComponent.generated.h"
    
    class FPrimitiveSceneProxy;
    
    USTRUCT(BlueprintType)
    struct FRayLineHitPointDesc
    {
    	GENERATED_USTRUCT_BODY()
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RayLineHitPoint)
    	FVector HitPosition;
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RayLineHitPoint)
    	FVector HitNextDir;
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RayLineHitPoint)
    	int32 HitPointIndex;
    };
    
    /** Component that allows you to specify custom triangle mesh geometry */
    UCLASS(hidecategories = (Object, LOD, Physics, Collision), editinlinenew, meta = (BlueprintSpawnableComponent), ClassGroup = Rendering)
    class RAYLINE_API URayBasicComponent : public UMeshComponent
    {
    	GENERATED_UCLASS_BODY()
    
    public:
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = RayLineComponent)
    	float DebugSec;
    
    private:
    
    	//~ Begin UPrimitiveComponent Interface.
    	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
    	//~ End UPrimitiveComponent Interface.
    
    	//~ Begin UMeshComponent Interface.
    	virtual int32 GetNumMaterials() const override;
    	//~ End UMeshComponent Interface.
    
    	//~ Begin USceneComponent Interface.
    	virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
    	//~ Begin USceneComponent Interface.
    
    	//~ Begin UActorComponent Interface.
    	virtual void OnRegister() override;
    	virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override;
    	virtual void SendRenderDynamicData_Concurrent() override;
    	virtual void CreateRenderState_Concurrent() override;
    	//~ End UActorComponent Interface.
    
    	TArray<FRayLineHitPointDesc> RayLineHitPoints;
    
    	friend class FRayLineMeshSceneProxy;
    };
    

    RayBasicComponent.cpp

    // Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. 
    
    #include "RayBasicComponent.h"
    #include "RenderingThread.h"
    #include "RenderResource.h"
    #include "PrimitiveViewRelevance.h"
    #include "PrimitiveSceneProxy.h"
    #include "VertexFactory.h"
    #include "MaterialShared.h"
    #include "Engine/CollisionProfile.h"
    #include "Materials/Material.h"
    #include "LocalVertexFactory.h"
    #include "SceneManagement.h"
    #include "DynamicMeshBuilder.h"
    #include "EngineGlobals.h"
    #include "Engine/Engine.h"
    
    /** Vertex Buffer */
    class FRayLineMeshVertexBuffer : public FVertexBuffer
    {
    public:
    	
    	virtual void InitRHI() override
    	{
    		FRHIResourceCreateInfo CreateInfo;
    		VertexBufferRHI = RHICreateVertexBuffer(NumVerts * sizeof(FDynamicMeshVertex), BUF_Dynamic, CreateInfo);
    	}
    
    	int32 NumVerts;
    };
    
    /** Index Buffer */
    class FRayLineMeshIndexBuffer : public FIndexBuffer
    {
    public:
    	
    	virtual void InitRHI() override
    	{
    		FRHIResourceCreateInfo CreateInfo;
    		IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), NumIndices * sizeof(int32), BUF_Dynamic, CreateInfo);
    	}
    
    	int32 NumIndices;
    };
    
    /** Vertex Factory */
    class FCustomMeshVertexFactory : public FLocalVertexFactory
    {
    public:
    
    	FCustomMeshVertexFactory()
    	{}
    
    
    	/** Initialization */
    	void Init(const FRayLineMeshVertexBuffer* VertexBuffer)
    	{
    		if (IsInRenderingThread())
    		{
    			// Initialize the vertex factory's stream components.
    			FDataType NewData;
    			NewData.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, Position, VET_Float3);
    			NewData.TextureCoordinates.Add(
    				FVertexStreamComponent(VertexBuffer, STRUCT_OFFSET(FDynamicMeshVertex, TextureCoordinate), sizeof(FDynamicMeshVertex), VET_Float2)
    			);
    			NewData.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentX, VET_PackedNormal);
    			NewData.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer, FDynamicMeshVertex, TangentZ, VET_PackedNormal);
    			SetData(NewData);
    		}
    		else
    		{
    			ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
    				InitCableVertexFactory,
    				FCustomMeshVertexFactory*, VertexFactory, this,
    				const FRayLineMeshVertexBuffer*, VertexBuffer, VertexBuffer,
    				{
    					// Initialize the vertex factory's stream components.
    					FDataType NewData;
    			NewData.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer,FDynamicMeshVertex,Position,VET_Float3);
    			NewData.TextureCoordinates.Add(
    				FVertexStreamComponent(VertexBuffer,STRUCT_OFFSET(FDynamicMeshVertex,TextureCoordinate),sizeof(FDynamicMeshVertex),VET_Float2)
    			);
    			NewData.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer,FDynamicMeshVertex,TangentX,VET_PackedNormal);
    			NewData.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(VertexBuffer,FDynamicMeshVertex,TangentZ,VET_PackedNormal);
    			VertexFactory->SetData(NewData);
    				});
    		}
    	}
    };
    
    struct FRayLineDynamicData
    {
    	TArray<FVector> HitpointsPosition;
    	//You can also define some other data to send
    };
    
    /** Scene proxy */
    class FRayLineMeshSceneProxy : public FPrimitiveSceneProxy
    {
    public:
    
    	FRayLineMeshSceneProxy(URayBasicComponent* Component)
    		: FPrimitiveSceneProxy(Component)
    		, MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel()))
    	{
    
    		VertexBuffer.NumVerts = GetRequiredVertexCount();
    		IndexBuffer.NumIndices = GetRequiredIndexCount();
    
    		const FColor VertexColor(255, 255, 255);
    
    		// Init vertex factory
    		VertexFactory.Init(&VertexBuffer);
    
    		// Enqueue initialization of render resource
    		BeginInitResource(&VertexBuffer);
    		BeginInitResource(&IndexBuffer);
    		BeginInitResource(&VertexFactory);
    
    		// Grab material
    		Material = Component->GetMaterial(0);
    		if (Material == NULL)
    		{
    			Material = UMaterial::GetDefaultMaterial(MD_Surface);
    		}
    
    	}
    
    	void BuildMesh(FRayLineDynamicData* NewDynamicData)
    	{
    		
    		TArray<FDynamicMeshVertex> Vertices;
    		TArray<int32> Indices;
    		for (int32 i = 0; i < NewDynamicData->HitpointsPosition.Num(); i++)
    		{
    			FDynamicMeshVertex newvert0;
    			newvert0.Position = NewDynamicData->HitpointsPosition[i] + FVector(-100, 100, 0);
    			FDynamicMeshVertex newvert1;
    			newvert1.Position = NewDynamicData->HitpointsPosition[i] + FVector(100, 100, 0);
    			FDynamicMeshVertex newvert2;
    			newvert2.Position = NewDynamicData->HitpointsPosition[i] + FVector(-100, -100, 0);
    			FDynamicMeshVertex newvert3;
    			newvert3.Position = NewDynamicData->HitpointsPosition[i] + FVector(100, -100, 0);
    
    			Vertices.Add(newvert0);
    			Vertices.Add(newvert1);
    			Vertices.Add(newvert2);
    			Vertices.Add(newvert3);
    
    			Indices.Add(4 * i);
    			Indices.Add(4 * i + 1);
    			Indices.Add(4 * i + 2);
    			Indices.Add(4 * i + 1);
    			Indices.Add(4 * i + 3);
    			Indices.Add(4 * i + 2);
    		}
    		
    		check(Vertices.Num() == GetRequiredVertexCount());
    		check(Indices.Num() == GetRequiredIndexCount());
    
    		void* VertexBufferData = RHILockVertexBuffer(VertexBuffer.VertexBufferRHI, 0, Vertices.Num() * sizeof(FDynamicMeshVertex), RLM_WriteOnly);
    		FMemory::Memcpy(VertexBufferData, &Vertices[0], Vertices.Num() * sizeof(FDynamicMeshVertex));
    		RHIUnlockVertexBuffer(VertexBuffer.VertexBufferRHI);
    
    		void* IndexBufferData = RHILockIndexBuffer(IndexBuffer.IndexBufferRHI, 0, Indices.Num() * sizeof(int32), RLM_WriteOnly);
    		FMemory::Memcpy(IndexBufferData, &Indices[0], Indices.Num() * sizeof(int32));
    		RHIUnlockIndexBuffer(IndexBuffer.IndexBufferRHI);
    	}
    
    	/** Called on render thread to assign new dynamic data */
    	void SetDynamicData_RenderThread(FRayLineDynamicData* NewDynamicData)
    	{
    		check(IsInRenderingThread());
    
    		BuildMesh(NewDynamicData);
    
    	}
    
    	virtual ~FRayLineMeshSceneProxy()
    	{
    		VertexBuffer.ReleaseResource();
    		IndexBuffer.ReleaseResource();
    		VertexFactory.ReleaseResource();
    	}
    
    	int32 GetRequiredVertexCount() const
    	{
    		return 40;
    	}
    
    	int32 GetRequiredIndexCount() const
    	{
    		return 60;
    	}
    
    	virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
    	{
    		QUICK_SCOPE_CYCLE_COUNTER(STAT_FRayLineMeshSceneProxy_GetDynamicMeshElements);
    
    		const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;
    
    		auto WireframeMaterialInstance = new FColoredMaterialRenderProxy(
    			GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : NULL,
    			FLinearColor(0, 0.5f, 1.f)
    		);
    
    		Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);
    
    		FMaterialRenderProxy* MaterialProxy = NULL;
    		if (bWireframe)
    		{
    			MaterialProxy = WireframeMaterialInstance;
    		}
    		else
    		{
    			MaterialProxy = Material->GetRenderProxy(IsSelected());
    		}
    
    		for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
    		{
    			if (VisibilityMap & (1 << ViewIndex))
    			{
    				const FSceneView* View = Views[ViewIndex];
    				// Draw the mesh.
    				FMeshBatch& Mesh = Collector.AllocateMesh();
    				FMeshBatchElement& BatchElement = Mesh.Elements[0];
    				BatchElement.IndexBuffer = &IndexBuffer;
    				Mesh.bWireframe = bWireframe;
    				Mesh.VertexFactory = &VertexFactory;
    				Mesh.MaterialRenderProxy = MaterialProxy;
    				BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, UseEditorDepthTest());
    				BatchElement.FirstIndex = 0;
    				BatchElement.NumPrimitives = GetRequiredIndexCount() / 3;
    				BatchElement.MinVertexIndex = 0;
    				BatchElement.MaxVertexIndex = GetRequiredVertexCount();
    				Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
    				Mesh.Type = PT_TriangleList;
    				Mesh.DepthPriorityGroup = SDPG_World;
    				Mesh.bCanApplyViewModeOverrides = false;
    				Collector.AddMesh(ViewIndex, Mesh);
    			}
    		}
    	}
    
    	virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
    	{
    		FPrimitiveViewRelevance Result;
    		Result.bDrawRelevance = IsShown(View);
    		Result.bShadowRelevance = IsShadowCast(View);
    		Result.bDynamicRelevance = true;
    		Result.bRenderInMainPass = ShouldRenderInMainPass();
    		Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask();
    		Result.bRenderCustomDepth = ShouldRenderCustomDepth();
    		MaterialRelevance.SetPrimitiveViewRelevance(Result);
    		return Result;
    	}
    
    	virtual bool CanBeOccluded() const override
    	{
    		return !MaterialRelevance.bDisableDepthTest;
    	}
    
    	virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); }
    
    	uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); }
    
    private:
    
    	UMaterialInterface * Material;
    	FRayLineMeshVertexBuffer VertexBuffer;
    	FRayLineMeshIndexBuffer IndexBuffer;
    	FCustomMeshVertexFactory VertexFactory;
    
    	FMaterialRelevance MaterialRelevance;
    };
    
    //
    
    URayBasicComponent::URayBasicComponent(const FObjectInitializer& ObjectInitializer)
    	: Super(ObjectInitializer)
    {
    	PrimaryComponentTick.bCanEverTick = true;
    	bTickInEditor = true;
    	bAutoActivate = true;
    
    	DebugSec = 200.0f;
    
    }
    
    void URayBasicComponent::OnRegister()
    {
    	Super::OnRegister();
    
    	RayLineHitPoints.Reset();
    	FVector RayDirection = FVector(1.0f, 0.0f, 0.0f);
    	FVector RayOrigin = FVector(0.0f, 0.0f, 0.0f);
    	int32 HitPointsNum = 10;
    	float SecLength = 50.0f;
    	
    	RayLineHitPoints.AddUninitialized(HitPointsNum);
    	RayLineHitPoints[0].HitPosition = RayOrigin;
    	RayLineHitPoints[0].HitNextDir = RayDirection;
    
    	float t = DebugSec;
    	for (int32 i = 1; i < HitPointsNum; i++)
    	{
    		RayLineHitPoints[i].HitPosition = RayDirection * t + RayOrigin;
    
    		t += DebugSec;
    	}
    
    	MarkRenderDynamicDataDirty();
    }
    
    void URayBasicComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
    {
    	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    
    	RayLineHitPoints.Reset();
    	FVector RayDirection = FVector(1.0f, 0.0f, 0.0f);
    	FVector RayOrigin = FVector(0.0f, 0.0f, 0.0f);
    	int32 HitPointsNum = 10;
    	float SecLength = 50.0f;
    
    	RayLineHitPoints.AddUninitialized(HitPointsNum);
    	RayLineHitPoints[0].HitPosition = RayOrigin;
    	RayLineHitPoints[0].HitNextDir = RayDirection;
    
    	float t = DebugSec;
    	for (int32 i = 1; i < HitPointsNum; i++)
    	{
    		RayLineHitPoints[i].HitPosition = RayDirection * t + RayOrigin;
    
    		t += DebugSec;
    	}
    
    	// Need to send new data to render thread
    	MarkRenderDynamicDataDirty();
    
    	UpdateComponentToWorld();
    }
    
    void URayBasicComponent::CreateRenderState_Concurrent()
    {
    	Super::CreateRenderState_Concurrent();
    
    	SendRenderDynamicData_Concurrent();
    }
    
    void URayBasicComponent::SendRenderDynamicData_Concurrent()
    {
    	if (SceneProxy)
    	{
    		FRayLineDynamicData* NewDynamicData = new FRayLineDynamicData;
    		NewDynamicData->HitpointsPosition.AddUninitialized(RayLineHitPoints.Num());
    		for (int32 i = 0; i < RayLineHitPoints.Num(); i++)
    		{
    			NewDynamicData->HitpointsPosition[i] = RayLineHitPoints[i].HitPosition;
    		}
    
    		// Enqueue command to send to render thread
    		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
    			FSendRayBasicComponentDynamicData,
    			FRayLineMeshSceneProxy*, CableSceneProxy, (FRayLineMeshSceneProxy*)SceneProxy,
    			FRayLineDynamicData*, NewDynamicData, NewDynamicData,
    			{
    				CableSceneProxy->SetDynamicData_RenderThread(NewDynamicData);
    			});
    	}
    }
    
    FPrimitiveSceneProxy* URayBasicComponent::CreateSceneProxy()
    {
    	return new FRayLineMeshSceneProxy(this);
    }
    
    int32 URayBasicComponent::GetNumMaterials() const
    {
    	return 1;
    }
    
    FBoxSphereBounds URayBasicComponent::CalcBounds(const FTransform& LocalToWorld) const
    {
    	FBoxSphereBounds NewBounds;
    	NewBounds.Origin = FVector::ZeroVector;
    	NewBounds.BoxExtent = FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX);
    	NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
    	return NewBounds;
    }
    

    RayLine.Build.cs

    v2-937179450bfc31ba03af0ca20d44dd98_b.jpg

    Enjoy It!

    展开全文
  • 这篇文章里面完全是中文翻译,不含英文,只是为了不熟悉英文又想快速了解AnimMontage的朋友,由于水平有限,里面一些翻译掺杂了我个人的理解,如果有任何问题欢迎提出,我会及时修改的~同时这里需要声明一点,官方的...
  • OpenGl 坐标转换

    千次阅读 2013-08-06 17:01:09
    下面这篇文章详细讲述了OpenGL里的坐标转换,清晰,明了...比如什么顶点着色、光栅化、送至帧缓存都没有涉及到。 原文地址:http://blog.csdn.net/zhulinpptor/article/details/5897102 1. OpenGL 渲染管线 OpenGL
  • 最近学习UE4的使用,发现这篇官方文档没有中文翻译,就想着翻译一下给大家参考参考吧。由于水平有限,里面一些翻译掺杂了我个人的理解,如果有任何问题欢迎提出,我会及时修改的~同时这里需要声明一点,官方的中文...
  • UnrealEngine3-渲染构架

    千次阅读 2013-11-03 21:23:53
    OverviewThis page covers all areas of the rendering subsystem in Unreal Engine 3.Getting StartedThere's a lot of rendering code in UE3 so it's hard to get a quick high level view of what's going on....
  • 首先看UE4中的 Third-Party Libraries UnrealEngine\Engine\Source\ThirdParty 下: 源码路径: exe的安装路径下: ADO 注意:这只是为Windows访问权限设置了Windows包含的一组lib的include路径。 AMD...
  • OpenGL 纹理映射

    千次阅读 2014-02-06 11:11:38
    纹理映射 (texture mapping) 本章你将学到 ...纹理映射基础知识纹理坐标纹理对象及绑定纹理过滤mipmap和自动生成mipmap纹理参数, 外包模式, 细节级别纹理环境和纹理函数 ...3.1 纹理对象3.2 纹理绑定3.3 删除...
  • 这篇文章主要是我对UE4中Shader编译过程以及变种的理解,了解这一块还是挺有必要的,毕竟动辄几千上万个Shader的编译在UE里简直是家常便饭。了解它底层的实现机制后内心踏实一点,如果要去修改,大方向也不会错。 ...
  • UE4优化

    2018-09-29 11:47:01
    https://www.unrealengine.com/zh-CN/blog/chn-pubg-mobile-ue4-development-experience https://docs.unrealengine.com/en-us/Engine/Performance/Guidelines 不透明性能&amp;amp;amp;amp;amp;amp;gt;cut out...
  • UE4性能优化

    千次阅读 2019-01-15 16:46:48
    UE4性能优化 摘自:https://blog.csdn.net/u011349044/article/details/80080244 在使用虚幻4做项目的过程中性能一直是不可忽视的话题,为了画面效果我们至少需要每秒20帧左右。根据不同的项目要求这个帧数可以能...
  • 这是侑虎科技第581篇文章,感谢作者王文涛供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859) 作者主页:... 动机和目...
  • (这当且仅当做Unreal的帮助文档)。 图形编程 模块 渲染器代码存在于其自身的模块中。此模块将编译为非单块版本的一个 dll 文件。这可以使迭代更快,因为在渲染代码变更时无需重新链接整个应用程序。渲染器...
  • 本篇继续Unreal搬山部分的渲染模块的Shader部分, 主要牵扯模块RenderCore, ShaderCore, RHI, Materia. 可能分成N篇。 (这里放入的UE的模块框) (下一篇主要是UE灯光和着色简要[ush以及对应结构,和UE代码和...
  • UE4 渲染数据传输

    2020-02-17 11:39:47
    Unreal Mesh Drawing 内存中的mesh是如何进行渲染的,主要涉及几个重要的数据结构 UPrimitiveComponent PrimitiveSceneProxy FMeshBatch FMeshDrawCommand 存在于Game Thread内的数据UPrimitiveComponent ...
  • OSG 设置背景,前景

    千次阅读 2014-10-22 13:14:39
    1。设置背景 #include #include #include ...HUD中的相机设置渲染顺序为PRE_RENDER,主相机清除深度缓存. */ osg::Camera* createHUDBg(std::string imagePath){ osg::ref_ptrcamera=new osg::Ca
  • UNREAL ENGINE 4.13 正式发布!

    千次阅读 2016-09-09 19:56:42
    这次的版本带来了数百个虚幻引擎 4 的更新,包括来自 GitHub 的社区成员们提交的 145 个改进!感谢所有为虚幻引擎 4 添砖加瓦贡献的人们: alk3ovation, Allegorithmic (Allegorithmic), Alwin Tom (alwintom), ...
1 2 3 4 5 ... 17
收藏数 321
精华内容 128
热门标签
关键字:

unreal4 修改顶点缓存