Browse Source

Shaders: rework Phong to support directional and attenuated point lights.

This is a -- long overdue -- breaking change to the rendering output of
this shader, finally adding support for lights that get darker over
distance. The attenuation equation is basically what's documented in
LightData, and the distinction between directional and point lights is
made using a newly added the fourth component of position (which means
the old three-component setters are all deprecated). This allows the
shader code to be practically branchless, which I find to be nice.

This breaks basically all rendering output so all existing Phong and
MeshTools::compile() test outputs had to be regenerated.
pull/470/head
Vladimír Vondruš 6 years ago
parent
commit
7257bbb871
  1. 17
      doc/changelog.dox
  2. 2
      doc/generated/primitives.cpp
  3. 2
      doc/generated/shaders.cpp
  4. 9
      doc/snippets/MagnumSceneGraph-gl.cpp
  5. 38
      doc/snippets/MagnumShaders.cpp
  6. 10
      src/Magnum/MeshTools/Test/CompileGLTest.cpp
  7. BIN
      src/Magnum/MeshTools/Test/CompileTestFiles/phong-flat.tga
  8. BIN
      src/Magnum/MeshTools/Test/CompileTestFiles/phong-smooth.tga
  9. BIN
      src/Magnum/MeshTools/Test/CompileTestFiles/phong.tga
  10. 2
      src/Magnum/Shaders/Flat.h
  11. 136
      src/Magnum/Shaders/Phong.cpp
  12. 28
      src/Magnum/Shaders/Phong.frag
  13. 204
      src/Magnum/Shaders/Phong.h
  14. 13
      src/Magnum/Shaders/Phong.vert
  15. 7
      src/Magnum/Shaders/Test/CMakeLists.txt
  16. 233
      src/Magnum/Shaders/Test/PhongGLTest.cpp
  17. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/colored.tga
  18. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/defaults.tga
  19. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/instanced-normal.tga
  20. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga
  21. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-directional-intensity2.tga
  22. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-directional.tga
  23. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-none.tga
  24. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-point-attenuated-specular.tga
  25. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-point-intensity10-range0.5.tga
  26. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-point-range1.5.tga
  27. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/light-point.tga
  28. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/low-light-angle.tga
  29. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/shininess-black-specular.tga
  30. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/shininess0.tga
  31. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/shininess10.tga
  32. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/shininess80.tga
  33. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-ambient.tga
  34. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse-alpha-mask0.5.tga
  35. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse-alpha.tga
  36. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse-transformed.tga
  37. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse.tga
  38. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-normal-left.tga
  39. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-normal.tga
  40. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.0.tga
  41. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.5.tga
  42. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured-specular.tga
  43. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/textured.tga
  44. BIN
      src/Magnum/Shaders/Test/PhongTestFiles/vertexColor.tga

17
doc/changelog.dox

@ -77,6 +77,9 @@ See also:
- Added @ref Shaders::Phong::setNormalTextureScale(), consuming the recently
added @ref Trade::MaterialAttribute::NormalTextureScale material attribute
- @ref Shaders::Phong was reworked to support directional and
range-attenuated point lights to follow the additions to
@ref Trade::LightData
@subsubsection changelog-latest-new-scenegraph SceneGraph library
@ -179,6 +182,13 @@ See also:
@subsection changelog-latest-deprecated Deprecated APIs
- @ref Shaders::Phong::setLightPositions() and
@ref Shaders::Phong::setLightPosition() taking three-component vectors are
deprecated in favor of variants taking four-component vectors, where the
last component distinguishes between directional and point lights
- @cpp Shaders::Phong::setLightPosition(const Vector3&) @ce is deprecated in
favor of @ref Shaders::Phong::setLightPositions() with a single item ---
it's short enough to not warrant the existence of a dedicated overload
- @cpp Trade::AbstractMaterialData @ce as well as its containing header
`Magnum/Trade/AbstractMaterialData.h` is now a deprecated alias to the new
and more flexible @ref Trade::MaterialData.
@ -264,6 +274,11 @@ See also:
afterwards. This can cause compilation breakages in case the type
constructor has the parent parameter non-optional, pass the parent
explicitly in that case.
- Due to the rework of @ref Shaders::Phong to support directional and
attenuated point lights, the original behavior of unattenuated point lights
isn't available anymore. For backwards compatibility, light positions
supplied through three-component vectors are now represented as directional
lights, which is close, but not exactly the same as before.
- Mutable access to @ref Trade::PhongMaterialData color and texture
information, deprecated in 2020.06, is now removed, as it's impossible to
implement through the redesigned @ref Trade::MaterialData APIs. However
@ -1499,7 +1514,7 @@ Released 2019-10-24, tagged as
- @ref Shaders::Phong now clamps the specular factor to minimize artifacts
when shininess is near zero
- @ref Shaders::Phong can now handle zero lights, in which case its output is
equivalent to @ref Shaders::Flat3D. See @ref Shaders-Phong-zero-lights for
equivalent to @ref Shaders::Flat3D. See @ref Shaders-Phong-lights-zero for
more information.
- @ref Shaders::MeshVisualizer is fixed to work properly on Intel Windows
drivers, adding a new

2
doc/generated/primitives.cpp

@ -321,7 +321,7 @@ int PrimitiveVisualizer::exec() {
phong.setAmbientColor(0x22272e_srgbf)
.setDiffuseColor(BaseColor)
.setSpecularColor(0x000000_srgbf)
.setLightPosition({5.0f, 5.0f, 7.0f})
.setLightPositions({{5.0f, 5.0f, 7.0f, 0.0f}})
.setProjectionMatrix(Projection3D)
.setTransformationMatrix(Transformation3D)
.setNormalMatrix(Transformation3D.normalMatrix());

2
doc/generated/shaders.cpp

@ -165,7 +165,7 @@ std::string ShaderVisualizer::phong() {
.setAmbientColor(0x22272e_srgbf)
.setDiffuseColor(BaseColor)
.setShininess(200.0f)
.setLightPosition({5.0f, 5.0f, 7.0f})
.setLightPositions({{5.0f, 5.0f, 7.0f, 0.0f}})
.setProjectionMatrix(Projection)
.setTransformationMatrix(Transformation)
.setNormalMatrix(Transformation.normalMatrix())

9
doc/snippets/MagnumSceneGraph-gl.cpp

@ -103,8 +103,6 @@ class RedCubeDrawable: public SceneGraph::Drawable3D {
using namespace Math::Literals;
_shader.setDiffuseColor(0xa5c9ea_rgbf)
.setLightPosition(camera.cameraMatrix().transformPoint(
{5.0f, 5.0f, 7.0f}))
.setTransformationMatrix(transformationMatrix)
.setNormalMatrix(transformationMatrix.normalMatrix())
.setProjectionMatrix(camera.projectionMatrix())
@ -185,7 +183,8 @@ struct MyApplication: Platform::Application {
SceneGraph::Object<SceneGraph::MatrixTransformation2D>* _cameraObject;
SceneGraph::Camera3D* _camera;
Vector3 _lightPositionRelativeToCamera, _lightColor, _ambientColor;
Vector4 _lightPositionRelativeToCamera;
Vector3 _lightColor, _ambientColor;
/* [Drawable-multiple-groups] */
// ...
@ -195,8 +194,8 @@ struct MyApplication: Platform::Application {
void MyApplication::drawEvent() {
_shader.setProjectionMatrix(_camera->projectionMatrix())
.setLightPosition(_lightPositionRelativeToCamera)
.setLightColor(_lightColor)
.setLightPositions({_lightPositionRelativeToCamera})
.setLightColors({_lightColor})
.setAmbientColor(_ambientColor);
/* Each drawable sets only unique properties such as transformation matrix

38
doc/snippets/MagnumShaders.cpp

@ -52,6 +52,9 @@
#include "Magnum/Shaders/Phong.h"
#include "Magnum/Shaders/Vector.h"
#include "Magnum/Shaders/VertexColor.h"
#include "Magnum/Trade/LightData.h"
#define DOXYGEN_IGNORE(...) __VA_ARGS__
using namespace Magnum;
using namespace Magnum::Math::Literals;
@ -90,7 +93,6 @@ GL::Texture2D diffuseTexture, specularTexture;
Shaders::Phong shader{Shaders::Phong::Flag::DiffuseTexture};
shader.bindDiffuseTexture(diffuseTexture)
.setLightPosition({5.0f, 5.0f, 7.0f})
.setTransformationMatrix(transformationMatrix)
.setNormalMatrix(transformationMatrix.normalMatrix())
.setProjectionMatrix(projectionMatrix)
@ -489,7 +491,6 @@ Matrix4 projectionMatrix =
Shaders::Phong shader;
shader.setDiffuseColor(0x2f83cc_rgbf)
.setShininess(200.0f)
.setLightPosition({5.0f, 5.0f, 7.0f})
.setTransformationMatrix(transformationMatrix)
.setNormalMatrix(transformationMatrix.normalMatrix())
.setProjectionMatrix(projectionMatrix)
@ -525,7 +526,6 @@ GL::Texture2D diffuseTexture, specularTexture;
Shaders::Phong shader{Shaders::Phong::Flag::DiffuseTexture|
Shaders::Phong::Flag::SpecularTexture};
shader.bindTextures(nullptr, &diffuseTexture, &specularTexture, nullptr)
.setLightPosition({5.0f, 5.0f, 7.0f})
.setTransformationMatrix(transformationMatrix)
.setNormalMatrix(transformationMatrix.normalMatrix())
.setProjectionMatrix(projectionMatrix)
@ -534,6 +534,38 @@ shader.bindTextures(nullptr, &diffuseTexture, &specularTexture, nullptr)
}
#endif
{
/* [Phong-usage-lights] */
Matrix4 directionalLight, pointLight1, pointLight2; // camera-relative
Shaders::Phong shader{{}, 3}; // 3 lights
shader
.setLightPositions({Vector4{directionalLight.up(), 0.0f},
Vector4{pointLight1.translation(), 1.0f},
Vector4{pointLight2.translation(), 1.0f}})
.setLightColors({0xf0f0ff_srgbf*0.1f,
0xff8080_srgbf*10.0f,
0x80ff80_srgbf*10.0f})
.setLightRanges({Constants::inf(),
2.0f,
2.0f});
/* [Phong-usage-lights] */
}
{
Color3 ambientColor;
GL::Texture2D diffuseTexture;
/* [Phong-usage-lights-ambient] */
Trade::LightData ambientLight = DOXYGEN_IGNORE(Trade::LightData{{}, {}, {}});
Shaders::Phong shader{Shaders::Phong::Flag::AmbientTexture|DOXYGEN_IGNORE(Shaders::Phong::Flag::DiffuseTexture), DOXYGEN_IGNORE(3)};
shader
.setAmbientColor(ambientColor + ambientLight.color()*ambientLight.intensity())
.bindAmbientTexture(diffuseTexture)
.bindDiffuseTexture(diffuseTexture);
/* [Phong-usage-lights-ambient] */
}
{
GL::Texture2D diffuseAlphaTexture;
Color3 diffuseRgb, specularRgb;

10
src/Magnum/MeshTools/Test/CompileGLTest.cpp

@ -303,6 +303,12 @@ CompileGLTest::CompileGLTest() {
#endif
{4, 4})
.setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {4, 4}, ImageData});
/* Use a point light instead of a directional light to better highlight the
difference in normals; disable specular as that only causes unnecessary
rounding errors across GPUs */
_phong.setLightPositions({{0.0f, 0.0f, -0.0f, 1.0f}})
.setLightColors({0xffffff_rgbf*9.0f})
.setSpecularColor(0x00000000_rgbaf);
#ifndef MAGNUM_TARGET_GLES2
#ifndef MAGNUM_TARGET_GLES
@ -762,7 +768,7 @@ template<class T> void CompileGLTest::threeDimensions() {
_framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}),
Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "phong-flat.tga"),
/* SwiftShader has some minor off-by-one precision differences */
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.0079f}));
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.012f}));
} else if(data.flags & Flag::GeneratedSmoothNormals) {
_framebuffer.clear(GL::FramebufferClear::Color);
_phong
@ -777,7 +783,7 @@ template<class T> void CompileGLTest::threeDimensions() {
_framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}),
Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "phong-smooth.tga"),
/* SwiftShader has some minor off-by-one precision differences */
(DebugTools::CompareImageToFile{_manager, 0.25f, 0.0059f}));
(DebugTools::CompareImageToFile{_manager, 0.5f, 0.0088f}));
}
/* Check with the colored shader, if we have colors */

BIN
src/Magnum/MeshTools/Test/CompileTestFiles/phong-flat.tga

Binary file not shown.

BIN
src/Magnum/MeshTools/Test/CompileTestFiles/phong-smooth.tga

Binary file not shown.

BIN
src/Magnum/MeshTools/Test/CompileTestFiles/phong.tga

Binary file not shown.

2
src/Magnum/Shaders/Flat.h

@ -90,7 +90,7 @@ Common rendering setup:
For coloring the texture based on intensity you can use the @ref Vector shader.
The 3D version of this shader is equivalent to @ref Phong with zero lights,
however this implementation is much simpler and thus likely also faster. See
@ref Shaders-Phong-zero-lights "its documentation" for more information.
@ref Shaders-Phong-lights-zero "its documentation" for more information.
Conversely, enabling @ref Flag::VertexColor and using a default color with no
texturing makes this shader equivalent to @ref VertexColor.

136
src/Magnum/Shaders/Phong.cpp

@ -25,7 +25,7 @@
#include "Phong.h"
#ifdef MAGNUM_TARGET_GLES
#if defined(MAGNUM_TARGET_GLES) || defined(MAGNUM_BUILD_DEPRECATED)
#include <Corrade/Containers/Array.h>
#endif
#include <Corrade/Containers/EnumSet.hpp>
@ -55,7 +55,7 @@ namespace {
};
}
Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _lightCount{lightCount}, _lightColorsUniform{_lightPositionsUniform + Int(lightCount)} {
Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _lightCount{lightCount}, _lightColorsUniform{_lightPositionsUniform + Int(lightCount)}, _lightRangesUniform{_lightPositionsUniform + 2*Int(lightCount)} {
CORRADE_ASSERT(!(flags & Flag::TextureTransformation) || (flags & (Flag::AmbientTexture|Flag::DiffuseTexture|Flag::SpecularTexture|Flag::NormalTexture)),
"Shaders::Phong: texture transformation enabled but the shader is not textured", );
@ -76,22 +76,53 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l
GL::Shader frag = Implementation::createCompatibilityShader(rs, version, GL::Shader::Type::Fragment);
#ifndef MAGNUM_TARGET_GLES
std::string lightInitializer;
std::string lightInitializerVertex, lightInitializerFragment;
if(lightCount) {
using namespace Containers::Literals;
/* 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 Containers::StringView lightInitializerPreamble = "#define LIGHT_COLOR_INITIALIZER "_s;
constexpr Containers::StringView lightInitializerItem = "vec4(1.0), "_s;
lightInitializer.reserve(lightInitializerPreamble.size() + lightCount*lightInitializerItem.size());
lightInitializer.append(lightInitializerPreamble.data(), lightInitializerPreamble.size());
/* Initializer for the light color / position / range arrays -- we need
a list of initializers joined by commas. For GLES we'll simply
upload the values directly. */
constexpr Containers::StringView lightPositionInitializerPreamble = "#define LIGHT_POSITION_INITIALIZER "_s;
constexpr Containers::StringView lightColorInitializerPreamble = "#define LIGHT_COLOR_INITIALIZER "_s;
constexpr Containers::StringView lightRangeInitializerPreamble = "#define LIGHT_RANGE_INITIALIZER "_s;
constexpr Containers::StringView lightPositionInitializerItem = "vec4(0.0, 0.0, 1.0, 0.0), "_s;
constexpr Containers::StringView lightColorInitializerItem = "vec4(1.0), "_s;
constexpr Containers::StringView lightRangeInitializerItem = "1.0/0.0, "_s;
lightInitializerVertex.reserve(
lightPositionInitializerPreamble.size() +
lightCount*(lightPositionInitializerItem.size()));
lightInitializerVertex.append(lightPositionInitializerPreamble.data(), lightPositionInitializerPreamble.size());
for(std::size_t i = 0; i != lightCount; ++i)
lightInitializer.append(lightInitializerItem.data(), lightInitializerItem.size());
lightInitializerVertex.append(lightPositionInitializerItem.data(), lightPositionInitializerItem.size());
/* Drop the last comma and add a newline at the end */
lightInitializer[lightInitializer.size() - 2] = '\n';
lightInitializer.resize(lightInitializer.size() - 1);
lightInitializerVertex[lightInitializerVertex.size() - 2] = '\n';
lightInitializerVertex.resize(lightInitializerVertex.size() - 1);
lightInitializerFragment.reserve(
lightColorInitializerPreamble.size() +
lightRangeInitializerPreamble.size() +
lightCount*(lightColorInitializerItem.size() +
lightRangeInitializerItem.size()));
lightInitializerFragment.append(lightColorInitializerPreamble.data(), lightColorInitializerPreamble.size());
for(std::size_t i = 0; i != lightCount; ++i)
lightInitializerFragment.append(lightColorInitializerItem.data(), lightColorInitializerItem.size());
/* Drop the last comma and add a newline at the end */
lightInitializerFragment[lightInitializerFragment.size() - 2] = '\n';
lightInitializerFragment.resize(lightInitializerFragment.size() - 1);
lightInitializerFragment.append(lightRangeInitializerPreamble.data(), lightRangeInitializerPreamble.size());
for(std::size_t i = 0; i != lightCount; ++i)
lightInitializerFragment.append(lightRangeInitializerItem.data(), lightRangeInitializerItem.size());
/* Drop the last comma and add a newline at the end */
lightInitializerFragment[lightInitializerFragment.size() - 2] = '\n';
lightInitializerFragment.resize(lightInitializerFragment.size() - 1);
}
#endif
@ -105,8 +136,11 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l
.addSource(flags >= Flag::InstancedObjectId ? "#define INSTANCED_OBJECT_ID\n" : "")
#endif
.addSource(flags & Flag::InstancedTransformation ? "#define INSTANCED_TRANSFORMATION\n" : "")
.addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "")
.addSource(rs.get("generic.glsl"))
.addSource(flags >= Flag::InstancedTextureOffset ? "#define INSTANCED_TEXTURE_OFFSET\n" : "");
#ifndef MAGNUM_TARGET_GLES
if(lightCount) vert.addSource(std::move(lightInitializerVertex));
#endif
vert.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" : "")
@ -121,9 +155,13 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l
#endif
.addSource(Utility::formatString(
"#define LIGHT_COUNT {}\n"
"#define LIGHT_COLORS_LOCATION {}\n", lightCount, _lightPositionsUniform + lightCount));
"#define LIGHT_COLORS_LOCATION {}\n"
"#define LIGHT_RANGES_LOCATION {}\n",
lightCount,
_lightPositionsUniform + lightCount,
_lightPositionsUniform + 2*lightCount));
#ifndef MAGNUM_TARGET_GLES
if(lightCount) frag.addSource(std::move(lightInitializer));
if(lightCount) frag.addSource(std::move(lightInitializerFragment));
#endif
frag.addSource(rs.get("generic.glsl"))
.addSource(rs.get("Phong.frag"));
@ -186,6 +224,7 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l
_normalTextureScaleUniform = uniformLocation("normalTextureScale");
_lightPositionsUniform = uniformLocation("lightPositions");
_lightColorsUniform = uniformLocation("lightColors");
_lightRangesUniform = uniformLocation("lightRanges");
}
if(flags & Flag::AlphaMask) _alphaMaskUniform = uniformLocation("alphaMask");
#ifndef MAGNUM_TARGET_GLES2
@ -218,7 +257,9 @@ Phong::Phong(const Flags flags, const UnsignedInt lightCount): _flags{flags}, _l
setShininess(80.0f);
if(flags & Flag::NormalTexture)
setNormalTextureScale(1.0f);
setLightPositions(Containers::Array<Vector4>{Containers::DirectInit, lightCount, Vector4{0.0f, 0.0f, 1.0f, 0.0f}});
setLightColors(Containers::Array<Magnum::Color4>{Containers::DirectInit, lightCount, Magnum::Color4{1.0f}});
setLightRanges(Containers::Array<Float>{Containers::DirectInit, lightCount, Constants::inf()});
/* Light position is zero by default */
setNormalMatrix({});
}
@ -328,26 +369,53 @@ Phong& Phong::setTextureMatrix(const Matrix3& matrix) {
return *this;
}
Phong& Phong::setLightPositions(const Containers::ArrayView<const Vector3> positions) {
Phong& Phong::setLightPositions(const Containers::ArrayView<const Vector4> positions) {
CORRADE_ASSERT(_lightCount == positions.size(),
"Shaders::Phong::setLightPositions(): expected" << _lightCount << "items but got" << positions.size(), *this);
if(_lightCount) setUniform(_lightPositionsUniform, positions);
return *this;
}
Phong& Phong::setLightPosition(UnsignedInt id, const Vector3& position) {
/* It's light, but can't be in the header because MSVC needs to know the size
of Vector3 for the initializer list use */
Phong& Phong::setLightPositions(const std::initializer_list<Vector4> positions) {
return setLightPositions(Containers::arrayView(positions));
}
#ifdef MAGNUM_BUILD_DEPRECATED
Phong& Phong::setLightPositions(const Containers::ArrayView<const Vector3> positions) {
Containers::Array<Vector4> fourComponent{Containers::NoInit, positions.size()};
for(std::size_t i = 0; i != positions.size(); ++i)
fourComponent[i] = Vector4{positions[i], 0.0f};
setLightPositions(fourComponent);
return *this;
}
Phong& Phong::setLightPositions(const std::initializer_list<Vector3> positions) {
CORRADE_IGNORE_DEPRECATED_PUSH
return setLightPositions(Containers::arrayView(positions));
CORRADE_IGNORE_DEPRECATED_POP
}
#endif
Phong& Phong::setLightPosition(const UnsignedInt id, const Vector4& 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;
}
/* It's light, but can't be in the header because MSVC needs to know the size
of Vector3 for the initializer list use */
Phong& Phong::setLightPositions(std::initializer_list<Vector3> lights) {
return setLightPositions({lights.begin(), lights.size()});
#ifdef MAGNUM_BUILD_DEPRECATED
Phong& Phong::setLightPosition(UnsignedInt id, const Vector3& position) {
return setLightPosition(id, Vector4{position, 0.0f});
}
Phong& Phong::setLightPosition(const Vector3& position) {
/* Use the list variant to check the shader really has just one light */
return setLightPositions({Vector4{position, 0.0f}});
}
#endif
Phong& Phong::setLightColors(const Containers::ArrayView<const Magnum::Color4> colors) {
CORRADE_ASSERT(_lightCount == colors.size(),
"Shaders::Phong::setLightColors(): expected" << _lightCount << "items but got" << colors.size(), *this);
@ -357,17 +425,35 @@ Phong& Phong::setLightColors(const Containers::ArrayView<const Magnum::Color4> c
/* It's light, but can't be in the header because MSVC needs to know the size
of Color for the initializer list use */
Phong& Phong::setLightColors(std::initializer_list<Magnum::Color4> colors) {
return setLightColors({colors.begin(), colors.size()});
Phong& Phong::setLightColors(const std::initializer_list<Magnum::Color4> colors) {
return setLightColors(Containers::arrayView(colors));
}
Phong& Phong::setLightColor(UnsignedInt id, const Magnum::Color4& color) {
Phong& Phong::setLightColor(const UnsignedInt id, const Magnum::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;
}
Phong& Phong::setLightRanges(const Containers::ArrayView<const Float> ranges) {
CORRADE_ASSERT(_lightCount == ranges.size(),
"Shaders::Phong::setLightRanges(): expected" << _lightCount << "items but got" << ranges.size(), *this);
if(_lightCount) setUniform(_lightRangesUniform, ranges);
return *this;
}
Phong& Phong::setLightRanges(const std::initializer_list<Float> ranges) {
return setLightRanges(Containers::arrayView(ranges));
}
Phong& Phong::setLightRange(const UnsignedInt id, const Float range) {
CORRADE_ASSERT(id < _lightCount,
"Shaders::Phong::setLightRange(): light ID" << id << "is out of bounds for" << _lightCount << "lights", *this);
setUniform(_lightRangesUniform + id, range);
return *this;
}
Debug& operator<<(Debug& debug, const Phong::Flag value) {
debug << "Shaders::Phong::Flag" << Debug::nospace;

28
src/Magnum/Shaders/Phong.frag

@ -149,6 +149,15 @@ uniform lowp vec4 lightColors[LIGHT_COUNT]
= vec4[](LIGHT_COLOR_INITIALIZER)
#endif
;
#ifdef EXPLICIT_UNIFORM_LOCATION
layout(location = LIGHT_RANGES_LOCATION)
#endif
uniform lowp float lightRanges[LIGHT_COUNT]
#ifndef GL_ES
= float[](LIGHT_RANGE_INITIALIZER)
#endif
;
#endif
#if LIGHT_COUNT
@ -161,7 +170,7 @@ in mediump vec3 transformedTangent;
in mediump vec3 transformedBitangent;
#endif
#endif
in highp vec3 lightDirections[LIGHT_COUNT];
in highp vec4 lightDirections[LIGHT_COUNT];
in highp vec3 cameraDirection;
#endif
@ -244,14 +253,25 @@ void main() {
/* 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));
/* Attenuation. Directional lights have the .w component set to 0, use
that to make the distance zero -- which will then ensure the
attenuation is always 1.0 */
highp float dist = length(lightDirections[i].xyz)*lightDirections[i].w;
/* If range is 0 for whatever reason, clamp it to a small value to
avoid a NaN when dist is 0 as well (which is the case for
directional lights). */
highp float attenuation = clamp(1.0 - pow(dist/max(lightRanges[i], 0.0001), 4.0), 0.0, 1.0)/(1.0 + dist);
attenuation = attenuation*attenuation;
highp vec3 normalizedLightDirection = normalize(lightDirections[i].xyz);
lowp float intensity = max(0.0, dot(normalizedTransformedNormal, normalizedLightDirection))*attenuation;
fragmentColor += vec4(finalDiffuseColor.rgb*lightColors[i].rgb*intensity, lightColors[i].a*finalDiffuseColor.a/float(LIGHT_COUNT));
/* Add specular color, if needed */
if(intensity > 0.001) {
highp vec3 reflection = reflect(-normalizedLightDirection, normalizedTransformedNormal);
mediump float specularity = clamp(pow(max(0.0, dot(normalize(cameraDirection), reflection)), shininess), 0.0, 1.0);
/* Use attenuation for the specularity as well */
mediump float specularity = clamp(pow(max(0.0, dot(normalize(cameraDirection), reflection)), shininess), 0.0, 1.0)*attenuation;
fragmentColor += vec4(finalSpecularColor.rgb*specularity, finalSpecularColor.a);
}
}

204
src/Magnum/Shaders/Phong.h

@ -73,6 +73,82 @@ Common rendering setup:
@snippet MagnumShaders.cpp Phong-usage-texture2
@section Shaders-Phong-lights Light specification
By default, the shader provides a single directional "fill" light, coming from
the center of the camera. Using the @p lightCount parameter in constructor, you
can specify how many lights you want, and then control light parameters using
the following @ref setLightPositions(), @ref setLightColors() and
@ref setLightRanges(). Light positions are specified as four-component vectors,
the last component distinguishing between directional and point lights.
<ul><li>
Point lights are specified with camera-relative position and the last component
set to @cpp 1.0f @ce together with @ref setLightRanges() describing the
attenuation. The range corresponds to the @ref Trade::LightData::range() and
the attenuation is calculated as the following --- see
@ref Trade-LightData-attenuation for more information: @f[
F_{att} = \frac{\operatorname{clamp}(1 - (\frac{d}{\color{m-info} R})^4, 0, 1)^2}{1 + d^2}
@f]
If you use @ref Constants::inf() as a range (which is also the default), the
equation reduces down to a simple inverse square: @f[
F_{att} = \lim_{{\color{m-info} R} \to \infty} \frac{{\color{m-dim} \operatorname{clamp}(} 1 \mathbin{\color{m-dim} -} {\color{m-dim} (\frac{d}{R})^4, 0, 1)^2}}{1 + d^2} = \frac{1}{1 + d^2}
@f]
</li><li>
Directional lights are specified with a camera-relative direction *to* the
light with the last component set to @cpp 0.0f @ce --- which effectively makes
@f$ d = 0 @f$ --- and are not affected by values from @ref setLightRanges() in
any way: @f[
F_{att} = \lim_{d \to 0} \frac{{\color{m-dim} \operatorname{clamp}(} 1 \mathbin{\color{m-dim} -} {\color{m-dim} (\frac{d}{R})^4, 0, 1)^2}}{1 \mathbin{\color{m-dim} +} {\color{m-dim} d^2}} = 1
@f]
</li></ul>
Light color and intensity, corresponding to @ref Trade::LightData::color() and
@ref Trade::LightData::intensity(), is meant to be multiplied together and
passed to @ref setLightColors().
The following example shows a three-light setup with one dim directional light
shining from the top and two stronger but range-limited point lights:
@snippet MagnumShaders.cpp Phong-usage-lights
@subsection Shaders-Phong-lights-ambient Ambient lights
In order to avoid redundant uniform inputs, there's no dedicated way to specify
ambient lights. Instead, they are handled by the ambient color input, as the
math for ambient color and lights is equivalent. Add the ambient colors
together and reuse the diffuse texture in the @ref bindAmbientTexture() slot to
have it affected by the ambient as well:
@snippet MagnumShaders.cpp Phong-usage-lights-ambient
@subsection Shaders-Phong-lights-zero Zero lights
As a special case, creating this shader with zero lights makes its output
equivalent to the @ref Flat3D shader --- only @ref setAmbientColor() and
@ref bindAmbientTexture() (if @ref Flag::AmbientTexture is enabled) are taken
into account, which corresponds to @ref Flat::setColor() and
@ref Flat::bindTexture(). This is useful to reduce complexity in apps that
render models with pre-baked lights. For instanced workflows using zero lights
means the @ref NormalMatrix instance attribute doesn't need to be supplied
either. In addition, enabling @ref Flag::VertexColor and using a default
ambient color with no texturing makes this shader equivalent to
@ref VertexColor.
@see @ref Trade::MaterialType::Flat
<b></b>
@m_class{m-note m-dim}
@par
Attenuation based on constant/linear/quadratic factors (the
@ref Trade::LightData::attenuation() property) and spot lights
(@ref Trade::LightData::innerConeAngle(),
@ref Trade::LightData::outerConeAngle() "outerConeAngle()") are not
implemented at the moment.
@section Shaders-Phong-alpha Alpha blending and masking
Alpha / transparency is supported by the shader implicitly, but to have it
@ -169,18 +245,6 @@ well to ensure lighting works:
@requires_webgl20 Extension @webgl_extension{ANGLE,instanced_arrays} in WebGL
1.0.
@section Shaders-Phong-zero-lights Zero lights
Creating this shader with zero lights makes its output equivalent to the
@ref Flat3D shader --- only @ref setAmbientColor() and @ref bindAmbientTexture()
(if @ref Flag::AmbientTexture is enabled) are taken into account, which
correspond to @ref Flat::setColor() and @ref Flat::bindTexture(). This is
useful to reduce complexity in apps that render models with pre-baked lights.
For instanced workflows using zero lights means the @ref NormalMatrix instance
attribute doesn't need to be supplied either. In addition, enabling
@ref Flag::VertexColor and using a default ambient color with no texturing
makes this shader equivalent to @ref VertexColor.
@see @ref shaders
*/
class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram {
@ -544,7 +608,7 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram {
* If @ref Flag::AmbientTexture is set, default value is
* @cpp 0xffffffff_rgbaf @ce and the color will be multiplied with
* ambient texture, otherwise default value is @cpp 0x00000000_rgbaf @ce.
* @see @ref bindAmbientTexture()
* @see @ref bindAmbientTexture(), @ref Shaders-Phong-lights-ambient
*/
Phong& setAmbientColor(const Magnum::Color4& color);
@ -554,7 +618,8 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram {
*
* Expects that the shader was created with @ref Flag::AmbientTexture
* enabled.
* @see @ref bindTextures(), @ref setAmbientColor()
* @see @ref bindTextures(), @ref setAmbientColor(),
* @ref Shaders-Phong-lights-ambient
*/
Phong& bindAmbientTexture(GL::Texture2D& texture);
@ -740,40 +805,77 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram {
/**
* @brief Set light positions
* @return Reference to self (for method chaining)
* @m_since_latest
*
* 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&)
* Depending on the fourth component, the value is treated as either a
*camera-relative position of a point light, if the fourth component is
* @cpp 1.0f @ce; or a direction *to* a directional light, if the
* fourth component is @cpp 0.0f @ce. Expects that the size of the
* @p positions array is the same as @ref lightCount(). Initial values
* are @cpp {0.0f, 0.0f, 1.0f, 0.0f} @ce --- a directional "fill" light
* coming from the camera.
* @see @ref Shaders-Phong-lights, @ref setLightPosition()
*/
Phong& setLightPositions(Containers::ArrayView<const Vector3> lights);
Phong& setLightPositions(Containers::ArrayView<const Vector4> positions);
/** @overload */
Phong& setLightPositions(std::initializer_list<Vector3> lights);
/**
* @overload
* @m_since_latest
*/
Phong& setLightPositions(std::initializer_list<Vector4> positions);
#ifdef MAGNUM_BUILD_DEPRECATED
/**
* @brief @copybrief setLightPositions(Containers::ArrayView<const Vector4>)
* @m_deprecated_since_latest Use @ref setLightPositions(Containers::ArrayView<const Vector4>)
* instead. This function sets the fourth component to
* @cpp 0.0f @ce to preserve the original behavior as close as
* possible.
*/
CORRADE_DEPRECATED("use setLightPositions(Containers::ArrayView<const Vector4>) instead") Phong& setLightPositions(Containers::ArrayView<const Vector3> positions);
/**
* @brief @copybrief setLightPositions(std::initializer_list<Vector4>)
* @m_deprecated_since_latest Use @ref setLightPositions(std::initializer_list<Vector4>)
* instead. This function sets the fourth component to
* @cpp 0.0f @ce to preserve the original behavior as close as
* possible.
*/
CORRADE_DEPRECATED("use setLightPositions(std::initializer_list<Vector4>) instead") Phong& setLightPositions(std::initializer_list<Vector3> positions);
#endif
/**
* @brief Set position for given light
* @return Reference to self (for method chaining)
* @m_since_latest
*
* Unlike @ref setLightPosition() updates just a single light position.
* Expects that @p id is less than @ref lightCount().
* @see @ref setLightPosition(const Vector3&)
* Unlike @ref setLightPositions() updates just a single light
* position. If updating more than one light, prefer the batch function
* instead to reduce the count of GL API calls. Expects that @p id is
* less than @ref lightCount().
*/
Phong& setLightPosition(UnsignedInt id, const Vector4& position);
#ifdef MAGNUM_BUILD_DEPRECATED
/**
* @brief @copybrief setLightPosition(UnsignedInt, const Vector4&)
* @m_deprecated_since_latest Use @ref setLightPosition(UnsignedInt, const Vector4&)
* instead. This function sets the fourth component to
* @cpp 0.0f @ce to preserve the original behavior as close as
* possible.
*/
Phong& setLightPosition(UnsignedInt id, const Vector3& position);
CORRADE_DEPRECATED("use setLightPosition(UnsignedInt, const Vector4&) instead") Phong& setLightPosition(UnsignedInt id, const Vector3& position);
/**
* @brief Set light position
* @return Reference to self (for method chaining)
*
* Convenience alternative to @ref setLightPositions() when there is
* just one light.
* @see @ref setLightPosition(UnsignedInt, const Vector3&)
* @m_deprecated_since_latest Use @ref setLightPositions(std::initializer_list<Vector4>)
* with a single item instead --- it's short enough to not warrant
* the existence of a dedicated overload. This function sets the
* fourth component to @cpp 0.0f @ce to preserve the original
* behavior as close as possible.
*/
Phong& setLightPosition(const Vector3& position) {
return setLightPositions({&position, 1});
}
CORRADE_DEPRECATED("use setLightPositions(std::initializer_list<Vector4>) instead") Phong& setLightPosition(const Vector3& position);
#endif
/**
* @brief Set light colors
@ -809,6 +911,35 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram {
return setLightColors({&color, 1});
}
/**
* @brief Set light attenuation ranges
* @return Reference to self (for method chaining)
* @m_since_latest
*
* Initial values are @ref Constants::inf(). Expects that the size of
* the @p ranges array is the same as @ref lightCount().
* @see @ref Shaders-Phong-lights, @ref setLightRange()
*/
Phong& setLightRanges(Containers::ArrayView<const Float> ranges);
/**
* @overload
* @m_since_latest
*/
Phong& setLightRanges(std::initializer_list<Float> ranges);
/**
* @brief Set attenuation range for given light
* @return Reference to self (for method chaining)
* @m_since_latest
*
* Unlike @ref setLightRanges() updates just a single light range. If
* updating more than one light, prefer the batch function instead to
* reduce the count of GL API calls. Expects that @p id is less than
* @ref lightCount().
*/
Phong& setLightRange(UnsignedInt id, Float range);
private:
/* Prevent accidentally calling irrelevant functions */
#ifndef MAGNUM_TARGET_GLES
@ -834,7 +965,8 @@ class MAGNUM_SHADERS_EXPORT Phong: public GL::AbstractShaderProgram {
Int _objectIdUniform{10};
#endif
Int _lightPositionsUniform{11},
_lightColorsUniform; /* 11 + lightCount, set in the constructor */
_lightColorsUniform, /* 11 + lightCount, set in the constructor */
_lightRangesUniform; /* 11 + 2*lightCount, set in the constructor */
};
/** @debugoperatorclassenum{Phong,Phong::Flag} */

13
src/Magnum/Shaders/Phong.vert

@ -77,7 +77,11 @@ uniform mediump mat3 textureMatrix
#ifdef EXPLICIT_UNIFORM_LOCATION
layout(location = 11)
#endif
uniform highp vec3 lightPositions[LIGHT_COUNT]; /* defaults to zero */
uniform highp vec4 lightPositions[LIGHT_COUNT]
#ifndef GL_ES
= vec4[](LIGHT_POSITION_INITIALIZER);
#endif
;
#endif
#ifdef EXPLICIT_ATTRIB_LOCATION
@ -168,7 +172,7 @@ out mediump vec3 transformedTangent;
out mediump vec3 transformedBitangent;
#endif
#endif
out highp vec3 lightDirections[LIGHT_COUNT];
out highp vec4 lightDirections[LIGHT_COUNT];
out highp vec3 cameraDirection;
#endif
@ -209,9 +213,10 @@ void main() {
#endif
#endif
/* Direction to the light */
/* Direction to the light. Directional lights have the last component set
to 0, which gets used to ignore the transformed position. */
for(int i = 0; i < LIGHT_COUNT; ++i)
lightDirections[i] = lightPositions[i] - transformedPosition;
lightDirections[i] = vec4(lightPositions[i].xyz - transformedPosition*lightPositions[i].w, lightPositions[i].w);
/* Direction to the camera */
cameraDirection = -transformedPosition;

7
src/Magnum/Shaders/Test/CMakeLists.txt

@ -240,6 +240,13 @@ if(BUILD_GL_TESTS)
PhongTestFiles/textured-specular.tga
PhongTestFiles/textured.tga
PhongTestFiles/vertexColor.tga
PhongTestFiles/light-directional-intensity2.tga
PhongTestFiles/light-directional.tga
PhongTestFiles/light-none.tga
PhongTestFiles/light-point-attenuated-specular.tga
PhongTestFiles/light-point-intensity10-range0.5.tga
PhongTestFiles/light-point-range1.5.tga
PhongTestFiles/light-point.tga
# For zero lights test (equivalency to Flat3D)
FlatTestFiles/textured3D-alpha-mask0.5.tga)

233
src/Magnum/Shaders/Test/PhongGLTest.cpp

@ -27,6 +27,7 @@
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/PluginManager/Manager.h>
#include <Corrade/TestSuite/Compare/Numeric.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Directory.h>
@ -103,6 +104,7 @@ struct PhongGLTest: GL::OpenGLTester {
void renderObjectId();
#endif
void renderLights();
void renderLowLightAngle();
void renderZeroLights();
@ -337,6 +339,93 @@ constexpr struct {
};
#endif
const struct {
const char* name;
const char* file;
Vector4 position;
Float intensity;
Float range;
Containers::Array<std::pair<Vector2i, Color3ub>> picks;
} RenderLightsData[] {
{"directional", "light-directional.tga",
{1.0f, -1.5f, 0.5f, 0.0f}, 1.0f, Constants::inf(),
{Containers::InPlaceInit, {
/* Ambient isn't affected by light direction, otherwise it's a
dot product of a normalized direction */
{{40, 40}, 0x222222_rgb + 0xff8080_rgb*dot(Vector3{1.0f, -1.5f, 0.5f}.normalized(), Vector3::zAxis())},
/* and it's the same across the whole surface */
{{70, 70}, 0x222222_rgb + 0xff8080_rgb*dot(Vector3{1.0f, -1.5f, 0.5f}.normalized(), Vector3::zAxis())},
}}},
/* These two should produce the same output as the *normalized* dot product
is the same */
{"directional, from the other side", "light-directional.tga",
{-1.0f, 1.5f, 0.5f, 0.0f}, 1.0f, Constants::inf(), {}},
{"directional, scaled direction", "light-directional.tga",
{10.0f, -15.0f, 5.0f, 0.0f}, 1.0f, Constants::inf(), {}},
/* Range should have no effect either, especially zero range should not
cause any NaNs */
{"directional, range=0.1", "light-directional.tga",
{1.0f, -1.5f, 0.5f, 0.0f}, 1.0f, 1.0f, {}},
{"directional, range=0", "light-directional.tga",
{1.0f, -1.5f, 0.5f, 0.0f}, 1.0f, 1.0f, {}},
/* Light from the other side doesn't contribute anything */
{"directional, from back", "light-none.tga",
{-1.0f, 1.5f, -0.5f, 0.0f}, 1.0f, Constants::inf(),
{Containers::InPlaceInit, {
/* Only ambient color left */
{{40, 40}, 0x222222_rgb}
}}},
/* This is the same as above, except that twice the intensity causes it to
be 2x brighter */
{"directional, intensity=2", "light-directional-intensity2.tga",
{1.0f, -1.5f, 0.5f, 0.0f}, 2.0f, 1.0f,
{Containers::InPlaceInit, {
{{40, 40}, 0x222222_rgb + 0xff8080_rgb*dot(Vector3{1.0f, -1.5f, 0.5f}.normalized(), Vector3::zAxis())*2.0f}
}}},
{"point", "light-point.tga",
{0.75f, -0.75f, -1.25f, 1.0f}, 1.0f, Constants::inf(),
{Containers::InPlaceInit, {
/* The range is inf, so it doesn't get fully ambient even at the
edge */
{{8, 71}, 0x242324_rgb},
/* Closest to the light. TODO figure out the equation, sigh */
{{63, 16}, 0xc57474_rgb /*0x222222_rgb + 0xff8080_rgb/(1 + 0.25f*0.25f)*/},
/* Specular highlight */
{{60, 19}, 0xfefefe_rgb}
}}},
{"point, attenuated specular", "light-point-attenuated-specular.tga",
{1.0f, -1.0f, -0.25f, 1.0f}, 1.0f, 2.5f,
{Containers::InPlaceInit, {
/* Specular highlight shouldn't be brighter than the attenuated
intensity */
{{57, 22}, 0x665656_rgb}
}}},
{"point, range=1.5", "light-point-range1.5.tga",
{0.75f, -0.75f, -1.25f, 1.0f}, 1.0f, 1.5f,
{Containers::InPlaceInit, {
/* Color goes back to ambient at distance = 1.5 */
{{59, 60}, 0x222222_rgb},
{{29, 50}, 0x222222_rgb},
{{19, 14}, 0x222222_rgb},
/* But the center and specular stays ~ the same */
{{63, 16}, 0xc57474_rgb},
{{60, 19}, 0xfefcfc_rgb}
}}},
{"point, intensity=10, range=0.5", "light-point-intensity10-range0.5.tga",
{0.75f, -0.75f, -1.25f, 1.0f}, 10.0f, 0.5f, {}},
/* Range ends right at the surface, so no contribution */
{"point, range=0.25", "light-none.tga",
{0.75f, -0.75f, -1.25f, 1.0f}, 1.0f, 0.25f, {}},
/* Zero range should not cause any NaNs, so the ambient contribution is
still there */
{"point, range=0.0", "light-none.tga",
{0.75f, -0.75f, -1.25f, 1.0f}, 1.0f, 0.0f, {}},
/* Distance is 0, which means the direction is always prependicular and
thus contributes nothing */
{"point, distance=0", "light-none.tga",
{0.75f, -0.75f, -1.25f, 1.0f}, 1.0f, 0.0f, {}}
};
constexpr struct {
const char* name;
const char* file;
@ -346,19 +435,19 @@ constexpr struct {
{"diffuse", "instanced.tga", {},
#if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL))
/* AMD has one off pixel; SwiftShader a bit more */
93.67f, 0.106f,
96.34f, 0.113f,
#else
/* WebGL 1 doesn't have 8bit renderbuffer storage */
93.67f, 0.106f,
96.34f, 0.113f,
#endif
},
{"diffuse + normal", "instanced-normal.tga", Phong::Flag::NormalTexture,
#if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL))
/* AMD has one off pixel, llvmpipe more */
94.5f, 0.333f,
96.0f, 0.333f,
#else
/* WebGL 1 doesn't have 8bit renderbuffer storage */
94.5f, 0.132f,
96.0f, 0.333f,
#endif
}
};
@ -425,6 +514,11 @@ PhongGLTest::PhongGLTest() {
&PhongGLTest::renderObjectIdTeardown);
#endif
addInstancedTests({&PhongGLTest::renderLights},
Containers::arraySize(RenderLightsData),
&PhongGLTest::renderSetup,
&PhongGLTest::renderTeardown);
addTests({&PhongGLTest::renderLowLightAngle,
&PhongGLTest::renderZeroLights},
#ifndef MAGNUM_TARGET_GLES2
@ -606,17 +700,20 @@ void PhongGLTest::setWrongLightCount() {
/* This is okay */
shader.setLightColors({{}, {}, {}, {}, {}})
.setLightPositions({{}, {}, {}, {}, {}});
.setLightPositions(Containers::arrayView<const Vector4>({{}, {}, {}, {}, {}}))
.setLightRanges({0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
MAGNUM_VERIFY_NO_GL_ERROR();
/* This is not */
shader.setLightColor({})
.setLightPosition({});
shader.setLightColors({Color3{}})
.setLightPositions({Vector4{}})
.setLightRanges({0.0f});
CORRADE_COMPARE(out.str(),
"Shaders::Phong::setLightColors(): expected 5 items but got 1\n"
"Shaders::Phong::setLightPositions(): expected 5 items but got 1\n");
"Shaders::Phong::setLightPositions(): expected 5 items but got 1\n"
"Shaders::Phong::setLightRanges(): expected 5 items but got 1\n");
}
void PhongGLTest::setWrongLightId() {
@ -631,17 +728,20 @@ void PhongGLTest::setWrongLightId() {
/* This is okay */
shader.setLightColor(2, {})
.setLightPosition(2, {});
.setLightPosition(2, Vector4{})
.setLightRange(2, 0.0f);
MAGNUM_VERIFY_NO_GL_ERROR();
/* This is not */
shader.setLightColor(3, {})
.setLightPosition(3, {});
.setLightPosition(3, Vector4{})
.setLightRange(3, 0.0f);
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");
"Shaders::Phong::setLightPosition(): light ID 3 is out of bounds for 3 lights\n"
"Shaders::Phong::setLightRange(): light ID 3 is out of bounds for 3 lights\n");
}
constexpr Vector2i RenderSize{80, 80};
@ -673,16 +773,7 @@ void PhongGLTest::renderTeardown() {
}
void PhongGLTest::renderDefaults() {
/* The light is at the center by default, so we scale the sphere to half
and move the vertices back a bit to avoid a fully-black render but
still have the thing in the default [-1; 1] cube */
Trade::MeshData meshData = Primitives::uvSphereSolid(16, 32);
Matrix4 transformation =
Matrix4::translation(Vector3::zAxis(-1.0f))*Matrix4::scaling(Vector3(1.0f, 1.0f, 0.25f));
MeshTools::transformPointsInPlace(transformation, meshData.mutableAttribute<Vector3>(Trade::MeshAttribute::Position));
/** @todo use Matrix4::normalMatrix() */
MeshTools::transformVectorsInPlace(transformation.inverted().transposed(), meshData.mutableAttribute<Vector3>(Trade::MeshAttribute::Normal));
GL::Mesh sphere = MeshTools::compile(meshData);
GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32));
Phong{}
.draw(sphere);
@ -716,8 +807,8 @@ void PhongGLTest::renderColored() {
Phong{{}, 2}
.setLightColors({data.lightColor1, data.lightColor2})
.setLightPositions({{data.lightPosition1, -3.0f, 0.0f},
{data.lightPosition2, -3.0f, 0.0f}})
.setLightPositions({{data.lightPosition1, -3.0f, 2.0f, 0.0f},
{data.lightPosition2, -3.0f, 2.0f, 0.0f}})
.setAmbientColor(0x330033_rgbf)
.setDiffuseColor(0xccffcc_rgbf)
.setSpecularColor(0x6666ff_rgbf)
@ -799,8 +890,8 @@ void PhongGLTest::renderSinglePixelTextured() {
Phong shader{Phong::Flag::AmbientTexture|Phong::Flag::DiffuseTexture|Phong::Flag::SpecularTexture, 2};
shader.setLightColors({0x993366_rgbf, 0x669933_rgbf})
.setLightPositions({{-3.0f, -3.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f}})
.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f},
{ 3.0f, -3.0f, 2.0f, 0.0f}})
.setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.15f)))
.setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f));
@ -905,8 +996,8 @@ void PhongGLTest::renderTextured() {
/* Using default (white) light colors to have the texture data visible
better */
shader.setLightPositions({{-3.0f, -3.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f}})
shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f},
{ 3.0f, -3.0f, 2.0f, 0.0f}})
.setTransformationMatrix(
Matrix4::translation(Vector3::zAxis(-2.15f))*
Matrix4::rotationY(-15.0_degf)*
@ -921,10 +1012,10 @@ void PhongGLTest::renderTextured() {
#if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL))
/* SwiftShader has few rounding errors at the edges (giving a large max
error), but that's basically it. Apple A8 has more. */
const Float maxThreshold = 210.4f, meanThreshold = 0.202f;
const Float maxThreshold = 227.0f, meanThreshold = 0.202f;
#else
/* WebGL 1 doesn't have 8bit renderbuffer storage, so it's a bit worse */
const Float maxThreshold = 210.4f, meanThreshold = 3.434f;
const Float maxThreshold = 227.0f, meanThreshold = 3.434f;
#endif
CORRADE_COMPARE_WITH(
/* Dropping the alpha channel, as it's always 1.0 */
@ -979,8 +1070,8 @@ void PhongGLTest::renderTexturedNormal() {
exactly the same images. */
Phong shader{Phong::Flag::NormalTexture|data.flags, 2};
shader.setLightPositions({
Matrix4::rotationZ(data.rotation).transformPoint({-3.0f, -3.0f, 0.0f}),
Matrix4::rotationZ(data.rotation).transformPoint({ 3.0f, -3.0f, 0.0f})})
Matrix4::rotationZ(data.rotation)*Vector4{-3.0f, -3.0f, 2.0f, 0.0f},
Matrix4::rotationZ(data.rotation)*Vector4{ 3.0f, -3.0f, 2.0f, 0.0f}})
.setTransformationMatrix(Matrix4::translation(Vector3::zAxis(-2.35f))*
Matrix4::rotationZ(data.rotation)*
Matrix4::rotationY(-15.0_degf)*
@ -1068,8 +1159,8 @@ template<class T> void PhongGLTest::renderVertexColor() {
.setSubImage(0, {}, *image);
Phong{Phong::Flag::DiffuseTexture|Phong::Flag::VertexColor, 2}
.setLightPositions({{-3.0f, -3.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f}})
.setLightPositions({{-3.0f, -3.0f, 0.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f, 0.0f}})
.setTransformationMatrix(
Matrix4::translation(Vector3::zAxis(-2.15f))*
Matrix4::rotationY(-15.0_degf)*
@ -1106,7 +1197,7 @@ void PhongGLTest::renderShininess() {
GL::Mesh sphere = MeshTools::compile(Primitives::uvSphereSolid(16, 32));
Phong{}
.setLightPosition({-3.0f, -3.0f, 0.0f})
.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f}})
.setDiffuseColor(0xff3333_rgbf)
.setSpecularColor(data.specular)
.setShininess(data.shininess)
@ -1180,7 +1271,7 @@ void PhongGLTest::renderShininess() {
Utility::Directory::join({_testDir, "PhongTestFiles", "shininess0-overflow.tga"}),
/* The threshold = 0.001 case has a slight reddish tone on
SwiftShader; ARM Mali has one pixel off */
(DebugTools::CompareImageToFile{_manager, 255.0f, 1.476f}));
(DebugTools::CompareImageToFile{_manager, 255.0f, 23.1f}));
}
}
@ -1240,8 +1331,8 @@ void PhongGLTest::renderAlpha() {
Primitives::UVSphereFlag::TextureCoordinates));
Phong shader{data.flags, 2};
shader.setLightPositions({{-3.0f, -3.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f}})
shader.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f},
{ 3.0f, -3.0f, 2.0f, 0.0f}})
.setTransformationMatrix(
Matrix4::translation(Vector3::zAxis(-2.15f))*
Matrix4::rotationY(-15.0_degf)*
@ -1272,10 +1363,10 @@ void PhongGLTest::renderAlpha() {
That's okay, as we have only 8bit texture precision. SwiftShader has
additionally a few minor rounding errors at the edges, Apple A8 a bit
more. */
const Float maxThreshold = 172.667f, meanThreshold = 0.385f;
const Float maxThreshold = 189.4f, meanThreshold = 0.385f;
#else
/* WebGL 1 doesn't have 8bit renderbuffer storage, so it's way worse */
const Float maxThreshold = 172.667f, meanThreshold = 4.736f;
const Float maxThreshold = 189.4f, meanThreshold = 4.736f;
#endif
CORRADE_COMPARE_WITH(
/* Dropping the alpha channel, as it's always 1.0 */
@ -1343,8 +1434,8 @@ void PhongGLTest::renderObjectId() {
Phong{data.flags, 2}
.setLightColors({0x993366_rgbf, 0x669933_rgbf})
.setLightPositions({{-3.0f, -3.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f}})
.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f},
{ 3.0f, -3.0f, 2.0f, 0.0f}})
.setAmbientColor(0x330033_rgbf)
.setDiffuseColor(0xccffcc_rgbf)
.setSpecularColor(0x6666ff_rgbf)
@ -1389,6 +1480,58 @@ void PhongGLTest::renderObjectId() {
}
#endif
void PhongGLTest::renderLights() {
auto&& data = RenderLightsData[testCaseInstanceId()];
setTestCaseDescription(data.name);
GL::Mesh plane = MeshTools::compile(Primitives::planeSolid());
Matrix4 transformation =
Matrix4::translation({0.0f, 0.0f, -1.5f});
Phong{{}, 1}
/* Set non-black ambient to catch accidental NaNs -- the render should
never be fully black */
.setAmbientColor(0x222222_rgbf)
.setLightPositions({data.position})
.setLightColors({0xff8080_rgbf*data.intensity})
.setLightRanges({data.range})
.setShininess(60.0f)
.setTransformationMatrix(transformation)
.setNormalMatrix(transformation.normalMatrix())
.setProjectionMatrix(Matrix4::perspectiveProjection(80.0_degf, 1.0f, 0.1f, 20.0f))
.draw(plane);
MAGNUM_VERIFY_NO_GL_ERROR();
const Image2D image = _framebuffer.read(_framebuffer.viewport(), {PixelFormat::RGBA8Unorm});
/* Analytical output check. Comment this out when image comparison fails
for easier debugging. */
for(const auto& pick: data.picks) {
CORRADE_ITERATION(pick.first);
CORRADE_COMPARE_WITH(
image.pixels<Color4ub>()[pick.first.y()][pick.first.x()].xyz(),
pick.second, TestSuite::Compare::around(0x010101_rgb));
}
if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) ||
!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded))
CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found.");
#if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL))
const Float maxThreshold = 3.0f, meanThreshold = 0.02f;
#else
/* WebGL 1 doesn't have 8bit renderbuffer storage, so it's way worse */
const Float maxThreshold = 3.0f, meanThreshold = 0.02f;
#endif
CORRADE_COMPARE_WITH(
/* Dropping the alpha channel, as it's always 1.0 */
Containers::arrayCast<const Color3ub>(image.pixels<Color4ub>()),
Utility::Directory::join({_testDir, "PhongTestFiles", data.file}),
(DebugTools::CompareImageToFile{_manager, maxThreshold, meanThreshold}));
}
void PhongGLTest::renderLowLightAngle() {
GL::Mesh plane = MeshTools::compile(Primitives::planeSolid());
@ -1404,7 +1547,7 @@ void PhongGLTest::renderLowLightAngle() {
fragment-interpolated light direction being incorrect, most visible with
long polygons and low light angles. */
Phong{{}, 1}
.setLightPosition({0.0f, 0.1f, 0.0f})
.setLightPositions({{0.0f, 0.1f, 0.0f, 1.0f}})
.setShininess(200)
.setTransformationMatrix(transformation)
.setNormalMatrix(transformation.normalMatrix())
@ -1476,7 +1619,7 @@ void PhongGLTest::renderZeroLights() {
.setProjectionMatrix(Matrix4::perspectiveProjection(60.0_degf, 1.0f, 0.1f, 10.0f))
/* Keep alpha mask at the default 0.5 to test the default */
/* Passing a zero-sized light position / color array, shouldn't assert */
.setLightPositions({})
.setLightPositions(Containers::ArrayView<const Vector4>{})
.setLightColors({})
/* Using a bogus normal matrix -- it's not used so it should be okay.
Same for all other unused values, they should get ignored. */
@ -1622,8 +1765,8 @@ void PhongGLTest::renderInstanced() {
Phong::Flag::InstancedTransformation|
Phong::Flag::InstancedTextureOffset|data.flags, 2};
shader
.setLightPositions({{-3.0f, -3.0f, 0.0f},
{ 3.0f, -3.0f, 0.0f}})
.setLightPositions({{-3.0f, -3.0f, 2.0f, 0.0f},
{ 3.0f, -3.0f, 2.0f, 0.0f}})
.setTransformationMatrix(
Matrix4::translation(Vector3::zAxis(-1.75f))*
Matrix4::rotationY(-15.0_degf)*

BIN
src/Magnum/Shaders/Test/PhongTestFiles/colored.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/defaults.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/instanced-normal.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/instanced.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-directional-intensity2.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-directional.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-none.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-point-attenuated-specular.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-point-intensity10-range0.5.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-point-range1.5.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/light-point.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/low-light-angle.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/shininess-black-specular.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/shininess0.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/shininess10.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/shininess80.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-ambient.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse-alpha-mask0.5.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse-alpha.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse-transformed.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-diffuse.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-normal-left.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-normal.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.0.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-normal0.5.tga

Binary file not shown.

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured-specular.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/textured.tga

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/Magnum/Shaders/Test/PhongTestFiles/vertexColor.tga

Binary file not shown.
Loading…
Cancel
Save