Unreal 4 Rendering Programming [Volume 3: Unreal 4 Rendering Programming Volume 2 Custom Global Shader Code Example Detailed Explanation]

First give the portal of the second volume, this section is explaining the code of the second volume.
https://blog.csdn.net/qq_16756235/article/details/80045173
Unreal 4 has a document that teaches you how to write a globalshader, but after I read it again, I feel that it still doesn't say the core principle, just teach you how to copy and paste. Let's use the example in our previous section (about 300 lines of code) to peek at the rendering process of Unreal. Although the example only has more than 300 lines of code, the sparrow is small and complete. The fog of the engine and the later period are all written by the globalshader.

First look at the ShadertestPlugin.Build.cs file.
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class ShadertestPlugin : ModuleRules
{
	public ShadertestPlugin(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PublicIncludePaths.AddRange(
			new string[] {
				"ShadertestPlugin/Public"
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange (
			new string[] {
				"ShadertestPlugin/Private",
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
                "CoreUObject",
                "Engine",
                "RHI",
                "Engine",
                "RenderCore",
                "ShaderCore",
				// ... add other public dependencies that you statically link with here ...
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
				// ... add private dependencies that you statically link with here ...	
			}
			);
		
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				// ... add any modules that your module loads dynamically here ...
			}
			);
	}
}

This file is mainly used for path configuration and modules you need to add. Only when modules are imported here can we include them in the header file. Unreal This is the idea of ​​​​implementing the module programming of c# in c++. What function is needed, write it here, don't write it if you don't need it. Can not help but sigh the illusory Niubi.
Let's take a look at ShadertestPlugin.uplugin, this file is a configuration file that describes some information of our plugin and the most critical loading order LoadingPhase.

You can check LoadingPhase and others.
After reading this, we need to pay attention to three more files

Let's take a look at MyShader.usf first

One is a vertex shader and the other is a pixel shader. There is also a float4 variable that declares a shader. Pixel shaders and vertex shaders do a simple job of outputting a simple color and position.

The #include "/Engine/Public/Platform.ush" at the top can be omitted. if you are in shader

If the ShouldCompilePermutation function has a judgment platform, then you need to add this include.
Let's take a look at MyShaderTest.h again
In fact, this is a static function library. The reason why we can call this function in the blueprint script is because this is a static function library. Provides a method for blueprints to call directly.

What this function does is help us pass some data from the blueprint script to our shader.

Next, look at the most important MyShaderTest.cpp file
First include the header files we need. These header files can only be included because we have introduced the modules in which they are located in c#. Otherwise, these include operations are illegal.

Next is to introduce our custom namespace. Correspondingly, at the very end of this cpp file, an end operation must be paired

Next is the constructor of the blueprint function library. Nothing needs to be written here.

Then there is our shader class
class FMyShaderTest : public FGlobalShader
{
public:

	FMyShaderTest(){}

	FMyShaderTest(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
		: FGlobalShader(Initializer)
	{
		SimpleColorVal.Bind(Initializer.ParameterMap, TEXT("SimpleColor"));
	}

	static bool ShouldCache(EShaderPlatform Platform)
	{
		return true;
	}

	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		//return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4);
		return true;
	}

	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
	{
		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
		OutEnvironment.SetDefine(TEXT("TEST_MICRO"), 1);
	}

	void SetParameters(
		FRHICommandListImmediate& RHICmdList,
		const FLinearColor &MyColor
		)
	{
		SetShaderValue(RHICmdList, GetPixelShader(), SimpleColorVal, MyColor);
	}

	virtual bool Serialize(FArchive& Ar) override
	{
		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
		Ar << SimpleColorVal;
		return bShaderHasOutdatedParameters;
	}

private:

	FShaderParameter SimpleColorVal;

};
There are several functions

Default constructor and constructor for shader class. In the constructor, we use the bind method to bind the shader's private member variable SimpleColorVal to the shader's variable float4 SimpleColor.

Then there are three functions:

The functions of these three functions are:

static bool ShouldCache (EShaderPlatform Platform) is mainly used as follows
For this function ModifyCompilationEnvironment, if you have read my previous blog with shadingmode, you will not be unfamiliar. It can stuff macros into shaders.

The SetParameters function is defined by yourself, you can name it whatever you want. Its role is to pass our color information to the shader. we set directly

This private variable will do. It is bound to the shader variable during the constructor.

This function is serialization. There is nothing to say about it. If you don't understand, you can go to Zhihu's article about illusory serialization and deserialization. It can be understood as how Unreal reads binary data from disk. Our shader is a file (you should know this after typing the renderer). If you are not sure, you can skip it directly. After all, this mechanism of Unreal is a huge topic.

Then our VS and PS
There is nothing in these two classes, mainly because we have written all the code in FMyShaderTest. That way you don't have to write it again. This is also the purpose of the existence of the FMyShaderTest class. Of course, it is no problem for you to let both PS and VS inherit from globalshader.
This macro will help us add our shader to the global shadermap, which is the key for Unreal to recognize our shader and compile it. This macro helps us do a lot of things, anyway, in general, let Unreal know, oh! Here's a shader, I'm going to compile it, I'm going to use it for rendering. If you want to know more, you can follow up.

Then there are these two macros. Its function is to bind the shader file to our shader class, and then identify what shader it is and where the HLSL entry code corresponding to the shader is. Probably that's how it works.
Finally, there are these two functions:

DrawTestShaderRenderTarget is the implementation of the blueprint function library in MyShaderTest.h, which is the part of the logic thread that calls the draw method.

In this function, we first judge whether the logical thread is calling it, and then judge whether the RT of the resource we input from the blueprint is empty. In fact, we should also judge whether Ac is empty.

Then get the rendering resources of this rendertarget in the rendering thread. Then get FeatureLevel. If you have written DX device initialization, you will be familiar with this FeatureLevelhen, so I won't go into details here. If you are not sure, go to Baidu to search for dxd11 device initialization.
The last is to call the ENQUEUE_RENDER_COMMAND macro. This macro will push a rendering command to the rendering thread, calling our

DrawTestShaderRenderTarget_RenderThread method. If you don't know the syntax here, take a look at C++ lambda expressions.

After this is done, the DrawTestShaderRenderTarget_RenderThread function of our rendering thread will be called.
static void DrawTestShaderRenderTarget_RenderThread(
	FRHICommandListImmediate& RHICmdList,
	FTextureRenderTargetResource* OutputRenderTargetResource,
	ERHIFeatureLevel::Type FeatureLevel,
	FName TextureRenderTargetName,
	FLinearColor MyColor
)
{
	check(IsInRenderingThread());

#if WANTS_DRAW_MESH_EVENTS
	FString EventName;
	TextureRenderTargetName.ToString(EventName);
	SCOPED_DRAW_EVENTF(RHICmdList, SceneCapture, TEXT("ShaderTest %s"), *EventName);
#else
	SCOPED_DRAW_EVENT(RHICmdList, DrawUVDisplacementToRenderTarget_RenderThread);
#endif

	//set render target
	SetRenderTarget(
		RHICmdList,
		OutputRenderTargetResource->GetRenderTargetTexture(),
		FTextureRHIRef(),
		ESimpleRenderTargetMode :: EUninitializedColorAndDepth,
		FExclusiveDepthStencil::DepthNop_StencilNop
	);

	//set the viewport
	//FIntPoint DrawTargetResolution(OutputRenderTargetResource->GetSizeX(), OutputRenderTargetResource->GetSizeY());
	//RHICmdList.SetViewport(0, 0, 0.0f, DrawTargetResolution.X, DrawTargetResolution.Y, 1.0f);

	TShaderMap<FGlobalShaderType>* GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
	TShaderMapRef<FShaderTestVS> VertexShader(GlobalShaderMap);
	TShaderMapRef<FShaderTestPS> PixelShader(GlobalShaderMap);

	// Set the graphic pipeline state.
	FGraphicsPipelineStateInitializer GraphicsPSOInit;
	RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
	GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
	GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
	GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
	GraphicsPSOInit.PrimitiveType = PT_TriangleList;
	GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
	GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
	GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
	SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);

	//RHICmdList.SetViewport(0, 0, 0.0f, DrawTargetResolution.X, DrawTargetResolution.Y, 1.0f);
	PixelShader->SetParameters(RHICmdList, MyColor);

	// Draw grid.
	// uint32 PrimitiveCount = 2;
	//RHICmdList.DrawPrimitive(PT_TriangleList, 0, PrimitiveCount, 1);
	FVector4 Vertices[4];
	Vertices[0].Set(-1.0f, 1.0f, 0, 1.0f);
	Vertices[1].Set(1.0f, 1.0f, 0, 1.0f);
	Vertices[2].Set(-1.0f, -1.0f, 0, 1.0f);
	Vertices[3].Set(1.0f, -1.0f, 0, 1.0f);
	static const uint16 Indices[6] =
	{
		0, 1, 2,
		0, 2, 3
	};
	//DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, Vertices, sizeof(Vertices[0]));
	DrawIndexedPrimitiveUP(
		RHICmdList,
		PT_TriangleList,
		0,
		ARRAY_COUNT(Vertices),
		2,
		Indices,
		sizeof(Indices[0]),
		Vertices,
		sizeof(Vertices[0])
	);

	// Resolve render target.
	RHICmdList.CopyToResolveTarget(
		OutputRenderTargetResource->GetRenderTargetTexture(),
		OutputRenderTargetResource->TextureRHI,
		false, FResolveParams());
}

Or judge the thread first

Then it is to set the render target, set the viewport, set the state of the pipeline, these clichés.
Here is the value passed to the shader.

Here is the actual drawing, which will pass in vertices and indices. Here DrawIndexedPrimitiveUP will set up a camera from the top of the patch, shoot the patch, and store the captured information on RT

The last step is to copy the rendered RT to the RT we passed in.

If you find that the shape is wrong, please change the Indices
This is wrong.

Change it to this ! !

At this point, the code explanation of the second volume is completed. We will continue our journey of discovery later. Add a pass in Unreal 4.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324938511&siteId=291194637