NazaraEngine/src/Nazara/Graphics/ForwardRenderTechnique.cpp

560 lines
21 KiB
C++

// Copyright (C) 2014 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/ForwardRenderTechnique.hpp>
#include <Nazara/Graphics/AbstractBackground.hpp>
#include <Nazara/Graphics/Camera.hpp>
#include <Nazara/Graphics/Drawable.hpp>
#include <Nazara/Graphics/Light.hpp>
#include <Nazara/Graphics/Material.hpp>
#include <Nazara/Graphics/Sprite.hpp>
#include <Nazara/Renderer/Config.hpp>
#include <Nazara/Renderer/Renderer.hpp>
#include <Nazara/Utility/BufferMapper.hpp>
#include <Nazara/Utility/StaticMesh.hpp>
#include <Nazara/Utility/VertexStruct.hpp>
#include <limits>
#include <memory>
#include <Nazara/Graphics/Debug.hpp>
///TODO: Surveiller les shaders et supprimer les données uniformes en cas de changement (recompilation/destruction)
namespace
{
static NzIndexBuffer* s_indexBuffer = nullptr;
unsigned int s_maxSprites = 8192;
NzIndexBuffer* BuildIndexBuffer()
{
std::unique_ptr<NzIndexBuffer> indexBuffer(new NzIndexBuffer(false, s_maxSprites*6, nzBufferStorage_Hardware, nzBufferUsage_Static));
indexBuffer->SetPersistent(false);
NzBufferMapper<NzIndexBuffer> mapper(indexBuffer.get(), nzBufferAccess_WriteOnly);
nzUInt16* indices = static_cast<nzUInt16*>(mapper.GetPointer());
for (unsigned int i = 0; i < s_maxSprites; ++i)
{
*indices++ = i*4 + 0;
*indices++ = i*4 + 2;
*indices++ = i*4 + 1;
*indices++ = i*4 + 2;
*indices++ = i*4 + 3;
*indices++ = i*4 + 1;
}
return indexBuffer.release();
}
}
NzForwardRenderTechnique::NzForwardRenderTechnique() :
m_spriteBuffer(NzVertexDeclaration::Get(nzVertexLayout_XYZ_UV), s_maxSprites*4, nzBufferStorage_Hardware, nzBufferUsage_Dynamic),
m_maxLightPassPerObject(3)
{
if (!s_indexBuffer)
s_indexBuffer = BuildIndexBuffer();
m_indexBuffer = s_indexBuffer;
}
NzForwardRenderTechnique::~NzForwardRenderTechnique()
{
if (m_indexBuffer.Reset())
s_indexBuffer = nullptr;
}
void NzForwardRenderTechnique::Clear(const NzScene* scene) const
{
NzRenderer::Enable(nzRendererParameter_DepthBuffer, true);
NzRenderer::Enable(nzRendererParameter_DepthWrite, true);
NzRenderer::Clear(nzRendererBuffer_Depth);
NzAbstractBackground* background = (scene->IsBackgroundEnabled()) ? scene->GetBackground() : nullptr;
if (background)
background->Draw(scene);
}
bool NzForwardRenderTechnique::Draw(const NzScene* scene) const
{
m_directionalLights.SetLights(&m_renderQueue.directionalLights[0], m_renderQueue.directionalLights.size());
m_lights.SetLights(&m_renderQueue.lights[0], m_renderQueue.lights.size());
m_renderQueue.Sort(scene->GetViewer());
if (!m_renderQueue.opaqueModels.empty())
DrawOpaqueModels(scene);
if (!m_renderQueue.transparentModels.empty())
DrawTransparentModels(scene);
if (!m_renderQueue.sprites.empty())
DrawSprites(scene);
// Les autres drawables (Exemple: Terrain)
for (const NzDrawable* drawable : m_renderQueue.otherDrawables)
drawable->Draw();
return true;
// Les billboards
/*if (!m_renderQueue.billboards.empty())
{
//NzRenderer::SetIndexBuffer(m_billboardIndexBuffer);
NzRenderer::SetMatrix(nzMatrixType_World, NzMatrix4f::Identity());
NzRenderer::SetShader(m_billboardShader);
NzRenderer::SetVertexBuffer(m_billboardVertexBuffer);
m_billboardShader->SendVector(s_cameraForwardLocation, camera->GetForward());
m_billboardShader->SendVector(s_cameraUpLocation, camera->GetUp());
m_billboardShader->SendVector(s_worldUpLocation, NzVector3f::Up());
// Couleur ambiante de la scène
m_billboardShader->SendColor(m_billboardShader->GetUniformLocation(nzShaderUniform_SceneAmbient), scene->GetAmbientColor());
// Position de la caméra
m_billboardShader->SendVector(m_billboardShader->GetUniformLocation(nzShaderUniform_CameraPosition), camera->GetPosition());
lightCount = 0;
// On envoie les lumières directionnelles s'il y a (Les mêmes pour tous)
m_renderQueue.lights[0]->Apply(m_billboardShader, 0);
for (auto& matIt : m_renderQueue.billboards)
{
const NzMaterial* material = matIt.first;
auto& billboards = matIt.second;
material->Apply(m_billboardShader);
unsigned int billboardCount = billboards.size();
const NzForwardRenderQueue::BillboardData* data = &billboards[0];
while (billboardCount > 0)
{
unsigned int renderedBillboardCount = std::min(billboardCount, maxBillboards);
billboardCount -= renderedBillboardCount;
m_billboardVertexBuffer->FillVertices(data, 0, renderedBillboardCount, true);
data += renderedBillboardCount;
NzRenderer::DrawPrimitives(nzPrimitiveMode_PointList, 0, renderedBillboardCount);
}
billboards.clear();
}
}*/
}
unsigned int NzForwardRenderTechnique::GetMaxLightPassPerObject() const
{
return m_maxLightPassPerObject;
}
NzAbstractRenderQueue* NzForwardRenderTechnique::GetRenderQueue()
{
return &m_renderQueue;
}
nzRenderTechniqueType NzForwardRenderTechnique::GetType() const
{
return nzRenderTechniqueType_BasicForward;
}
void NzForwardRenderTechnique::SetMaxLightPassPerObject(unsigned int passCount)
{
m_maxLightPassPerObject = passCount;
}
void NzForwardRenderTechnique::DrawOpaqueModels(const NzScene* scene) const
{
NzAbstractViewer* viewer = scene->GetViewer();
const LightUniforms* lightUniforms = nullptr;
const NzShader* lastShader = nullptr;
for (auto& matIt : m_renderQueue.opaqueModels)
{
bool& used = std::get<0>(matIt.second);
if (used)
{
bool& renderQueueInstancing = std::get<1>(matIt.second);
NzForwardRenderQueue::MeshInstanceContainer& meshInstances = std::get<2>(matIt.second);
if (!meshInstances.empty())
{
const NzMaterial* material = matIt.first;
// Nous utilisons de l'instancing que lorsqu'aucune lumière (autre que directionnelle) n'est active
// Ceci car l'instancing n'est pas compatible avec la recherche des lumières les plus proches
// (Le deferred shading n'a pas ce problème)
bool instancing = m_instancingEnabled && (!material->IsLightingEnabled() || m_lights.IsEmpty()) && renderQueueInstancing;
// On commence par appliquer du matériau (et récupérer le shader ainsi activé)
const NzShader* shader = material->Apply((instancing) ? nzShaderFlags_Instancing : 0);
// Les uniformes sont conservées au sein d'un programme, inutile de les renvoyer tant qu'il ne change pas
if (shader != lastShader)
{
// Couleur ambiante de la scène
shader->SendColor(shader->GetUniformLocation(nzShaderUniform_SceneAmbient), scene->GetAmbientColor());
// Position de la caméra
shader->SendVector(shader->GetUniformLocation(nzShaderUniform_EyePosition), viewer->GetEyePosition());
// Index des uniformes d'éclairage dans le shader
lightUniforms = GetLightUniforms(shader);
lastShader = shader;
}
// Meshes
for (auto& subMeshIt : meshInstances)
{
const NzMeshData& meshData = subMeshIt.first;
const NzSpheref& boundingSphere = subMeshIt.second.first;
std::vector<NzMatrix4f>& instances = subMeshIt.second.second;
if (!instances.empty())
{
const NzIndexBuffer* indexBuffer = meshData.indexBuffer;
const NzVertexBuffer* vertexBuffer = meshData.vertexBuffer;
// Gestion du draw call avant la boucle de rendu
std::function<void(nzPrimitiveMode, unsigned int, unsigned int)> DrawFunc;
std::function<void(unsigned int, nzPrimitiveMode, unsigned int, unsigned int)> InstancedDrawFunc;
unsigned int indexCount;
if (indexBuffer)
{
DrawFunc = NzRenderer::DrawIndexedPrimitives;
InstancedDrawFunc = NzRenderer::DrawIndexedPrimitivesInstanced;
indexCount = indexBuffer->GetIndexCount();
}
else
{
DrawFunc = NzRenderer::DrawPrimitives;
InstancedDrawFunc = NzRenderer::DrawPrimitivesInstanced;
indexCount = vertexBuffer->GetVertexCount();
}
NzRenderer::SetIndexBuffer(indexBuffer);
NzRenderer::SetVertexBuffer(vertexBuffer);
if (instancing)
{
// On calcule le nombre d'instances que l'on pourra afficher cette fois-ci (Selon la taille du buffer d'instancing)
NzVertexBuffer* instanceBuffer = NzRenderer::GetInstanceBuffer();
instanceBuffer->SetVertexDeclaration(NzVertexDeclaration::Get(nzVertexLayout_Matrix4));
// Avec l'instancing, impossible de sélectionner les lumières pour chaque objet
// Du coup, il n'est activé que pour les lumières directionnelles
unsigned int lightCount = m_directionalLights.GetLightCount();
unsigned int lightIndex = 0;
nzRendererComparison oldDepthFunc = NzRenderer::GetDepthFunc();
unsigned int passCount = (lightCount == 0) ? 1 : (lightCount-1)/NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS + 1;
for (unsigned int pass = 0; pass < passCount; ++pass)
{
if (lightUniforms->exists)
{
unsigned int renderedLightCount = std::min(lightCount, NazaraSuffixMacro(NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS, U));
lightCount -= renderedLightCount;
if (pass == 1)
{
// Pour additionner le résultat des calculs de lumière
// Aucune chance d'interférer avec les paramètres du matériau car nous ne rendons que les objets opaques
// (Autrement dit, sans blending)
// Quant à la fonction de profondeur, elle ne doit être appliquée que la première fois
NzRenderer::Enable(nzRendererParameter_Blend, true);
NzRenderer::SetBlendFunc(nzBlendFunc_One, nzBlendFunc_One);
NzRenderer::SetDepthFunc(nzRendererComparison_Equal);
}
for (unsigned int i = 0; i < renderedLightCount; ++i)
m_directionalLights.GetLight(lightIndex++)->Enable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
for (unsigned int i = renderedLightCount; i < NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS; ++i)
NzLight::Disable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
}
const NzMatrix4f* instanceMatrices = &instances[0];
unsigned int instanceCount = instances.size();
unsigned int maxInstanceCount = instanceBuffer->GetVertexCount(); // On calcule le nombre d'instances que l'on pourra afficher cette fois-ci (Selon la taille du buffer d'instancing)
while (instanceCount > 0)
{
// On calcule le nombre d'instances que l'on pourra afficher cette fois-ci (Selon la taille du buffer d'instancing)
unsigned int renderedInstanceCount = std::min(instanceCount, maxInstanceCount);
instanceCount -= renderedInstanceCount;
// On remplit l'instancing buffer avec nos matrices world
instanceBuffer->Fill(instanceMatrices, 0, renderedInstanceCount, true);
instanceMatrices += renderedInstanceCount;
// Et on affiche
InstancedDrawFunc(renderedInstanceCount, meshData.primitiveMode, 0, indexCount);
}
}
// On n'oublie pas de désactiver le blending pour ne pas interférer sur le reste du rendu
NzRenderer::Enable(nzRendererParameter_Blend, false);
NzRenderer::SetDepthFunc(oldDepthFunc);
}
else
{
if (lightUniforms->exists)
{
for (const NzMatrix4f& matrix : instances)
{
unsigned int directionalLightCount = m_directionalLights.GetLightCount();
unsigned int otherLightCount = m_lights.ComputeClosestLights(matrix.GetTranslation() + boundingSphere.GetPosition(), boundingSphere.radius, m_maxLightPassPerObject*NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS - directionalLightCount);
unsigned int lightCount = directionalLightCount + otherLightCount;
NzRenderer::SetMatrix(nzMatrixType_World, matrix);
unsigned int directionalLightIndex = 0;
unsigned int otherLightIndex = 0;
nzRendererComparison oldDepthFunc = NzRenderer::GetDepthFunc(); // Dans le cas où nous aurions à le changer
unsigned int passCount = (lightCount == 0) ? 1 : (lightCount-1)/NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS + 1;
for (unsigned int pass = 0; pass < passCount; ++pass)
{
unsigned int renderedLightCount = std::min(lightCount, NazaraSuffixMacro(NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS, U));
lightCount -= renderedLightCount;
if (pass == 1)
{
// Pour additionner le résultat des calculs de lumière
// Aucune chance d'interférer avec les paramètres du matériau car nous ne rendons que les objets opaques
// (Autrement dit, sans blending)
// Quant à la fonction de profondeur, elle ne doit être appliquée que la première fois
NzRenderer::Enable(nzRendererParameter_Blend, true);
NzRenderer::SetBlendFunc(nzBlendFunc_One, nzBlendFunc_One);
NzRenderer::SetDepthFunc(nzRendererComparison_Equal);
}
// On active les lumières de cette passe-ci
for (unsigned int i = 0; i < renderedLightCount; ++i)
{
if (directionalLightIndex >= directionalLightCount)
m_lights.GetResult(otherLightIndex++)->Enable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
else
m_directionalLights.GetLight(directionalLightIndex++)->Enable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
}
// On désactive l'éventuel surplus
for (unsigned int i = renderedLightCount; i < NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS; ++i)
NzLight::Disable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
// Et on passe à l'affichage
DrawFunc(meshData.primitiveMode, 0, indexCount);
}
NzRenderer::Enable(nzRendererParameter_Blend, false);
NzRenderer::SetDepthFunc(oldDepthFunc);
}
}
else
{
// Sans instancing, on doit effectuer un drawcall pour chaque instance
// Cela reste néanmoins plus rapide que l'instancing en dessous d'un certain nombre d'instances
// À cause du temps de modification du buffer d'instancing
for (const NzMatrix4f& matrix : instances)
{
NzRenderer::SetMatrix(nzMatrixType_World, matrix);
DrawFunc(meshData.primitiveMode, 0, indexCount);
}
}
}
instances.clear();
}
}
}
// Et on remet à zéro les données
renderQueueInstancing = false;
used = false;
}
}
}
void NzForwardRenderTechnique::DrawSprites(const NzScene* scene) const
{
NzAbstractViewer* viewer = scene->GetViewer();
const NzShader* lastShader = nullptr;
NzRenderer::SetIndexBuffer(m_indexBuffer);
NzRenderer::SetMatrix(nzMatrixType_World, NzMatrix4f::Identity());
NzRenderer::SetVertexBuffer(&m_spriteBuffer);
for (auto& matIt : m_renderQueue.sprites)
{
const NzMaterial* material = matIt.first;
auto& spriteVector = matIt.second;
unsigned int spriteCount = spriteVector.size();
if (spriteCount > 0)
{
// On commence par appliquer du matériau (et récupérer le shader ainsi activé)
const NzShader* shader = material->Apply();
// Les uniformes sont conservées au sein d'un programme, inutile de les renvoyer tant qu'il ne change pas
if (shader != lastShader)
{
// Couleur ambiante de la scène
shader->SendColor(shader->GetUniformLocation(nzShaderUniform_SceneAmbient), scene->GetAmbientColor());
// Position de la caméra
shader->SendVector(shader->GetUniformLocation(nzShaderUniform_EyePosition), viewer->GetEyePosition());
lastShader = shader;
}
const NzSprite** spritePtr = &spriteVector[0];
do
{
unsigned int renderedSpriteCount = std::min(spriteCount, 64U);
spriteCount -= renderedSpriteCount;
NzBufferMapper<NzVertexBuffer> vertexMapper(m_spriteBuffer, nzBufferAccess_DiscardAndWrite, 0, renderedSpriteCount*4);
NzVertexStruct_XYZ_UV* vertices = reinterpret_cast<NzVertexStruct_XYZ_UV*>(vertexMapper.GetPointer());
for (unsigned int i = 0; i < renderedSpriteCount; ++i)
{
const NzSprite* sprite = *spritePtr++;
const NzRectf& textureCoords = sprite->GetTextureCoords();
const NzVector2f& halfSize = sprite->GetSize()*0.5f;
NzVector3f center = sprite->GetPosition();
NzQuaternionf rotation = sprite->GetRotation();
vertices->position = center + rotation * NzVector3f(-halfSize.x, halfSize.y, 0.f);
vertices->uv.Set(textureCoords.x, textureCoords.y + textureCoords.height);
vertices++;
vertices->position = center + rotation * NzVector3f(halfSize.x, halfSize.y, 0.f);
vertices->uv.Set(textureCoords.width, textureCoords.y + textureCoords.height);
vertices++;
vertices->position = center + rotation * NzVector3f(-halfSize.x, -halfSize.y, 0.f);
vertices->uv.Set(textureCoords.x, textureCoords.y);
vertices++;
vertices->position = center + rotation * NzVector3f(halfSize.x, -halfSize.y, 0.f);
vertices->uv.Set(textureCoords.width, textureCoords.y);
vertices++;
}
vertexMapper.Unmap();
NzRenderer::DrawIndexedPrimitives(nzPrimitiveMode_TriangleList, 0, renderedSpriteCount*6);
}
while (spriteCount > 0);
spriteVector.clear();
}
}
}
void NzForwardRenderTechnique::DrawTransparentModels(const NzScene* scene) const
{
NzAbstractViewer* viewer = scene->GetViewer();
const LightUniforms* lightUniforms = nullptr;
const NzShader* lastShader = nullptr;
unsigned int lightCount = 0;
for (unsigned int index : m_renderQueue.transparentModels)
{
const NzForwardRenderQueue::TransparentModelData& modelData = m_renderQueue.transparentModelData[index];
// Matériau
const NzMaterial* material = modelData.material;
// On commence par appliquer du matériau (et récupérer le shader ainsi activé)
const NzShader* shader = material->Apply();
// Les uniformes sont conservées au sein d'un programme, inutile de les renvoyer tant qu'il ne change pas
if (shader != lastShader)
{
// Couleur ambiante de la scène
shader->SendColor(shader->GetUniformLocation(nzShaderUniform_SceneAmbient), scene->GetAmbientColor());
// Position de la caméra
shader->SendVector(shader->GetUniformLocation(nzShaderUniform_EyePosition), viewer->GetEyePosition());
// Index des uniformes d'éclairage dans le shader
lightUniforms = GetLightUniforms(shader);
// On envoie les lumières directionnelles s'il y a (Les mêmes pour tous)
lightCount = std::min(m_directionalLights.GetLightCount(), NazaraSuffixMacro(NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS, U));
for (unsigned int i = 0; i < lightCount; ++i)
m_directionalLights.GetLight(i)->Enable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
lastShader = shader;
}
// Mesh
const NzMatrix4f& matrix = modelData.transformMatrix;
const NzMeshData& meshData = modelData.meshData;
const NzIndexBuffer* indexBuffer = meshData.indexBuffer;
const NzVertexBuffer* vertexBuffer = meshData.vertexBuffer;
// Gestion du draw call avant la boucle de rendu
std::function<void(nzPrimitiveMode, unsigned int, unsigned int)> DrawFunc;
unsigned int indexCount;
if (indexBuffer)
{
DrawFunc = NzRenderer::DrawIndexedPrimitives;
indexCount = indexBuffer->GetIndexCount();
}
else
{
DrawFunc = NzRenderer::DrawPrimitives;
indexCount = vertexBuffer->GetVertexCount();
}
NzRenderer::SetIndexBuffer(indexBuffer);
NzRenderer::SetVertexBuffer(vertexBuffer);
// Calcul des lumières les plus proches
if (lightCount < NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS && !m_lights.IsEmpty())
{
unsigned int count = std::min(NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS - lightCount, m_lights.ComputeClosestLights(matrix.GetTranslation() + modelData.boundingSphere.GetPosition(), modelData.boundingSphere.radius, NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS));
for (unsigned int i = 0; i < count; ++i)
m_lights.GetResult(i)->Enable(shader, lightUniforms->uniforms, lightUniforms->offset*(lightCount++));
}
for (unsigned int i = lightCount; i < NAZARA_GRAPHICS_MAX_LIGHT_PER_PASS; ++i)
NzLight::Disable(shader, lightUniforms->uniforms, lightUniforms->offset*i);
NzRenderer::SetMatrix(nzMatrixType_World, matrix);
DrawFunc(meshData.primitiveMode, 0, indexCount);
}
}
const NzForwardRenderTechnique::LightUniforms* NzForwardRenderTechnique::GetLightUniforms(const NzShader* shader) const
{
auto it = m_lightUniforms.find(shader);
if (it != m_lightUniforms.end())
return &(it->second);
else
{
int type0Location = shader->GetUniformLocation("Lights[0].type");
int type1Location = shader->GetUniformLocation("Lights[1].type");
LightUniforms lightUniforms;
if (type0Location > 0 && type1Location > 0)
{
lightUniforms.exists = true;
lightUniforms.offset = type1Location - type0Location;
lightUniforms.uniforms.ubo = false;
lightUniforms.uniforms.locations.type = type0Location;
lightUniforms.uniforms.locations.color = shader->GetUniformLocation("Lights[0].color");
lightUniforms.uniforms.locations.factors = shader->GetUniformLocation("Lights[0].factors");
lightUniforms.uniforms.locations.parameters1 = shader->GetUniformLocation("Lights[0].parameters1");
lightUniforms.uniforms.locations.parameters2 = shader->GetUniformLocation("Lights[0].parameters2");
lightUniforms.uniforms.locations.parameters3 = shader->GetUniformLocation("Lights[0].parameters3");
}
else
lightUniforms.exists = false;
auto pair = m_lightUniforms.emplace(shader, lightUniforms);
return &(pair.first->second);
}
}