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 000000000..0ab168617 Binary files /dev/null and b/src/Magnum/Shaders/Test/PhongTestFiles/double-sided.tga differ