找回密码
 立即注册
查看: 208|回复: 0

UE4.27自定义光照模型

[复制链接]
发表于 2023-3-6 16:31 | 显示全部楼层 |阅读模式
概述:

本篇只是记录自己学习在UE4中自定义光照模型的一个过程.
在自定义光照模型时,需要分为两个层面:

  • C++层
  • Shader层
实践:

C++层:


  • EnguneTypes.h
  • HLSLMaterialTranslator.cpp
  • Material.cpp
1.打开EnguneTypes.h,找到EMaterialShadingModel,可以看到UE默认的光照模型枚举,在下面我们可以添加新的光照模型枚举(此时我们只是添加了一个切换枚举,可以看到材质编辑器下出现了我们自定义的shadingModel):


2.现在切换到自定义光照模型之后,UE也不知道我们要做什么。那么如何让UE知道切换光照模型分支呢?(TODO:材质编辑器是如何压入HLSL的)
找到HLSLMaterialTranslator.cpp,在GetMaterialEnvironment中找到光照模型判断,添加我们自己的宏




3.当切换光照模型之后,我们需要新的接口暴露怎么办呢?
打开Material.cpp,寻找名为IsPropertyActive的函数,它调用的IsPropertyActiveInDerived函数,继续查看,发现真正的定义在IsPropertyActive_Internal函数内。可以暴露激活任意参数,此次测试只暴露customData0、customData1(它们是一组范围为0-1的float值)。






编译。使用我们自定义光照模型会发现customData0、customData1已经激活。至此,C++层面已经修改完毕,回到shader层面。


Shader层面:


  • ShandingCommon.ush
  • BasePassCommon.ush
  • ShadingModelsMaterial.ush
  • DeferredShadingCommon.ush
  • Shadingmodels.ush
  • DeferredLightingCommon.ush
1.在ShandingCommon.ush中定义一个新的宏,做出如下图修改,注意排序。这个宏是告诉我们,模型的shader是使用的哪种光照模型。


在下面可以找到GetShadingModelColor函数,这个函数设置不同光照模型的返回颜色。我们给我们的光照模型定义一个土黄色(0.3f, 0.2f, 0.1f).


做完之后编译,回到编辑器。我们就能在ShadingModle预览模式下看到如下效果:


2.在BasePassCommon.ush中修改宏,允许我们将customData0、customData1写入GBuffer
// Only some shader models actually need custom data.
#define WRITES_CUSTOMDATA_TO_GBUFFER      (USES_GBUFFER && (MATERIAL_SHADINGMODEL_SUBSURFACE || MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN || MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || MATERIAL_SHADINGMODEL_CLEAR_COAT || MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE || MATERIAL_SHADINGMODEL_HAIR || MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_EYE || MATERIAL_SHADINGMODEL_NEWTESTSHADINGMODEL)) //begin //end3.再然后,在ShadingModelsMaterial.ush中处理这两个数据


4.进入DeferredShadingCommon.ush,找到HasCustomGBufferData函数,加入我们自定义光照模型的判定(如果这一步不加入,则GBuffer无法读到customData0、customData1)


5.然后在Shadingmodels.ush中对我们的光照模型进行自定义的BRDF。


我直接Copy了大佬的代码,只修改了函数名------源码如下:
float3 CustomStep(float Range, float Input)
{
   return smoothstep(0.5 - Range, 0.5 + Range, Input);
}
FDirectLighting NewTestShadingModelBxDF(FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow)
{
#if GBUFFER_HAS_TANGENT
   half3 X = GBuffer.WorldTangent;
   half3 Y = normalize(cross(N, X));
#else
   half3 X = 0;
   half3 Y = 0;
#endif
   BxDFContext Context;
   Init(Context, N, X, Y, V, L);
   SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);
   Context.NoV = saturate(abs(Context.NoV) + 1e-5);
   float SpecularOffset = 0.5;
   float SpecularRange = GBuffer.CustomData.x;
   float3 ShadowColor = 0;
   ShadowColor = GBuffer.DiffuseColor * ShadowColor;
   float offset = GBuffer.CustomData.y;
   float SoftScatterStrength = 0;
   offset = offset * 2 - 1;
   half3 H = normalize(V + L);
   float NoH = saturate(dot(N, H));
   NoL = (dot(N, L) + 1) / 2; // overwrite NoL to get more range out of it
   half NoLOffset = saturate(NoL + offset);
   FDirectLighting Lighting;
   Lighting.Diffuse = AreaLight.FalloffColor * (smoothstep(0, 1, NoLOffset) * Falloff) * Diffuse_Lambert(GBuffer.DiffuseColor) * 2.2;
   float InScatter = pow(saturate(dot(L, -V)), 12) * lerp(3, .1f, 1);
   float NormalContribution = saturate(dot(N, H));
   float BackScatter = GBuffer.GBufferAO * NormalContribution / (PI * 2);
   Lighting.Specular = CustomStep(SpecularRange, (saturate(D_GGX(SpecularOffset, NoH)))) * (AreaLight.FalloffColor * GBuffer.SpecularColor * Falloff * 8);
   float3 TransmissionSoft = AreaLight.FalloffColor * (Falloff * lerp(BackScatter, 1, InScatter)) * ShadowColor * SoftScatterStrength;
   float3 ShadowLightener = 0;
   ShadowLightener = (saturate(smoothstep(0, 1, saturate(1 - NoLOffset))) * ShadowColor * 0.1);
   Lighting.Transmission = (ShadowLightener + TransmissionSoft) * Falloff;
   return Lighting;
}在下面找到IntegrateBxDF函数,对上一步我们定义的函数调用:


6.其实到这里,一个基本的自定义光照模型的流程就结束了,为了符合我们这个光照模型(让它更好看),我们最后在DeferredLightingCommon.ush中进行一些修改;
/** Calculates lighting for a given position, normal, etc with a fully featured lighting model designed for quality. */
FDeferredLightingSplit GetDynamicLightingSplit(
   float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID,
   FDeferredLightData LightData, float4 LightAttenuation, float Dither, uint2 SVPos, FRectTexture SourceTexture,
   inout float SurfaceShadow)
{
   FLightAccumulator LightAccumulator = (FLightAccumulator)0;
   float3 V = -CameraVector;
   float3 N = GBuffer.WorldNormal;
   BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL)
   {
      const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal);
      N = OctahedronToUnitVector(oct1);        
   }
   float3 L = LightData.Direction;    // Already normalized
   float3 ToLight = L;
   float LightMask = 1;
   if (LightData.bRadialLight)
   {
      LightMask = GetLocalLightAttenuation( WorldPosition, LightData, ToLight, L );
   }
   LightAccumulator.EstimatedCost += 0.3f;       // running the PixelShader at all has a cost
   BRANCH
   if( LightMask > 0 )
   {
      FShadowTerms Shadow;
      Shadow.SurfaceShadow = AmbientOcclusion;
      Shadow.TransmissionShadow = 1;
      Shadow.TransmissionThickness = 1;
      Shadow.HairTransmittance.OpaqueVisibility = 1;
      GetShadowTerms(GBuffer, LightData, WorldPosition, L, LightAttenuation, Dither, Shadow);
      SurfaceShadow = Shadow.SurfaceShadow;
      LightAccumulator.EstimatedCost += 0.3f;       // add the cost of getting the shadow terms
      BRANCH
      if( Shadow.SurfaceShadow + Shadow.TransmissionShadow > 0 )
      {
         const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID);
         float3 LightColor = LightData.Color;
      //begin
         float3 Attenuation = 1;
      BRANCH
         if (GBuffer.ShadingModelID == SHADINGMODELID_NEWTESTSHADINGMODEL)
         {
            float offset = GBuffer.CustomData.b;
            float TerminatorRange = saturate(GBuffer.Roughness - 0.5);
            offset = offset * 2 - 1;
            BRANCH
            if (offset >= 1)
            {
               Attenuation = 1;
            }
            else
            {
               float NoL = (dot(N, L) + 1) / 2;
               float NoLOffset = saturate(NoL + offset);
               float LightAttenuationOffset = saturate(Shadow.SurfaceShadow + offset);
               float ToonSurfaceShadow = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, LightAttenuationOffset);
               Attenuation = smoothstep(0.5 - TerminatorRange, 0.5 + TerminatorRange, NoLOffset) * ToonSurfaceShadow;
            }
         }
      //end
      #if NON_DIRECTIONAL_DIRECT_LIGHTING
         float Lighting;
         if( LightData.bRectLight )
         {
            FRect Rect = GetRect( ToLight, LightData );
            Lighting = IntegrateLight( Rect, SourceTexture);
         }
         else
         {
            FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
            Lighting = IntegrateLight( Capsule, LightData.bInverseSquared );
         }
         float3 LightingDiffuse = Diffuse_Lambert( GBuffer.DiffuseColor ) * Lighting;
         LightAccumulator_AddSplit(LightAccumulator, LightingDiffuse, 0.0f, 0, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation);
      #else
         FDirectLighting Lighting;
         if (LightData.bRectLight)
         {
            FRect Rect = GetRect( ToLight, LightData );
            #if REFERENCE_QUALITY
               Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture, SVPos );
            #else
               Lighting = IntegrateBxDF( GBuffer, N, V, Rect, Shadow, SourceTexture);
            #endif
         }
         else
         {
            FCapsuleLight Capsule = GetCapsule( ToLight, LightData );
            #if REFERENCE_QUALITY
               Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, SVPos );
            #else
               Lighting = IntegrateBxDF( GBuffer, N, V, Capsule, Shadow, LightData.bInverseSquared );
            #endif
         }
         Lighting.Specular *= LightData.SpecularScale;
         //begin
         BRANCH
         if (GBuffer.ShadingModelID == SHADINGMODELID_NEWTESTSHADINGMODEL)
         {
            LightAccumulator_AddSplit(LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow * Attenuation * 0.25, bNeedsSeparateSubsurfaceLightAccumulation);
         }
         else
         {
            LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );
         }
         //end
         LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );
         LightAccumulator.EstimatedCost += 0.4f;       // add the cost of the lighting computations (should sum up to 1 form one light)
      #endif
      }
   }
   return LightAccumulator_GetResultSplit(LightAccumulator);
}完成。
效果:

编译,让我们看看编辑器里的效果(多灯光测试)。


Reference:

[UE4] Custom Shading Model
虚幻4渲染编程(材质编辑器篇)【第二卷:自定义光照模型】
UE4(虚幻)学习笔记--虚幻4.26自定义shadermodel
Unreal Engine 4 Rendering Part 6: Adding a new Shading Model

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-3 08:33 , Processed in 0.089818 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表