From 4caeb30f7b996a19b3a460d472c387be9eadadc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 10 Jan 2015 18:10:35 +0100 Subject: [PATCH] Shaders: there's never enough documentation. Each shader now has sample image, example mesh configuration and example rendering setup. Also properly documented all attribute types and made introductory chapter for whole Shaders namespace. --- doc/features.dox | 1 + doc/generated/.gitattributes | 1 + doc/generated/CMakeLists.txt | 60 +++++ doc/generated/README.md | 32 +++ doc/generated/configure.h.cmake | 32 +++ doc/generated/shaders.cpp | 301 +++++++++++++++++++++++ doc/generated/vector-distancefield.png | Bin 0 -> 852 bytes doc/generated/vector.png | Bin 0 -> 2444 bytes doc/generated/vector.svg | 79 ++++++ doc/namespaces.dox | 2 +- doc/plugins.dox | 4 +- doc/scenegraph.dox | 4 +- doc/shaders-distancefieldvector.png | Bin 0 -> 11044 bytes doc/shaders-flat.png | Bin 0 -> 4828 bytes doc/shaders-meshvisualizer.png | Bin 0 -> 24834 bytes doc/shaders-phong.png | Bin 0 -> 28879 bytes doc/shaders-vector.png | Bin 0 -> 10704 bytes doc/shaders-vertexcolor.png | Bin 0 -> 20356 bytes doc/shaders.dox | 136 ++++++++++ src/Magnum/MeshTools/Compile.h | 4 + src/Magnum/Shaders/AbstractVector.h | 16 +- src/Magnum/Shaders/DistanceFieldVector.h | 50 +++- src/Magnum/Shaders/Flat.h | 79 +++++- src/Magnum/Shaders/Generic.h | 33 +-- src/Magnum/Shaders/MeshVisualizer.h | 105 +++++++- src/Magnum/Shaders/Phong.h | 107 +++++++- src/Magnum/Shaders/Vector.h | 43 +++- src/Magnum/Shaders/VertexColor.h | 53 +++- 28 files changed, 1080 insertions(+), 62 deletions(-) create mode 100644 doc/generated/.gitattributes create mode 100644 doc/generated/CMakeLists.txt create mode 100644 doc/generated/README.md create mode 100644 doc/generated/configure.h.cmake create mode 100644 doc/generated/shaders.cpp create mode 100644 doc/generated/vector-distancefield.png create mode 100644 doc/generated/vector.png create mode 100644 doc/generated/vector.svg create mode 100644 doc/shaders-distancefieldvector.png create mode 100644 doc/shaders-flat.png create mode 100644 doc/shaders-meshvisualizer.png create mode 100644 doc/shaders-phong.png create mode 100644 doc/shaders-vector.png create mode 100644 doc/shaders-vertexcolor.png create mode 100644 doc/shaders.dox diff --git a/doc/features.dox b/doc/features.dox index dd909093e..2614e21cc 100644 --- a/doc/features.dox +++ b/doc/features.dox @@ -32,6 +32,7 @@ namespace Magnum { - @subpage matrix-vector -- @copybrief matrix-vector - @subpage transformations -- @copybrief transformations - @subpage plugins -- @copybrief plugins +- @subpage shaders -- @copybrief shaders - @subpage scenegraph -- @copybrief scenegraph - @subpage shapes -- @copybrief shapes - @subpage debug-tools -- @copybrief debug-tools diff --git a/doc/generated/.gitattributes b/doc/generated/.gitattributes new file mode 100644 index 000000000..2117a73f8 --- /dev/null +++ b/doc/generated/.gitattributes @@ -0,0 +1 @@ +vector.svg -diff diff --git a/doc/generated/CMakeLists.txt b/doc/generated/CMakeLists.txt new file mode 100644 index 000000000..0c591a08d --- /dev/null +++ b/doc/generated/CMakeLists.txt @@ -0,0 +1,60 @@ +# +# This file is part of Magnum. +# +# Copyright © 2010, 2011, 2012, 2013, 2014 +# Vladimír Vondruš +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# + +cmake_minimum_required(VERSION 2.8.9) +project(MyApplication) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../../modules/") + +find_package(Magnum REQUIRED + MeshTools + Primitives + Shaders) + +if(CORRADE_TARGET_APPLE) + find_package(Magnum REQUIRED WindowlessCglApplication) +elseif(CORRADE_TARGET_UNIX) + find_package(Magnum REQUIRED WindowlessGlxApplication) +elseif(CORRADE_TARGET_WINDOWS) + find_package(Magnum REQUIRED WindowlessWglApplication) +else() + message(FATAL_ERROR "No windowless application available on this platform") +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/configure.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CORRADE_CXX_FLAGS}") +include_directories(${MAGNUM_INCLUDE_DIRS} + ${MAGNUM_WINDOWLESSAPPLICATION_INCLUDE_DIRS}) + +add_executable(shaders shaders.cpp) +target_link_libraries(shaders + ${MAGNUM_LIBRARIES} + ${MAGNUM_MESHTOOLS_LIBRARIES} + ${MAGNUM_PRIMITIVES_LIBRARIES} + ${MAGNUM_SHADERS_LIBRARIES} + ${MAGNUM_WINDOWLESSAPPLICATION_LIBRARIES}) diff --git a/doc/generated/README.md b/doc/generated/README.md new file mode 100644 index 000000000..23b8f5c64 --- /dev/null +++ b/doc/generated/README.md @@ -0,0 +1,32 @@ +Source files for images in Magnum documentation +----------------------------------------------- + +Compile and install Magnum with windowless application for your platform and +`magnum-distancefieldconverter` utility and any `PngImporter` and +`PngImageConverter` plugins from Magnum Plugins. + +Create build dir, point CMake to this directory and compile the executables: + + mkdir build-doc + cd build-doc + cmake ../doc/generated + cmake --build . + +### Shader images + +Generated by the `shaders` executable. Must be run in this directory, the +output is put into `doc/` directory. The executable requires two textures: + +- `vector.png`, generated as full-page PNG output at 90 DPI from `vector.svg`, + converted to pure grayscale using imagemagick: + + ```bash + mogrify -flatten -background '#ffffff' -format grayscale vector.png + ``` + +- `vector-distancefield.png`, generated as full-page PNG output at 360 DPI + (1024x1024) and then processed through `magnum-distancefieldconverter` + + ```bash + magnum-distancefieldconverter --importer PngImporter --converter PngImageConverter --output-size "64 64" --radius 16 vector-src.png vector-distancefield.png + ``` diff --git a/doc/generated/configure.h.cmake b/doc/generated/configure.h.cmake new file mode 100644 index 000000000..68174eb16 --- /dev/null +++ b/doc/generated/configure.h.cmake @@ -0,0 +1,32 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_IMAGECONVERTER_DIR "${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_DIR "${MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR}" +#else +#define MAGNUM_PLUGINS_IMAGECONVERTER_DIR "${MAGNUM_PLUGINS_IMAGECONVERTER_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_DIR "${MAGNUM_PLUGINS_IMPORTER_DIR}" +#endif diff --git a/doc/generated/shaders.cpp b/doc/generated/shaders.cpp new file mode 100644 index 000000000..73e12010e --- /dev/null +++ b/doc/generated/shaders.cpp @@ -0,0 +1,301 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#ifdef CORRADE_TARGET_APPLE +#include +#elif defined(CORRADE_TARGET_UNIX) +#include +#elif defined(CORRADE_TARGET_WINDOWS) +#include +#else +#error No windowless application available on this platform +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "configure.h" + +using namespace Magnum; + +struct ShaderVisualizer: Platform::WindowlessApplication { + using Platform::WindowlessApplication::WindowlessApplication; + + int exec() override; + + std::string phong(); + std::string meshVisualizer(); + std::string flat(); + std::string vertexColor(); + + std::string vector(); + std::string distanceFieldVector(); + + std::unique_ptr _importer; +}; + +namespace { + constexpr const Vector2i ImageSize{256}; +} + +int ShaderVisualizer::exec() { + PluginManager::Manager converterManager{MAGNUM_PLUGINS_IMAGECONVERTER_DIR}; + std::unique_ptr converter = converterManager.loadAndInstantiate("PngImageConverter"); + if(!converter) { + Error() << "Cannot load image converter plugin"; + std::exit(1); + } + + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_DIR}; + _importer = importerManager.loadAndInstantiate("PngImporter"); + if(!_importer) { + Error() << "Cannot load image importer plugin"; + std::exit(1); + } + + Renderbuffer multisampleColor, multisampleDepth; + multisampleColor.setStorageMultisample(16, RenderbufferFormat::RGBA8, ImageSize); + multisampleDepth.setStorageMultisample(16, RenderbufferFormat::DepthComponent24, ImageSize); + + Framebuffer multisampleFramebuffer{{{}, ImageSize}}; + multisampleFramebuffer.attachRenderbuffer(Framebuffer::ColorAttachment{0}, multisampleColor) + .attachRenderbuffer(Framebuffer::BufferAttachment::Depth, multisampleDepth) + .bind(FramebufferTarget::Draw); + CORRADE_INTERNAL_ASSERT(multisampleFramebuffer.checkStatus(FramebufferTarget::ReadDraw) == Framebuffer::Status::Complete); + + Renderbuffer color; + color.setStorage(RenderbufferFormat::RGBA8, ImageSize); + Framebuffer framebuffer{{{}, ImageSize}}; + framebuffer.attachRenderbuffer(Framebuffer::ColorAttachment{0}, color); + + Renderer::enable(Renderer::Feature::DepthTest); + + for(auto fun: {&ShaderVisualizer::phong, + &ShaderVisualizer::meshVisualizer, + &ShaderVisualizer::flat, + &ShaderVisualizer::vertexColor, + &ShaderVisualizer::vector, + &ShaderVisualizer::distanceFieldVector}) { + multisampleFramebuffer.clear(FramebufferClear::Color|FramebufferClear::Depth); + + std::string filename = (this->*fun)(); + + AbstractFramebuffer::blit(multisampleFramebuffer, framebuffer, framebuffer.viewport(), FramebufferBlit::Color); + Image2D result = framebuffer.read(framebuffer.viewport(), {ColorFormat::RGBA, ColorType::UnsignedByte}); + converter->exportToFile(result, Utility::Directory::join("../", "shaders-" + filename)); + } + + _importer.reset(); + + return 0; +} + +namespace { + const auto Projection = Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f); + const auto Transformation = Matrix4::translation(Vector3::zAxis(-5.0f)); + const auto BaseColor = Color3::fromHSV(216.0_degf, 0.85f, 1.0f); + const auto OutlineColor = Color3{0.95f}; +} + +std::string ShaderVisualizer::phong() { + std::unique_ptr vertices, indices; + Mesh mesh; + std::tie(mesh, vertices, indices) = MeshTools::compile(Primitives::UVSphere::solid(16, 32), BufferUsage::StaticDraw); + + Shaders::Phong shader; + shader.setAmbientColor(Color3(0.025f)) + .setDiffuseColor(BaseColor) + .setShininess(200.0f) + .setLightPosition({5.0f, 5.0f, 7.0f}) + .setProjectionMatrix(Projection) + .setTransformationMatrix(Transformation) + .setNormalMatrix(Transformation.rotationScaling()); + + mesh.draw(shader); + + return "phong.png"; +} + +std::string ShaderVisualizer::meshVisualizer() { + std::unique_ptr vertices, indices; + Mesh mesh; + std::tie(mesh, vertices, indices) = MeshTools::compile(Primitives::Icosphere::solid(1), BufferUsage::StaticDraw); + + const Matrix4 projection = Projection*Transformation* + Matrix4::rotationZ(13.7_degf)* + Matrix4::rotationX(-12.6_degf); + + Shaders::MeshVisualizer shader{Shaders::MeshVisualizer::Flag::Wireframe}; + shader.setColor(BaseColor) + .setWireframeColor(OutlineColor) + .setViewportSize(Vector2{ImageSize}) + .setTransformationProjectionMatrix(projection); + + mesh.draw(shader); + + return "meshvisualizer.png"; +} + +std::string ShaderVisualizer::flat() { + std::unique_ptr vertices, indices; + Mesh mesh; + std::tie(mesh, vertices, indices) = MeshTools::compile(Primitives::UVSphere::solid(16, 32), BufferUsage::StaticDraw); + + Shaders::Flat3D shader; + shader.setColor(BaseColor) + .setTransformationProjectionMatrix(Projection*Transformation); + + mesh.draw(shader); + + return "flat.png"; +} + +std::string ShaderVisualizer::vertexColor() { + Trade::MeshData3D sphere = Primitives::UVSphere::solid(32, 64); + + /* Color vertices nearest to given position */ + auto target = Vector3{2.0f, 2.0f, 7.0f}.normalized(); + std::vector colors; + colors.reserve(sphere.positions(0).size()); + for(Vector3 position: sphere.positions(0)) + colors.push_back(Color3::fromHSV(Math::lerp(240.0_degf, 420.0_degf, Math::max(1.0f - (position - target).length(), 0.0f)), 0.85f, 0.85f)); + + Buffer vertices, indices; + vertices.setData(MeshTools::interleave(sphere.positions(0), colors), BufferUsage::StaticDraw); + indices.setData(sphere.indices(), BufferUsage::StaticDraw); + + Mesh mesh; + mesh.setPrimitive(MeshPrimitive::Triangles) + .setCount(sphere.indices().size()) + .addVertexBuffer(vertices, 0, Shaders::VertexColor3D::Position{}, Shaders::VertexColor3D::Color{}) + .setIndexBuffer(indices, 0, Mesh::IndexType::UnsignedInt); + + Shaders::VertexColor3D shader; + shader.setTransformationProjectionMatrix(Projection*Transformation); + + mesh.draw(shader); + + return "vertexcolor.png"; +} + +std::string ShaderVisualizer::vector() { + std::optional image; + if(!_importer->openFile("vector.png") || !(image = _importer->image2D(0))) { + Error() << "Cannot open vector.png"; + return "vector.png"; + } + + Texture2D texture; + texture.setMinificationFilter(Sampler::Filter::Linear) + .setMagnificationFilter(Sampler::Filter::Linear) + .setWrapping(Sampler::Wrapping::ClampToEdge) + .setStorage(1, TextureFormat::RGBA8, image->size()) + .setSubImage(0, {}, *image); + + Mesh mesh; + std::unique_ptr vertices; + std::tie(mesh, vertices, std::ignore) = MeshTools::compile(Primitives::Square::solid(Primitives::Square::TextureCoords::Generate), BufferUsage::StaticDraw); + + Shaders::Vector2D shader; + shader.setColor(BaseColor) + .setVectorTexture(texture) + .setTransformationProjectionMatrix({}); + + Renderer::enable(Renderer::Feature::Blending); + Renderer::setBlendFunction(Renderer::BlendFunction::One, Renderer::BlendFunction::OneMinusSourceAlpha); + Renderer::setBlendEquation(Renderer::BlendEquation::Add, Renderer::BlendEquation::Add); + + mesh.draw(shader); + + Renderer::disable(Renderer::Feature::Blending); + + return "vector.png"; +} + +std::string ShaderVisualizer::distanceFieldVector() { + std::optional image; + if(!_importer->openFile("vector-distancefield.png") || !(image = _importer->image2D(0))) { + Error() << "Cannot open vector-distancefield.png"; + return "distancefieldvector.png"; + } + + Texture2D texture; + texture.setMinificationFilter(Sampler::Filter::Linear) + .setMagnificationFilter(Sampler::Filter::Linear) + .setWrapping(Sampler::Wrapping::ClampToEdge) + .setStorage(1, TextureFormat::RGBA8, image->size()) + .setSubImage(0, {}, *image); + + Mesh mesh; + std::unique_ptr vertices; + std::tie(mesh, vertices, std::ignore) = MeshTools::compile(Primitives::Square::solid(Primitives::Square::TextureCoords::Generate), BufferUsage::StaticDraw); + + Shaders::DistanceFieldVector2D shader; + shader.setColor(BaseColor) + .setOutlineColor(OutlineColor) + .setOutlineRange(0.6f, 0.4f) + .setVectorTexture(texture) + .setTransformationProjectionMatrix({}); + + Renderer::enable(Renderer::Feature::Blending); + Renderer::setBlendFunction(Renderer::BlendFunction::One, Renderer::BlendFunction::OneMinusSourceAlpha); + Renderer::setBlendEquation(Renderer::BlendEquation::Add, Renderer::BlendEquation::Add); + + mesh.draw(shader); + + Renderer::disable(Renderer::Feature::Blending); + + return "distancefieldvector.png"; +} + +MAGNUM_WINDOWLESSAPPLICATION_MAIN(ShaderVisualizer) diff --git a/doc/generated/vector-distancefield.png b/doc/generated/vector-distancefield.png new file mode 100644 index 0000000000000000000000000000000000000000..5a94b2dc4297f8dfcfc2e8e9db5da6ed7d9439fd GIT binary patch literal 852 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 W6!)ZX$wE!K1UMdav8}caIQLJu$qLf| literal 0 HcmV?d00001 diff --git a/doc/generated/vector.svg b/doc/generated/vector.svg new file mode 100644 index 000000000..78056b8c6 --- /dev/null +++ b/doc/generated/vector.svg @@ -0,0 +1,79 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/doc/namespaces.dox b/doc/namespaces.dox index 365efd7f4..7e329e676 100644 --- a/doc/namespaces.dox +++ b/doc/namespaces.dox @@ -182,7 +182,7 @@ Collection of shaders for easy prototyping and basic usage. This library is built if `WITH_SHADERS` is enabled when building Magnum. To use this library, you need to request `Shaders` component of `Magnum` package in CMake and link to `${MAGNUM_MESHTOOLS_SHADERS}`. See @ref building and -@ref cmake for more information. +@ref cmake and @ref shaders for more information. */ /** @dir Magnum/Shapes diff --git a/doc/plugins.dox b/doc/plugins.dox index 2eae069b4..20b5b7da1 100644 --- a/doc/plugins.dox +++ b/doc/plugins.dox @@ -28,7 +28,7 @@ namespace Magnum { @brief Extending Magnum with additional functionality - Previous page: @ref transformations -- Next page: @ref scenegraph +- Next page: @ref shaders The base Magnum library contains math support, scene graph implementation and is able to interact with graphics and audio hardware. However, the base library @@ -211,7 +211,7 @@ to how static plugins are found above. See @ref cmake and @ref cmake-plugins for more information. - Previous page: @ref transformations -- Next page: @ref scenegraph +- Next page: @ref shaders */ } diff --git a/doc/scenegraph.dox b/doc/scenegraph.dox index 7a0ed5288..a333a1594 100644 --- a/doc/scenegraph.dox +++ b/doc/scenegraph.dox @@ -27,7 +27,7 @@ namespace Magnum { /** @page scenegraph Using scene graph @brief Overview of scene management capabilities. -- Previous page: @ref plugins +- Previous page: @ref shaders - Next page: @ref shapes Scene graph provides way to hiearchically manage your objects, their @@ -286,7 +286,7 @@ On destruction, Object3D destructor is called first, deleting MyFeature, which is wrong, because MyFeature is in the same object. After that (if the program didn't already crash) destructor of MyFeature is called (again). -- Previous page: @ref plugins +- Previous page: @ref shaders - Next page: @ref shapes */ } diff --git a/doc/shaders-distancefieldvector.png b/doc/shaders-distancefieldvector.png new file mode 100644 index 0000000000000000000000000000000000000000..c54d99240bd50ef4669fb6f7ade9db44a5769c3a GIT binary patch literal 11044 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 literal 0 HcmV?d00001 diff --git a/doc/shaders-meshvisualizer.png b/doc/shaders-meshvisualizer.png new file mode 100644 index 0000000000000000000000000000000000000000..896dda5756d0e843e72b718e13a8b89a16461a06 GIT binary patch literal 24834 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 Fe*pnn>W%;a literal 0 HcmV?d00001 diff --git a/doc/shaders-phong.png b/doc/shaders-phong.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1a44a33a7ca226f10ade0e8044e713fbaf6ac7 GIT binary patch literal 28879 zcmb5VX;>3k*EU*}N-6;gk}xI+CJ>}SK|=zHiX;IHQBfmKh^S$XI5gtWXrrkxBT<6V z2xv-h|skQg6T5I2H z-S;k#E(y1`bhZQlzKnLi!> zC;V3pjq!${s{PXQpH_*t)?jYWbe-Itfj{zp-Br@*gz9*p+F~zmIhCSv1ghH<_@x!v z-uP{>p|Y?Xo!m+0uP?$aA<+t_B{AK`eo&V2u{a-hkZrnLAx8e@Ig2X(aUH+PSoo4DXG#iV4DjE=2 zQ6VTlDJfYdi%T|j-D=kv46R01Yw{4V=NR3POuVa+%Em`Xwv6in=mmzTu3rb
5m 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 OUo2W0c_v(x_kRFad^;}y literal 0 HcmV?d00001 diff --git a/doc/shaders-vector.png b/doc/shaders-vector.png new file mode 100644 index 0000000000000000000000000000000000000000..9697fdd7af88157008c3a734d8e74470867ff70e GIT binary patch literal 10704 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ⅇAC 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 BVOanG literal 0 HcmV?d00001 diff --git a/doc/shaders.dox b/doc/shaders.dox new file mode 100644 index 000000000..980364caf --- /dev/null +++ b/doc/shaders.dox @@ -0,0 +1,136 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +namespace Magnum { +/** @page shaders Builtin shaders +@brief Overview and basic usage of builtin shaders. + +- Previous page: @ref plugins +- Next page: @ref scenegraph + +@tableofcontents + +Magnum contains a set of general-purpose shaders for easy prototyping, UI +rendering and data visualization/debugging in both 2D and 3D scenes. The +following shaders are available, see documentation of each class for sample +output and example setup: + +- @ref Shaders::Flat "Shaders::Flat*D" -- flat shading using single color or + texture +- @ref Shaders::Vector "Shaders::Vector*D" -- colored vector graphics +- @ref Shaders::DistanceFieldVector "Shaders::DistanceFieldVector*D" -- + colored and outlined vector graphics +- @ref Shaders::VertexColor "Shaders::VertexColor*D" -- vertex-colored meshes +- @ref Shaders::Phong -- Phong shading using colors or textures, 3D only +- @ref Shaders::MeshVisualizer -- wireframe visualization, 3D only + +All the builtin shaders can be used on unextended OpenGL 2.1 and OpenGL ES 2.0 +/ WebGL 1.0, but they try to use the most recent technology available to have +them as efficient as possible on every configuration. + +@section shaders-usage Usage + +Shader usage is divided into two parts: configuring vertex attributes in the +mesh and configuring the shader itself. + +Each shader expects some set of vertex attributes, thus when adding vertex +buffer into the mesh, you need to specify which shader attributes are on which +position in the buffer. See @ref Mesh::addVertexBuffer() for details and usage +examples. Example mesh configuration for @ref Shaders::Phong shader: +@code +struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinates; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::Phong::Position{}, + Shaders::Phong::Normal{}, + Shaders::Phong::TextureCoordinates{}); +@endcode + +Each shader then has its own set of configuration functions. Some configuration +is static, specified commonly as flags in constructor, directly affecting +compiled shader code. Other configuration is specified through uniforms and +various binding points, commonly exposed through various setters. Example +configuration and rendering using @ref Shaders::Phong "Shaders::Phong": +@code +Matrix4 transformationMatrix, projectionMatrix; +Texture2D diffuseTexture, specularTexture; + +Shaders::Phong shader{Shaders::Phong::DiffuseTexture}; +shader.setDiffuseTexture(diffuseTexture) + .setLightPosition({5.0f, 5.0f, 7.0f}) + .setTransformationMatrix(transformationMatrix) + .setNormalMatrix(transformationMatrix.rotation()) + .setProjectionMatrix(projectionMatrix); + +mesh.draw(shader); +@endcode + +@section shaders-generic Generic vertex attributes + +Many shaders share the same vertex attribute definitions, such as positions, +normals, texture coordinates etc. It's thus possible to configure the mesh +for *generic* shader and then render it with any compatible shader. Definition +of generic attributes is available in @ref Shaders::Generic class. +Configuration of the above mesh using generic attributes could then look like +this: +@code +mesh.addVertexBuffer(vertices, 0, + Shaders::Generic3D::Position{}, + Shaders::Generic3D::Normal{}, + Shaders::Generic3D::TextureCoordinates{}); +@endcode +Note that in this particular case both configurations are equivalent, because +@ref Shaders::Phong also uses generic vertex attribute definitions. + +Then you can render the mesh using @ref Shaders::Phong shader like above, or +use for example @ref Shaders::Flat3D or even @ref Shaders::MeshVisualizer with +the same mesh reconfiguration. The unused attributes will be simply ignored. +@code +Shaders::MeshVisualizer visualizerShader{Shaders::MeshVisualizer::Wireframe}; +visualizerShader.setColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setWireframeColor(Color3{0.95f}) + .setViewportSize(defaultFramebuffer.viewport().size()) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(visualizerShader); +@endcode + +The @ref MeshTools::compile() utility configures meshes using generic vertex +attribute definitions to make them usable with any shader. + +- Previous page: @ref plugins +- Next page: @ref scenegraph + +*/ +} diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index 3cb41c795..e044a6c45 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -54,6 +54,8 @@ The second returned buffer may be `nullptr` if the mesh is not indexed. This is just a convenience function for creating generic meshes, you might want to use @ref interleave() and @ref compressIndices() functions instead for greater flexibility. + +@see @ref shaders-generic */ MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, BufferUsage usage); @@ -73,6 +75,8 @@ The second returned buffer may be `nullptr` if the mesh is not indexed. This is just a convenience function for creating generic meshes, you might want to use @ref interleave() and @ref compressIndices() functions instead for greater flexibility. + +@see @ref shaders-generic */ MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, BufferUsage usage); diff --git a/src/Magnum/Shaders/AbstractVector.h b/src/Magnum/Shaders/AbstractVector.h index 4c1fc5399..a882fa669 100644 --- a/src/Magnum/Shaders/AbstractVector.h +++ b/src/Magnum/Shaders/AbstractVector.h @@ -36,14 +36,24 @@ namespace Magnum { namespace Shaders { /** @brief Base for vector shaders -@see @ref AbstractVector2D, @ref AbstractVector3D +See @ref DistanceFieldVector and @ref Vector for more information. +@see @ref shaders, @ref AbstractVector2D, @ref AbstractVector3D */ template class AbstractVector: public AbstractShaderProgram { public: - /** @brief Vertex position */ + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", @ref Vector2 in 2D, + * @ref Vector3 in 3D. + */ typedef typename Generic::Position Position; - /** @brief Texture coordinates */ + /** + * @brief 2D texture coordinates + * + * @ref shaders-generic "Generic attribute", @ref Vector2. + */ typedef typename Generic::TextureCoordinates TextureCoordinates; #ifdef MAGNUM_BUILD_DEPRECATED diff --git a/src/Magnum/Shaders/DistanceFieldVector.h b/src/Magnum/Shaders/DistanceFieldVector.h index ad9986bc5..630094fea 100644 --- a/src/Magnum/Shaders/DistanceFieldVector.h +++ b/src/Magnum/Shaders/DistanceFieldVector.h @@ -41,10 +41,52 @@ namespace Magnum { namespace Shaders { /** @brief Distance field vector shader -Renders vector art in form of signed distance field. See @ref TextureTools::distanceField() -for more information. Note that the final rendered outlook will greatly depend -on radius of input distance field and value passed to @ref setSmoothness(). -@see @ref DistanceFieldVector2D, @ref DistanceFieldVector3D +Renders vector graphics in form of signed distance field. See +@ref TextureTools::distanceField() for more information. Note that the final +rendered outlook will greatly depend on radius of input distance field and +value passed to @ref setSmoothness(). You need to provide @ref Position and +@ref TextureCoordinates attributes in your triangle mesh and call at least +@ref setTransformationProjectionMatrix(), @ref setColor() and +@ref setVectorTexture(). + +@image html shaders-distancefieldvector.png +@image latex shaders-distancefieldvector.png + +## Example usage + +Common mesh setup: +@code +struct Vertex { + Vector2 position; + Vector2 textureCoordinates; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::DistanceFieldVector2D::Position{}, + Shaders::DistanceFieldVector2D::TextureCoordinates{}); +@endcode + +Common rendering setup: +@code +Matrix3 transformationMatrix, projectionMatrix; +Texture2D texture; + +Shaders::DistanceFieldVector2D shader; +shader.setColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setOutlineColor(Color3{0.95f}) + .setOutlineRange(0.6f, 0.4f) + .setVectorTexture(texture) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(shader); +@endcode + +@see @ref shaders, @ref DistanceFieldVector2D, @ref DistanceFieldVector3D @todo Use fragment shader derivations to have proper smoothness in perspective/ large zoom levels, make it optional as it might have negative performance impact diff --git a/src/Magnum/Shaders/Flat.h b/src/Magnum/Shaders/Flat.h index cf920012c..5ceb14329 100644 --- a/src/Magnum/Shaders/Flat.h +++ b/src/Magnum/Shaders/Flat.h @@ -57,17 +57,88 @@ The texture will be multiplied with the color (which is white by default, thus it doesn't change texture color). For coloring the texture based on intensity you can use the @ref Vector shader. -@see @ref Flat2D, @ref Flat3D + +@image html shaders-flat.png +@image latex shaders-flat.png + +## Example usage + +### Colored mesh + +Common mesh setup: +@code +struct Vertex { + Vector3 position; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, Shaders::Flat3D::Position{}); +@endcode + +Common rendering setup: +@code +Matrix4 transformationMatrix = Matrix4::translation(Vector3::zAxis(-5.0f)); +Matrix4 projectionMatrix = Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f); + +Shaders::Flat3D shader; +shader.setColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(shader); +@endcode + +### Textured mesh + +Common mesh setup: +@code +struct Vertex { + Vector3 position; + Vector2 textureCoordinates; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::Flat3D::Position{}, + Shaders::Flat3D::TextureCoordinates{}); +@endcode + +Common rendering setup: +@code +Matrix4 transformationMatrix, projectionMatrix; +Texture2D texture; + +Shaders::Flat3D shader{Shaders::Flat3D::Textured}; +shader.setTransformationProjectionMatrix(projectionMatrix*transformationMatrix) + .setTexture(texture); + +mesh.draw(shader); +@endcode + +@see @ref shaders, @ref Flat2D, @ref Flat3D */ template class MAGNUM_SHADERS_EXPORT Flat: public AbstractShaderProgram { public: - /** @brief Vertex position */ + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", @ref Vector2 in 2D, + * @ref Vector3 in 3D. + */ typedef typename Generic::Position Position; /** - * @brief Texture coordinates + * @brief 2D texture coordinates * - * Used only if @ref Flag::Textured is set. + * @ref shaders-generic "Generic attribute", @ref Vector2. Used only if + * @ref Flag::Textured is set. */ typedef typename Generic::TextureCoordinates TextureCoordinates; diff --git a/src/Magnum/Shaders/Generic.h b/src/Magnum/Shaders/Generic.h index 24a386d5c..12e91d489 100644 --- a/src/Magnum/Shaders/Generic.h +++ b/src/Magnum/Shaders/Generic.h @@ -37,27 +37,10 @@ namespace Magnum { namespace Shaders { @brief Generic shader definition Definitions common for majority of shaders in @ref Shaders namespace, allowing -mesh configured for the generic shader to be used with any of them. +mesh configured for the generic shader to be used with any of them. See +@ref shaders-generic for more information. -Example usage (configuring the mesh for generic shader, then using it with -@ref Shaders::Phong): -@code -Mesh mesh; -Buffer vertexBuffer; - -// ... - -mesh.addVertexBuffer(vertexBuffer, 0, - Shaders::Generic3D::Position(), - Shaders::Generic3D::Normal(), - Shaders::Generic3D::TextureCoordinates()); - -Shaders::Phong phong; -// ... -mesh.draw(phong); -@endcode - -@see @ref Generic2D, @ref Generic3D +@see @ref shaders, @ref Generic2D, @ref Generic3D */ #ifndef DOXYGEN_GENERATING_OUTPUT template struct Generic; @@ -66,17 +49,21 @@ template struct Generic { /** * @brief Vertex position * - * Defined as @ref Vector2 in 2D and @ref Vector3 in 3D. + * @ref Vector2 in 2D and @ref Vector3 in 3D. */ typedef Attribute<0, T> Position; - /** @brief 2D texture coordinates */ + /** + * @brief 2D texture coordinates + * + * @ref Vector2. + */ typedef Attribute<1, Vector2> TextureCoordinates; /** * @brief Vertex normal * - * Defined only in 3D. + * @ref Vector3, defined only in 3D. */ typedef Attribute<2, Vector3> Normal; }; diff --git a/src/Magnum/Shaders/MeshVisualizer.h b/src/Magnum/Shaders/MeshVisualizer.h index dd9da04ed..1d19e8342 100644 --- a/src/Magnum/Shaders/MeshVisualizer.h +++ b/src/Magnum/Shaders/MeshVisualizer.h @@ -40,9 +40,12 @@ namespace Magnum { namespace Shaders { /** @brief Mesh visualization shader -Uses geometry shader to visualize wireframe. You need to provide @ref Position -attribute in your triangle mesh and call at least @ref setTransformationProjectionMatrix() -to be able to render. +Uses geometry shader to visualize wireframe of 3D meshes. You need to provide +@ref Position attribute in your triangle mesh and call at least +@ref setTransformationProjectionMatrix() to be able to render. + +@image html shaders-meshvisualizer.png +@image latex shaders-meshvisualizer.png ## Wireframe visualization @@ -63,20 +66,104 @@ you have OpenGL < 3.1 or OpenGL ES 2.0, you need to provide also @requires_es_extension Extension @extension{OES,standard_derivatives} for wireframe rendering. +## Example usage + +### Wireframe visualization with geometry shader (desktop GL) + +Common mesh setup: +@code +struct Vertex { + Vector3 position; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer::Position{}); +@endcode + +Common rendering setup: +@code +Matrix4 transformationMatrix = Matrix4::translation(Vector3::zAxis(-5.0f)); +Matrix4 projectionMatrix = Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f); + +Shaders::MeshVisualizer shader{Shaders::MeshVisualizer::Wireframe}; +shader.setColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setWireframeColor(Color3{0.95f}) + .setViewportSize(defaultFramebuffer.viewport().size()) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(shader); +@endcode + +### Wireframe visualization without geometry shader on older hardware + +You need to provide also the @ref VertexIndex attribute. Mesh setup *in +addition to the above*: +@code +constexpr std::size_t vertexCount = std::extent::value; +Float vertexIndex[vertexCount]; +std::iota(vertexIndex, vertexIndex + vertexCount, 0.0f); + +Buffer vertexIndices; +vertexIndices.setData(vertexIndex, BufferUsage::StaticDraw); + +mesh.addVertexBuffer(vertexIndices, 0, Shaders::MeshVisualizer::VertexIndex{}); +@endcode + +Rendering setup: +@code +Matrix4 transformationMatrix, projectionMatrix; + +Shaders::MeshVisualizer shader{Shaders::MeshVisualizer::Wireframe| + Shaders::MeshVisualizer::NoGeometryShader}; +shader.setColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setWireframeColor(Color3{0.95f}) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(shader); +@endcode + +### Wireframe visualization of indexed meshes without geometry shader + +The vertices must be converted to non-indexed array. Mesh setup: +@code +std::vector indices{ ... }; +std::vector indexedPositions{ ... }; + +// De-indexing the position array +Buffer vertices; +vertices.setData(MeshTools::duplicate(indices, indexedPositions), BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer::Position{}); +@endcode + +Rendering setup the same as above. + +@see @ref shaders @todo Understand and add support wireframe width/smoothness without GS */ class MAGNUM_SHADERS_EXPORT MeshVisualizer: public AbstractShaderProgram { public: - typedef Attribute<0, Vector3> Position; /**< @brief Vertex position */ + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", @ref Vector3. + */ + typedef Attribute<0, Vector3> Position; /** * @brief Vertex index * - * Used only in OpenGL < 3.1 and OpenGL ES 2.0 if @ref Flag::Wireframe - * is enabled. This attribute specifies index of given vertex in - * triangle, i.e. `0` for first, `1` for second, `2` for third. In - * OpenGL 3.1, OpenGL ES 3.0 and newer this value is provided by the - * shader itself, so the attribute is not needed. + * @ref Magnum::Float "Float", used only in OpenGL < 3.1 and OpenGL ES + * 2.0 if @ref Flag::Wireframe is enabled. This attribute (modulo 3) + * specifies index of given vertex in triangle, i.e. `0` for first, `1` + * for second, `2` for third. In OpenGL 3.1, OpenGL ES 3.0 and newer + * this value is provided by the shader itself, so the attribute is not + * needed. */ typedef Attribute<3, Float> VertexIndex; diff --git a/src/Magnum/Shaders/Phong.h b/src/Magnum/Shaders/Phong.h index 63e5348de..91ddf8fbe 100644 --- a/src/Magnum/Shaders/Phong.h +++ b/src/Magnum/Shaders/Phong.h @@ -48,17 +48,108 @@ If you want to use texture instead of color, you need to provide also @ref TextureCoordinates attribute. Pass appropriate flags to constructor and then at render time don't forget to also call appropriate subset of @ref setAmbientTexture(), @ref setDiffuseTexture() and @ref setSpecularTexture(). + +@image html shaders-phong.png +@image latex shaders-phong.png + +## Example usage + +### Colored mesh + +Common mesh setup: +@code +struct Vertex { + Vector3 position; + Vector3 normal; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::Phong::Position{}, + Shaders::Phong::Normal{}); +@endcode + +Common rendering setup: +@code +Matrix4 transformationMatrix = Matrix4::translation(Vector3::zAxis(-5.0f)); +Matrix4 projectionMatrix = Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f); + +Shaders::Phong shader; +shader.setDiffuseColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setShininess(200.0f) + .setLightPosition({5.0f, 5.0f, 7.0f}) + .setTransformationMatrix(transformationMatrix) + .setNormalMatrix(transformationMatrix.rotation()) + .setProjectionMatrix(projectionMatrix); + +mesh.draw(shader); +@endcode + +### Diffuse and specular texture + +Common mesh setup: +@code +struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinates; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::Phong::Position{}, + Shaders::Phong::Normal{}, + Shaders::Phong::TextureCoordinates{}); +@endcode + +Common rendering setup: +@code +Matrix4 transformationMatrix, projectionMatrix; +Texture2D diffuseTexture, specularTexture; + +Shaders::Phong shader{Shaders::Phong::DiffuseTexture| + Shaders::Phong::SpecularTexture}; +shader.setTextures(nullptr, &diffuseTexture, &specularTexture) + .setLightPosition({5.0f, 5.0f, 7.0f}) + .setTransformationMatrix(transformationMatrix) + .setNormalMatrix(transformationMatrix.rotation()) + .setProjectionMatrix(projectionMatrix); + +mesh.draw(shader); +@endcode + +@see @ref shaders */ class MAGNUM_SHADERS_EXPORT Phong: public AbstractShaderProgram { public: - typedef Generic3D::Position Position; /**< @brief Vertex position */ - typedef Generic3D::Normal Normal; /**< @brief Normal direction */ + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", @ref Vector3. + */ + typedef Generic3D::Position Position; + + /** + * @brief Normal direction + * + * @ref shaders-generic "Generic attribute", @ref Vector3. + */ + typedef Generic3D::Normal Normal; /** - * @brief Texture coordinates + * @brief 2D texture coordinates * - * Used only if one of @ref Flag::AmbientTexture, @ref Flag::DiffuseTexture - * or @ref Flag::SpecularTexture is set. + * @ref shaders-generic "Generic attribute", @ref Vector2, used only if + * at least one of @ref Flag::AmbientTexture, @ref Flag::DiffuseTexture + * and @ref Flag::SpecularTexture is set. */ typedef Generic3D::TextureCoordinates TextureCoordinates; @@ -121,7 +212,7 @@ class MAGNUM_SHADERS_EXPORT Phong: public AbstractShaderProgram { * @brief Set ambient color * @return Reference to self (for method chaining) * - * If not set, default value is `(0.0f, 0.0f, 0.0f)`. Has no effect if + * If not set, default value is `{0.0f, 0.0f, 0.0f}`. Has no effect if * @ref Flag::AmbientTexture is set. * @see @ref setAmbientTexture() */ @@ -158,7 +249,7 @@ class MAGNUM_SHADERS_EXPORT Phong: public AbstractShaderProgram { * @brief Set specular color * @return Reference to self (for method chaining) * - * If not set, default value is `(1.0f, 1.0f, 1.0f)`. Has no effect if + * If not set, default value is `{1.0f, 1.0f, 1.0f}`. Has no effect if * @ref Flag::SpecularTexture is set. * @see @ref setSpecularTexture() */ @@ -240,7 +331,7 @@ class MAGNUM_SHADERS_EXPORT Phong: public AbstractShaderProgram { * @brief Set light color * @return Reference to self (for method chaining) * - * If not set, default value is `(1.0f, 1.0f, 1.0f)`. + * If not set, default value is `{1.0f, 1.0f, 1.0f}`. */ Phong& setLightColor(const Color3& color) { setUniform(lightColorUniform, color); diff --git a/src/Magnum/Shaders/Vector.h b/src/Magnum/Shaders/Vector.h index 196b13366..13c69662a 100644 --- a/src/Magnum/Shaders/Vector.h +++ b/src/Magnum/Shaders/Vector.h @@ -43,8 +43,47 @@ namespace Magnum { namespace Shaders { Renders vector art in plain grayscale form. See also @ref DistanceFieldVector for more advanced effects. For rendering unchanged texture you can use the -@ref Flat shader. -@see @ref Vector2D, @ref Vector3D +@ref Flat shader. You need to provide @ref Position and @ref TextureCoordinates +attributes in your triangle mesh and call at least +@ref setTransformationProjectionMatrix(), @ref setColor() and +@ref setVectorTexture(). + +@image html shaders-vector.png +@image latex shaders-vector.png + +## Example usage + +Common mesh setup: +@code +struct Vertex { + Vector2 position; + Vector2 textureCoordinates; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::Vector2D::Position{}, + Shaders::Vector2D::TextureCoordinates{}); +@endcode + +Common rendering setup: +@code +Matrix3 transformationMatrix, projectionMatrix; +Texture2D texture; + +Shaders::Vector2D shader; +shader.setColor(Color3::fromHSV(216.0_degf, 0.85f, 1.0f)) + .setVectorTexture(texture) + .setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(shader); +@endcode + +@see @ref shaders, @ref Vector2D, @ref Vector3D */ template class MAGNUM_SHADERS_EXPORT Vector: public AbstractVector { public: diff --git a/src/Magnum/Shaders/VertexColor.h b/src/Magnum/Shaders/VertexColor.h index b7b8aafbd..97881ec57 100644 --- a/src/Magnum/Shaders/VertexColor.h +++ b/src/Magnum/Shaders/VertexColor.h @@ -41,15 +41,60 @@ namespace Magnum { namespace Shaders { /** @brief Vertex color shader -Draws vertex-colored mesh. -@see @ref VertexColor2D, @ref VertexColor3D +Draws vertex-colored mesh. You need to provide @ref Position and @ref Color +attributes in your triangle mesh and call at least +@ref setTransformationProjectionMatrix(). + +@image html shaders-vertexcolor.png +@image latex shaders-vertexcolor.png + +## Example usage + +Common mesh setup: +@code +struct Vertex { + Vector3 position; + Color3 color; +}; +Vertex data[] = { ... }; + +Buffer vertices; +vertices.setData(data, BufferUsage::StaticDraw); + +Mesh mesh; +mesh.addVertexBuffer(vertices, 0, + Shaders::VertexColor3D::Position{}, + Shaders::VertexColor3D::Color{}); +@endcode + +Common rendering setup: +@code +Matrix4 transformationMatrix = Matrix4::translation(Vector3::zAxis(-5.0f)); +Matrix4 projectionMatrix = Matrix4::perspectiveProjection(35.0_degf, 1.0f, 0.001f, 100.0f); + +Shaders::VertexColor3D shader; +shader.setTransformationProjectionMatrix(projectionMatrix*transformationMatrix); + +mesh.draw(shader); +@endcode + +@see @ref shaders, @ref VertexColor2D, @ref VertexColor3D */ template class MAGNUM_SHADERS_EXPORT VertexColor: public AbstractShaderProgram { public: - /** @brief Vertex position */ + /** + * @brief Vertex position + * + * @ref shaders-generic "Generic attribute", @ref Vector2 in 2D, + * @ref Vector3 in 3D. + */ typedef typename Generic::Position Position; - /** @brief Vertex color */ + /** + * @brief Vertex color + * + * @ref shaders-generic "Generic attribute", @ref Vector3. + */ typedef Attribute<3, Color3> Color; explicit VertexColor();