Browse Source

Shaders: support multiple lights in Phong.

pull/284/head
Vladimír Vondruš 8 years ago
parent
commit
b615e1ae28
  1. 1
      doc/changelog.dox
  2. 53
      src/Magnum/Shaders/Phong.cpp
  3. 45
      src/Magnum/Shaders/Phong.frag
  4. 87
      src/Magnum/Shaders/Phong.h
  5. 10
      src/Magnum/Shaders/Phong.vert
  6. 79
      src/Magnum/Shaders/Test/PhongGLTest.cpp

1
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,

53
src/Magnum/Shaders/Phong.cpp

@ -25,7 +25,11 @@
#include "Phong.h"
#ifdef MAGNUM_TARGET_GLES
#include <Corrade/Containers/Array.h>
#endif
#include <Corrade/Containers/EnumSet.hpp>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Resource.h>
#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<Color4>{Containers::DirectInit, lightCount, Color4{1.0f}});
setTransformationMatrix({});
setProjectionMatrix({});
@ -174,6 +205,13 @@ Phong& Phong::setLightPositions(const Containers::ArrayView<const Vector3> 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<const Color4> 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<const Color4> 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 */

45
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

87
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<const Vector3> lights);
/** @overload */
Phong& setLightPositions(std::initializer_list<Vector3> 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<const Color4> colors);
/** @overload */
Phong& setLightColors(std::initializer_list<Color4> 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} */

10
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;

79
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)

Loading…
Cancel
Save