From 5285dcc986af6b62f732984afddce8e6717436c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 4 Sep 2020 20:10:29 +0200 Subject: [PATCH] Shaders: add a normal texture scale parameter to Phong. It's needed to support the new material attributes supported by glTF. The test output is slightly different as the normal coming from the texture wasn't normalized before. --- doc/changelog.dox | 8 +++++ src/Magnum/Shaders/Phong.cpp | 11 +++++++ src/Magnum/Shaders/Phong.frag | 23 +++++++++---- src/Magnum/Shaders/Phong.h | 31 ++++++++++++++---- src/Magnum/Shaders/Phong.vert | 4 +-- src/Magnum/Shaders/Test/CMakeLists.txt | 2 ++ src/Magnum/Shaders/Test/PhongGLTest.cpp | 25 ++++++++++---- .../Test/PhongTestFiles/instanced-normal.tga | Bin 9469 -> 9875 bytes .../Test/PhongTestFiles/textured-normal.tga | Bin 11108 -> 11108 bytes .../PhongTestFiles/textured-normal0.0.tga | Bin 0 -> 3070 bytes .../PhongTestFiles/textured-normal0.5.tga | Bin 0 -> 10855 bytes 11 files changed, 83 insertions(+), 21 deletions(-) create mode 100644 src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.0.tga create mode 100644 src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.5.tga diff --git a/doc/changelog.dox b/doc/changelog.dox index 39eef6aa9..bc33ca812 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -73,6 +73,11 @@ See also: - Added @ref MeshTools::generateQuadIndices() for quad triangulation including non-convex and non-planar quads +@subsubsection changelog-latest-new-shaders Shaders library + +- Added @ref Shaders::Phong::setNormalTextureScale(), consuming the recently + added @ref Trade::MaterialAttribute::NormalTextureScale material attribute + @subsubsection changelog-latest-new-scenegraph SceneGraph library - Added @ref SceneGraph::Object::move() @@ -148,6 +153,9 @@ See also: - @ref Shaders::Phong was normalizing light direction in vertex shader, causing the fragment-interpolated direction being incorrect with visible artifacts on long polygons under low light angle +- @ref Shaders::Phong wasn't normalizing normals coming from normal textures, + which may have caused slight artifacts due to limited precision of 8-bit + pixel formats @subsection changelog-latest-deprecated Deprecated APIs diff --git a/src/Magnum/Shaders/Phong.cpp b/src/Magnum/Shaders/Phong.cpp index dfdd6f915..18a10f990 100644 --- a/src/Magnum/Shaders/Phong.cpp +++ b/src/Magnum/Shaders/Phong.cpp @@ -178,6 +178,8 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l _diffuseColorUniform = uniformLocation("diffuseColor"); _specularColorUniform = uniformLocation("specularColor"); _shininessUniform = uniformLocation("shininess"); + if(flags & Flag::NormalTexture) + _normalTextureScaleUniform = uniformLocation("normalTextureScale"); _lightPositionsUniform = uniformLocation("lightPositions"); _lightColorsUniform = uniformLocation("lightColors"); } @@ -210,6 +212,8 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l setDiffuseColor(Magnum::Color4{1.0f}); setSpecularColor(Magnum::Color4{1.0f, 0.0f}); setShininess(80.0f); + if(flags & Flag::NormalTexture) + setNormalTextureScale(1.0f); setLightColors(Containers::Array{Containers::DirectInit, lightCount, Magnum::Color4{1.0f}}); /* Light position is zero by default */ setNormalMatrix({}); @@ -275,6 +279,13 @@ Phong& Phong::setShininess(Float shininess) { return *this; } +Phong& Phong::setNormalTextureScale(const Float scale) { + CORRADE_ASSERT(_flags & Flag::NormalTexture, + "Shaders::Phong::setNormalTextureScale(): the shader was not created with normal texture enabled", *this); + if(_lightCount) setUniform(_normalTextureScaleUniform, scale); + return *this; +} + Phong& Phong::setAlphaMask(Float mask) { CORRADE_ASSERT(_flags & Flag::AlphaMask, "Shaders::Phong::setAlphaMask(): the shader was not created with alpha mask enabled", *this); diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index 43a05e571..05e284e69 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -107,10 +107,21 @@ uniform mediump float shininess ; #endif -#ifdef ALPHA_MASK +#ifdef NORMAL_TEXTURE #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 8) #endif +uniform mediump float normalTextureScale + #ifndef GL_ES + = 1.0 + #endif + ; +#endif + +#ifdef ALPHA_MASK +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 9) +#endif uniform lowp float alphaMask #ifndef GL_ES = 0.5 @@ -120,16 +131,16 @@ uniform lowp float alphaMask #ifdef OBJECT_ID #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 9) +layout(location = 10) #endif /* mediump is just 2^10, which might not be enough, this is 2^16 */ uniform highp uint objectId; /* defaults to zero */ #endif #if LIGHT_COUNT -/* Needs to be last because it uses locations 10 + LIGHT_COUNT to - 10 + 2*LIGHT_COUNT - 1. Location 10 is lightPositions. Also it can't be - specified as 10 + LIGHT_COUNT because that requires ARB_enhanced_layouts. */ +/* Needs to be last because it uses locations 11 + LIGHT_COUNT to + 11 + 2*LIGHT_COUNT - 1. Location 11 is lightPositions. Also it can't be + specified as 11 + LIGHT_COUNT because that requires ARB_enhanced_layouts. */ #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = LIGHT_COLORS_LOCATION) /* I fear this will blow up some drivers */ #endif @@ -214,7 +225,7 @@ void main() { normalizedTransformedTangent)), normalizedTransformedNormal ); - normalizedTransformedNormal = tbn*(texture(normalTexture, interpolatedTextureCoordinates).rgb*2.0 - vec3(1.0)); + normalizedTransformedNormal = tbn*(normalize((texture(normalTexture, interpolatedTextureCoordinates).rgb*2.0 - vec3(1.0))*vec3(normalTextureScale, normalTextureScale, 1.0))); #endif /* Add diffuse color for each light */ diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index fcd5b5557..6e03e6143 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -513,6 +513,24 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { */ Phong& bindDiffuseTexture(GL::Texture2D& texture); + /** + * @brief Set normal texture scale + * @return Reference to self (for method chaining) + * @m_since_latest + * + * Affects strength of the normal mapping. Initial value is + * @cpp 1.0f @ce, meaning the normal texture is not changed in any way; + * a value of @cpp 0.0f @ce disables the normal texture effect + * altogether. + * + * Expects that the shader was created with @ref Flag::NormalTexture + * enabled. If @ref lightCount() is zero, this function is a no-op, as + * normals don't contribute to the output in that case. + * @see @ref bindNormalTexture(), + * @ref Trade::MaterialAttribute::NormalTextureScale + */ + Phong& setNormalTextureScale(Float scale); + /** * @brief Bind a normal texture * @return Reference to self (for method chaining) @@ -521,8 +539,8 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { * Expects that the shader was created with @ref Flag::NormalTexture * enabled and the @ref Tangent attribute was supplied. If * @ref lightCount() is zero, this function is a no-op, as normals - * dosn't contribute to the output in that case. - * @see @ref bindTextures() + * don't contribute to the output in that case. + * @see @ref bindTextures(), @ref setNormalTextureScale() */ Phong& bindNormalTexture(GL::Texture2D& texture); @@ -743,12 +761,13 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { _diffuseColorUniform{5}, _specularColorUniform{6}, _shininessUniform{7}, - _alphaMaskUniform{8}; + _normalTextureScaleUniform{8}, + _alphaMaskUniform{9}; #ifndef MAGNUM_TARGET_GLES2 - Int _objectIdUniform{9}; + Int _objectIdUniform{10}; #endif - Int _lightPositionsUniform{10}, - _lightColorsUniform; /* 10 + lightCount, set in the constructor */ + Int _lightPositionsUniform{11}, + _lightColorsUniform; /* 11 + lightCount, set in the constructor */ }; /** @debugoperatorclassenum{Phong,Phong::Flag} */ diff --git a/src/Magnum/Shaders/Phong.vert b/src/Magnum/Shaders/Phong.vert index c89ee394a..981063770 100644 --- a/src/Magnum/Shaders/Phong.vert +++ b/src/Magnum/Shaders/Phong.vert @@ -73,9 +73,9 @@ uniform mediump mat3 textureMatrix #endif #if LIGHT_COUNT -/* Needs to be last because it uses locations 10 to 10 + LIGHT_COUNT - 1 */ +/* Needs to be last because it uses locations 11 to 11 + LIGHT_COUNT - 1 */ #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 10) +layout(location = 11) #endif uniform highp vec3 lightPositions[LIGHT_COUNT]; /* defaults to zero */ #endif diff --git a/src/Magnum/Shaders/Test/CMakeLists.txt b/src/Magnum/Shaders/Test/CMakeLists.txt index 805405e70..83cd3f7e7 100644 --- a/src/Magnum/Shaders/Test/CMakeLists.txt +++ b/src/Magnum/Shaders/Test/CMakeLists.txt @@ -234,6 +234,8 @@ if(BUILD_GL_TESTS) PhongTestFiles/textured-diffuse.tga PhongTestFiles/textured-diffuse-transformed.tga PhongTestFiles/textured-normal.tga + PhongTestFiles/textured-normal0.0.tga + PhongTestFiles/textured-normal0.5.tga PhongTestFiles/textured-specular.tga PhongTestFiles/textured.tga PhongTestFiles/vertexColor.tga diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 50ce4718f..0e301e6ad 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -206,13 +206,17 @@ const struct { /* MSVC 2015 doesn't like constexpr here due to the angles */ const struct { const char* name; + const char* expected; bool multiBind; Deg rotation; + Float scale; } RenderTexturedNormalData[]{ - {"", false, {}}, - {"multi bind", true, {}}, - {"rotated 90°", false, 90.0_degf}, - {"rotated -90°", false, -90.0_degf} + {"", "textured-normal.tga", false, {}, 1.0f}, + {"multi bind", "textured-normal.tga", true, {}, 1.0f}, + {"rotated 90°", "textured-normal.tga", false, 90.0_degf, 1.0f}, + {"rotated -90°", "textured-normal.tga", false, -90.0_degf, 1.0f}, + {"0.5 scale", "textured-normal0.5.tga", false, {}, 0.5f}, + {"0.0 scale", "textured-normal0.0.tga", false, {}, 0.0f} }; const struct { @@ -490,6 +494,7 @@ void PhongGLTest::bindTexturesNotEnabled() { .bindDiffuseTexture(texture) .bindSpecularTexture(texture) .bindNormalTexture(texture) + .setNormalTextureScale(0.5f) .bindTextures(&texture, &texture, &texture, &texture); CORRADE_COMPARE(out.str(), @@ -497,6 +502,7 @@ void PhongGLTest::bindTexturesNotEnabled() { "Shaders::Phong::bindDiffuseTexture(): the shader was not created with diffuse texture enabled\n" "Shaders::Phong::bindSpecularTexture(): the shader was not created with specular texture enabled\n" "Shaders::Phong::bindNormalTexture(): the shader was not created with normal texture enabled\n" + "Shaders::Phong::setNormalTextureScale(): the shader was not created with normal texture enabled\n" "Shaders::Phong::bindTextures(): the shader was not created with any textures enabled\n"); } @@ -931,6 +937,10 @@ void PhongGLTest::renderTexturedNormal() { .setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f)) .setDiffuseColor(0x999999_rgbf); + /* Verify the default is working properly */ + if(data.scale != 1.0f) + shader.setNormalTextureScale(data.scale); + if(data.multiBind) shader.bindTextures(nullptr, nullptr, nullptr, &normal); else @@ -965,7 +975,7 @@ void PhongGLTest::renderTexturedNormal() { const Float maxThreshold = 191.0f, meanThreshold = 3.017f; #endif CORRADE_COMPARE_WITH(pixels, - Utility::Directory::join(_testDir, "PhongTestFiles/textured-normal.tga"), + Utility::Directory::join({_testDir, "PhongTestFiles", data.expected}), (DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold})); } @@ -1377,7 +1387,7 @@ void PhongGLTest::renderZeroLights() { Primitives::UVSphereFlag::TextureCoordinates)); /* Enable also Object ID, if supported */ - Phong::Flags flags = Phong::Flag::AmbientTexture|Phong::Flag::AlphaMask; + Phong::Flags flags = Phong::Flag::AmbientTexture|Phong::Flag::NormalTexture|Phong::Flag::AlphaMask; #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES if(GL::Context::current().isExtensionSupported()) @@ -1419,7 +1429,8 @@ void PhongGLTest::renderZeroLights() { .setNormalMatrix(Matrix3x3{Math::ZeroInit}) .setDiffuseColor(0xfa9922_rgbf) .setSpecularColor(0xfa9922_rgbf) - .setShininess(0.2f); + .setShininess(0.2f) + .setNormalTextureScale(-0.3f); #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/instanced-normal.tga b/src/Magnum/Shaders/Test/PhongTestFiles/instanced-normal.tga index ae238e262e5605e36128abd58051193b0fd8fdda..7b2fe00b4106bb2eb3afa16c59e4e62156428fed 100644 GIT binary patch literal 9875 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 diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/textured-normal.tga b/src/Magnum/Shaders/Test/PhongTestFiles/textured-normal.tga index 87b5d7ea65e92bd0cb2e33185afc7fffd78f7594..f6d72ad7888216be9ee81f527a8894f4e3829641 100644 GIT binary patch literal 11108 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 T=XLz(MEf9m(gy$gf6x90XTTvO literal 11108 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 AL;wH) literal 0 HcmV?d00001 diff --git a/src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.5.tga b/src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.5.tga new file mode 100644 index 0000000000000000000000000000000000000000..b85fd7aacd44666f9633311ff2df29da239d9dbc GIT binary patch literal 10855 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 literal 0 HcmV?d00001