From c1239b66199926c610cd863e18a4d27a4cef67f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 17 Apr 2023 20:10:20 +0200 Subject: [PATCH] Shaders: add PhongGL::Flag::DoubleSided. --- doc/changelog.dox | 2 + src/Magnum/Shaders/Phong.frag | 4 + src/Magnum/Shaders/PhongGL.cpp | 3 + src/Magnum/Shaders/PhongGL.h | 14 +++ src/Magnum/Shaders/Test/CMakeLists.txt | 1 + src/Magnum/Shaders/Test/PhongGLTest.cpp | 97 ++++++++++++++++++ .../Test/PhongTestFiles/double-sided.tga | Bin 0 -> 8024 bytes 7 files changed, 121 insertions(+) create mode 100644 src/Magnum/Shaders/Test/PhongTestFiles/double-sided.tga diff --git a/doc/changelog.dox b/doc/changelog.dox index ad28d9ef2..9d2229ff7 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -316,6 +316,8 @@ See also: - Added @ref Shaders::PhongGL::Flag::NoSpecular as a significantly faster alternative to setting specular color to @cpp 0x00000000_rgbaf @ce in case specular highlights are not desired +- Added @ref Shaders::PhongGL::Flag::DoubleSided for rendering double-sided + meshes @subsubsection changelog-latest-new-shadertools ShaderTools library diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index e31b8b483..9a8952ed2 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -462,6 +462,10 @@ void main() { ); normalizedTransformedNormal = tbn*(normalize((texture(normalTexture, interpolatedTextureCoordinates).rgb*2.0 - vec3(1.0))*vec3(normalTextureScale, normalTextureScale, 1.0))); #endif + #ifdef DOUBLE_SIDED + if(!gl_FrontFacing) + normalizedTransformedNormal = -normalizedTransformedNormal; + #endif highp const vec3 cameraDirection = normalize(-transformedPosition); diff --git a/src/Magnum/Shaders/PhongGL.cpp b/src/Magnum/Shaders/PhongGL.cpp index 8e65da948..b13dc5f90 100644 --- a/src/Magnum/Shaders/PhongGL.cpp +++ b/src/Magnum/Shaders/PhongGL.cpp @@ -342,6 +342,7 @@ PhongGL::CompileState PhongGL::compile(const Configuration& configuration) { #endif .addSource(configuration.flags() & Flag::Bitangent ? "#define BITANGENT\n"_s : ""_s) .addSource(configuration.flags() & Flag::VertexColor ? "#define VERTEX_COLOR\n"_s : ""_s) + .addSource(configuration.flags() & Flag::DoubleSided ? "#define DOUBLE_SIDED\n"_s : ""_s) .addSource(configuration.flags() & Flag::AlphaMask ? "#define ALPHA_MASK\n"_s : ""_s) #ifndef MAGNUM_TARGET_GLES2 .addSource(configuration.flags() & Flag::ObjectId ? "#define OBJECT_ID\n"_s : ""_s) @@ -1337,6 +1338,7 @@ Debug& operator<<(Debug& debug, const PhongGL::Flag value) { _c(Bitangent) _c(AlphaMask) _c(VertexColor) + _c(DoubleSided) _c(TextureTransformation) #ifndef MAGNUM_TARGET_GLES2 _c(ObjectId) @@ -1374,6 +1376,7 @@ Debug& operator<<(Debug& debug, const PhongGL::Flags value) { PhongGL::Flag::Bitangent, PhongGL::Flag::AlphaMask, PhongGL::Flag::VertexColor, + PhongGL::Flag::DoubleSided, PhongGL::Flag::InstancedTextureOffset, /* Superset of TextureTransformation */ PhongGL::Flag::TextureTransformation, #ifndef MAGNUM_TARGET_GLES2 diff --git a/src/Magnum/Shaders/PhongGL.h b/src/Magnum/Shaders/PhongGL.h index 3c6935813..1b3aab3fb 100644 --- a/src/Magnum/Shaders/PhongGL.h +++ b/src/Magnum/Shaders/PhongGL.h @@ -667,6 +667,20 @@ class MAGNUM_SHADERS_EXPORT PhongGL: public GL::AbstractShaderProgram { */ VertexColor = 1 << 5, + /** + * Double-sided rendering. By default, lighting is applied only to + * front-facing triangles, with back-facing triangles receiving + * just the ambient color or being culled away. If enabled, the + * shader will evaluate the lighting also on back-facing triangles + * with the normal flipped. Has no effect if no lights are used. + * + * Rendering back-facing triangles requires + * @ref GL::Renderer::Feature::FaceCulling to be disabled. + * @see @ref Trade::MaterialAttribute::DoubleSided + * @m_since_latest + */ + DoubleSided = 1 << 20, + /** * Use the separate @ref Bitangent attribute for retrieving vertex * bitangents. If this flag is not present, the last component of diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index fbeceff2b..1fc51e0a5 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -306,6 +306,7 @@ if(MAGNUM_BUILD_GL_TESTS) PhongTestFiles/colored.tga PhongTestFiles/defaults.tga + PhongTestFiles/double-sided.tga PhongTestFiles/instanced.tga PhongTestFiles/instanced-textured.tga PhongTestFiles/instanced-normal.tga diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 514455e83..851930c4f 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -59,6 +59,7 @@ #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix4.h" #include "Magnum/Math/Swizzle.h" +#include "Magnum/MeshTools/FlipNormals.h" #include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Primitives/Plane.h" @@ -170,6 +171,9 @@ struct PhongGLTest: GL::OpenGLTester { template void renderZeroLights(); + /* This tests something that's irrelevant to UBOs */ + void renderDoubleSided(); + #ifndef MAGNUM_TARGET_GLES2 template void renderSkinning(); #endif @@ -842,6 +846,15 @@ const struct { }; #endif +const struct { + const char* name; + PhongGL::Flags flags; + bool flipNormals; +} RenderDoubleSidedData[]{ + {"normals flipped", {}, true}, + {"double-sided rendering", PhongGL::Flag::DoubleSided, false} +}; + #ifndef MAGNUM_TARGET_GLES2 const struct { const char* name; @@ -1457,6 +1470,11 @@ PhongGLTest::PhongGLTest() { &PhongGLTest::renderSetup, &PhongGLTest::renderTeardown); + addInstancedTests({&PhongGLTest::renderDoubleSided}, + Containers::arraySize(RenderDoubleSidedData), + &PhongGLTest::renderSetup, + &PhongGLTest::renderTeardown); + #ifndef MAGNUM_TARGET_GLES2 /* MSVC needs explicit type due to default template args */ addInstancedTests({ @@ -4398,6 +4416,85 @@ template void PhongGLTest::renderZeroLights() { #endif } +void PhongGLTest::renderDoubleSided() { + auto&& data = RenderDoubleSidedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::MeshData sphere = Primitives::uvSphereSolid(16, 32); + + Trade::MeshData sphereFlippedWinding = Primitives::uvSphereSolid(16, 32); + MeshTools::flipFaceWindingInPlace(sphereFlippedWinding.mutableIndices()); + + Trade::MeshData sphereFlippedNormalsWinding = Primitives::uvSphereSolid(16, 32); + MeshTools::flipNormalsInPlace( + sphereFlippedNormalsWinding.mutableIndices(), + sphereFlippedNormalsWinding.mutableAttribute(Trade::MeshAttribute::Normal)); + + /* Double-sided sphere, renders from both sides if DoubleSided is + enabled and face culling disabled, otherwise only one depending on the + normal direction */ + Trade::MeshData sphereDoubleSided = Primitives::uvSphereSolid(16, 32); + if(data.flipNormals) + MeshTools::flipNormalsInPlace(sphereDoubleSided.mutableAttribute(Trade::MeshAttribute::Normal)); + + PhongGL shader{PhongGL::Configuration{} + .setFlags(data.flags) + .setLightCount(1)}; + shader + .setLightPositions({{-3.0f, 3.0f, 3.0f, 0.0f}}) + .setAmbientColor(0x111111_rgbf) + .setDiffuseColor(0xff3333_rgbf) + .setSpecularColor(0x00000000_rgbaf); + + /* Top left is a sphere from the outside, with CCW triangles, with the back + cut off by the far plane */ + shader + .setProjectionMatrix(Matrix4::orthographicProjection(Vector2{4.5f}, -1.0f, 0.0f)) + .setTransformationMatrix(Matrix4::translation({-1.05f, 1.05f, 0.0f})) + .draw(MeshTools::compile(sphere)); + + /* Bottom left is a sphere from the inside, with CCW triangles, with the + front cut off by the near plane. Normals pointing outside so only top + left should be slightly lighted. */ + shader + .setProjectionMatrix(Matrix4::orthographicProjection(Vector2{4.5f}, 0.0f, 1.0f)) + .setTransformationMatrix(Matrix4::translation({-1.05f, -1.05f, 0.0f})) + .draw(MeshTools::compile(sphereFlippedWinding)); + + /* Top right is a sphere from the inside, with CCW triangles, with face + winding and normals flipped */ + shader + .setProjectionMatrix(Matrix4::orthographicProjection(Vector2{4.5f}, 0.0f, 1.0f)) + .setTransformationMatrix(Matrix4::translation({+1.05f, 1.05f, 0.0f})) + .draw(MeshTools::compile(sphereFlippedNormalsWinding)); + + GL::Renderer::disable(GL::Renderer::Feature::FaceCulling); + + /* Bottom right is a sphere from the inside, with CW triangles and face + culling disabled. Should render like bottom right. + - If DoubleSided isn't enabled on the shader, the code above flipped + normals to point inside. If DoubleSided is accidentally active + always, it will flip them back outside, resulting in the same result + as on the bottom left. + - If DoubleSided is enabled on the shader, the normals weren't flipped + by the code above and the shader should do that instead. If it + doesn't, it will again wrongly render as on the bottom left. */ + shader + .setProjectionMatrix(Matrix4::orthographicProjection(Vector2{4.5f}, 0.0f, 1.0f)) + .setTransformationMatrix(Matrix4::translation({+1.05f, -1.05f, 0.0f})) + .draw(MeshTools::compile(sphereDoubleSided)); + + GL::Renderer::enable(GL::Renderer::Feature::FaceCulling); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE_WITH( + /* Dropping the alpha channel, as it's always 1.0 */ + Containers::arrayCast(_framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm}).pixels()), + Utility::Path::join(_testDir, "PhongTestFiles/double-sided.tga"), + (DebugTools::CompareImageToFile{_manager, 1.34f, 0.04f})); +} + #ifndef MAGNUM_TARGET_GLES2 template void PhongGLTest::renderSkinning() { auto&& data = RenderSkinningData[testCaseInstanceId()]; diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/double-sided.tga b/src/Magnum/Shaders/Test/PhongTestFiles/double-sided.tga new file mode 100644 index 0000000000000000000000000000000000000000..0ab168617f9398c7eb988d65d762ea6370f11170 GIT binary patch literal 8024 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 Omh