From 7976ea27b93a9a70c371944a5c32e1e3df5f7643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Leclercq?= Date: Tue, 18 Jan 2022 06:01:15 +0100 Subject: [PATCH] Add initial support for normal mapping and other light types --- examples/GraphicsTest/main.cpp | 17 ++- src/Nazara/Graphics/ForwardFramePipeline.cpp | 21 +++- src/Nazara/Graphics/PhongLightingMaterial.cpp | 29 ++++- .../Resources/Shaders/phong_material.nzsl | 102 ++++++++++++++---- 4 files changed, 144 insertions(+), 25 deletions(-) diff --git a/examples/GraphicsTest/main.cpp b/examples/GraphicsTest/main.cpp index 00c76499e..7d37b372e 100644 --- a/examples/GraphicsTest/main.cpp +++ b/examples/GraphicsTest/main.cpp @@ -30,7 +30,7 @@ int main() meshParams.center = true; meshParams.storage = Nz::DataStorage::Software; meshParams.matrix = Nz::Matrix4f::Rotate(Nz::EulerAnglesf(0.f, -90.f, 0.f)) * Nz::Matrix4f::Scale(Nz::Vector3f(0.002f)); - meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV); + meshParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ_Normal_UV_Tangent); std::shared_ptr device = Nz::Graphics::Instance()->GetRenderDevice(); @@ -70,10 +70,13 @@ int main() material->AddPass("ForwardPass", materialPass); + std::shared_ptr normalMap = Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/normal.png", texParams); + Nz::PhongLightingMaterial phongMat(*materialPass); phongMat.EnableAlphaTest(false); phongMat.SetAlphaMap(Nz::Texture::LoadFromFile(resourceDir / "alphatile.png", texParams)); phongMat.SetDiffuseMap(Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/diffuse.png", texParams)); + phongMat.SetNormalMap(Nz::Texture::LoadFromFile(resourceDir / "Spaceship/Texture/normal.png", texParams)); Nz::Model model(std::move(gfxMesh), spaceshipMesh->GetAABB()); for (std::size_t i = 0; i < model.GetSubMeshCount(); ++i) @@ -127,6 +130,18 @@ int main() case Nz::WindowEventType::KeyPressed: if (event.key.virtualKey == Nz::Keyboard::VKey::A) phongMat.EnableAlphaTest(!phongMat.IsAlphaTestEnabled()); + else if (event.key.virtualKey == Nz::Keyboard::VKey::N) + { + if (phongMat.GetNormalMap()) + phongMat.SetNormalMap({}); + else + phongMat.SetNormalMap(normalMap); + } + else if (event.key.virtualKey == Nz::Keyboard::VKey::Space) + { + modelInstance->UpdateWorldMatrix(Nz::Matrix4f::Translate(viewerPos)); + framePipeline.InvalidateWorldInstance(modelInstance.get()); + } break; diff --git a/src/Nazara/Graphics/ForwardFramePipeline.cpp b/src/Nazara/Graphics/ForwardFramePipeline.cpp index ad0eff987..9bac97537 100644 --- a/src/Nazara/Graphics/ForwardFramePipeline.cpp +++ b/src/Nazara/Graphics/ForwardFramePipeline.cpp @@ -9,12 +9,13 @@ #include #include #include -#include #include +#include #include #include #include #include +#include #include #include #include @@ -44,13 +45,29 @@ namespace Nz throw std::runtime_error("failed to create light data buffer"); std::vector staticLightData(lightOffset.totalSize); - AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; + /*AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.type) = 0; AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.color) = Vector4f(1.f, 1.f, 1.f, 1.f); AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.factor) = Vector2f(0.2f, 1.f); AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter1) = Vector4f(0.f, 0.f, -1.f, 1.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.shadowMappingFlag) = 0;*/ + + AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.type) = 1; + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.color) = Vector4f(1.f, 1.f, 1.f, 1.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.factor) = Vector2f(0.2f, 1.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter1) = Vector4f(0.f, 0.f, 0.f, 1.f / 3.f); AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.shadowMappingFlag) = 0; + /*AccessByOffset(staticLightData.data(), lightOffset.lightCountOffset) = 1; + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.type) = 2; + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.color) = Vector4f(1.f, 1.f, 1.f, 1.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.factor) = Vector2f(0.2f, 1.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter1) = Vector4f(0.f, 0.f, 0.f, 1.f / 3.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter2) = Vector4f(0.f, 0.f, -1.f, 0.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.parameter3) = Vector4f(DegreeAnglef(15.f).GetCos(), DegreeAnglef(20.f).GetCos(), 0.f, 0.f); + AccessByOffset(staticLightData.data(), lightOffset.lightsOffset + lightOffset.lightMemberOffsets.shadowMappingFlag) = 0;*/ + if (!m_lightDataBuffer->Fill(staticLightData.data(), 0, staticLightData.size())) throw std::runtime_error("failed to fill light data buffer"); } diff --git a/src/Nazara/Graphics/PhongLightingMaterial.cpp b/src/Nazara/Graphics/PhongLightingMaterial.cpp index 663184e72..0c6cb5a61 100644 --- a/src/Nazara/Graphics/PhongLightingMaterial.cpp +++ b/src/Nazara/Graphics/PhongLightingMaterial.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Nz @@ -259,6 +260,7 @@ namespace Nz std::size_t positionLocationIndex = FetchLocationOption("PosLocation"); std::size_t colorLocationIndex = FetchLocationOption("ColorLocation"); std::size_t normalLocationIndex = FetchLocationOption("NormalLocation"); + std::size_t tangentLocationIndex = FetchLocationOption("TangentLocation"); std::size_t uvLocationIndex = FetchLocationOption("UvLocation"); uberShader->UpdateConfigCallback([=](UberShader::Config& config, const std::vector& vertexBuffers) @@ -292,6 +294,12 @@ namespace Nz break; + case VertexComponent::Tangent: + if (tangentLocationIndex != InvalidOption) + config.optionValues[tangentLocationIndex] = static_cast(locationIndex); + + break; + case VertexComponent::TexCoord: if (uvLocationIndex != InvalidOption) config.optionValues[uvLocationIndex] = static_cast(locationIndex); @@ -339,7 +347,26 @@ namespace Nz std::vector> PhongLightingMaterial::BuildShaders() { - ShaderAst::StatementPtr shaderAst = ShaderLang::Parse(std::string_view(reinterpret_cast(r_shader), sizeof(r_shader))); + ShaderAst::StatementPtr shaderAst; + +#ifdef NAZARA_DEBUG + std::filesystem::path shaderPath = "../../src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl"; + if (std::filesystem::exists(shaderPath)) + { + try + { + shaderAst = ShaderLang::ParseFromFile(shaderPath); + } + catch (const std::exception& e) + { + NazaraError(std::string("failed to load shader from engine folder: ") + e.what()); + } + } +#endif + + if (!shaderAst) + shaderAst = ShaderLang::Parse(std::string_view(reinterpret_cast(r_shader), sizeof(r_shader))); + auto shader = std::make_shared(ShaderStageType::Fragment | ShaderStageType::Vertex, shaderAst); return { std::move(shader) }; diff --git a/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl b/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl index 9a03855a8..6328ec0f9 100644 --- a/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl +++ b/src/Nazara/Graphics/Resources/Shaders/phong_material.nzsl @@ -21,12 +21,15 @@ option BillboardSizeRotLocation: i32 = -1; option ColorLocation: i32 = -1; option NormalLocation: i32 = -1; option PosLocation: i32 = -1; +option TangentLocation: i32 = -1; option UvLocation: i32 = -1; const HasNormal = (NormalLocation >= 0); const HasVertexColor = (ColorLocation >= 0); const HasColor = (HasVertexColor || Billboard); +const HasTangent = (TangentLocation >= 0); const HasUV = (UvLocation >= 0); +const HasNormalMapping = HasNormalTexture && HasNormal && HasTangent; [layout(std140)] struct MaterialSettings @@ -101,22 +104,24 @@ external [binding(10)] MaterialSpecularMap: sampler2D, } -// Fragment stage -struct FragIn +struct VertToFrag { [location(0)] worldPos: vec3, [location(1), cond(HasUV)] uv: vec2, [location(2), cond(HasColor)] color: vec4, [location(3), cond(HasNormal)] normal: vec3, + [location(4), cond(HasNormalMapping)] tbnMatrix: mat3, + [builtin(position)] position: vec4, } +// Fragment stage struct FragOut { [location(0)] RenderTarget0: vec4 } [entry(frag)] -fn main(input: FragIn) -> FragOut +fn main(input: VertToFrag) -> FragOut { let diffuseColor = settings.DiffuseColor; @@ -146,6 +151,12 @@ fn main(input: FragIn) -> FragOut let eyeVec = normalize(viewerData.eyePosition - input.worldPos); + let normal: vec3; + const if (HasNormalMapping) + normal = normalize(input.tbnMatrix * (MaterialNormalMap.Sample(input.uv).xyz * 2.0 - vec3(1.0, 1.0, 1.0))); + else + normal = normalize(input.normal); + for i in 0 -> lightData.lightCount { let light = lightData.lights[i]; @@ -156,15 +167,15 @@ fn main(input: FragIn) -> FragOut // TODO: Add switch instruction if (light.type == DirectionalLight) { - let lightDir = -(light.parameter1.xyz); //< FIXME + let lightDir = light.parameter1.xyz; lightAmbient += light.color.rgb * lightAmbientFactor * settings.AmbientColor; - let lambert = max(dot(input.normal, lightDir), 0.0); + let lambert = max(dot(normal, -lightDir), 0.0); lightDiffuse += lambert * light.color.rgb * lightDiffuseFactor; - let reflection = reflect(-lightDir, input.normal); + let reflection = reflect(lightDir, normal); let specFactor = max(dot(reflection, eyeVec), 0.0); specFactor = pow(specFactor, settings.Shininess); @@ -172,11 +183,56 @@ fn main(input: FragIn) -> FragOut } else if (light.type == PointLight) { - + let lightPos = light.parameter1.xyz; + let lightInvRadius = light.parameter1.w; + + let lightToPos = input.worldPos - lightPos; + let dist = length(lightToPos); + let lightToPosNorm = lightToPos / max(dist, 0.0001); + + let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0); + + lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor; + + let lambert = max(dot(normal, -lightToPosNorm), 0.0); + + lightDiffuse += attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor; + + let reflection = reflect(lightToPosNorm, normal); + let specFactor = max(dot(reflection, eyeVec), 0.0); + specFactor = pow(specFactor, settings.Shininess); + + lightSpecular += attenuationFactor * specFactor * light.color.rgb; } else if (light.type == SpotLight) { - + let lightPos = light.parameter1.xyz; + let lightDir = light.parameter2.xyz; + let lightInvRadius = light.parameter1.w; + let lightInnerAngle = light.parameter3.x; + let lightOuterAngle = light.parameter3.y; + + let lightToPos = input.worldPos - lightPos; + let dist = length(lightToPos); + let lightToPosNorm = lightToPos / max(dist, 0.0001); + + let curAngle = dot(lightDir, lightToPosNorm); + let innerMinusOuterAngle = lightInnerAngle - lightOuterAngle; + + let attenuationFactor = max(1.0 - dist * lightInvRadius, 0.0); + attenuationFactor *= max((curAngle - lightOuterAngle) / innerMinusOuterAngle, 0.0); + + lightAmbient += attenuationFactor * light.color.rgb * lightAmbientFactor * settings.AmbientColor; + + let lambert = max(dot(normal, -lightToPosNorm), 0.0); + + lightDiffuse += attenuationFactor * lambert * light.color.rgb * lightDiffuseFactor; + + let reflection = reflect(lightToPosNorm, normal); + let specFactor = max(dot(reflection, eyeVec), 0.0); + specFactor = pow(specFactor, settings.Shininess); + + lightSpecular += attenuationFactor * specFactor * light.color.rgb; } } @@ -214,6 +270,9 @@ struct VertIn [cond(HasNormal), location(NormalLocation)] normal: vec3, + [cond(HasTangent), location(TangentLocation)] + tangent: vec3, + [cond(Billboard), location(BillboardCenterLocation)] billboardCenter: vec3, @@ -224,15 +283,6 @@ struct VertIn billboardColor: vec4 } -struct VertOut -{ - [location(0)] worldPos: vec3, - [location(1), cond(HasUV)] uv: vec2, - [location(2), cond(HasColor)] color: vec4, - [location(3), cond(HasNormal)] normal: vec3, - [builtin(position)] position: vec4, -} - [entry(vert), cond(Billboard)] fn billboardMain(input: VertIn) -> VertOut { @@ -252,7 +302,7 @@ fn billboardMain(input: VertIn) -> VertOut vertexPos += cameraRight * rotatedPosition.x; vertexPos += cameraUp * rotatedPosition.y; - let output: VertOut; + let output: VertToFrag; output.position = viewerData.viewProjMatrix * instanceData.worldMatrix * vec4(vertexPos, 1.0); const if (HasColor) @@ -265,22 +315,32 @@ fn billboardMain(input: VertIn) -> VertOut } [entry(vert), cond(!Billboard)] -fn main(input: VertIn) -> VertOut +fn main(input: VertIn) -> VertToFrag { let worldPosition = instanceData.worldMatrix * vec4(input.pos, 1.0); - let output: VertOut; + let output: VertToFrag; output.worldPos = worldPosition.xyz; output.position = viewerData.viewProjMatrix * worldPosition; + let rotationMatrix = mat3(instanceData.worldMatrix); + const if (HasColor) output.color = input.color; const if (HasNormal) - output.normal = input.normal; + output.normal = rotationMatrix * input.normal; const if (HasUV) output.uv = input.uv; + const if (HasNormalMapping) + { + let binormal = cross(input.normal, input.tangent); + output.tbnMatrix[0] = normalize(rotationMatrix * input.tangent); + output.tbnMatrix[1] = normalize(rotationMatrix * binormal); + output.tbnMatrix[2] = normalize(rotationMatrix * input.normal); + } + return output; }