Graphics: Add Billboard support

This commit is contained in:
SirLynix
2023-11-25 15:52:58 +01:00
parent 4cbb5b91a3
commit 1ac992b5c7
12 changed files with 472 additions and 98 deletions

View File

@@ -0,0 +1,88 @@
// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com)
// 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/Billboard.hpp>
#include <Nazara/Graphics/ElementRendererRegistry.hpp>
#include <Nazara/Graphics/Graphics.hpp>
#include <Nazara/Graphics/MaterialInstance.hpp>
#include <Nazara/Graphics/RenderSpriteChain.hpp>
#include <Nazara/Graphics/WorldInstance.hpp>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
Billboard::Billboard(std::shared_ptr<MaterialInstance> material) :
m_material(std::move(material)),
m_color(Color::White()),
m_rotation(RadianAnglef::Zero()),
m_textureCoords(0.f, 0.f, 1.f, 1.f)
{
m_cornerColor.fill(Color::White());
m_size = Vector2f(Vector2ui(GetTextureSize()));
UpdateVertices();
}
Billboard::Billboard(std::shared_ptr<MaterialInstance> material, const Vector2f& size) :
m_material(std::move(material)),
m_color(Color::White()),
m_rotation(RadianAnglef::Zero()),
m_textureCoords(0.f, 0.f, 1.f, 1.f)
{
m_cornerColor.fill(Color::White());
m_size = size;
UpdateVertices();
}
void Billboard::BuildElement(ElementRendererRegistry& registry, const ElementData& elementData, std::size_t passIndex, std::vector<RenderElementOwner>& elements) const
{
const auto& materialPipeline = m_material->GetPipeline(passIndex);
if (!materialPipeline)
return;
MaterialPassFlags passFlags = m_material->GetPassFlags(passIndex);
const std::shared_ptr<VertexDeclaration>& vertexDeclaration = VertexDeclaration::Get(VertexLayout::UV_SizeSinCos_Color);
RenderPipelineInfo::VertexBufferData vertexBufferData = {
0,
vertexDeclaration
};
const auto& renderPipeline = materialPipeline->GetRenderPipeline(&vertexBufferData, 1);
const auto& whiteTexture = Graphics::Instance()->GetDefaultTextures().whiteTextures[ImageType::E2D];
elements.emplace_back(registry.AllocateElement<RenderSpriteChain>(GetRenderLayer(), m_material, passFlags, renderPipeline, *elementData.worldInstance, vertexDeclaration, whiteTexture, 1, m_vertices.data(), *elementData.scissorBox));
}
const std::shared_ptr<MaterialInstance>& Billboard::GetMaterial(std::size_t i) const
{
assert(i == 0);
NazaraUnused(i);
return m_material;
}
std::size_t Billboard::GetMaterialCount() const
{
return 1;
}
Vector3ui Billboard::GetTextureSize() const
{
assert(m_material);
//TODO: Cache index in registry?
if (const std::shared_ptr<Texture>* textureOpt = m_material->GetTextureProperty("BaseColorMap"))
{
// Material should always have textures but we're better safe than sorry
if (const std::shared_ptr<Texture>& texture = *textureOpt)
return texture->GetSize();
}
// Couldn't get material pass or texture
return Vector3ui::Unit(); //< prevents division by zero
}
}

View File

@@ -146,6 +146,10 @@ namespace Nz
config.optionValues[CRC32("VertexPositionLoc")] = locationIndex;
break;
case VertexComponent::SizeSinCos:
config.optionValues[CRC32("VertexSizeRotLocation")] = locationIndex;
break;
case VertexComponent::Tangent:
config.optionValues[CRC32("VertexTangentLoc")] = locationIndex;
break;

View File

@@ -15,12 +15,14 @@ namespace Nz
{
settings.AddValueProperty<Color>("BaseColor", Color::White());
settings.AddValueProperty<bool>("AlphaTest", false);
settings.AddValueProperty<bool>("Billboard", false);
settings.AddValueProperty<float>("AlphaTestThreshold", 0.2f);
settings.AddValueProperty<float>("ShadowMapNormalOffset", 0.f);
settings.AddValueProperty<float>("ShadowPosScale", 1.f - 0.0025f);
settings.AddTextureProperty("BaseColorMap", ImageType::E2D);
settings.AddTextureProperty("AlphaMap", ImageType::E2D);
settings.AddPropertyHandler(std::make_unique<OptionValuePropertyHandler>("AlphaTest", "AlphaTest"));
settings.AddPropertyHandler(std::make_unique<OptionValuePropertyHandler>("Billboard", "Billboard"));
settings.AddPropertyHandler(std::make_unique<TexturePropertyHandler>("BaseColorMap", "HasBaseColorTexture"));
settings.AddPropertyHandler(std::make_unique<TexturePropertyHandler>("AlphaMap", "HasAlphaTexture"));
settings.AddPropertyHandler(std::make_unique<UniformValuePropertyHandler>("BaseColor"));

View File

@@ -18,14 +18,12 @@ option AlphaTest: bool = false;
// Billboard related options
option Billboard: bool = false;
option BillboardCenterLocation: i32 = -1;
option BillboardColorLocation: i32 = -1;
option BillboardSizeRotLocation: i32 = -1;
// Vertex declaration related options
option VertexColorLoc: i32 = -1;
option VertexNormalLoc: i32 = -1;
option VertexPositionLoc: i32;
option VertexPositionLoc: i32 = -1;
option VertexSizeRotLocation: i32 = -1;
option VertexUvLoc: i32 = -1;
option VertexJointIndicesLoc: i32 = -1;
@@ -34,7 +32,8 @@ option VertexJointWeightsLoc: i32 = -1;
const HasNormal = (VertexNormalLoc >= 0);
const HasVertexColor = (VertexColorLoc >= 0);
const HasColor = (HasVertexColor || Billboard);
const HasUV = (VertexUvLoc >= 0);
const HasVertexUV = (VertexUvLoc >= 0);
const HasUV = (HasVertexUV);
const HasSkinning = (VertexJointIndicesLoc >= 0 && VertexJointWeightsLoc >= 0);
[layout(std140)]
@@ -152,6 +151,7 @@ fn FragDepth(input: VertOut) -> FragOut
fn FragDepthNoAlpha() {} //< dummy
// Vertex stage
[cond(!Billboard)]
struct VertIn
{
[location(VertexPositionLoc)]
@@ -163,55 +163,68 @@ struct VertIn
[cond(HasNormal), location(VertexNormalLoc)]
normal: vec3[f32],
[cond(HasUV), location(VertexUvLoc)]
[cond(HasVertexUV), location(VertexUvLoc)]
uv: vec2[f32],
[cond(HasSkinning), location(VertexJointIndicesLoc)]
jointIndices: vec4[i32],
[cond(HasSkinning), location(VertexJointWeightsLoc)]
jointWeights: vec4[f32],
[cond(Billboard), location(BillboardCenterLocation)]
billboardCenter: vec3[f32],
[cond(Billboard), location(BillboardSizeRotLocation)]
billboardSizeRot: vec4[f32], //< width,height,sin,cos
[cond(Billboard), location(BillboardColorLocation)]
billboardColor: vec4[f32]
jointWeights: vec4[f32]
}
[entry(vert), cond(Billboard)]
fn VertBillboard(input: VertIn) -> VertOut
[cond(Billboard)]
struct BillboardVertIn
{
let size = input.billboardSizeRot.xy;
let sinCos = input.billboardSizeRot.zw;
[builtin(vertex_index)] vertIndex: i32,
[location(VertexSizeRotLocation)]
sizeRot: vec4[f32], //< width,height,sin,cos
[cond(HasVertexUV), location(VertexUvLoc)]
uv: vec2[f32],
[cond(HasVertexColor), location(VertexColorLoc)]
color: vec4[f32]
}
const billboardPos = array[vec2[f32]](
vec2[f32](-0.5, -0.5),
vec2[f32]( 0.5, -0.5),
vec2[f32](-0.5, 0.5),
vec2[f32]( 0.5, 0.5)
);
[entry(vert), cond(Billboard)]
fn VertBillboard(input: BillboardVertIn) -> VertOut
{
let position = billboardPos[input.vertIndex % 4];
let size = input.sizeRot.xy;
let sinCos = input.sizeRot.zw;
let rotatedPosition = vec2[f32](
input.pos.x * sinCos.y - input.pos.y * sinCos.x,
input.pos.y * sinCos.y + input.pos.x * sinCos.x
position.x * sinCos.y - position.y * sinCos.x,
position.y * sinCos.y + position.x * sinCos.x
);
rotatedPosition *= size;
let cameraRight = vec3[f32](viewerData.viewMatrix[0][0], viewerData.viewMatrix[1][0], viewerData.viewMatrix[2][0]);
let cameraUp = vec3[f32](viewerData.viewMatrix[0][1], viewerData.viewMatrix[1][1], viewerData.viewMatrix[2][1]);
let vertexPos = input.billboardCenter;
vertexPos += cameraRight * rotatedPosition.x;
vertexPos += cameraUp * rotatedPosition.y;
let worldPosition = instanceData.worldMatrix * vec4[f32](vertexPos, 1.0);
let worldPosition = vec3[f32](instanceData.worldMatrix[3].xyz);
worldPosition += cameraRight * rotatedPosition.x;
worldPosition += cameraUp * rotatedPosition.y;
let output: VertOut;
output.worldPos = worldPosition.xyz;
output.position = viewerData.viewProjMatrix * worldPosition;
output.worldPos = worldPosition;
output.position = viewerData.viewProjMatrix * vec4[f32](worldPosition, 1.0);
const if (HasVertexUV)
output.uv = input.uv;
const if (HasColor)
output.color = input.billboardColor;
const if (HasUV)
output.uv = input.pos.xy + vec2[f32](0.5, 0.5);
output.color = input.color;
return output;
}
@@ -252,7 +265,7 @@ fn VertMain(input: VertIn) -> VertOut
const if (HasColor)
output.color = input.color;
const if (HasUV)
const if (HasVertexUV)
output.uv = input.uv;
return output;

View File

@@ -27,14 +27,12 @@ option HasSpecularTexture: bool = false;
// Billboard related options
option Billboard: bool = false;
option BillboardCenterLocation: i32 = -1;
option BillboardColorLocation: i32 = -1;
option BillboardSizeRotLocation: i32 = -1;
// Vertex declaration related options
option VertexColorLoc: i32 = -1;
option VertexNormalLoc: i32 = -1;
option VertexPositionLoc: i32;
option VertexPositionLoc: i32 = -1;
option VertexSizeRotLocation: i32 = -1;
option VertexTangentLoc: i32 = -1;
option VertexUvLoc: i32 = -1;
@@ -47,7 +45,8 @@ const HasNormal = (VertexNormalLoc >= 0);
const HasVertexColor = (VertexColorLoc >= 0);
const HasColor = (HasVertexColor || Billboard);
const HasTangent = (VertexTangentLoc >= 0);
const HasUV = (VertexUvLoc >= 0);
const HasVertexUV = (VertexUvLoc >= 0);
const HasUV = (HasVertexUV);
const HasNormalMapping = HasNormalTexture && HasNormal && HasTangent && !DepthPass;
const HasSkinning = (VertexJointIndicesLoc >= 0 && VertexJointWeightsLoc >= 0);
const HasLighting = HasNormal && !DepthPass;
@@ -295,6 +294,7 @@ fn FragDepth(input: VertOut) -> FragOut
fn FragDepthNoAlpha() {} //< dummy
// Vertex stage
[cond(!Billboard)]
struct VertIn
{
[location(VertexPositionLoc)]
@@ -303,7 +303,7 @@ struct VertIn
[cond(HasVertexColor), location(VertexColorLoc)]
color: vec4[f32],
[cond(HasUV), location(VertexUvLoc)]
[cond(HasVertexUV), location(VertexUvLoc)]
uv: vec2[f32],
[cond(HasNormal), location(VertexNormalLoc)]
@@ -316,45 +316,61 @@ struct VertIn
jointIndices: vec4[i32],
[cond(HasSkinning), location(VertexJointWeightsLoc)]
jointWeights: vec4[f32],
[cond(Billboard), location(BillboardCenterLocation)]
billboardCenter: vec3[f32],
[cond(Billboard), location(BillboardSizeRotLocation)]
billboardSizeRot: vec4[f32], //< width,height,sin,cos
[cond(Billboard), location(BillboardColorLocation)]
billboardColor: vec4[f32]
jointWeights: vec4[f32]
}
[entry(vert), cond(Billboard)]
fn billboardMain(input: VertIn) -> VertOut
[cond(Billboard)]
struct BillboardVertIn
{
let size = input.billboardSizeRot.xy;
let sinCos = input.billboardSizeRot.zw;
[builtin(vertex_index)] vertIndex: i32,
[location(VertexSizeRotLocation)]
sizeRot: vec4[f32], //< width,height,sin,cos
[cond(HasVertexUV), location(VertexUvLoc)]
uv: vec2[f32],
[cond(HasVertexColor), location(VertexColorLoc)]
color: vec4[f32]
}
const billboardPos = array[vec2[f32]](
vec2[f32](-0.5, -0.5),
vec2[f32]( 0.5, -0.5),
vec2[f32](-0.5, 0.5),
vec2[f32]( 0.5, 0.5)
);
[entry(vert), cond(Billboard)]
fn VertBillboard(input: BillboardVertIn) -> VertOut
{
let position = billboardPos[input.vertIndex % 4];
let size = input.sizeRot.xy;
let sinCos = input.sizeRot.zw;
let rotatedPosition = vec2[f32](
input.pos.x * sinCos.y - input.pos.y * sinCos.x,
input.pos.y * sinCos.y + input.pos.x * sinCos.x
position.x * sinCos.y - position.y * sinCos.x,
position.y * sinCos.y + position.x * sinCos.x
);
rotatedPosition *= size;
let cameraRight = vec3[f32](viewerData.viewMatrix[0][0], viewerData.viewMatrix[1][0], viewerData.viewMatrix[2][0]);
let cameraUp = vec3[f32](viewerData.viewMatrix[0][1], viewerData.viewMatrix[1][1], viewerData.viewMatrix[2][1]);
let vertexPos = input.billboardCenter;
vertexPos += cameraRight * rotatedPosition.x;
vertexPos += cameraUp * rotatedPosition.y;
let worldPosition = vec3[f32](instanceData.worldMatrix[3].xyz);
worldPosition += cameraRight * rotatedPosition.x;
worldPosition += cameraUp * rotatedPosition.y;
let output: VertOut;
output.position = viewerData.viewProjMatrix * instanceData.worldMatrix * vec4[f32](vertexPos, 1.0);
output.worldPos = worldPosition;
output.position = viewerData.viewProjMatrix * vec4[f32](worldPosition, 1.0);
const if (HasVertexUV)
output.uv = input.uv;
const if (HasColor)
output.color = input.billboardColor;
const if (HasUV)
output.uv = input.pos.xy + vec2[f32](0.5, 0.5);
output.color = input.color;
return output;
}
@@ -414,7 +430,7 @@ fn main(input: VertIn) -> VertOut
const if (HasNormal)
output.normal = rotationMatrix * normal;
const if (HasUV)
const if (HasVertexUV)
output.uv = input.uv;
const if (HasNormalMapping)

View File

@@ -29,14 +29,12 @@ option HasSpecularTexture: bool = false;
// Billboard related options
option Billboard: bool = false;
option BillboardCenterLocation: i32 = -1;
option BillboardColorLocation: i32 = -1;
option BillboardSizeRotLocation: i32 = -1;
// Vertex declaration related options
option VertexColorLoc: i32 = -1;
option VertexNormalLoc: i32 = -1;
option VertexPositionLoc: i32;
option VertexPositionLoc: i32 = -1;
option VertexSizeRotLocation: i32 = -1;
option VertexTangentLoc: i32 = -1;
option VertexUvLoc: i32 = -1;
@@ -49,7 +47,8 @@ const HasNormal = (VertexNormalLoc >= 0);
const HasVertexColor = (VertexColorLoc >= 0);
const HasColor = (HasVertexColor || Billboard);
const HasTangent = (VertexTangentLoc >= 0);
const HasUV = (VertexUvLoc >= 0);
const HasVertexUV = (VertexUvLoc >= 0);
const HasUV = (HasVertexUV);
const HasNormalMapping = HasNormalTexture && HasNormal && HasTangent && !DepthPass;
const HasSkinning = (VertexJointIndicesLoc >= 0 && VertexJointWeightsLoc >= 0);
@@ -292,6 +291,7 @@ fn FragDepth(input: VertOut) -> FragOut
fn FragDepthNoAlpha() {} //< dummy
// Vertex stage
[cond(!Billboard)]
struct VertIn
{
[location(VertexPositionLoc)]
@@ -300,7 +300,7 @@ struct VertIn
[cond(HasVertexColor), location(VertexColorLoc)]
color: vec4[f32],
[cond(HasUV), location(VertexUvLoc)]
[cond(HasVertexUV), location(VertexUvLoc)]
uv: vec2[f32],
[cond(HasNormal), location(VertexNormalLoc)]
@@ -313,45 +313,61 @@ struct VertIn
jointIndices: vec4[i32],
[cond(HasSkinning), location(VertexJointWeightsLoc)]
jointWeights: vec4[f32],
[cond(Billboard), location(BillboardCenterLocation)]
billboardCenter: vec3[f32],
[cond(Billboard), location(BillboardSizeRotLocation)]
billboardSizeRot: vec4[f32], //< width,height,sin,cos
[cond(Billboard), location(BillboardColorLocation)]
billboardColor: vec4[f32]
jointWeights: vec4[f32]
}
[entry(vert), cond(Billboard)]
fn VertBillboard(input: VertIn) -> VertOut
[cond(Billboard)]
struct BillboardVertIn
{
let size = input.billboardSizeRot.xy;
let sinCos = input.billboardSizeRot.zw;
[builtin(vertex_index)] vertIndex: i32,
[location(VertexSizeRotLocation)]
sizeRot: vec4[f32], //< width,height,sin,cos
[cond(HasVertexUV), location(VertexUvLoc)]
uv: vec2[f32],
[cond(HasVertexColor), location(VertexColorLoc)]
color: vec4[f32]
}
const billboardPos = array[vec2[f32]](
vec2[f32](-0.5, -0.5),
vec2[f32]( 0.5, -0.5),
vec2[f32](-0.5, 0.5),
vec2[f32]( 0.5, 0.5)
);
[entry(vert), cond(Billboard)]
fn VertBillboard(input: BillboardVertIn) -> VertOut
{
let position = billboardPos[input.vertIndex % 4];
let size = input.sizeRot.xy;
let sinCos = input.sizeRot.zw;
let rotatedPosition = vec2[f32](
input.pos.x * sinCos.y - input.pos.y * sinCos.x,
input.pos.y * sinCos.y + input.pos.x * sinCos.x
position.x * sinCos.y - position.y * sinCos.x,
position.y * sinCos.y + position.x * sinCos.x
);
rotatedPosition *= size;
let cameraRight = vec3[f32](viewerData.viewMatrix[0][0], viewerData.viewMatrix[1][0], viewerData.viewMatrix[2][0]);
let cameraUp = vec3[f32](viewerData.viewMatrix[0][1], viewerData.viewMatrix[1][1], viewerData.viewMatrix[2][1]);
let vertexPos = input.billboardCenter;
vertexPos += cameraRight * rotatedPosition.x;
vertexPos += cameraUp * rotatedPosition.y;
let worldPosition = vec3[f32](instanceData.worldMatrix[3].xyz);
worldPosition += cameraRight * rotatedPosition.x;
worldPosition += cameraUp * rotatedPosition.y;
let output: VertOut;
output.position = viewerData.viewProjMatrix * instanceData.worldMatrix * vec4[f32](vertexPos, 1.0);
output.worldPos = worldPosition;
output.position = viewerData.viewProjMatrix * vec4[f32](worldPosition, 1.0);
const if (HasVertexUV)
output.uv = input.uv;
const if (HasColor)
output.color = input.billboardColor;
const if (HasUV)
output.uv = input.pos.xy + vec2[f32](0.5, 0.5);
output.color = input.color;
return output;
}
@@ -411,7 +427,7 @@ fn VertMain(input: VertIn) -> VertOut
const if (HasNormal)
output.normal = rotationMatrix * input.normal;
const if (HasUV)
const if (HasVertexUV)
output.uv = input.uv;
const if (HasNormalMapping)

View File

@@ -292,6 +292,43 @@ namespace Nz
});
NazaraAssert(s_declarations[VertexLayout::XYZ_Normal_UV_Tangent_Skinning]->GetStride() == sizeof(VertexStruct_XYZ_Normal_UV_Tangent_Skinning), "Invalid stride for declaration VertexLayout::XYZ_Normal_UV_Tangent_Skinning");
// VertexLayout::XYZ_SizeRot : VertexStruct_XYZ_SizeRot
s_declarations[VertexLayout::UV_SizeSinCos] = NewDeclaration(VertexInputRate::Vertex, {
{
VertexComponent::TexCoord,
ComponentType::Float2,
0
},
{
VertexComponent::SizeSinCos,
ComponentType::Float4,
0
}
});
NazaraAssert(s_declarations[VertexLayout::UV_SizeSinCos]->GetStride() == sizeof(VertexStruct_UV_SizeSinCos), "Invalid stride for declaration VertexLayout::UV_SizeSinCos");
// VertexLayout::XYZ_SizeRot_Color : VertexStruct_XYZ_SizeRot_Color
s_declarations[VertexLayout::UV_SizeSinCos_Color] = NewDeclaration(VertexInputRate::Vertex, {
{
VertexComponent::TexCoord,
ComponentType::Float2,
0
},
{
VertexComponent::SizeSinCos,
ComponentType::Float4,
0
},
{
VertexComponent::Color,
ComponentType::Color,
0
}
});
NazaraAssert(s_declarations[VertexLayout::UV_SizeSinCos_Color]->GetStride() == sizeof(VertexStruct_UV_SizeSinCos_Color), "Invalid stride for declaration VertexLayout::UV_SizeSinCos_Color");
// VertexLayout::XYZ_UV : VertexStruct_XYZ_UV
s_declarations[VertexLayout::XYZ_UV] = NewDeclaration(VertexInputRate::Vertex, {