Files
FlaxEngine/Source/Engine/Graphics/Materials/TerrainMaterialShader.cpp

196 lines
7.4 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "TerrainMaterialShader.h"
#include "MaterialShaderFeatures.h"
#include "MaterialParams.h"
#include "Engine/Core/Math/Matrix3x4.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPULimits.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/RenderBuffers.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
#include "Engine/Level/Scene/Lightmap.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Terrain/TerrainPatch.h"
PACK_STRUCT(struct TerrainMaterialShaderData {
Matrix3x4 WorldMatrix;
Float3 WorldInvScale;
float WorldDeterminantSign;
float PerInstanceRandom;
float CurrentLOD; // Index of the current LOD
float ChunkSizeNextLOD; // ChunkSize for the next current LOD (after applying LOD down-scaling)
float TerrainChunkSizeLOD0; // Size of the terrain chunk in world units of the top-most LOD0
Float4 HeightmapUVScaleBias; // xy-scale, zw-offset for chunk geometry UVs into heightmap UVs (as single MAD instruction)
Float4 NeighborLOD; // Per component LOD index for chunk neighbors ordered: top, left, right, bottom
Float2 OffsetUV; // Offset applied to the texture coordinates (used to implement seamless UVs based on chunk location relative to terrain root)
Float2 Dummy0;
});
DrawPass TerrainMaterialShader::GetDrawModes() const
{
return DrawPass::Depth | DrawPass::GBuffer | DrawPass::GlobalSurfaceAtlas;
}
bool TerrainMaterialShader::CanUseLightmap() const
{
return true;
}
void TerrainMaterialShader::Bind(BindParameters& params)
{
// Prepare
auto context = params.GPUContext;
auto& view = params.RenderContext.View;
auto& drawCall = *params.FirstDrawCall;
Span<byte> cb(_cbData.Get(), _cbData.Count());
ASSERT_LOW_LAYER(cb.Length() >= sizeof(TerrainMaterialShaderData));
auto materialData = reinterpret_cast<TerrainMaterialShaderData*>(cb.Get());
cb = Span<byte>(cb.Get() + sizeof(TerrainMaterialShaderData), cb.Length() - sizeof(TerrainMaterialShaderData));
int32 srv = 3;
// Setup features
const bool useLightmap = LightmapFeature::Bind(params, cb, srv);
// Setup parameters
MaterialParameter::BindMeta bindMeta;
bindMeta.Context = context;
bindMeta.Constants = cb;
bindMeta.Input = nullptr;
bindMeta.Buffers = params.RenderContext.Buffers;
bindMeta.CanSampleDepth = false;
bindMeta.CanSampleGBuffer = false;
MaterialParams::Bind(params.ParamsLink, bindMeta);
// Setup material constants
{
materialData->WorldMatrix.SetMatrixTranspose(drawCall.World);
const float scaleX = Float3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length();
const float scaleY = Float3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length();
const float scaleZ = Float3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length();
materialData->WorldInvScale = Float3(
scaleX > 0.00001f ? 1.0f / scaleX : 0.0f,
scaleY > 0.00001f ? 1.0f / scaleY : 0.0f,
scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f);
materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign;
materialData->PerInstanceRandom = drawCall.PerInstanceRandom;
materialData->CurrentLOD = drawCall.Terrain.CurrentLOD;
materialData->ChunkSizeNextLOD = drawCall.Terrain.ChunkSizeNextLOD;
materialData->TerrainChunkSizeLOD0 = drawCall.Terrain.TerrainChunkSizeLOD0;
materialData->HeightmapUVScaleBias = drawCall.Terrain.HeightmapUVScaleBias;
materialData->NeighborLOD = drawCall.Terrain.NeighborLOD;
materialData->OffsetUV = drawCall.Terrain.OffsetUV;
}
// Bind terrain textures
const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture();
const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr;
const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr;
context->BindSR(0, heightmap);
context->BindSR(1, splatmap0);
context->BindSR(2, splatmap1);
// Bind constants
if (_cb)
{
context->UpdateCB(_cb, _cbData.Get());
context->BindCB(0, _cb);
}
// Select pipeline state based on current pass and render mode
const bool wireframe = EnumHasAnyFlags(_info.FeaturesFlags, MaterialFeaturesFlags::Wireframe) || view.Mode == ViewMode::Wireframe;
CullMode cullMode = view.Pass == DrawPass::Depth ? CullMode::TwoSided : _info.CullMode;
#if USE_EDITOR
if (IsRunningRadiancePass)
cullMode = CullMode::TwoSided;
#endif
if (cullMode != CullMode::TwoSided && drawCall.WorldDeterminantSign < 0)
{
// Invert culling when scale is negative
if (cullMode == CullMode::Normal)
cullMode = CullMode::Inverted;
else
cullMode = CullMode::Normal;
}
const PipelineStateCache* psCache = _cache.GetPS(view.Pass, useLightmap);
ASSERT(psCache);
GPUPipelineState* state = ((PipelineStateCache*)psCache)->GetPS(cullMode, wireframe);
// Bind pipeline
context->SetState(state);
}
void TerrainMaterialShader::Unload()
{
// Base
MaterialShader::Unload();
_cache.Release();
}
bool TerrainMaterialShader::Load()
{
GPUPipelineState::Description psDesc = GPUPipelineState::Description::Default;
psDesc.DepthEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthTest) == MaterialFeaturesFlags::None;
psDesc.DepthWriteEnable = (_info.FeaturesFlags & MaterialFeaturesFlags::DisableDepthWrite) == MaterialFeaturesFlags::None;
// Check if use tessellation (both material and runtime supports it)
const bool useTess = _info.TessellationMode != TessellationMethod::None && GPUDevice::Instance->Limits.HasTessellation;
if (useTess)
{
psDesc.HS = _shader->GetHS("HS");
psDesc.DS = _shader->GetDS("DS");
}
// Support blending but then use only emissive channel
switch (_info.BlendMode)
{
case MaterialBlendMode::Transparent:
psDesc.BlendMode = BlendingMode::AlphaBlend;
break;
case MaterialBlendMode::Additive:
psDesc.BlendMode = BlendingMode::Additive;
break;
case MaterialBlendMode::Multiply:
psDesc.BlendMode = BlendingMode::Multiply;
break;
default: ;
}
// GBuffer Pass
psDesc.VS = _shader->GetVS("VS");
psDesc.PS = _shader->GetPS("PS_GBuffer");
_cache.Default.Init(psDesc);
// GBuffer Pass with lightmap (use pixel shader permutation for USE_LIGHTMAP=1)
psDesc.PS = _shader->GetPS("PS_GBuffer", 1);
_cache.DefaultLightmap.Init(psDesc);
#if USE_EDITOR
if (_shader->HasShader("PS_QuadOverdraw"))
{
// Quad Overdraw
psDesc.PS = _shader->GetPS("PS_QuadOverdraw");
_cache.QuadOverdraw.Init(psDesc);
}
#endif
// Depth Pass
psDesc.CullMode = CullMode::TwoSided;
psDesc.BlendMode = BlendingMode::Opaque;
psDesc.DepthClipEnable = false;
psDesc.DepthWriteEnable = true;
psDesc.DepthEnable = true;
psDesc.DepthFunc = ComparisonFunc::Less;
psDesc.HS = nullptr;
psDesc.DS = nullptr;
// TODO: masked terrain materials (depth pass should clip holes)
psDesc.PS = nullptr;
_cache.Depth.Init(psDesc);
return false;
}