diff --git a/doc/changelog.dox b/doc/changelog.dox index 8ad44a3a4..51afaf6c6 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -138,6 +138,7 @@ See also: - New dedicated @ref Shaders::VertexColor::Color3 and @ref Shaders::VertexColor::Color4 attribute specifiers for more convenient distinction between three- and four-component vertex color attribute. +- Support for multiple lights in @ref Shaders::Phong - Classical alpha masking support in @ref Shaders::Flat and @ref Shaders::Phong - Debug output for the @ref Shaders::Flat::Flag / @ref Shaders::Flat::Flags, diff --git a/src/Magnum/Shaders/Phong.cpp b/src/Magnum/Shaders/Phong.cpp index 0a1461f74..f663a1783 100644 --- a/src/Magnum/Shaders/Phong.cpp +++ b/src/Magnum/Shaders/Phong.cpp @@ -25,7 +25,11 @@ #include "Phong.h" +#ifdef MAGNUM_TARGET_GLES +#include +#endif #include +#include #include #include "Magnum/GL/Context.h" @@ -45,7 +49,7 @@ namespace { }; } -Phong::Phong(const Flags flags): _flags(flags) { +Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _lightCount{lightCount}, _lightColorsUniform{9 + Int(lightCount)} { #ifdef MAGNUM_BUILD_STATIC /* Import resources on static build, if not already */ if(!Utility::Resource::hasGroup("MagnumShaders")) @@ -62,13 +66,40 @@ Phong::Phong(const Flags flags): _flags(flags) { GL::Shader vert = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Vertex); GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment); + #ifndef MAGNUM_TARGET_GLES + /* Initializer for the light color array -- we need a list of vec4(1.0) + joined by commas. For GLES we'll simply upload the values directly. */ + constexpr const char lightInitializerPreamble[] = "#define LIGHT_COLOR_INITIALIZER "; + constexpr std::size_t lightInitializerPreambleSize = + Containers::arraySize(lightInitializerPreamble) - 1; + constexpr const char lightInitializerItem[] = "vec4(1.0), "; + constexpr std::size_t lightInitializerItemSize = + Containers::arraySize(lightInitializerItem) - 1; + std::string lightInitializer; + lightInitializer.reserve(Containers::arraySize(lightInitializerPreamble) - 1 + lightCount*lightInitializerItemSize); + lightInitializer.append(lightInitializerPreamble, lightInitializerPreambleSize); + for(std::size_t i = 0; i != lightCount; ++i) + lightInitializer.append(lightInitializerItem, lightInitializerItemSize); + + /* Drop the last comma and add a newline at the end */ + lightInitializer[lightInitializer.size() - 2] = '\n'; + lightInitializer.resize(lightInitializer.size() - 1); + #endif + vert.addSource(flags ? "#define TEXTURED\n" : "") + .addSource(Utility::formatString("#define LIGHT_COUNT {}\n", lightCount)) .addSource(rs.get("generic.glsl")) .addSource(rs.get("Phong.vert")); frag.addSource(flags & Flag::AmbientTexture ? "#define AMBIENT_TEXTURE\n" : "") .addSource(flags & Flag::DiffuseTexture ? "#define DIFFUSE_TEXTURE\n" : "") .addSource(flags & Flag::SpecularTexture ? "#define SPECULAR_TEXTURE\n" : "") .addSource(flags & Flag::AlphaMask ? "#define ALPHA_MASK\n" : "") + .addSource(Utility::formatString( + "#define LIGHT_COUNT {}\n" + "#define LIGHT_COLORS_LOCATION {}\n", lightCount, 9 + lightCount)) + #ifndef MAGNUM_TARGET_GLES + .addSource(std::move(lightInitializer)) + #endif .addSource(rs.get("Phong.frag")); CORRADE_INTERNAL_ASSERT_OUTPUT(GL::Shader::compile({vert, frag})); @@ -96,13 +127,13 @@ Phong::Phong(const Flags flags): _flags(flags) { _transformationMatrixUniform = uniformLocation("transformationMatrix"); _projectionMatrixUniform = uniformLocation("projectionMatrix"); _normalMatrixUniform = uniformLocation("normalMatrix"); - _lightPositionUniform = uniformLocation("lightPosition"); _ambientColorUniform = uniformLocation("ambientColor"); _diffuseColorUniform = uniformLocation("diffuseColor"); _specularColorUniform = uniformLocation("specularColor"); - _lightColorUniform = uniformLocation("lightColor"); _shininessUniform = uniformLocation("shininess"); if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask"); + _lightPositionsUniform = uniformLocation("lightPositions"); + _lightColorsUniform = uniformLocation("lightColors"); } #ifndef MAGNUM_TARGET_GLES @@ -121,9 +152,9 @@ Phong::Phong(const Flags flags): _flags(flags) { else setAmbientColor(Color4{0.0f}); setDiffuseColor(Color4{1.0f}); setSpecularColor(Color4{1.0f}); - setLightColor(Color4{1.0f}); setShininess(80.0f); if(flags & Flag::AlphaMask) setAlphaMask(0.5f); + setLightColors(Containers::Array{Containers::DirectInit, lightCount, Color4{1.0f}}); setTransformationMatrix({}); setProjectionMatrix({}); @@ -174,6 +205,13 @@ Phong& Phong::setLightPositions(const Containers::ArrayView posit return *this; } +Phong& Phong::setLightPosition(UnsignedInt id, const Vector3& position) { + CORRADE_ASSERT(id < _lightCount, + "Shaders::Phong::setLightPosition(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); + setUniform(_lightPositionsUniform + id, position); + return *this; +} + Phong& Phong::setLightColors(const Containers::ArrayView colors) { CORRADE_ASSERT(_lightCount == colors.size(), "Shaders::Phong::setLightColors(): expected" << _lightCount << "items but got" << colors.size(), *this); @@ -181,6 +219,13 @@ Phong& Phong::setLightColors(const Containers::ArrayView colors) { return *this; } +Phong& Phong::setLightColor(UnsignedInt id, const Color4& color) { + CORRADE_ASSERT(id < _lightCount, + "Shaders::Phong::setLightColor(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this); + setUniform(_lightColorsUniform + id, color); + return *this; +} + Debug& operator<<(Debug& debug, const Phong::Flag value) { switch(value) { /* LCOV_EXCL_START */ diff --git a/src/Magnum/Shaders/Phong.frag b/src/Magnum/Shaders/Phong.frag index 8ac7e9d84..e9c6afc89 100644 --- a/src/Magnum/Shaders/Phong.frag +++ b/src/Magnum/Shaders/Phong.frag @@ -88,34 +88,37 @@ uniform lowp vec4 specularColor #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 7) #endif -uniform lowp vec4 lightColor +uniform mediump float shininess #ifndef GL_ES - = vec4(1.0) + = 80.0 #endif ; +#ifdef ALPHA_MASK #ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 8) #endif -uniform mediump float shininess +uniform lowp float alphaMask #ifndef GL_ES - = 80.0 + = 0.5 #endif ; +#endif -#ifdef ALPHA_MASK +/* Needs to be last because it uses locations 9 + LIGHT_COUNT to + 9 + 2*LIGHT_COUNT - 1. Location 9 is lightPositions. Also it can't be + specified as 9 + LIGHT_COUNT because that requires ARB_enhanced_layouts. */ #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 9) +layout(location = LIGHT_COLORS_LOCATION) /* I fear this will blow up some drivers */ #endif -uniform lowp float alphaMask +uniform lowp vec4 lightColors[LIGHT_COUNT] #ifndef GL_ES - = 0.5 + = vec4[](LIGHT_COLOR_INITIALIZER) #endif ; -#endif in mediump vec3 transformedNormal; -in highp vec3 lightDirection; +in highp vec3 lightDirections[LIGHT_COUNT]; in highp vec3 cameraDirection; #if defined(AMBIENT_TEXTURE) || defined(DIFFUSE_TEXTURE) || defined(SPECULAR_TEXTURE) @@ -147,17 +150,19 @@ void main() { color = finalAmbientColor; mediump vec3 normalizedTransformedNormal = normalize(transformedNormal); - highp vec3 normalizedLightDirection = normalize(lightDirection); - - /* Add diffuse color */ - lowp float intensity = max(0.0, dot(normalizedTransformedNormal, normalizedLightDirection)); - color += vec4(finalDiffuseColor.rgb*lightColor.rgb*intensity, lightColor.a*finalDiffuseColor.a); - /* Add specular color, if needed */ - if(intensity > 0.001) { - highp vec3 reflection = reflect(-normalizedLightDirection, normalizedTransformedNormal); - mediump float specularity = pow(max(0.0, dot(normalize(cameraDirection), reflection)), shininess); - color += vec4(finalSpecularColor.rgb*specularity, finalSpecularColor.a); + /* Add diffuse color for each light */ + for(int i = 0; i < LIGHT_COUNT; ++i) { + highp vec3 normalizedLightDirection = normalize(lightDirections[i]); + lowp float intensity = max(0.0, dot(normalizedTransformedNormal, normalizedLightDirection)); + color += vec4(finalDiffuseColor.rgb*lightColors[i].rgb*intensity, lightColors[i].a*finalDiffuseColor.a); + + /* Add specular color, if needed */ + if(intensity > 0.001) { + highp vec3 reflection = reflect(-normalizedLightDirection, normalizedTransformedNormal); + mediump float specularity = pow(max(0.0, dot(normalize(cameraDirection), reflection)), shininess); + color += vec4(finalSpecularColor.rgb*specularity, finalSpecularColor.a); + } } #ifdef ALPHA_MASK diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index b98af8e37..421b960f8 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -172,9 +172,10 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { /** * @brief Constructor - * @param flags Flags + * @param flags Flags + * @param lightCount Count of light sources */ - explicit Phong(Flags flags = {}); + explicit Phong(Flags flags = {}, UnsignedInt lightCount = 1); /** * @brief Construct without creating the underlying OpenGL object @@ -203,6 +204,9 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { /** @brief Flags */ Flags flags() const { return _flags; } + /** @brief Light count */ + UnsignedInt lightCount() const { return _lightCount; } + /** * @brief Set ambient color * @return Reference to self (for method chaining) @@ -387,42 +391,95 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram { return *this; } + /** + * @brief Set light positions + * @return Reference to self (for method chaining) + * + * Initial values are zero vectors --- that will in most cases cause + * the object to be rendered black (or in the ambient color), as the + * lights are is inside of it. Expects that the size of the @p lights + * array is the same as @ref lightCount(). + * @see @ref setLightPosition(UnsignedInt, const Vector3&), + * @ref setLightPosition(const Vector3&) + */ + Phong& setLightPositions(Containers::ArrayView lights); + + /** @overload */ + Phong& setLightPositions(std::initializer_list lights) { + return setLightPositions({lights.begin(), lights.size()}); + } + + /** + * @brief Set position for given light + * @return Reference to self (for method chaining) + * + * Unlike @ref setLightPosition() updates just a single light position. + * Expects that @p id is less than @ref lightCount(). + * @see @ref setLightPosition(const Vector3&) + */ + Phong& setLightPosition(UnsignedInt id, const Vector3& position); + /** * @brief Set light position * @return Reference to self (for method chaining) * - * Initial value is a zero vector --- that will in most cases cause the - * object to be rendered black (or in the ambient color), as the light - * is inside of it. + * Convenience alternative to @ref setLightPositions() when there is + * just one light. + * @see @ref setLightPosition(UnsignedInt, const Vector3&) */ - Phong& setLightPosition(const Vector3& light) { - setUniform(_lightPositionUniform, light); - return *this; + Phong& setLightPosition(const Vector3& position) { + return setLightPositions({&position, 1}); } + /** + * @brief Set light colors + * @return Reference to self (for method chaining) + * + * Initial values are @cpp 0xffffffff_rgbaf @ce. Expects that the size + * of the @p colors array is the same as @ref lightCount(). + */ + Phong& setLightColors(Containers::ArrayView colors); + + /** @overload */ + Phong& setLightColors(std::initializer_list colors) { + return setLightColors({colors.begin(), colors.size()}); + } + + /** + * @brief Set position for given light + * @return Reference to self (for method chaining) + * + * Unlike @ref setLightColors() updates just a single light color. + * Expects that @p id is less than @ref lightCount(). + * @see @ref setLightColor(const Color4&) + */ + Phong& setLightColor(UnsignedInt id, const Color4& color); + /** * @brief Set light color * @return Reference to self (for method chaining) * - * Initial value is @cpp 0xffffffff_rgbaf @ce. + * Convenience alternative to @ref setLightColors() when there is just + * one light. + * @see @ref setLightColor(UnsignedInt, const Color4&) */ Phong& setLightColor(const Color4& color) { - setUniform(_lightColorUniform, color); - return *this; + return setLightColors({&color, 1}); } private: Flags _flags; + UnsignedInt _lightCount; Int _transformationMatrixUniform{0}, _projectionMatrixUniform{1}, _normalMatrixUniform{2}, - _lightPositionUniform{3}, _ambientColorUniform{4}, _diffuseColorUniform{5}, _specularColorUniform{6}, - _lightColorUniform{7}, - _shininessUniform{8}, - _alphaMaskUniform{9}; + _shininessUniform{7}, + _alphaMaskUniform{8}, + _lightPositionsUniform{9}, + _lightColorsUniform; /* 9 + lightCount, set in the constructor */ }; /** @debugoperatorclassenum{Phong,Phong::Flag} */ diff --git a/src/Magnum/Shaders/Phong.vert b/src/Magnum/Shaders/Phong.vert index 31093f288..4558334d1 100644 --- a/src/Magnum/Shaders/Phong.vert +++ b/src/Magnum/Shaders/Phong.vert @@ -55,10 +55,11 @@ uniform mediump mat3 normalMatrix #endif ; +/* Needs to be last because it uses locations 9 to 9 + LIGHT_COUNT - 1 */ #ifdef EXPLICIT_UNIFORM_LOCATION -layout(location = 3) +layout(location = 9) #endif -uniform highp vec3 lightPosition; /* defaults to zero */ +uniform highp vec3 lightPositions[LIGHT_COUNT]; /* defaults to zero */ #ifdef EXPLICIT_ATTRIB_LOCATION layout(location = POSITION_ATTRIBUTE_LOCATION) @@ -80,7 +81,7 @@ out mediump vec2 interpolatedTextureCoords; #endif out mediump vec3 transformedNormal; -out highp vec3 lightDirection; +out highp vec3 lightDirections[LIGHT_COUNT]; out highp vec3 cameraDirection; void main() { @@ -92,7 +93,8 @@ void main() { transformedNormal = normalMatrix*normal; /* Direction to the light */ - lightDirection = normalize(lightPosition - transformedPosition); + for(int i = 0; i < LIGHT_COUNT; ++i) + lightDirections[i] = normalize(lightPositions[i] - transformedPosition); /* Direction to the camera */ cameraDirection = -transformedPosition; diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index c48ea12a0..6fd25173c 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -46,22 +46,27 @@ struct PhongGLTest: GL::OpenGLTester { void setAlphaMask(); void setAlphaMaskNotEnabled(); + + void setWrongLightCount(); + void setWrongLightId(); }; constexpr struct { const char* name; Phong::Flags flags; + UnsignedInt lightCount; } ConstructData[]{ - {"", {}}, - {"ambient texture", Phong::Flag::AmbientTexture}, - {"diffuse texture", Phong::Flag::DiffuseTexture}, - {"specular texture", Phong::Flag::SpecularTexture}, - {"ambient + diffuse texture", Phong::Flag::AmbientTexture|Phong::Flag::DiffuseTexture}, - {"ambient + specular texture", Phong::Flag::AmbientTexture|Phong::Flag::SpecularTexture}, - {"diffuse + specular texture", Phong::Flag::DiffuseTexture|Phong::Flag::SpecularTexture}, - {"ambient + diffuse + specular texture", Phong::Flag::AmbientTexture|Phong::Flag::DiffuseTexture|Phong::Flag::SpecularTexture}, - {"alpha mask", Phong::Flag::AlphaMask}, - {"alpha mask + diffuse texture", Phong::Flag::AlphaMask|Phong::Flag::DiffuseTexture} + {"", {}, 1}, + {"ambient texture", Phong::Flag::AmbientTexture, 1}, + {"diffuse texture", Phong::Flag::DiffuseTexture, 1}, + {"specular texture", Phong::Flag::SpecularTexture, 1}, + {"ambient + diffuse texture", Phong::Flag::AmbientTexture|Phong::Flag::DiffuseTexture, 1}, + {"ambient + specular texture", Phong::Flag::AmbientTexture|Phong::Flag::SpecularTexture, 1}, + {"diffuse + specular texture", Phong::Flag::DiffuseTexture|Phong::Flag::SpecularTexture, 1}, + {"ambient + diffuse + specular texture", Phong::Flag::AmbientTexture|Phong::Flag::DiffuseTexture|Phong::Flag::SpecularTexture, 1}, + {"alpha mask", Phong::Flag::AlphaMask, 1}, + {"alpha mask + diffuse texture", Phong::Flag::AlphaMask|Phong::Flag::DiffuseTexture, 1}, + {"five lights", {}, 5} }; PhongGLTest::PhongGLTest() { @@ -73,15 +78,19 @@ PhongGLTest::PhongGLTest() { &PhongGLTest::bindTexturesNotEnabled, &PhongGLTest::setAlphaMask, - &PhongGLTest::setAlphaMaskNotEnabled}); + &PhongGLTest::setAlphaMaskNotEnabled, + + &PhongGLTest::setWrongLightCount, + &PhongGLTest::setWrongLightId}); } void PhongGLTest::construct() { auto&& data = ConstructData[testCaseInstanceId()]; setTestCaseDescription(data.name); - Phong shader{data.flags}; + Phong shader{data.flags, data.lightCount}; CORRADE_COMPARE(shader.flags(), data.flags); + CORRADE_COMPARE(shader.lightCount(), data.lightCount); { #ifdef CORRADE_TARGET_APPLE CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); @@ -92,7 +101,7 @@ void PhongGLTest::construct() { } void PhongGLTest::constructMove() { - Phong a{Phong::Flag::AlphaMask}; + Phong a{Phong::Flag::AlphaMask, 3}; const GLuint id = a.id(); CORRADE_VERIFY(id); @@ -101,12 +110,14 @@ void PhongGLTest::constructMove() { Phong b{std::move(a)}; CORRADE_COMPARE(b.id(), id); CORRADE_COMPARE(b.flags(), Phong::Flag::AlphaMask); + CORRADE_COMPARE(b.lightCount(), 3); CORRADE_VERIFY(!a.id()); Phong c{NoCreate}; c = std::move(b); CORRADE_COMPARE(c.id(), id); CORRADE_COMPARE(c.flags(), Phong::Flag::AlphaMask); + CORRADE_COMPARE(c.lightCount(), 3); CORRADE_VERIFY(!b.id()); } @@ -169,6 +180,48 @@ void PhongGLTest::setAlphaMaskNotEnabled() { "Shaders::Phong::setAlphaMask(): the shader was not created with alpha mask enabled\n"); } +void PhongGLTest::setWrongLightCount() { + std::ostringstream out; + Error redirectError{&out}; + + Phong shader{{}, 5}; + + /* This is okay */ + shader.setLightColors({{}, {}, {}, {}, {}}) + .setLightPositions({{}, {}, {}, {}, {}}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* This is not */ + shader.setLightColor({}) + .setLightPosition({}); + + CORRADE_COMPARE(out.str(), + "Shaders::Phong::setLightColors(): expected 5 items but got 1\n" + "Shaders::Phong::setLightPositions(): expected 5 items but got 1\n"); +} + +void PhongGLTest::setWrongLightId() { + std::ostringstream out; + Error redirectError{&out}; + + Phong shader{{}, 3}; + + /* This is okay */ + shader.setLightColor(2, {}) + .setLightPosition(2, {}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* This is not */ + shader.setLightColor(3, {}) + .setLightPosition(3, {}); + + CORRADE_COMPARE(out.str(), + "Shaders::Phong::setLightColor(): light ID 3 is out of bounds for 3 lights\n" + "Shaders::Phong::setLightPosition(): light ID 3 is out of bounds for 3 lights\n"); +} + }}} CORRADE_TEST_MAIN(Magnum::Shaders::Test::PhongGLTest)