mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
20 KiB
381 lines
20 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz> |
|
|
|
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. |
|
|
|
@m_keywords{Shaders} |
|
|
|
@tableofcontents |
|
@m_footernavigation |
|
|
|
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: |
|
|
|
- @ref Shaders::FlatGL "Shaders::FlatGL*D" --- flat shading using single |
|
color or texture |
|
- @ref Shaders::VectorGL "Shaders::VectorGL*D" --- colored vector graphics |
|
- @ref Shaders::DistanceFieldVectorGL "Shaders::DistanceFieldVectorGL*D" -- |
|
colored and outlined vector graphics |
|
- @ref Shaders::VertexColorGL "Shaders::VertexColorGL*D" --- vertex-colored |
|
meshes |
|
- @ref Shaders::PhongGL --- Phong shading using colors or textures, 3D only |
|
- @ref Shaders::MeshVisualizerGL2D / @ref Shaders::MeshVisualizerGL3D --- |
|
wireframe visualization |
|
|
|
The essential functionality of builtin shaders can be used even on unextended |
|
OpenGL 2.1 and OpenGL ES 2.0 / WebGL 1.0, but the code will try to use the most |
|
recent technology available to have them as efficient as possible on every |
|
configuration. Some functionality, such as uniform buffers, texture arrays or |
|
object ID rendering, requires newer versions or extensions, as noted in |
|
documentation of a particular feature. |
|
|
|
@section shaders-usage Usage |
|
|
|
Shader usage is divided into two parts: describing vertex attributes in the |
|
mesh and setting up the shader itself. |
|
|
|
Each shader expects some set of vertex attributes, thus when adding a vertex |
|
buffer into the mesh, you need to specify which shader attributes are on which |
|
position in the buffer. See @ref GL::Mesh::addVertexBuffer() for details and |
|
usage examples. Example mesh configuration for the @ref Shaders::PhongGL |
|
shader: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-setup |
|
|
|
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. For uniforms |
|
there's two different workflows --- a classical one, where uniforms have |
|
immediate setters, and a uniform buffer workflow, where the uniform parameters |
|
are saved to a structure and then uploaded to a GPU buffer. Let's compare both |
|
approaches: |
|
|
|
@subsection shaders-usage-classic Using classic uniforms |
|
|
|
The most straightforward and portable way, working even on old OpenGL ES 2.0 |
|
and WebGL 1.0 platforms, is using classic uniform setters. All shader uniforms |
|
have a reasonable defaults so you are able to see at least something when using |
|
the shader directly without any further configuration, but in most cases you |
|
may want to specify at least the transformation/projection matrices. |
|
Example configuration and rendering using @link Shaders::PhongGL @endlink --- |
|
by default it's just colored and uses a single light, and we set a color of |
|
both in addition to transformation, projection and normal matrices: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-classic |
|
|
|
@subsection shaders-usage-ubo Using uniform buffers |
|
|
|
Uniform buffers require GL 3.1, OpenGL ES 3.0 or WebGL 2.0 and are more verbose |
|
to set up, but when used the right way they can result in greatly reduced |
|
driver overhead. Uniform buffers get enabled using the |
|
@relativeref{Shaders::PhongGL,Flag::UniformBuffers} flag that's implemented for |
|
all builtin shaders, and after that you're not supposed to use most of the |
|
`set*()` APIs anymore, instead you have to fill uniform structures, upload them |
|
to @ref GL::Buffer instances and then bind those via various `bind*Buffer()` |
|
APIs. To simplify porting, documentation of each classic uniform setter lists |
|
the equivalent uniform buffer APIs. |
|
|
|
Because some parameters such as projection, material or light setup don't |
|
change every draw, they are organized into buffers based on expected frequency |
|
of change. This way you can fill the projection and material buffers just once |
|
at the start, light setup only when the camera position changes and with much |
|
less to upload for every draw. The separation is also done in a way that makes |
|
it possible to reuse projection/transformation data among different shaders, |
|
e.g. for a depth pre-pass. |
|
|
|
In the following example, projection and transformation parameters are supplied |
|
via generic shader-independent @ref Shaders::ProjectionUniform3D and |
|
@ref Shaders::TransformationUniform3D structures and Phong-specific parameters |
|
then via @ref Shaders::PhongDrawUniform, @ref Shaders::PhongMaterialUniform and |
|
@ref Shaders::PhongLightUniform structures. While the structures expose the |
|
fields directly, the data layout may be non-trivial and it's thus recommended |
|
to use the setters unless they prove to be a performance bottleneck: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-ubo |
|
|
|
Altogether, this results in the same output as in the classic uniform case |
|
shown above. Similarly to the classic uniforms, default-constructed structures |
|
have reasonable defaults to make the shader render at least something, but note |
|
that you *have to* bind the buffer to get the defaults, without a buffer bound |
|
you'll get a fully black mesh at best and nothing rendered at all in the worst |
|
cases. |
|
|
|
@m_class{m-block m-success} |
|
|
|
@par Importance of buffer usage and storage flags |
|
With uniform buffers, it's very important what kind of memory gets used for |
|
the backing storage. In the above snippets, the (implicit) |
|
@ref GL::BufferUsage::StaticDraw got used for simplicity, but for data that |
|
are changed for every draw it could make sense to pick |
|
@ref GL::BufferUsage::DynamicDraw instead. |
|
@par |
|
The most ideal way may be to use @gl_extension{ARB,buffer_storage} |
|
from OpenGL 4.4, and instead of @ref GL::Buffer::setData() calling |
|
@relativeref{GL::Buffer,setStorage()} directly with the uniform data and |
|
with empty @ref GL::Buffer::StorageFlags, which makes the buffer immutable. |
|
Updating such immutable buffer can be still done via |
|
@ref GL::Buffer::copy() from another buffer, but setting or mapping the |
|
data from the CPU side won't be possible. |
|
@par |
|
As with everything, be sure to profile and pick the best workflow for your |
|
target platform --- what's best for desktop may not be the best in WebGL, |
|
and what works with WebGL running on top GL may not be the best with WebGL |
|
that's itself implemented using D3D or Metal. |
|
|
|
@subsection shaders-usage-multidraw Multidraw and reducing driver overhead |
|
|
|
The main advantage of uniform buffers is the ability to specify data for |
|
multiple draws together --- after all, having to reupload three or four buffers |
|
for every draw like shown above wouldn't be really faster or easier than |
|
setting the uniforms directly. On the other hand, uploading everything first |
|
and binding a different subrange each time would avoid the reupload, but since |
|
most drivers have uniform buffer alignment requirement as high as 256 bytes |
|
(@ref GL::Buffer::uniformOffsetAlignment()), the per-draw buffers would have to |
|
be very sparse. |
|
|
|
Instead, it's possible to construct the shaders with a statically defined |
|
draw count, fill the buffers with data for that many draws at once and then use |
|
@relativeref{Shaders::PhongGL,setDrawOffset()} to pick concrete per-draw |
|
parameters. Since material parameters are commonly shared among multiple draws, |
|
the desired usage is to upload unique materials and then reference them via a |
|
@ref Shaders::PhongDrawUniform::materialId "Shaders::*DrawUniform::materialId". |
|
The following snippet shows drawing three different meshes, where two of them |
|
share the same material definition. The projection and light buffer is the same |
|
as above: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-multi |
|
|
|
While this minimizes the state changes to just a single immediate uniform being |
|
changed between draws, it's possible to go even further by using |
|
@ref GL::MeshView instances onto a single @ref GL::Mesh instead of several |
|
different @ref GL::Mesh objects --- that way the attribute layout doesn't need |
|
to be updated and it's just submitting draws with different offsets and counts. |
|
|
|
Finally, with mesh views and on platforms that support @gl_extension{ARB,shader_draw_parameters} from OpenGL 4.6 or the |
|
@gl_extension{ANGLE,multi_draw} / @webgl_extension{WEBGL,multi_draw} ES and |
|
WebGL extension, it's possible to directly submit a multi-draw command. The |
|
shader needs to have @relativeref{Shaders::PhongGL,Flag::MultiDraw} enabled, |
|
which will make it use the @glsl gl_DrawID @ce builtin to pick the per-draw |
|
parameters on its own. The above snippet modified for multidraw would then look |
|
like this, uniform upload and binding is the same as before: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-multidraw |
|
|
|
<b></b> |
|
|
|
@m_class{m-block m-warning} |
|
|
|
@par Uniform buffer size limits |
|
Note that size of a single uniform buffer that can be bound to a shader is |
|
quite limited (@ref GL::AbstractShaderProgram::maxUniformBlockSize(), |
|
usually just 16 or 64 kB), and another reason the parameters are separated |
|
and deduplicated among several buffers is to maximize use of that memory. |
|
With that you should always be able to submit at least 256 draws at once as |
|
the biggest per-draw uniform structure used by builtin shaders has a size |
|
of a 4x4 matrix. See documentation of |
|
@ref Shaders::FlatGL::Configuration::setDrawCount() "Shaders::*::Configuration::setDrawCount()" |
|
and related APIs of a particular shader for concrete limits. |
|
@par |
|
For larger batches the expected workflow is to still upload everything at |
|
once but then bind and draw smaller (and properly aligned) subranges that |
|
fit into the limit. For convenience, all uniform structures are guaranteed |
|
to fit evenly into multiples of 768 bytes, which should be large enough for |
|
even the strictest @ref GL::Buffer::uniformOffsetAlignment() requirements. |
|
@par |
|
An alternative solution to overcome the size limits is to enable @relativeref{Shaders::PhongGL,Flag::ShaderStorageBuffers} instead |
|
(available on OpenGL 4.3+ and OpenGL ES 3.1+), which have no size limits. |
|
The cost is narrower platform support and potentially slower access |
|
compared to uniform buffers. |
|
|
|
@subsection shaders-usage-instancing Instancing |
|
|
|
@ref Shaders::FlatGL and @ref Shaders::PhongGL support instancing, which allows |
|
them to render the same mesh several times but with different transformation |
|
and material applied. It can be thought of as a more constrained variant of the |
|
multidraw mentioned above, but instead of uniform buffers the per-instance |
|
parameters are passed through instanced mesh attributes. |
|
|
|
No uniform buffer requirement means this feature can be used even on OpenGL ES |
|
2.0 and WebGL 1.0 targets if corresponding instancing extensions are available. |
|
Using attributes instead of uniform buffers also means there's no limitation on |
|
how many instances can be drawn at once, on the other hand a mesh can have only |
|
a certain amount of attribute bindings and thus only the basic properties can |
|
be specified per-instance such as the transformation matrix or base color. |
|
|
|
The following snippet shows a setup similar to the multidraw above, except that |
|
it's just the same sphere drawn three times in different locations and with a |
|
different material applied. Note that the per-instance color is achieved by |
|
using the usual vertex color attribute, only instanced: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-instancing |
|
|
|
@subsection shaders-usage-skinning Skinning |
|
|
|
@ref Shaders::FlatGL, @ref Shaders::MeshVisualizerGL3D "Shaders::MeshVisualizerGL*D" |
|
and @ref Shaders::PhongGL are capable of rendering skinned meshes. Such meshes |
|
are commonly imported from files such as glTF or FBX together with the skeleton |
|
hierarchy and associated animations. The following snippet shows compiling a |
|
@ref Trade::MeshData to a @ref GL::Mesh using @ref MeshTools::compile(), using |
|
@ref Trade::SkinData and @ref MeshTools::compiledPerVertexJointCount() to |
|
set up shader parameters and finally uploading calculated joint matrices to |
|
perform the skinning animation: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-skinning |
|
|
|
The above hardcodes the joint counts in the shader, which makes it the most |
|
optimal for rendering given mesh. However, with multiple skinned meshes it'd |
|
mean having a dedicated shader instance tailored for each. To avoid that, you |
|
can set the joint count and per-vertex joint count to the maximum that the |
|
meshes would need, enable @ref Shaders::PhongGL::Flag::DynamicPerVertexJointCount "Flag::DynamicPerVertexJointCount", |
|
for a particular draw upload just a subset of joint matrices the mesh would |
|
reference and set the count of actually used per-vertex joints via |
|
@ref Shaders::PhongGL::setPerVertexJointCount(). Thus, compared to above: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-skinning-dynamic |
|
|
|
@subsection shaders-usage-textures Using textures |
|
|
|
Unless the shader requires a texture to work (which is the case of |
|
@ref Shaders::VectorGL and @ref Shaders::DistanceFieldVectorGL), by default all |
|
shaders are just colored. Enabling a texture is done via a flag (such as |
|
@ref Shaders::PhongGL::Flag::DiffuseTexture) and then the texture is bound via |
|
an appropriate `bind*Texture()` call. In most cases the texture value is |
|
multiplied with the corresponding color uniform. |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-textures |
|
|
|
All shaders that support textures are also able to apply arbitrary |
|
transformation to the texture coordinate attribute by enabling |
|
@relativeref{Shaders::PhongGL,Flag::TextureTransformation} on a particular |
|
shader. Desired transformation is then supplied via |
|
@relativeref{Shaders::PhongGL,setTextureMatrix()} (or a |
|
@ref Shaders::TextureTransformationUniform in case uniform buffers are used). |
|
This can be useful for animations, when you have a larger atlas with switchable |
|
texture variations for a single mesh, or when you have texture coordinates |
|
quantized in some nontrivial way. |
|
|
|
Texture transformation is also useful in the |
|
@ref shaders-usage-multidraw "multidraw" or |
|
@ref shaders-usage-instancing "instancing" scenarios mentioned above, since |
|
each draw will most likely require a different texture. There are two options: |
|
|
|
- Upload the textures to subrectangles of a larger @ref GL::Texture2D and |
|
then specify @ref Shaders::TextureTransformationUniform::offset and |
|
@relativeref{Shaders::TextureTransformationUniform,rotationScaling} for |
|
each draw, or in case of an instanced draw supply an instanced |
|
@relativeref{Shaders::PhongGL,TextureOffset} attribute and have a global |
|
scale set for all instanced via |
|
@relativeref{Shaders::PhongGL,setTextureMatrix()}. |
|
- Enable @relativeref{Shaders::PhongGL,Flag::TextureArrays} in the shader |
|
(not available on OpenGL ES 2.0 or WebGL 1.0), upload the textures to |
|
slices of a @ref GL::Texture2DArray and specify |
|
@ref Shaders::TextureTransformationUniform::layer for each draw, or in case |
|
of an instanced draw supply a layer in an instanced |
|
@relativeref{Shaders::PhongGL,TextureOffsetLayer} attribute. |
|
|
|
While with a @ref GL::Texture2D you may hit texture size limits (not to mention |
|
you possible issues with materials that relied on a certain wrapping mode), |
|
@ref GL::Texture2DArray is generally able to contain a lot more data, however |
|
all slices have to be of the same size. You can also combine the two approaches |
|
and pack differently sized textures to slices of a texture array and then set |
|
both offset/scale and a layer per-draw. |
|
|
|
The following snippet shows a multi-draw setup with a different texture array |
|
layer used by each draw. While the projection, transformation, draw material |
|
and light buffers are the same as before, there's a new per-draw |
|
@ref Shaders::TextureTransformationUniform buffer supplying the layer |
|
information: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-texture-arrays |
|
|
|
While the primary use case of texture arrays is with uniform buffers and |
|
multidraw, they work in the classic uniform workflow as well --- use |
|
@relativeref{Shaders::PhongGL,setTextureLayer()} there instead. |
|
|
|
@section shaders-async Async shader compilation and linking |
|
|
|
By default, shaders are compiled and linked directly in their constructor. |
|
While that's convenient and easy to use, applications using heavier shaders, |
|
many shader combinations or running on platforms that translate GLSL to other |
|
APIs such as HLSL or MSL, may spend a significant portion of their startup |
|
time just on shader compilation and linking. |
|
|
|
To mitigate this problem, shaders can be compiled in an asynchronous way. |
|
Depending on the driver and system, this can mean that for example eight |
|
shaders get compiled at the same time in eight parallel threads, instead of |
|
sequentially one after another. To achieve such parallelism, the construction |
|
needs to be broken into two parts --- first submitting compilation of all |
|
shaders using @ref Shaders::FlatGL::compile() "Shaders::*GL::compile()", |
|
forming temporary @ref Shaders::FlatGL::CompileState "Shaders::*GL::CompileState" |
|
instances, then possibly doing other work until it's completed, and finally |
|
constructing final shader instances out of the temporary state: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-async |
|
|
|
The above code will work correctly also on drivers that implement async |
|
compilation partially or not at all --- there |
|
@ref GL::AbstractShaderProgram::isLinkFinished() will implicitly return |
|
@cpp true @ce, and the final construction will stall if it happens before a |
|
(potentially async) compilation is finished. See also the |
|
@ref GL-AbstractShaderProgram-async "GL::AbstractShaderProgram documentation" |
|
for more information. |
|
|
|
@section shaders-generic Generic vertex attributes and framebuffer attachments |
|
|
|
Many shaders share the same vertex attribute definitions, such as positions, |
|
normals, texture coordinates etc. It's thus possible to configure the mesh |
|
for a *generic* shader and then render it with any compatible shader. |
|
Definition of all generic attributes is available in the |
|
@ref Shaders::GenericGL class. Setup of the mesh @ref shaders-usage "shown above" |
|
using generic attributes could then look like this: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-generic |
|
|
|
Note that in this particular case both setups are equivalent, because |
|
@ref Shaders::PhongGL attribute definitions are just aliases to the generic |
|
ones. Then you can render the mesh using the @ref Shaders::PhongGL shader like |
|
above, or use for example @ref Shaders::FlatGL3D or even |
|
@ref Shaders::MeshVisualizerGL3D with the same mesh reconfiguration. The unused |
|
attributes will be simply ignored. |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-meshvisualizer |
|
|
|
The @ref MeshTools::compile() utility configures meshes using generic vertex |
|
attribute definitions to make them usable with any builtin shader. |
|
|
|
Besides vertex attributes, @ref Shaders::GenericGL contains generic definitions |
|
for framebuffer outputs as well --- in many cases a shader has just one (color) |
|
output, but some shaders such as @ref Shaders::FlatGL or @ref Shaders::PhongGL |
|
offer an object ID output as well. A setup equivalent to what's done in Flat |
|
shader's @ref Shaders-FlatGL-object-id but using the generic definitions would |
|
look like this: |
|
|
|
@snippet MagnumShaders-gl.cpp shaders-generic-object-id |
|
*/ |
|
}
|
|
|