From cube material to the shaders on GPU
- Review
- Material Graph
- Vertex Factory
- Compile
- Example
- Usage of the Vertex Factory
- Summary
- Questions for the Next Chapter
Review
Let’s focus on the Depth Prepass since this is a simple pass.
As we discussed, the cube drawing command has been transferred into these graphics API calls.
And we also have this image:
But let me ask a simple question:
For the depth prepass, we only need to discuss the vertex shader, which simplifies things.
The answer is that the material compiles into one or more vertex shaders and pixel shaders.
Let's discuss this further.
Material Graph
The runtime object of our material called UMaterial
, but this one is not UMaterial
:
This is UMaterial
Graph
. UMaterial
holds a reference to it, but only in editor.
UMaterialGraph
will be compiled and then transformed into actual Shader code. This portion of Shader code is what is used during runtime.
Vertex Factory
The material graph is compiled with VertexFactory
to generate the final HLSL shaders.
So what exactly is VertexFactory
?
In Unreal Engine, materials are independent of mesh types. If you click the UseWithXXX
option, it will directly support that mesh type. We need something to abstract different mesh types, like static meshes and skeletal meshes. This is where VertexFactory
comes in. You can treat it as a set of functions that is inserted into the vertex and pixel shaders, and called by those shaders to get the vertex information, without needing to know what the original mesh type was.
Compile
As shown in the image, the combination of the material node graph, platform settings, passes, vertex factories…, will generate multiple vertex/pixel shader pairs, instead of just one pair. And FMaterialShaderMap
manages shaders and provides the appropriate ones based on rendering requests from the mesh.
And finally, the compiled shaders (which contains shader byte code) will be used to create pipeline state object. This defines all the shaders, primitive topology type and so on.
PSO will be used here :
Example
Let’s see an example:
This is the final vertex shader of the cube, for depth prepass:
struct FPositionOnlyVertexFactoryInput
{
float4 Position : ATTRIBUTE0;
uint InstanceIdOffset : ATTRIBUTE1 ;
uint DrawInstanceId : SV_InstanceID;
uint VertexId : SV_VertexID;
};
struct FOptimizedReturn
{
float4 OutPosition : SV_POSITION ;
};
FOptimizedReturn Main__OPTIMIZED(FPositionOnlyVertexFactoryInput Input)
{
FOptimizedReturn OptimizedReturn;
Main(Input, OptimizedReturn.OutPosition);
return OptimizedReturn;
}
void Main(
FPositionOnlyVertexFactoryInput Input,
out float4 OutPosition : SV_POSITION
)
{
ResolvedView = (ResolveView());
float4 WorldPos = VertexFactoryGetWorldPosition(Input);
{
OutPosition = MakePrecise( mul(WorldPos, ResolvedView.TranslatedWorldToClip) );
}
}
Basically this shader did two things:
- Get world position from the input, with
LocaVertexFactory
’sVertexFactoryGetWorldPosition
- Translate the world space position into clip space position and return.
Usage of the Vertex Factory
You may want to ask, why we need VertexFactory
here? Can we directly get the position from Input
?
Yes, for the cube, you can do so. But I can give you some reasons:
- If there are more instances, we need
LocaVertexFactory
to do instancing, translate the vertex based on each instance’s transform. - We may need other vertex info for complex vertex node graph in our material.
- We may use the material for skinned meshes. We need the factory to do vertex skinning for us.
Summary
Finally, let’s have a summary
Questions for the Next Chapter
Now we have identified one of the sources of the data: Shader.
This is not the end, there is still a significant portion of data whose source we have yet to determine, and that is the vertex data used for rendering. How are they read and utilized?