Previously, the engine has supported shaders only if their GLSL code was written as a whole and any change in base shader code -which is produced by a ShaderBuilder class- would most likely break their functionality.

To prevent custom shaders from breaking, I have re-implemented the ShaderBuilder so that it supports every rendering operation (e.g., forward rendering, geometry buffer, shadow map, etc.).

MaterialInitializationData

I achieved this by implementing a MaterialInitializationData class, which holds a snippet of corresponding functionality.

In the MaterialInitializationData class, emissive color, fragment normal, vertex normal, UV, and vertex position offset can be set separately by assigning their calculation and result variables.

The following scripts may look a little complicated, but they will be much easier to implement once there is a material editor via the editor.


struct GOKNAR_API ShaderFunctionAndResult
{
    std::string calculation{ "" };
    std::string result{ "" };
};

struct GOKNAR_API MaterialInitializationData
{
    MaterialInitializationData() = delete;
    MaterialInitializationData(const Material* ownerMaterial) : owner(ownerMaterial) {}

    const Material* owner;
    ShaderFunctionAndResult baseColor;
    ShaderFunctionAndResult emmisiveColor;
    ShaderFunctionAndResult fragmentNormal;
    ShaderFunctionAndResult vertexNormal;
    ShaderFunctionAndResult uv;
    ShaderFunctionAndResult vertexPositionOffset;
    std::string vertexShaderFunctions{ "" };
    std::string fragmentShaderFunctions{ "" };
    std::string vertexShaderUniforms{ "" };
    std::string fragmentShaderUniforms{ "" };

    void AddVertexShaderFunction(const std::string& function)
    {
        vertexShaderFunctions += "\n";
        vertexShaderFunctions += function;
        vertexShaderFunctions += "\n";
    }

    void AddFragmentShaderFunction(const std::string& function)
    {
        fragmentShaderFunctions += "\n";
        fragmentShaderFunctions += function;
        fragmentShaderFunctions += "\n";
    }

    void AddVertexShaderUniform(const std::string& uniform)
    {
        vertexShaderUniforms += uniform;
        vertexShaderUniforms += ";\n";
    }

    void AddFragmentShaderUniform(const std::string& uniform)
    {
        fragmentShaderUniforms += uniform;
        fragmentShaderUniforms += ";\n";
    }

    int boneCount{ 0 };
};

ShaderBuilder class

The ShaderBuilder class automates the process of creating shaders in the Goknar Engine, which simplifies the management and customization of shader properties for rendering tasks. It includes methods for defining shader stages (vertex, fragment, etc.), linking resources (textures, uniforms), and compiling shader code.

For visual effect implementations, the ShaderBuilder class simplifies the creation and management of shaders, enabling seamless customization of visual effects. It allows developers to define shader parameters for creating dynamic visuals, such as moving vertices and materials with bloom effects, etc.


    struct FragmentShaderInitializationData
    {
        FragmentShaderInitializationData() = delete;
        FragmentShaderInitializationData(const std::string& outVariables, const std::string& outVariableAssignments) :
            outputVariables(outVariables),
            outputVariableAssignments(outVariableAssignments)
        {}

        MaterialInitializationData* materialInitializationData{ nullptr };
        const Shader* shader{ nullptr };
        const std::string& outputVariables;
        const std::string& outputVariableAssignments;
        RenderPassType renderPassType{ RenderPassType::Forward };
    };

    struct VertexShaderInitializationData
    {
        VertexShaderInitializationData() {}

        MaterialInitializationData* materialInitializationData{ nullptr };
        const Shader* shader{ nullptr };
        RenderPassType renderPassType{ RenderPassType::Forward };
    };
std::string ShaderBuilderNew::General_FS_GetScript(const FragmentShaderInitializationData& fragmentShaderInitializationData) const
{
    std::string fragmentShader = "#version " + shaderVersion_ + "\n\n";
    fragmentShader += General_FS_GetMaterialVariables(fragmentShaderInitializationData);
    fragmentShader += fragmentShaderInitializationData.outputVariables;

    bool includeLightOperations =
        fragmentShaderInitializationData.renderPassType == RenderPassType::Forward ||
        fragmentShaderInitializationData.renderPassType == RenderPassType::Deferred;

    if (fragmentShaderInitializationData.renderPassType == RenderPassType::Deferred)
    {
        fragmentShader += DeferredRenderPass_GetGBufferTextureUniforms();
        fragmentShader += DeferredRenderPass_GetGBufferVariables();
        fragmentShader += VS_GetLightShadowViewMatrixUniforms();
    }

    if (includeLightOperations)
    {
        fragmentShader += FS_GetDirectionalLightStruct();
        fragmentShader += FS_GetPointLightStruct();
        fragmentShader += FS_GetSpotLightStruct();

        fragmentShader += FS_GetLightArrayUniforms();
        fragmentShader += FS_GetShadowMapUniforms();
        fragmentShader += FS_GetLightSpaceFragmentPositions(fragmentShaderInitializationData);

        fragmentShader += FS_GetDirectionalLightColorFunction();
        fragmentShader += FS_GetPointLightColorFunction();
        fragmentShader += FS_GetSpotLightColorFunction();
    }

    fragmentShader += General_FS_GetShaderTextureUniforms(fragmentShaderInitializationData.materialInitializationData, fragmentShaderInitializationData.shader);

    if (fragmentShaderInitializationData.materialInitializationData && !fragmentShaderInitializationData.materialInitializationData->fragmentShaderFunctions.empty())
    {
        fragmentShader += fragmentShaderInitializationData.materialInitializationData->fragmentShaderFunctions;
    }

    if (fragmentShaderInitializationData.materialInitializationData && !fragmentShaderInitializationData.materialInitializationData->fragmentShaderUniforms.empty())
    {
        fragmentShader += fragmentShaderInitializationData.materialInitializationData->fragmentShaderUniforms;
    }

    fragmentShader += R"(
void main()
{
)";
    if (fragmentShaderInitializationData.renderPassType == RenderPassType::Forward ||
        fragmentShaderInitializationData.renderPassType == RenderPassType::GeometryBuffer)
    {
        fragmentShader += FS_InitializeBaseColor(fragmentShaderInitializationData.materialInitializationData);
        fragmentShader += FS_InitializeEmmisiveColor(fragmentShaderInitializationData.materialInitializationData);
    }
    else if(fragmentShaderInitializationData.renderPassType == RenderPassType::Deferred)
    {
        fragmentShader += DeferredRenderPass_GetGBufferVariableAssignments();
        fragmentShader += VS_GetLightSpaceFragmentPositionCalculations();
    }

    if (includeLightOperations)
    {
        fragmentShader += "\t vec3 " + std::string(SHADER_VARIABLE_NAMES::LIGHT::LIGHT_INTENSITY) + " = " + SHADER_VARIABLE_NAMES::CALCULATIONS::FINAL_EMMISIVE_COLOR + "; \n";;
        fragmentShader += FS_GetLightCalculationIterators();
    }
    fragmentShader += fragmentShaderInitializationData.outputVariableAssignments;
    fragmentShader += R"(
})";

    return fragmentShader;
}

std::string ShaderBuilderNew::General_VS_GetScript(const VertexShaderInitializationData& vertexShaderInitializationData) const
{
    std::string vertexShader = "#version " + shaderVersion_ + "\n\n";
    vertexShader += VS_GetMainLayouts();

    std::string vertexShaderModelMatrixVariable = std::string(SHADER_VARIABLE_NAMES::POSITIONING::MODEL_MATRIX);

    if (0 < vertexShaderInitializationData.materialInitializationData->boneCount)
    {
        vertexShader += VS_GetSkeletalMeshLayouts();
        vertexShaderModelMatrixVariable = std::string(SHADER_VARIABLE_NAMES::POSITIONING::BONE_TRANSFORMATION_MATRIX) + " * " + vertexShaderModelMatrixVariable;
        vertexShader += VS_GetSkeletalMeshVariables();
        vertexShader += VS_GetSkeletalMeshUniforms(vertexShaderInitializationData.materialInitializationData->boneCount);
    }

    vertexShader += VS_GetUniforms();

    bool includeLightOperations = vertexShaderInitializationData.renderPassType == RenderPassType::Forward;

    if (includeLightOperations)
    {
        vertexShader += FS_GetDirectionalLightStruct();
        vertexShader += FS_GetPointLightStruct();
        vertexShader += FS_GetSpotLightStruct();
        vertexShader += FS_GetLightArrayUniforms();

        vertexShader += VS_GetLightShadowViewMatrixUniforms();
        vertexShader += VS_GetLightOutputs();
    }

    if (!vertexShaderInitializationData.materialInitializationData->vertexShaderFunctions.empty())
    {
        vertexShader += vertexShaderInitializationData.materialInitializationData->vertexShaderFunctions;
    }

    if (!vertexShaderInitializationData.materialInitializationData->vertexShaderUniforms.empty())
    {
        vertexShader += vertexShaderInitializationData.materialInitializationData->vertexShaderUniforms;
    }

    vertexShader += R"(
void main()
{
)";
    if (0 < vertexShaderInitializationData.materialInitializationData->boneCount)
    {
        vertexShader += VS_GetSkeletalMeshWeightCalculation();
    }
    vertexShader += VS_GetMain(vertexShaderInitializationData, vertexShaderModelMatrixVariable);
    vertexShader += R"(
}
)";

    return vertexShader;
}

Generating Forward Rendering Shaders


std::string ShaderBuilderNew::ForwardRenderPass_GetVertexShaderScript(MaterialInitializationData* initializationData, const Shader* shader) const
{
    VertexShaderInitializationData vertexShaderInitializationData;
    vertexShaderInitializationData.materialInitializationData = initializationData;
    vertexShaderInitializationData.shader = shader;
    vertexShaderInitializationData.renderPassType = RenderPassType::Forward;
    return General_VS_GetScript(vertexShaderInitializationData);
}

std::string ShaderBuilderNew::ForwardRenderPass_GetFragmentShaderScript(MaterialInitializationData* initializationData, const Shader* shader) const
{
    std::string outputVariables = FS_GetOutputVariables();
    std::string outputVariableAssignments = FS_GetOutputVariableAssignments();

    FragmentShaderInitializationData fragmentShaderInitializationData(outputVariables, outputVariableAssignments);
    fragmentShaderInitializationData.materialInitializationData = initializationData;
    fragmentShaderInitializationData.shader = shader;
    fragmentShaderInitializationData.renderPassType = RenderPassType::Forward;
    return General_FS_GetScript(fragmentShaderInitializationData);
}

Examples

Soul Pond and Waterfall

I have generated a soul waterfall and pond effect using the new custom shader system. I have created the corresponding assets in Blender and modified their UV channels accordingly to achieve this effect.

Shader data

I primarily used texture panning operations to generate the movement of the water. I used the vertex normal to darken the deepest areas of the pond and set the waterfall foams to dynamically change their intensity.

void MaterialInitializer::Environment_InitializePondMaterials()
{
    ResourceManager* resourceManager = engine->GetResourceManager();

    {
        StaticMesh* pondStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Environment/Waterfall/SM_Pond.fbx");

        Material* pondMaterial = pondStaticMesh->GetMaterial();
        pondMaterial->SetEmmisiveColor(Vector3{ 0.25f });

        std::string flowTextureName = "flowTexture";
        Image* flowImage = resourceManager->GetContent<Image>("Textures/Noises/T_Noise_02.png");
        flowImage->SetTextureUsage(TextureUsage::Diffuse);
        flowImage->SetName(flowTextureName);

        pondMaterial->AddTextureImage(flowImage);

        MaterialInitializationData* materialInitializationData = pondMaterial->GetInitializationData();
        materialInitializationData->baseColor.calculation = R"(
    vec2 modifiedUV1 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV1 *= vec2(0.125f, 0.125f);
    modifiedUV1 += vec2(1.f, -0.5f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;
    
    vec2 modifiedUV2 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV2 *= vec2(0.25f, 0.25f);
    modifiedUV2 += vec2(1.f, -0.625f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;

    vec4 totalFlowValue = texture()" + flowTextureName + R"(, modifiedUV1) * texture()" + flowTextureName + R"(, modifiedUV2);
    float totalFlowValueFloat = smoothstep(0.75f, 0.25f, pow(totalFlowValue.r, 0.2f));

    vec3 backgroundColor = vec3(0.294, 0.42, 0.353);
    vec3 foregroundColor = vec3(0.471, 0.722, 0.584);

    float dotValue = min(1.f, pow(clamp((6.f - textureUV.y) / 5.f, 0.f, 1.f), 1.f));

    vec3 finalColor = backgroundColor * (1.f - totalFlowValueFloat) + foregroundColor * totalFlowValueFloat;
    finalColor *= dotValue;
)";
        materialInitializationData->baseColor.result = "vec4(finalColor, 1.f); ";
    }

    {
        StaticMesh* pondSpiralStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Environment/Waterfall/SM_SpiralFlow.fbx");

        Material* pondSpiralMaterial = pondSpiralStaticMesh->GetMaterial();
        pondSpiralMaterial->SetEmmisiveColor(Vector3{ 0.f });
        pondSpiralMaterial->SetBlendModel(MaterialBlendModel::Transparent);

        std::string spiralFlowTextureName = "spiralFlowTexture";
        Image* spiralFlowImage = resourceManager->GetContent<Image>("Textures/Noises/T_Trail_01.png");
        spiralFlowImage->SetTextureUsage(TextureUsage::Diffuse);
        spiralFlowImage->SetName(spiralFlowTextureName);
        spiralFlowImage->SetTextureWrappingR(TextureWrapping::CLAMP_TO_BORDER);
        spiralFlowImage->SetTextureWrappingT(TextureWrapping::CLAMP_TO_BORDER);
        spiralFlowImage->SetTextureWrappingS(TextureWrapping::REPEAT);

        pondSpiralMaterial->AddTextureImage(spiralFlowImage);

        MaterialInitializationData* materialInitializationData = pondSpiralMaterial->GetInitializationData();
        materialInitializationData->baseColor.calculation = R"(
    vec2 modifiedUV1 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV1 *= vec2(1.f, 1.f);
    modifiedUV1 -= vec2(1.0f, 0.f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;

    vec4 totalFlowValue = texture()" + spiralFlowTextureName + R"(, modifiedUV1);
    float totalFlowValueFloat = totalFlowValue.r * 4.f;

    vec3 backgroundColor = vec3(0.294, 0.42, 0.353);//vec3(0.702, 0.722, 0.698);
    vec3 foregroundColor = vec3(0.471, 0.722, 0.584);//vec3(0.467, 0.573, 0.588);

    vec3 finalColor = backgroundColor * (1.f - totalFlowValueFloat) + foregroundColor * totalFlowValueFloat;
    float opacity = totalFlowValueFloat.r;

    float uvMultiplier = 1.f;
    if(textureUV.x <= 1.f)
    {
        uvMultiplier = textureUV.x;
    }
    else if(3.f <= textureUV.x)
    {
        uvMultiplier = 4.f - textureUV.x;
    }

    opacity *= min(uvMultiplier, 1.f);
)";
        materialInitializationData->baseColor.result = "vec4(finalColor, opacity); ";
    }

    {
        StaticMesh* waterfallInnerStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Environment/Waterfall/SM_WaterfallInner.fbx");
        Material* waterfallInnerMaterial = waterfallInnerStaticMesh->GetMaterial();
        waterfallInnerMaterial->SetEmmisiveColor(Vector3{ 0.25f });

        std::string flowTextureName = "flowTexture";
        Image* flowImage = resourceManager->GetContent<Image>("Textures/Noises/T_Noise_02.png");
        flowImage->SetTextureUsage(TextureUsage::Diffuse);
        flowImage->SetName(flowTextureName);

        waterfallInnerMaterial->AddTextureImage(flowImage);

        MaterialInitializationData* materialInitializationData = waterfallInnerMaterial->GetInitializationData();
        materialInitializationData->baseColor.calculation = R"(
    vec2 modifiedUV1 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV1 *= vec2(1.f, 1.f);
    modifiedUV1 -= vec2(1.f, -0.5f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;
    
    vec2 modifiedUV2 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV2 *= vec2(2.f, 2.f);
    modifiedUV2 -= vec2(1.f, 0.625f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;

    vec4 totalFlowValue = texture()" + flowTextureName + R"(, modifiedUV1) * texture()" + flowTextureName + R"(, modifiedUV2);
    float totalFlowValueFloat = totalFlowValue.r * 4.f;

    vec3 backgroundColor = vec3(0.294, 0.42, 0.353);//vec3(0.702, 0.722, 0.698);
    vec3 foregroundColor = vec3(0.471, 0.722, 0.584);//vec3(0.467, 0.573, 0.588);

    vec3 finalColor = backgroundColor * (1.f - totalFlowValueFloat) + foregroundColor * totalFlowValueFloat;
)";
        materialInitializationData->baseColor.result = "vec4(finalColor, 1.f); ";
    }

    {
        StaticMesh* waterfallOuterStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Environment/Waterfall/SM_WaterfallOuter.fbx");
        Material* waterfallOuterMaterial = waterfallOuterStaticMesh->GetMaterial();

        waterfallOuterMaterial->SetEmmisiveColor(Vector3{ 0.f });
        waterfallOuterMaterial->SetBlendModel(MaterialBlendModel::Transparent);

        std::string spiralFlowTextureName = "spiralFlowTexture";
        Image* spiralFlowImage = resourceManager->GetContent<Image>("Textures/Noises/T_Trail_01.png");
        spiralFlowImage->SetTextureUsage(TextureUsage::Diffuse);
        spiralFlowImage->SetName(spiralFlowTextureName);
        waterfallOuterMaterial->AddTextureImage(spiralFlowImage);

        Image* noiseImage = resourceManager->GetContent<Image>("Textures/Noises/T_Noise_02.png");
        std::string noiseTextureName = noiseImage->GetName();
        noiseImage->SetName(noiseTextureName);
        waterfallOuterMaterial->AddTextureImage(noiseImage);

        MaterialInitializationData* materialInitializationData = waterfallOuterMaterial->GetInitializationData();
        materialInitializationData->baseColor.calculation = R"(
    vec2 modifiedUV1 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV1 *= vec2(0.25f, 1.f);
    modifiedUV1 -= vec2(2.0f, 0.f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;

    vec2 modifiedUV2 = )" + std::string(SHADER_VARIABLE_NAMES::TEXTURE::UV) + R"(;
    modifiedUV2 *= vec2(0.06125f, 0.06125f);
    modifiedUV2 -= vec2(0.5f, 0.25f) * )" + SHADER_VARIABLE_NAMES::TIMING::ELAPSED_TIME + R"(;

    vec4 flowValue = texture()" + spiralFlowTextureName + R"(, modifiedUV1);
    vec4 noiseValue = texture()" + noiseTextureName + R"(, modifiedUV2);
    noiseValue *= 2.f;

    vec4 totalFlowValue = flowValue * noiseValue;

    float totalFlowValueFloat = totalFlowValue.r * 8.f;

    vec3 backgroundColor = vec3(0.294, 0.42, 0.353);//vec3(0.702, 0.722, 0.698);
    vec3 foregroundColor = vec3(0.471, 0.722, 0.584);//vec3(0.467, 0.573, 0.588);

    vec3 finalColor = backgroundColor * (1.f - totalFlowValueFloat) + foregroundColor * totalFlowValueFloat;
    float opacity = totalFlowValueFloat.r;

    float uvMultiplier = 1.f;
    if(textureUV.x <= 1.f)
    {
        uvMultiplier = textureUV.x;
    }
    else if(11.f <= textureUV.x)
    {
        uvMultiplier = (13.5f - textureUV.x) * 0.5f;
    }

    opacity *= min(uvMultiplier, 1.f);
)";
        materialInitializationData->baseColor.result = "vec4(finalColor, opacity); ";
    }
}

Assets

Portal

The portal is another object for which I have created a visual effect. For the portal, I moved its vertices to give it a lifelike feeling.

Shader data

As you can see from the following code snippet, I have implemented materialInitializationData->vertexPositionOffset to achieve vertex position changes.

void MaterialInitializer::Environment_InitializePortalMaterials()
{
    ResourceManager* resourceManager = engine->GetResourceManager();

    StaticMesh* portalStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Environment/Portals/SM_Portal.fbx");

    Material* targetIndicatorStaticMeshMaterial = portalStaticMesh->GetMaterial();

    Vector3 color = Vector3{ 0.3725490196f, 0.73725490196f, 0.95294117647f };
    targetIndicatorStaticMeshMaterial->SetBaseColor(Vector3{ 0.1f });
    targetIndicatorStaticMeshMaterial->SetTranslucency(1.f);
    targetIndicatorStaticMeshMaterial->SetEmmisiveColor(color);
    targetIndicatorStaticMeshMaterial->SetBlendModel(MaterialBlendModel::Transparent);

    MaterialInitializationData* materialInitializationData = targetIndicatorStaticMeshMaterial->GetInitializationData();

    materialInitializationData->vertexPositionOffset.calculation = R"(
    const float PI = 3.1415f;

    vec3 scaledWorldPosition = position;

    vec3 origin1 = scaledWorldPosition + vec3(3.f, 3.f, 1.5f);
    vec3 origin2 = scaledWorldPosition + vec3(-3.f, 3.f, -1.5f);
    vec3 origin3 = scaledWorldPosition + vec3(3.f, -3.f, 1.5f);
    vec3 origin4 = scaledWorldPosition + vec3(-3.f, -3.f, -1.5f); 

    float distance1 = length(origin1);
    float distance2 = length(origin2);
    float distance3 = length(origin3);
    float distance4 = length(origin4);

    float scaledTime = elapsedTime * 2.f;

    float wave =    sin(3.3f * PI * distance1 * 0.13f + scaledTime) * 0.025f +
                    sin(3.2f * PI * distance2 * 0.12f + scaledTime) * 0.025f +
                    sin(3.1f * PI * distance3 * 0.24f + scaledTime) * 0.025f +
                    sin(3.5f * PI * distance4 * 0.32f + scaledTime) * 0.025f;
)";
    materialInitializationData->vertexPositionOffset.result = "vec3(wave)";

    materialInitializationData->baseColor.calculation = R"(
    vec3 worldPosToView = normalize(viewPosition - fragmentPositionWorldSpace.xyz);
    float cosAngleToViewPosition = dot(worldPosToView, normalize(vertexNormal));

    float opacity = (1.f - abs(sin(3.141593f * cosAngleToViewPosition)));
)";
    materialInitializationData->baseColor.result = "vec4(" + std::string(SHADER_VARIABLE_NAMES::MATERIAL::BASE_COLOR) + ".xyz, opacity); ";
}

Assets

Fire burst skill

Another visual effect I have implemented is a cast skill. For this purpose, I have altered the color of the fire beam based on its vertex normal and created a fire effect at the far bottom where it intersects with the flat floor.

Shader data

void MaterialInitializer::Skills_InitializeFireBurstCastedObjectMaterials()
{
    ResourceManager* resourceManager = engine->GetResourceManager();

    {
        StaticMesh* skillObjectStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Skills/SkillObjects/SM_FireBurstCastedSkillObject.fbx");

        std::string noiseImageTextureName = "flameTexture";
        Image* flowImage = resourceManager->GetContent<Image>("Textures/VFX/T_Flame.png");
        flowImage->SetTextureUsage(TextureUsage::None);
        flowImage->SetName(noiseImageTextureName);

        std::string trailImageTextureName = "trailTexture";
        Image* trailImage = resourceManager->GetContent<Image>("Textures/VFX/T_Trail01.jpg");
        trailImage->SetName(trailImageTextureName);

        Material* newMaterial = new Material();
        newMaterial->SetBlendModel(MaterialBlendModel::Transparent);
        newMaterial->SetBaseColor(Vector4{ 0.75f, 0.75f, 0.75f, 0.9f });
        newMaterial->SetEmmisiveColor(2.f * Vector3{ 1.f, 0.064f, 0.f });
        newMaterial->AddTextureImage(flowImage);
        newMaterial->AddTextureImage(trailImage);

        MaterialInitializationData* materialInitializationData = newMaterial->GetInitializationData();

        materialInitializationData->baseColor.calculation =
            R"(
    vec2 scaledUV = vec2(2.f, 2.f) * textureUV;

    vec2 flowUVMoveSpeed = vec2(-0.1, 1.f);
    vec2 flowUV = scaledUV * vec2(1.f, 0.25f);
    flowUV += elapsedTime * flowUVMoveSpeed;

    vec2 flowUVReversedMoveSpeed = vec2(0.2f, 0.8f);
    vec2 flowUVReversed = scaledUV * vec2(1.f, 0.25f);
    flowUVReversed += elapsedTime * flowUVReversedMoveSpeed;

    vec4 noiseTextureColor = texture2D()" + noiseImageTextureName + R"(, flowUV);
    vec4 noiseTextureColorReversed = texture2D()" + noiseImageTextureName + R"(, flowUVReversed);
    float noiseTextureColorMultipliedR = noiseTextureColor.r * noiseTextureColorReversed.r;
    float fireColorInterpolator = smoothstep(0.f, 1.f, 1.f - pow(noiseTextureColorMultipliedR, 0.125f));
    fireColorInterpolator = smoothstep(fireColorInterpolator * 4.f, 1.f, 0.f);
    
    vec3 fireColor = mix(vec3(0.7f, 0.2f, 0.2f), vec3(0.275f, 0.08f, 0.05f), fireColorInterpolator);

    vec2 worldPosToView2D = normalize(viewPosition.xy - fragmentPositionWorldSpace.xy);
    float cosAngleToViewPosition2D = dot(worldPosToView2D, normalize(vertexNormal.xy));

    float lightBeamForMiddle = smoothstep(0.f, 1.f, cosAngleToViewPosition2D);
    lightBeamForMiddle = pow(lightBeamForMiddle, 64.f);

    float lightBeamForEdges = (1.f - pow(abs(sin(3.141593f * cosAngleToViewPosition2D / 2.f)), 0.5f));

    float lightBeam = clamp(lightBeamForEdges + lightBeamForMiddle, 0.f, 1.f);
    lightBeam = smoothstep(0.f, 1.f, lightBeam);

    float distanceFromGroundMultiplier = clamp(fragmentPositionWorldSpace.z * 0.1f + 0.5f, 0.f, 1.f);

    vec3 emmisiveColorResult =
        (1.f - fireColorInterpolator) * 
        lightBeam * 
        vec3(10.f, 5.f, 1.5f) * 
        distanceFromGroundMultiplier + 
        fireColor;
)";
        materialInitializationData->baseColor.result = std::string(SHADER_VARIABLE_NAMES::MATERIAL::BASE_COLOR) + "; \n";

        materialInitializationData->emmisiveColor.result = "emmisiveColorResult;\n";

        skillObjectStaticMesh->SetMaterial(newMaterial);
    }

    {
        StaticMesh* skillObjectStaticMesh = resourceManager->GetContent<StaticMesh>("Meshes/Skills/SkillObjects/SM_FireBurstCastedSkillFloorFlameObject.fbx");

        std::string flameNoiseTextureName = "flameTexture";
        Image* flameImage = resourceManager->GetContent<Image>("Textures/VFX/T_Flame.png");
        flameImage->SetTextureUsage(TextureUsage::None);
        flameImage->SetName(flameNoiseTextureName);

        Material* newMaterial = new Material();
        newMaterial->SetBlendModel(MaterialBlendModel::Masked);
        newMaterial->SetBaseColor(Vector3{ 0.23529411764, 0.03921568627f, 0.03921568627f });
        newMaterial->SetEmmisiveColor(Vector3{ 1.f });
        newMaterial->AddTextureImage(flameImage);
        newMaterial->SetShadingModel(MaterialShadingModel::TwoSided);

        MaterialInitializationData* materialInitializationData = newMaterial->GetInitializationData();
        materialInitializationData->baseColor.calculation =
            R"(
    vec2 uv1 = textureUV * vec2(8.f, 1.f) + vec2(0.7f, 0.8f);
    uv1 += vec2(0.3f, 1.2f) * elapsedTime;
    vec4 texture1 = texture2D()" + flameNoiseTextureName + R"(, uv1);

    vec2 uv2 = textureUV * vec2(8.f, 1.f) + vec2(0.5f, 1.0f);
    uv2 += vec2(-0.2f, 0.8f) * elapsedTime;
    vec4 texture2 = texture2D()" + flameNoiseTextureName + R"(, uv2);

    float combinedTextureR = texture1.r * texture2.r;
    combinedTextureR += textureUV.y;
    combinedTextureR *= 2.f;

    float opacity = smoothstep(1.f, 2.f, combinedTextureR);
)";
        materialInitializationData->baseColor.result = "vec4(" + std::string(SHADER_VARIABLE_NAMES::MATERIAL::BASE_COLOR) + ".xyz, opacity); ";

        skillObjectStaticMesh->SetMaterial(newMaterial);
    }
}

Assets

Overall Visual Effects

As always, you can find the repository of the engine via GitHUB.