| 1 | // Copyright (C) 2019 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
| 3 | |
| 4 | #include "qquick3dcustommaterial_p.h" |
| 5 | #include <QtQuick3DRuntimeRender/private/qssgrendercustommaterial_p.h> |
| 6 | #include <ssg/qssgrendercontextcore.h> |
| 7 | #include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h> |
| 8 | #include <QtQuick/QQuickWindow> |
| 9 | |
| 10 | #include "qquick3dobject_p.h" |
| 11 | #include "qquick3dviewport_p.h" |
| 12 | #include "qquick3dscenemanager_p.h" |
| 13 | |
| 14 | QT_BEGIN_NAMESPACE |
| 15 | |
| 16 | /*! |
| 17 | \qmltype CustomMaterial |
| 18 | \inherits Material |
| 19 | \inqmlmodule QtQuick3D |
| 20 | \brief Base component for creating custom materials used to shade models. |
| 21 | |
| 22 | The custom material allows using custom shader code for a material, enabling |
| 23 | programmability on graphics shader level. A vertex, fragment, or both |
| 24 | shaders can be provided. The \l vertexShader and \l fragmentShader |
| 25 | properties are URLs, referencing files containing shader snippets, and work |
| 26 | very similarly to ShaderEffect or \l{Image::source}{Image.source}. Only the |
| 27 | \c file and \c qrc schemes are supported with custom materials. It is also |
| 28 | possible to omit the \c file scheme, allowing to specify a relative path in |
| 29 | a convenient way. Such a path is resolved relative to the component's (the |
| 30 | \c{.qml} file's) location. |
| 31 | |
| 32 | For a getting started guide to custom materials, see the page \l{Programmable |
| 33 | Materials, Effects, Geometry, and Texture data}. |
| 34 | |
| 35 | \section1 Introduction |
| 36 | |
| 37 | Consider the following versions of the same scene. On the left, the cylinder |
| 38 | is using a built-in, non-programmable material. Such materials are |
| 39 | configurable through a wide range of properties, but there is no further |
| 40 | control given over the shaders that are generated under the hood. On the |
| 41 | right, the same cylinder is now associated with a CustomMaterial referencing |
| 42 | application-provided vertex and fragment shader snippets. This allows |
| 43 | inserting custom, application-specific logic into the vertex shader to |
| 44 | transform the geometry, and to determine certain color properties in a |
| 45 | custom manner in the fragment shader. As this is a |
| 46 | \l{shadingMode}{shaded} custom material, the cylinder still |
| 47 | participates in the scene lighting normally. |
| 48 | |
| 49 | \table 70% |
| 50 | \row |
| 51 | \li \qml |
| 52 | View3D { |
| 53 | anchors.fill: parent |
| 54 | PerspectiveCamera { |
| 55 | id: camera |
| 56 | position: Qt.vector3d(0, 0, 600) |
| 57 | } |
| 58 | camera: camera |
| 59 | DirectionalLight { |
| 60 | position: Qt.vector3d(-500, 500, -100) |
| 61 | color: Qt.rgba(0.2, 0.2, 0.2, 1.0) |
| 62 | ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0) |
| 63 | } |
| 64 | Model { |
| 65 | source: "#Cylinder" |
| 66 | eulerRotation: Qt.vector3d(30, 30, 0) |
| 67 | scale: Qt.vector3d(1.5, 1.5, 1.5) |
| 68 | materials: [ |
| 69 | DefaultMaterial { |
| 70 | diffuseColor: Qt.rgba(0, 1, 0, 1) |
| 71 | } |
| 72 | ] |
| 73 | } |
| 74 | } |
| 75 | \endqml |
| 76 | \li \qml |
| 77 | View3D { |
| 78 | anchors.fill: parent |
| 79 | PerspectiveCamera { |
| 80 | id: camera |
| 81 | position: Qt.vector3d(0, 0, 600) |
| 82 | } |
| 83 | camera: camera |
| 84 | DirectionalLight { |
| 85 | position: Qt.vector3d(-500, 500, -100) |
| 86 | color: Qt.rgba(0.2, 0.2, 0.2, 1.0) |
| 87 | ambientColor: Qt.rgba(0.1, 0.1, 0.1, 1.0) |
| 88 | } |
| 89 | Model { |
| 90 | source: "#Cylinder" |
| 91 | eulerRotation: Qt.vector3d(30, 30, 0) |
| 92 | scale: Qt.vector3d(1.5, 1.5, 1.5) |
| 93 | materials: [ |
| 94 | CustomMaterial { |
| 95 | vertexShader: "material.vert" |
| 96 | fragmentShader: "material.frag" |
| 97 | property real uTime |
| 98 | property real uAmplitude: 50 |
| 99 | NumberAnimation on uTime { from: 0; to: 100; duration: 10000; loops: -1 } |
| 100 | } |
| 101 | ] |
| 102 | } |
| 103 | } |
| 104 | \endqml |
| 105 | \endtable |
| 106 | |
| 107 | Let's assume that the shader snippets in \c{material.vert} and \c{material.frag} are |
| 108 | the following: |
| 109 | |
| 110 | \table 70% |
| 111 | \row |
| 112 | \li \badcode |
| 113 | void MAIN() |
| 114 | { |
| 115 | VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude; |
| 116 | } |
| 117 | \endcode |
| 118 | \li \badcode |
| 119 | void MAIN() |
| 120 | { |
| 121 | BASE_COLOR = vec4(0.0, 1.0, 0.0, 1.0); |
| 122 | } |
| 123 | \endcode |
| 124 | \endtable |
| 125 | |
| 126 | Notice how \c uTime and \c uAmplitude are properties of the CustomMaterial |
| 127 | element. They can change values and get animated normally, the values will |
| 128 | be exposed to the shaders automatically without any further action from the |
| 129 | developer. |
| 130 | |
| 131 | The result is a cylinder that animates its vertices: |
| 132 | |
| 133 | \image custommaterial_cylinder.png |
| 134 | |
| 135 | \section1 Two flavors of custom materials |
| 136 | |
| 137 | There are two main types of custom materials. This is specified by the \l |
| 138 | shadingMode property. In \l{CustomMaterial::shadingMode}{unshaded} custom |
| 139 | materials the fragment shader outputs a single \c vec4 color, ignoring |
| 140 | lights, light probes, shadowing in the scene. In |
| 141 | \l{CustomMaterial::shadingMode}{shaded} materials the shader is expected to |
| 142 | implement certain functions and work with built-in variables to take |
| 143 | lighting and shadow contribution into account. |
| 144 | |
| 145 | The default choice is typically a shaded material, this is reflected in the |
| 146 | default value of the \l shadingMode property. This fits materials that needs |
| 147 | to transform vertices or other incoming data from the geometry, or determine |
| 148 | values like \c BASE_COLOR or \c EMISSIVE_COLOR in a custom manner, perhaps |
| 149 | by sampling \c SCREEN_TEXTURE or \c DEPTH_TEXTURE, while still reciving |
| 150 | light and shadow contributions from the scene. Additionally, such materials |
| 151 | can also override and reimplement the equations used to calculate the |
| 152 | contributions from directional, point, and other lights. The |
| 153 | application-provided shader snippets are heavily amended by the Qt Quick 3D |
| 154 | engine under the hood, in order to provide the features, such as lighting, |
| 155 | the standard materials have. |
| 156 | |
| 157 | Unshaded materials are useful when the object's appearance is determined |
| 158 | completely by the custom shader code. The shaders for such materials |
| 159 | receive minimal additions by the engine, and therefore it is completely up |
| 160 | to the shader to determine the final fragment color. This gives more |
| 161 | freedom, but also limits possiblities to integrate with other elements of |
| 162 | the scene, such as lights. |
| 163 | |
| 164 | \note Shader code is always provided using Vulkan-style GLSL, regardless of |
| 165 | the graphics API used by Qt at run time. |
| 166 | |
| 167 | \note The vertex and fragment shader code provided by the material are not |
| 168 | full, complete GLSL shaders on their own. Rather, they provide a set of |
| 169 | functions, which are then amended with further shader code by the engine. |
| 170 | |
| 171 | \section1 Exposing data to the shaders |
| 172 | |
| 173 | The dynamic properties of the CustomMaterial can be changed and animated |
| 174 | using QML and Qt Quick facilities, and the values are exposed to the |
| 175 | shaders automatically. This in practice is very similar ShaderEffect. The |
| 176 | following list shows how properties are mapped: |
| 177 | |
| 178 | \list |
| 179 | \li bool, int, real -> bool, int, float |
| 180 | \li QColor, \l{QtQml::Qt::rgba()}{color} -> vec4, and the color gets |
| 181 | converted to linear, assuming sRGB space for the color value specified in |
| 182 | QML. The built-in Qt colors, such as \c{"green"} are in sRGB color space as |
| 183 | well, and the same conversion is performed for all color properties of |
| 184 | DefaultMaterial and PrincipledMaterial, so this behavior of CustomMaterial |
| 185 | matches those. Unlike Qt Quick, for Qt Quick 3D linearizing is essential as |
| 186 | there will typically be tonemapping performed on the 3D scene. |
| 187 | \li QRect, QRectF, \l{QtQml::Qt::rect()}{rect} -> vec4 |
| 188 | \li QPoint, QPointF, \l{QtQml::Qt::point()}{point}, QSize, QSizeF, \l{QtQml::Qt::size()}{size} -> vec2 |
| 189 | \li QVector2D, \l{QtQml::Qt::vector2d()}{vector2d} -> vec2 |
| 190 | \li QVector3D, \l{QtQml::Qt::vector3d()}{vector3d} -> vec3 |
| 191 | \li QVector4D, \l{QtQml::Qt::vector4d()}{vector4d} -> vec4 |
| 192 | \li QMatrix4x4, \l{QtQml::Qt::matrix4x4()}{matrix4x4} -> mat4 |
| 193 | \li QQuaternion, \l{QtQml::Qt::quaternion()}{quaternion} -> vec4, scalar value is \c w |
| 194 | |
| 195 | \li TextureInput -> sampler2D or samplerCube, depending on whether \l |
| 196 | Texture or \l CubeMapTexture is used in the texture property of the |
| 197 | TextureInput. Setting the \l{TextureInput::enabled}{enabled} property to |
| 198 | false leads to exposing a dummy texture to the shader, meaning the shaders |
| 199 | are still functional but will sample a texture with opaque black image |
| 200 | content. Pay attention to the fact that properties for samplers must always |
| 201 | reference a \l TextureInput object, not a \l Texture directly. When it |
| 202 | comes to the \l Texture properties, the source, tiling, and filtering |
| 203 | related ones are the only ones that are taken into account implicitly with |
| 204 | custom materials, as the rest (such as, UV transformations) is up to the |
| 205 | custom shaders to implement as they see fit. |
| 206 | |
| 207 | \endlist |
| 208 | |
| 209 | \note When a uniform referenced in the shader code does not have a |
| 210 | corresponding property, it will cause a shader compilation error when |
| 211 | processing the material at run time. There are some exceptions to this, |
| 212 | such as, sampler uniforms, that get a dummy texture bound when no |
| 213 | corresponding QML property is present, but as a general rule, all uniforms |
| 214 | and samplers must have a corresponding property declared in the |
| 215 | CustomMaterial object. |
| 216 | |
| 217 | \section1 Unshaded custom materials |
| 218 | |
| 219 | The following is an example of an \l{CustomMaterial::shadingMode}{unshaded} |
| 220 | custom material. |
| 221 | |
| 222 | \qml |
| 223 | CustomMaterial { |
| 224 | // These properties are automatically exposed to the shaders |
| 225 | property real time: 0.0 |
| 226 | property real amplitude: 5.0 |
| 227 | property real alpha: 1.0 |
| 228 | property TextureInput tex: TextureInput { |
| 229 | enabled: true |
| 230 | texture: Texture { source: "image.png" } |
| 231 | } |
| 232 | |
| 233 | shadingMode: CustomMaterial.Unshaded |
| 234 | sourceBlend: alpha < 1.0 ? CustomMaterial.SrcAlpha : CustomMaterial.NoBlend |
| 235 | destinationBlend: alpha < 1.0 ? CustomMaterial.OneMinusSrcAlpha : CustomMaterial.NoBlend |
| 236 | cullMode: CustomMaterial.BackFaceCulling |
| 237 | |
| 238 | vertexShader: "customshader.vert" |
| 239 | fragmentShader: "customshader.frag" |
| 240 | } |
| 241 | \endqml |
| 242 | |
| 243 | With the above example, the \l{CustomMaterial::shadingMode}{unshaded} vertex |
| 244 | and fragment shaders snippets could look like the following. Note how the |
| 245 | shaders do not, and must not, declare uniforms or vertex inputs as that is |
| 246 | taken care of by Qt when assembling the final shader code. |
| 247 | |
| 248 | \badcode |
| 249 | VARYING vec3 pos; |
| 250 | VARYING vec2 texcoord; |
| 251 | |
| 252 | void MAIN() |
| 253 | { |
| 254 | pos = VERTEX; |
| 255 | pos.x += sin(time * 4.0 + pos.y) * amplitude; |
| 256 | texcoord = UV0; |
| 257 | POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0); |
| 258 | } |
| 259 | \endcode |
| 260 | |
| 261 | \badcode |
| 262 | VARYING vec3 pos; |
| 263 | VARYING vec2 texcoord; |
| 264 | |
| 265 | void MAIN() |
| 266 | { |
| 267 | vec4 c = texture(tex, texcoord); |
| 268 | FRAGCOLOR = vec4(pos.x * 0.02, pos.y * 0.02, pos.z * 0.02, alpha) * c; |
| 269 | } |
| 270 | \endcode |
| 271 | |
| 272 | The following special, uppercase keywords are available: |
| 273 | |
| 274 | \list |
| 275 | |
| 276 | \li MAIN -> the name of the entry point in the vertex or fragment shader |
| 277 | snippet must always be \c MAIN. Providing this function is mandatory in |
| 278 | shader snippets for unshaded custom materials. |
| 279 | |
| 280 | \li VARYING -> declares an output from the vertex shader or an input to the |
| 281 | fragment shader |
| 282 | |
| 283 | \li POSITION -> vec4, the output from the vertex shader |
| 284 | |
| 285 | \li FRAGCOLOR -> vec4, the output from the fragment shader. Available only |
| 286 | for unshaded custom materials. |
| 287 | |
| 288 | \li VERTEX -> vec3, the vertex position in the vertex shader. |
| 289 | |
| 290 | \li NORMAL -> vec3, the vertex normal in the vertex shader. When the mesh |
| 291 | for the associated model does not provide normals, the value is vec3(0.0). |
| 292 | |
| 293 | \li UV0 -> vec2, the first set of texture coordinates in the vertex shader. |
| 294 | When the mesh for the associated model does not provide texture |
| 295 | coordinates, the value is vec2(0.0). |
| 296 | |
| 297 | \li UV1 -> vec2, the second set of texture coordinates in the vertex |
| 298 | shader. When the mesh for the associated model does not provide a second |
| 299 | set of texture coordinates, the value is vec2(0.0). |
| 300 | |
| 301 | \li COLOR -> vec4, the vertex color in the vertex shader. When the mesh for |
| 302 | the associated model does not provide per-vertex colors, the value is |
| 303 | vec4(1.0). |
| 304 | |
| 305 | \li TANGENT -> vec3, tangent in the vertex shader. When the mesh for the |
| 306 | associated model does not provide tangent data, the value is vec3(0.0). |
| 307 | |
| 308 | \li BINORMAL -> vec3, binormal in the vertex shader. When the mesh for the |
| 309 | associated model does not provide binormal data, the value is vec3(0.0). |
| 310 | |
| 311 | \li JOINTS -> ivec4, joint indexes in the vertex shader. When the mesh for |
| 312 | the associated model does not provide joint indexes data, the value is |
| 313 | ivec4(0). |
| 314 | |
| 315 | \li WEIGHTS -> vec4, joint weights in the vertex shader. When the mesh for |
| 316 | the associated model does not provide joint weights data, the value is |
| 317 | vec4(0.0). |
| 318 | |
| 319 | \li MORPH_POSITION(\e{n}) -> vec3, the \e{n+1}th morph target position in the vertex |
| 320 | shader. The associated model should provide proper data. |
| 321 | |
| 322 | \li MORPH_NORMAL(\e{n}) -> vec3, the \e{n+1}th morph target normal in the vertex |
| 323 | shader. The associated model should provide proper data. |
| 324 | |
| 325 | \li MORPH_TANGENT(\e{n}) -> vec3, the \e{n+1}th morph target tangent in the vertex |
| 326 | shader. The associated model should provide proper data. |
| 327 | |
| 328 | \li MORPH_BINORMAL(\e{n}) -> vec3, the \e{n+1}th morph target binormal in the vertex |
| 329 | shader. The associated model should provide proper data. |
| 330 | |
| 331 | \li MODELVIEWPROJECTION_MATRIX -> mat4, the model-view-projection matrix. |
| 332 | Projection matrices always follow OpenGL conventions, with a baked-in |
| 333 | transformation for the Y axis direction and clip depth, depending on the |
| 334 | graphics API used at run time. |
| 335 | |
| 336 | \li VIEWPROJECTION_MATRIX -> mat4, the view-projection matrix |
| 337 | |
| 338 | \li PROJECTION_MATRIX -> mat4, the projection matrix |
| 339 | |
| 340 | \li INVERSE_PROJECTION_MATRIX -> mat4, the inverse projection matrix |
| 341 | |
| 342 | \li VIEW_MATRIX -> mat4, the view (camera) matrix |
| 343 | |
| 344 | \li MODEL_MATRIX -> mat4, the model (world) matrix |
| 345 | |
| 346 | \li NORMAL_MATRIX -> mat3, the normal matrix (the transpose of the inverse |
| 347 | of the top-left 3x3 part of the model matrix) |
| 348 | |
| 349 | \li BONE_TRANSFORMS -> mat4[], the array of the model's bone matrixes |
| 350 | |
| 351 | \li BONE_NORMAL_TRANSFORMS -> mat3[], the array of the model's bone normal |
| 352 | matrixes (the transpose of the inverse of the top-left 3x3 part of the each |
| 353 | bone matrixes) |
| 354 | |
| 355 | \li MORPH_WEIGHTS -> float[], the array of the morph weights. The associated model |
| 356 | should provide proper data. For safety, \b {QT_MORPH_MAX_COUNT} is defined to the |
| 357 | size of this array. |
| 358 | |
| 359 | \li CAMERA_POSITION -> vec3, the camera position in world space |
| 360 | |
| 361 | \li CAMERA_DIRECTION -> vec3, the camera direction vector |
| 362 | |
| 363 | \li CAMERA_PROPERTIES -> vec2, the near and far clip values for the camera |
| 364 | |
| 365 | \li POINT_SIZE -> float, writable in the vertex shader only. When rendering |
| 366 | geometry with a topology of points, the custom vertex shader must set this |
| 367 | to either 1.0 or another value, both in shaded and unshaded custom |
| 368 | materials. See \l{PrincipledMaterial::pointSize} for further notes on |
| 369 | support for sizes other than 1. |
| 370 | |
| 371 | \endlist |
| 372 | |
| 373 | \section1 Shaded custom materials |
| 374 | |
| 375 | A \l{CustomMaterial::shadingMode}{shaded} material \c augments the shader code |
| 376 | that would be generated by a PrincipledMaterial. Unlike unshaded materials, |
| 377 | that provide almost all logic for the vertex and fragment shader main |
| 378 | functions on their own, preventing adding generated code for lighting, |
| 379 | shadowing, global illumination, etc., shaded materials let shader |
| 380 | generation happen normally, as if the CustomMaterial was a |
| 381 | PrincipledMaterial. The vertex and fragment shader snippets are expected to |
| 382 | provide optional functions that are then invoked at certain points, giving |
| 383 | them the possibility to customize the colors and other values that are then |
| 384 | used for calculating lighting and the final fragment color. |
| 385 | |
| 386 | Rather than implementing just a \c MAIN function, the fragment shader for a |
| 387 | shaded custom material can implement multiple functions. All functions, |
| 388 | including \c MAIN, are optional to implement in shaded custom materials. An |
| 389 | empty shader snippet, or, even, not specifying the |
| 390 | \l{CustomMaterial::vertexShader}{vertexShader} or |
| 391 | \l{CustomMaterial::fragmentShader}{fragmentShader} properties at all can be |
| 392 | perfectly valid too. |
| 393 | |
| 394 | \section2 Vertex shader snippets in a shaded custom material |
| 395 | |
| 396 | The following functions can be implemented in a vertex shader snippet: |
| 397 | |
| 398 | \list |
| 399 | |
| 400 | \li \c{void MAIN()} When present, this function is called in order to set |
| 401 | the value of \c POSITION, the vec4 output from the vertex shader, and, |
| 402 | optionally, to modify the values of \c VERTEX, \c COLOR, \c NORMAL, \c UV0, |
| 403 | \c UV1, \c TANGENT, \c BINORMAL, \c JOINTS, and \c WEIGHTS. Unlike in |
| 404 | unshaded materials, writing to these makes sense because the modified values |
| 405 | are then taken into account in the rest of the generated shader code |
| 406 | (whereas for unshaded materials there is no additional shader code |
| 407 | generated). For example, if the custom vertex shader displaces the vertices |
| 408 | or the normals, it will want to store the modified values to \c VERTEX or |
| 409 | \c NORMAL, to achieve correct lighting calculations afterwards. |
| 410 | Additionally, the function can write to variables defined with \c VARYING in |
| 411 | order to pass interpolated data to the fragment shader. When this function |
| 412 | or a redefinition of \c POSITION is not present, \c POSITION is calculated |
| 413 | based on \c VERTEX and \c MODELVIEWPROJECTION_MATRIX, just like a |
| 414 | PrincipledMaterial would do. |
| 415 | |
| 416 | Example, with relying both on QML properties exposed as uniforms, and also |
| 417 | passing data to the fragment shader: |
| 418 | \badcode |
| 419 | VARYING vec3 vNormal; |
| 420 | VARYING vec3 vViewVec; |
| 421 | |
| 422 | void MAIN() |
| 423 | { |
| 424 | VERTEX.x += sin(uTime * 4.0 + VERTEX.y) * uAmplitude; |
| 425 | vNormal = normalize(NORMAL_MATRIX * NORMAL); |
| 426 | vViewVec = CAMERA_POSITION - (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; |
| 427 | POSITION = MODELVIEWPROJECTION_MATRIX * vec4(VERTEX, 1.0); |
| 428 | } |
| 429 | \endcode |
| 430 | |
| 431 | \note In the above example, assigning a value to \c POSITION is optional as |
| 432 | the usage in this case is identical to the default behavior. |
| 433 | |
| 434 | \endlist |
| 435 | |
| 436 | \note To pass data without interpolation from the vertex to the fragment |
| 437 | stage, add the \c flat keyword before the type in the \c VARYING |
| 438 | declarations. |
| 439 | |
| 440 | \section2 Fragment shader snippets in a shaded custom material |
| 441 | |
| 442 | The following functions can be implemented in a fragment shader snippet: |
| 443 | |
| 444 | \list |
| 445 | |
| 446 | \li \c{void MAIN()} When present, this function is called to set the values |
| 447 | of the special writable variables \c BASE_COLOR, \c METALNESS, \c ROUGHNESS, \c |
| 448 | SPECULAR_AMOUNT, NORMAL, CLEARCOAT_FRESNEL_POWER, CLEARCOAT_FRESNEL_SCALE, |
| 449 | CLEARCOAT_FRESNEL_BIAS, CLEARCOAT_AMOUNT, CLEARCOAT_ROUGHNESS, CLEARCOAT_NORMAL, |
| 450 | FRESNEL_BIAS, FRESNEL_SCALE, FRESNEL_POWER, IOR, \c TRANSMISSION_FACTOR, |
| 451 | THICKNESS_FACTOR, ATTENUATION_COLOR, ATTENUATION_DISTANCE and \c OCCLUSION_AMOUNT. |
| 452 | |
| 453 | One common use case is to set the value of \c BASE_COLOR based on sampling |
| 454 | a texture, be it a base color map, \c SCREEN_TEXTURE, or some other kind of |
| 455 | source. This can be relevant and convenient especially when no custom light |
| 456 | processor functions are implemented. Setting \c{BASE_COLOR.a} to something |
| 457 | other than the default 1.0 allows affecting the final alpha value of the |
| 458 | fragment. (note that this will often require also enabling alpha blending |
| 459 | in \l sourceBlend and \l destinationBlend) |
| 460 | |
| 461 | Another scenario is when there is no custom \c SPECULAR_LIGHT function |
| 462 | provided, or when there is a light probe set in the SceneEnvironment. The |
| 463 | metalness, roughness, and other values that affect the specular |
| 464 | contribution calculation can be set in \c MAIN to their desired custom |
| 465 | values. |
| 466 | |
| 467 | The function can write to the following special variables. The values |
| 468 | written to these will typically be either hardcoded or be calculated based |
| 469 | on QML properties mapped to uniforms. The semantics are identical to |
| 470 | PrincipledMaterial. |
| 471 | |
| 472 | \list |
| 473 | |
| 474 | \li vec4 \c BASE_COLOR - The base color and material alpha value. |
| 475 | Corresponds to the \l{PrincipledMaterial::baseColor}{built-in materials' |
| 476 | color property}. When light processor functions are not implemented, it can |
| 477 | be convenient to set a custom base color in \c MAIN because that is then |
| 478 | taken into account in the default lighting calculations. The default value |
| 479 | is \c{vec4(1.0)}, meaning white with an alpha of 1.0. The alpha value |
| 480 | effects the final alpha of the fragment. The final alpha value is the |
| 481 | object (model) opacity multiplied by the base color alpha. When specifying |
| 482 | the value directly in shader code, not relying on uniform values exposed |
| 483 | from \b color properties in QML, be aware that it is up to the shader to |
| 484 | perform the sRGB to linear conversion, if needed. For example, assuming |
| 485 | a \c{vec3 color} and \c{float alpha} this can be achieved like the following: |
| 486 | \badcode |
| 487 | float C1 = 0.305306011; |
| 488 | vec3 C2 = vec3(0.682171111, 0.682171111, 0.682171111); |
| 489 | vec3 C3 = vec3(0.012522878, 0.012522878, 0.012522878); |
| 490 | BASE_COLOR = vec4(rgb * (rgb * (rgb * C1 + C2) + C3), alpha); |
| 491 | \endcode |
| 492 | |
| 493 | \li vec3 \c EMISSIVE_COLOR - The color of self-illumination. Corresponds to |
| 494 | the built-in materials' emissive color which is combined by |
| 495 | \l {PrincipledMaterial::emissiveFactor}{built-in materials's emissiveFactor property} |
| 496 | and \l {PrincipledMaterial::emissiveMap}{built-in materials's emissiveMap property}. |
| 497 | The default value is \c{vec3(0.0)}. When specifying the value |
| 498 | directly in shader code, not relying on uniform values exposed from \b color |
| 499 | properties in QML, be aware that it is up to the shader to perform the sRGB |
| 500 | to linear conversion, if needed. |
| 501 | |
| 502 | \li float \c IOR Specifies the index of refraction of the material. A typical value, |
| 503 | and also the default, is \c{1.5} as that is what a PrincipledMaterial would use. |
| 504 | |
| 505 | \li float \c TRANSMISSION_FACTOR Specifies the amount of the translucency. A typical value, |
| 506 | would be \c{1.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use. |
| 507 | |
| 508 | \li float \c THICKNESS_FACTOR Specifies the amount of the translucent material thickness. A typical value, |
| 509 | would be \c{10.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use. |
| 510 | |
| 511 | \li vec3 \c ATTENUATION_COLOR Specifies the color shift of the translucent material by distance. A typical value, |
| 512 | would be \c{vec3(1.0, 0.0, 0.0)} and also the default, is \c{vec3(1.0)} as that is what a PrincipledMaterial would use. |
| 513 | |
| 514 | \li float \c ATTENUATION_DISTANCE Specifies the distance attenuation of color shift of the translucent material. A typical value, |
| 515 | would be \c{100.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use. |
| 516 | |
| 517 | \li float \c METALNESS Metalness amount in range 0.0 - 1.0. The default |
| 518 | value is 0. Must be set to a non-zero value to have effect. |
| 519 | |
| 520 | \li float \c ROUGHNESS Roughness value in range 0.0 - 1.0. The default value is 0. |
| 521 | |
| 522 | \li float \c CLEARCOAT_FRESNEL_POWER Specifies the fresnel power of the clearcoat layer. A typical value, |
| 523 | and also the default, is \c{5.0} as that is what a PrincipledMaterial would use. |
| 524 | |
| 525 | \li float \c CLEARCOAT_FRESNEL_SCALE Specifies the fresnel scale of the clearcoat layer. A typical value, |
| 526 | and also the default, is \c{1.0} as that is what a PrincipledMaterial would use. |
| 527 | |
| 528 | \li float \c CLEARCOAT_FRESNEL_BIAS Specifies the fresnel bias of the clearcoat layer. A typical value, |
| 529 | and also the default, is \c{0.0} as that is what a PrincipledMaterial would use. |
| 530 | |
| 531 | \li float \c CLEARCOAT_AMOUNT Specifies the amount of the clearcoat layer on top of the material. A typical value, |
| 532 | would be \c{1.0} and also the default, is \c{0.0} as that is what a PrincipledMaterial would use. |
| 533 | |
| 534 | \li float \c CLEARCOAT_ROUGHNESS Specifies the roughness of the clearcoat layer. A typical value, |
| 535 | would be \c{1.0} for fully blurred clearcoat layer and also the default, is \c{0.0} as that is |
| 536 | what a PrincipledMaterial would use. |
| 537 | |
| 538 | \li vec3 \c CLEARCOAT_NORMAL - The clearcoat layer normal that comes from the vertex shader in world |
| 539 | space. While this property has the same initial value as \c VAR_WORLD_NORMAL, |
| 540 | only changing the value of \c CLEARCOAT_NORMAL will have an effect on clearcoat layer normal. |
| 541 | |
| 542 | \li float \c FRESNEL_POWER Specifies the fresnel power. A typical value, |
| 543 | and also the default, is \c{5.0} as that is what a PrincipledMaterial would use. |
| 544 | |
| 545 | \li float \c FRESNEL_SCALE Specifies the fresnel scale. A typical value, |
| 546 | and also the default, is \c{1.0} as that is what a PrincipledMaterial would use. |
| 547 | |
| 548 | \li float \c FRESNEL_BIAS Specifies the fresnel bias. A typical value, |
| 549 | and also the default, is \c{0.0} as that is what a PrincipledMaterial would use. |
| 550 | |
| 551 | \li float \c SPECULAR_AMOUNT Specular amount in range 0.0 - 1.0. The |
| 552 | default value is \c{0.5}, matching \l{PrincipledMaterial::specularAmount}. Must |
| 553 | be set to a non-zero value to have effect. |
| 554 | |
| 555 | \li float \c OCCLUSION_AMOUNT Specifies the AO factor. A typical value, |
| 556 | and also the default, is \c{1.0} as that is what a PrincipledMaterial would use. |
| 557 | |
| 558 | \li vec3 \c NORMAL - The normal that comes from the vertex shader in world |
| 559 | space. While this property has the same initial value as \c VAR_WORLD_NORMAL, |
| 560 | only changing the value of \c NORMAL will have an effect on lighting. |
| 561 | |
| 562 | \li vec3 \c TANGENT - The tanget that comes from the vertex shader in world |
| 563 | space. This value is potentially adjusted for double-sidedness. |
| 564 | |
| 565 | \li vec3 \c BINORMAL - The binormal that comes from the vertex shader in |
| 566 | world space. This value is potentially adjusted for double-sidedness. |
| 567 | |
| 568 | \li vec2 \c UV0 - The first set of texture coordinates from the vertex shader. |
| 569 | This property is readonly in the fragment shader. |
| 570 | |
| 571 | \li vec2 \c UV1 - The second set of texture coordinates from the vertex shader. |
| 572 | This property is readonly in the fragment shader. |
| 573 | |
| 574 | \endlist |
| 575 | |
| 576 | \note Unlike with unshaded materials, the fragment \c MAIN for a shaded |
| 577 | material has no direct control over \c FRAGCOLOR. Rather, it is the \c |
| 578 | DIFFUSE and \c SPECULAR values written in the light processor functions |
| 579 | that decide what the final fragment color is. When a light processor |
| 580 | function is not implemented, the relevant default shading calculations are |
| 581 | performed as with a PrincipledMaterial, taking \c BASE_COLOR and other |
| 582 | values from the list above into account. |
| 583 | |
| 584 | An example of a simple, metallic custom material shader could be the following: |
| 585 | \badcode |
| 586 | void MAIN() |
| 587 | { |
| 588 | METALNESS = 1.0; |
| 589 | ROUGHNESS = 0.5; |
| 590 | FRESNEL_POWER = 5.0; |
| 591 | } |
| 592 | \endcode |
| 593 | |
| 594 | Another example, where the base color and alpha are set by sampling a texture: |
| 595 | \badcode |
| 596 | VARYING vec2 texcoord; |
| 597 | void MAIN() |
| 598 | { |
| 599 | BASE_COLOR = texture(uColorMap, texcoord); |
| 600 | } |
| 601 | \endcode |
| 602 | |
| 603 | \li \c{void AMBIENT_LIGHT()} When present, this function is called once for |
| 604 | each fragment. The task of the function is to add the total ambient |
| 605 | contribution to a writable special variable \c DIFFUSE. It can of course |
| 606 | choose to calculate a different value, or not touch \c DIFFUSE at all (to |
| 607 | ignore ambient lighting completely). When this function is not present at |
| 608 | all, the ambient contribution is calculated normally, like a |
| 609 | PrincipledMaterial would do. |
| 610 | |
| 611 | The function can write to the following special variables: |
| 612 | |
| 613 | \list |
| 614 | |
| 615 | \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per |
| 616 | fragment. The light processor functions will typically add (\c{+=}) to it, |
| 617 | since overwriting the value would lose the contribution from other lights. |
| 618 | |
| 619 | \endlist |
| 620 | |
| 621 | The function can read the following special variables, in addition to the |
| 622 | matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION) |
| 623 | uniforms from the table above: |
| 624 | |
| 625 | \list |
| 626 | \li vec3 \c TOTAL_AMBIENT_COLOR The total ambient contribution in the scene. |
| 627 | \endlist |
| 628 | |
| 629 | Example: |
| 630 | \badcode |
| 631 | void AMBIENT_LIGHT() |
| 632 | { |
| 633 | DIFFUSE += TOTAL_AMBIENT_COLOR; |
| 634 | } |
| 635 | \endcode |
| 636 | |
| 637 | \li \c{void DIRECTIONAL_LIGHT()} When present, this function is called for |
| 638 | each active directional light in the scene for each fragment. The task of |
| 639 | the function is to add the diffuse contribution to a writable special |
| 640 | variable \c DIFFUSE. The function can also choose to do nothing, in which |
| 641 | case diffuse contributions from directional lights are ignored. When the |
| 642 | function is not present at all, the diffuse contributions from directional |
| 643 | lights are accumulated normally, like a PrincipledMaterial would do. |
| 644 | |
| 645 | The function can write to the following special variables: |
| 646 | |
| 647 | \list |
| 648 | |
| 649 | \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per |
| 650 | fragment. The light processor functions will typically add (\c{+=}) to it, |
| 651 | since overwriting the value would lose the contribution from other lights. |
| 652 | |
| 653 | \endlist |
| 654 | |
| 655 | The function can read the following special variables, in addition to the |
| 656 | matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION) |
| 657 | uniforms from the table above: |
| 658 | |
| 659 | \list |
| 660 | |
| 661 | \li vec3 \c LIGHT_COLOR Diffuse light color. |
| 662 | \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows. |
| 663 | \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source. |
| 664 | \li vec3 \c NORMAL The normal vector in world space. |
| 665 | \li vec4 \c BASE_COLOR The base color and material alpha value. |
| 666 | \li float \c METALNESS The Metalness amount. |
| 667 | \li float \c ROUGHNESS The Roughness amount. |
| 668 | |
| 669 | \endlist |
| 670 | |
| 671 | Example: |
| 672 | \badcode |
| 673 | void DIRECTIONAL_LIGHT() |
| 674 | { |
| 675 | DIFFUSE += LIGHT_COLOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR))); |
| 676 | } |
| 677 | \endcode |
| 678 | |
| 679 | \li \c{void POINT_LIGHT()} When present, this function is called for |
| 680 | each active point light in the scene for each fragment. The task of |
| 681 | the function is to add the diffuse contribution to a writable special |
| 682 | variable \c DIFFUSE. The function can also choose to do nothing, in which |
| 683 | case diffuse contributions from point lights are ignored. When the |
| 684 | function is not present at all, the diffuse contributions from point |
| 685 | lights are accumulated normally, like a PrincipledMaterial would do. |
| 686 | |
| 687 | The function can write to the following special variables: |
| 688 | |
| 689 | \list |
| 690 | \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per fragment. |
| 691 | \endlist |
| 692 | |
| 693 | The function can read the following special variables, in addition to the |
| 694 | matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION) |
| 695 | uniforms from the table above: |
| 696 | |
| 697 | \list |
| 698 | \li vec3 \c LIGHT_COLOR Diffuse light color. |
| 699 | \li float \c LIGHT_ATTENUATION Light attenuation. |
| 700 | \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows. |
| 701 | \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source. |
| 702 | \li vec3 \c NORMAL The normal vector in world space. |
| 703 | \li vec4 \c BASE_COLOR The base color and material alpha value. |
| 704 | \li float \c METALNESS The Metalness amount. |
| 705 | \li float \c ROUGHNESS The Roughness amount. |
| 706 | \endlist |
| 707 | |
| 708 | Example: |
| 709 | \badcode |
| 710 | void POINT_LIGHT() |
| 711 | { |
| 712 | DIFFUSE += LIGHT_COLOR * LIGHT_ATTENUATION * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR))); |
| 713 | } |
| 714 | \endcode |
| 715 | |
| 716 | \li \c{void SPOT_LIGHT()} When present, this function is called for |
| 717 | each active spot light in the scene for each fragment. The task of |
| 718 | the function is to add the diffuse contribution to a writable special |
| 719 | variable \c DIFFUSE. The function can also choose to do nothing, in which |
| 720 | case diffuse contributions from spot lights are ignored. When the |
| 721 | function is not present at all, the diffuse contributions from spot |
| 722 | lights are accumulated normally, like a PrincipledMaterial would do. |
| 723 | |
| 724 | The function can write to the following special variables: |
| 725 | |
| 726 | \list |
| 727 | \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per fragment. |
| 728 | \endlist |
| 729 | |
| 730 | The function can read the following special variables, in addition to the |
| 731 | matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION) |
| 732 | uniforms from the table above: |
| 733 | |
| 734 | \list |
| 735 | \li vec3 \c LIGHT_COLOR Diffuse light color. |
| 736 | \li float \c LIGHT_ATTENUATION Light attenuation. |
| 737 | \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows. |
| 738 | \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source. |
| 739 | \li float \c SPOT_FACTOR Spot light factor. |
| 740 | \li vec3 \c NORMAL The normal vector in world space. |
| 741 | \li vec4 \c BASE_COLOR The base color and material alpha value. |
| 742 | \li float \c METALNESS The Metalness amount. |
| 743 | \li float \c ROUGHNESS The Roughness amount. |
| 744 | \endlist |
| 745 | |
| 746 | Example: |
| 747 | \badcode |
| 748 | void SPOT_LIGHT() |
| 749 | { |
| 750 | DIFFUSE += LIGHT_COLOR * LIGHT_ATTENUATION * SPOT_FACTOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR))); |
| 751 | } |
| 752 | \endcode |
| 753 | |
| 754 | \li \c{void SPECULAR_LIGHT()} When present, this function is called for |
| 755 | each active light in the scene for each fragment. The task of the function |
| 756 | is to add the specular contribution to a writable special variable \c |
| 757 | SPECULAR. The function can also choose to do nothing, in which case |
| 758 | specular contributions from lights are ignored. When the function is not |
| 759 | present at all, the specular contributions from lights are accumulated |
| 760 | normally, like a PrincipledMaterial would do. |
| 761 | |
| 762 | The function can write to the following special variables: |
| 763 | |
| 764 | \list |
| 765 | |
| 766 | \li vec3 \c SPECULAR Accumulates the specular light contributions, per |
| 767 | frament. The light processor functions will typically add (\c{+=}) to it, |
| 768 | since overwriting the value would lose the contribution from other lights. |
| 769 | |
| 770 | \endlist |
| 771 | |
| 772 | The function can read the following special variables, in addition to the |
| 773 | matrix (such as, \c MODEL_MATRIX) and vector (such as, \c CAMERA_POSITION) |
| 774 | uniforms from the table above: |
| 775 | |
| 776 | \list |
| 777 | \li vec3 \c LIGHT_COLOR Specular light color. |
| 778 | \li float \c LIGHT_ATTENUATION Light attenuation. For directional lights the value is 1.0. For spot lights the value is the same as \c {LIGHT_ATTENUATION * SPOT_FACTOR} of \c {void SPOT_LIGHT()}. |
| 779 | \li float \c SHADOW_CONTRIB Shadow contribution, or 1.0 if not shadowed at all or not reciving shadows. |
| 780 | \li vec3 \c FRESNEL_CONTRIB Fresnel contribution from built in Fresnel calculation. |
| 781 | \li vec3 \c TO_LIGHT_DIR Vector pointing towards the light source. |
| 782 | \li vec3 \c NORMAL The normal vector in world space. |
| 783 | \li vec4 \c BASE_COLOR The base color and material alpha value. |
| 784 | \li float \c METALNESS The Metalness amount. |
| 785 | \li float \c ROUGHNESS The Roughness amount. |
| 786 | \li float \c SPECULAR_AMOUNT The specular amount. This value will be between |
| 787 | 0.0 and 1.0 will be the same value set in the custom \c MAIN function. This |
| 788 | value will useful for calculating Fresnel contributions when not using the |
| 789 | built-in Fresnel contribution provided by \c FRESNEL_CONTRIB. |
| 790 | \endlist |
| 791 | |
| 792 | \badcode |
| 793 | void SPECULAR_LIGHT() |
| 794 | { |
| 795 | vec3 H = normalize(VIEW_VECTOR + TO_LIGHT_DIR); |
| 796 | float cosAlpha = max(0.0, dot(H, normalize(NORMAL))); |
| 797 | float shine = pow(cosAlpha, exp2(15.0 * (1.0 - ROUGHNESS) + 1.0) * 0.25); |
| 798 | SPECULAR += shine * LIGHT_COLOR * FRESNEL_CONTRIB * SHADOW_CONTRIB * LIGHT_ATTENUATION; |
| 799 | } |
| 800 | \endcode |
| 801 | |
| 802 | \li \c{void POST_PROCESS()} When present, this function is called at the |
| 803 | end of the fragment pipeline. The task of the function is to finalize |
| 804 | \c COLOR_SUM with final diffuse, specular and emissive terms. Unlike |
| 805 | \c FRAGCOLOR for a unshaded material, \c COLOR_SUM will be automatically |
| 806 | tonemapped before written to the framebuffer. For debugging purposes it is |
| 807 | sometimes useful to output a value that should not be treated as a color. |
| 808 | To avoid the tonemapping distorting this value it can be disabled by |
| 809 | setting the \l {SceneEnvironment::tonemapMode}{tonemapMode} property |
| 810 | to \c TonemapModeNone |
| 811 | |
| 812 | The function can write to the following special variables: |
| 813 | |
| 814 | \list |
| 815 | \li vec4 \c COLOR_SUM the output from the fragment shader. The default value |
| 816 | is vec4(DIFFUSE.rgb + SPECULAR + EMISSIVE, DIFFUSE.a) |
| 817 | \endlist |
| 818 | |
| 819 | The function can read the following special variables. |
| 820 | |
| 821 | \list |
| 822 | \li vec4 \c DIFFUSE The final diffuse term of the fragment pipeline. |
| 823 | \li vec3 \c SPECULAR The final specular term of the fragment pipeline. |
| 824 | \li vec3 \c EMISSIVE The final emissive term of the fragment pipeline. |
| 825 | \li vec2 \c UV0 - The first set of texture coordinates from the vertex shader. |
| 826 | \li vec2 \c UV1 - The second set of texture coordinates from the vertex shader. |
| 827 | \endlist |
| 828 | |
| 829 | \badcode |
| 830 | void POST_PROCESS() |
| 831 | { |
| 832 | float center_x = textureSize(SCREEN_TEXTURE, 0).x * 0.5; |
| 833 | if (gl_FragCoord.x > center_x) |
| 834 | COLOR_SUM = DIFFUSE; |
| 835 | else |
| 836 | COLOR_SUM = vec4(EMISSIVE, DIFFUSE.a); |
| 837 | } |
| 838 | \endcode |
| 839 | |
| 840 | \li \c{void IBL_PROBE()} When present, this function is called for IBL |
| 841 | (Image-Based Lighting). |
| 842 | The task of the function is to add both the diffuse and the specular |
| 843 | contributions of IBL to writable special variables \c DIFFUSE and |
| 844 | \c SPECULAR. |
| 845 | |
| 846 | The function can write to the following special variables: |
| 847 | |
| 848 | \list |
| 849 | \li vec3 \c DIFFUSE Accumulates the diffuse light contributions, per fragment. |
| 850 | \li vec3 \c SPECULAR Accumulates the specular light contributions, per |
| 851 | frament. |
| 852 | \endlist |
| 853 | |
| 854 | The function can read the following special variables. |
| 855 | |
| 856 | \list |
| 857 | \li vec4 \c BASE_COLOR The base color and material alpha value. |
| 858 | \li float \c AO_FACTOR The screen space occlusion factor. |
| 859 | \li float \c SPECULAR_AMOUNT The specular amount. |
| 860 | \li float \c ROUGHNESS The final emissive term of the fragment pipeline. |
| 861 | \li vec3 \c NORMAL The normal vector in world space. |
| 862 | \li vec3 \c VIEW_VECTOR Points towards the camera. |
| 863 | \li mat3 \c IBL_ORIENTATION The orientation of the light probe. It comes |
| 864 | from \l {SceneEnvironment::probeOrientation}. |
| 865 | \endlist |
| 866 | |
| 867 | \badcode |
| 868 | void IBL_PROBE() |
| 869 | { |
| 870 | vec3 smpDir = IBL_ORIENTATION * NORMAL; |
| 871 | DIFFUSE += AO_FACTOR * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, smpDir, IBL_MAXMIPMAP).rgb; |
| 872 | } |
| 873 | \endcode |
| 874 | |
| 875 | \endlist |
| 876 | |
| 877 | \sa SceneEnvironment::tonemapMode, {Using Image-Based Lighting} |
| 878 | |
| 879 | \section2 Custom variables between functions |
| 880 | |
| 881 | Additional variables can be delivered from the MAIN function to the others. |
| 882 | The \c SHARED_VARS keyword can be used for defining new custom variables. |
| 883 | These user-defined variables can be accessed with SHARED.<variable name>. |
| 884 | |
| 885 | For example, a shaded custom material can fetch a shared value in the MAIN |
| 886 | and use it in other functions. |
| 887 | |
| 888 | \badcode |
| 889 | SHARED_VARS { |
| 890 | vec3 colorThreshold; |
| 891 | }; |
| 892 | void MAIN() |
| 893 | { |
| 894 | BASE_COLOR = texture(baseColorMap, UV0); |
| 895 | SHARED.colorThreshold = texture(thresholdMap, UV0).rgb; |
| 896 | } |
| 897 | void DIRECTIONAL_LIGHT() |
| 898 | { |
| 899 | if (DIFFUSE >= SHARED.colorThreshold) { |
| 900 | DIFFUSE = SHARED.colorThreshold; |
| 901 | return; |
| 902 | } |
| 903 | DIFFUSE += LIGHT_COLOR * SHADOW_CONTRIB; |
| 904 | } |
| 905 | \endcode |
| 906 | |
| 907 | \note SHARED can be written on all the functions without POST_PROCESS but it |
| 908 | is safe to write it on MAIN and read on the other functions. |
| 909 | |
| 910 | \note A recommended use case to write SHARED on LIGHT functions is |
| 911 | reseting it on MAIN first and then accumulating it on each LIGHT functions. |
| 912 | |
| 913 | \badcode |
| 914 | SHARED_VARS { |
| 915 | float sheenIntensity; |
| 916 | float sheenRoughness; |
| 917 | vec3 sheenColor; |
| 918 | vec3 outSheenColor; |
| 919 | }; |
| 920 | void MAIN() |
| 921 | { |
| 922 | ... |
| 923 | vec4 tex = texture(uSheenMap, UV0); |
| 924 | SHARED.sheenColor = tex.rgb; |
| 925 | SHARED.sheenIntensity = tex.a; |
| 926 | SHARED.sheenRoughness = uSheenRoughness; |
| 927 | SHARED.outSheenColor = vec3(0.0); |
| 928 | } |
| 929 | void SPECULAR_LIGHT() |
| 930 | { |
| 931 | SHARED.outSheenColor += ...; |
| 932 | } |
| 933 | void POST_PROCESS() |
| 934 | { |
| 935 | COLOR_SUM = DIFFUSE + SPECULAR + EMISSIVE + SHARED.outSheenColor; |
| 936 | } |
| 937 | \endcode |
| 938 | |
| 939 | \note MAIN is called before others, and POST_PROCESS after all others, |
| 940 | but that there is no guarantee for any other ordering for light processors. |
| 941 | |
| 942 | \section2 Additional special keywords |
| 943 | |
| 944 | The custom fragment shader code can freely access uniforms (such as, \c |
| 945 | CAMERA_DIRECTION or \c CAMERA_POSITION), and varyings passed on from the |
| 946 | custom vertex shader. Additionally, there are a number of built-in varyings |
| 947 | available as special keywords. Some of these are optional in the sense that |
| 948 | a vertex \c MAIN could calculate and pass on these on its own, but to |
| 949 | reduce duplicated data fragment shaders can also rely on these built-ins |
| 950 | instead. These built-ins are available in light processor functions and in |
| 951 | the fragment MAIN. |
| 952 | |
| 953 | \list |
| 954 | |
| 955 | \li vec3 \c VAR_WORLD_NORMAL - Interpolated normal transformed by \c |
| 956 | NORMAL_MATRIX. |
| 957 | |
| 958 | \li vec3 \c VAR_WORLD_TANGENT - Interpolated tangent transformed by \c |
| 959 | MODEL_MATRIX. |
| 960 | |
| 961 | \li vec3 \c VAR_WORLD_BINORMAL - Interpolated binormal transformed by \c |
| 962 | MODEL_MATRIX |
| 963 | |
| 964 | \li vec3 \c NORMAL - Unlike \c VAR_WORLD_NORMAL, which is the |
| 965 | interpolated normal as-is, this value is potentially adjusted for |
| 966 | double-sidedness: when rendering with culling disabled, the normal will get |
| 967 | inverted as necessary. Therefore lighting and other calculations are |
| 968 | recommended to use \c NORMAL instead of \c VAR_WORLD_NORMAL in order |
| 969 | behave correctly with all culling modes. |
| 970 | |
| 971 | \li vec3 \c TANGENT - Like \c NORMAL, this value is potentially adjusted for |
| 972 | double-sidedness: when rendering with culling disabled, the tangent will get |
| 973 | inverted as necessary. |
| 974 | |
| 975 | \li vec3 \c BINORMAL - Like \c NORMAL, this value is potentially adjusted for |
| 976 | double-sidedness: when rendering with culling disabled, the binormal will get |
| 977 | inverted as necessary. |
| 978 | |
| 979 | \li vec3 \c VAR_WORLD_POSITION - Interpolated world space vertex position |
| 980 | (\c{(MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz}) |
| 981 | |
| 982 | \li vec4 \c VAR_COLOR - The interpolated vertex color when colors are |
| 983 | provided in the mesh. \c{vec4(1.0)} otherwise. |
| 984 | |
| 985 | \li vec3 \c VIEW_VECTOR - Points towards the camera. This is |
| 986 | effectively the \c{CAMERA_POSITION - VAR_WORLD_POSITION} vector normalized. |
| 987 | |
| 988 | \li vec4 \c FRAGCOORD - Contains the window-relative coordinates of the |
| 989 | current fragment. |
| 990 | |
| 991 | \li float \c FRAMEBUFFER_Y_UP - The value is \c 1 when the Y axis points up |
| 992 | in the coordinate system for framebuffers (textures), meaning \c{(0, 0)} is |
| 993 | the bottom-left corner. The value is \c{-1} when the Y axis points down, |
| 994 | \c{(0, 0)} being the top-left corner. Such differences in the underlying |
| 995 | graphics APIs do not concern most custom materials. One notable exception |
| 996 | is sampling \c SCREEN_TEXTURE with texture coordinates \b not based on |
| 997 | \c FRAGCOORD. As the orientation of \c SCREEN_TEXTURE is tied to the |
| 998 | underlying graphics API by nature, using texture coordinates from a mesh |
| 999 | may need appropriate adjustments to the Y coordinate. |
| 1000 | |
| 1001 | For example, the following fragment shader, suitable for Rectangle or Cube |
| 1002 | meshes, will display the opaque objects from the scene on the model: |
| 1003 | |
| 1004 | \badcode |
| 1005 | VARYING vec2 texcoord; |
| 1006 | void MAIN() |
| 1007 | { |
| 1008 | vec2 screencoord = texcoord; |
| 1009 | if (FRAMEBUFFER_Y_UP < 0.0) // effectively: if not OpenGL |
| 1010 | screencoord.y = 1.0 - screencoord.y; |
| 1011 | BASE_COLOR = texture(SCREEN_TEXTURE, screencoord); |
| 1012 | } |
| 1013 | \endcode |
| 1014 | |
| 1015 | When sampling textures other than \c SCREEN_TEXTURE and \c DEPTH_TEXTURE, |
| 1016 | or when \c FRAGCOORD is used to calculate the texture coordinate (which |
| 1017 | would be the typical use case for accessing the screen and depth textures), |
| 1018 | such an adjustment is not necessary. |
| 1019 | |
| 1020 | \li float \c NDC_Y_UP - The value is \c 1 when the Y axis points up in |
| 1021 | normalized device coordinate space, and \c{-1} when the Y axis points down. |
| 1022 | Y pointing down is the case when rendering happens with Vulkan. Most |
| 1023 | materials do not need to be concerned by this, but being able to branch |
| 1024 | based on this can become useful in certain advanced use cases. |
| 1025 | |
| 1026 | \li float \c NEAR_CLIP_VALUE - The value is \c -1 for when the clipping plane |
| 1027 | range's starts at \c -1 and goes to \c 1. This is true when using OpenGL for |
| 1028 | rendering. For other rendering backends the value of this property will be |
| 1029 | \c 0 meaning the clipping plane range is \c 0 to \c 1. This value is useful |
| 1030 | with certain techniques involving the \c DEPTH_TEXTURE |
| 1031 | |
| 1032 | For example, the following fragment shader demonstrates a technique for |
| 1033 | reconstructing the position of a value from the depth buffer to determine |
| 1034 | the distance from the current position being rendered. When used in |
| 1035 | combination with \c INVERSE_PROJECTION_MATRIX the value of depth needs |
| 1036 | to be in normalized device coordinates so it is important to make sure that |
| 1037 | the range of depth value reflects that. When the \c NEAR_CLIP_VALUE is |
| 1038 | \c -1 then the depth value gets scaled to be between \c -1 and \c 1. |
| 1039 | |
| 1040 | \badcode |
| 1041 | void MAIN() { |
| 1042 | vec2 screen_uv = FRAGCOORD.xy / vec2(textureSize(SCREEN_TEXTURE, 0)); |
| 1043 | float depth = texture(DEPTH_TEXTURE, screen_uv).r; |
| 1044 | |
| 1045 | if (NEAR_CLIP_VALUE < 0.0) // effectively: if opengl |
| 1046 | depth = depth * 2.0 - 1.0; |
| 1047 | |
| 1048 | vec4 unproject = INVERSE_PROJECTION_MATRIX * vec4(screen_uv, depth, 1.0); |
| 1049 | depth = (unproject.xyz / unproject.w).z; |
| 1050 | float viewVectorZ = (VIEW_MATRIX * vec4(VAR_WORLD_POSITION, 1.0)).z; |
| 1051 | depth = viewVectorZ - depth; |
| 1052 | |
| 1053 | BASE_COLOR = vec4(depth, depth, depth, 1.0); |
| 1054 | } |
| 1055 | \endcode |
| 1056 | |
| 1057 | \li float \c IBL_EXPOSE - The amount of light emitted by the light probe. |
| 1058 | It comes from \l {SceneEnvironment::probeExposure}. |
| 1059 | \badcode |
| 1060 | DIFFUSE += AO_FACTOR * IBL_EXPOSE * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, NORMAL, IBL_MAXMIPMAP).rgb; |
| 1061 | \endcode |
| 1062 | |
| 1063 | \li float \c IBL_HORIZON - The horizontal cut-off value of reflections from |
| 1064 | the lower half environment. It comes from \l {SceneEnvironment::probeHorizon} |
| 1065 | {Horizon Cut-Off} but remapped to [-1, 0). |
| 1066 | \badcode |
| 1067 | vec3 diffuse += AO_FACTOR * IBL_EXPOSE * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, NORMAL, IBL_MAXMIPMAP).rgb; |
| 1068 | if (IBL_HORIZON > -1.0) { |
| 1069 | float ctr = 0.5 + 0.5 * IBL_HORIZON; |
| 1070 | float vertWt = smoothstep(ctr * 0.25, ctr + 0.25, NORMAL.y); |
| 1071 | float wtScaled = mix(1.0, vertWt, IBL_HORIZON + 1.0); |
| 1072 | diffuse *= wtScaled; |
| 1073 | } |
| 1074 | \endcode |
| 1075 | |
| 1076 | \li float \c IBL_MAXMIPMAP - The maximum mipmap level of IBL_TEXTURE. |
| 1077 | |
| 1078 | \endlist |
| 1079 | |
| 1080 | \section2 Instancing |
| 1081 | |
| 1082 | When doing instanced rendering, some of the keywords above do not apply. |
| 1083 | The following keywords are only available with instancing: |
| 1084 | |
| 1085 | \list |
| 1086 | \li \c INSTANCE_MODEL_MATRIX -> mat4, replacement for \c MODEL_MATRIX, including the instancing transformation. |
| 1087 | \li \c INSTANCE_MODELVIEWPROJECTION_MATRIX -> mat4, replacement for \c MODELVIEWPROJECTION_MATRIX, including the instancing transformation. |
| 1088 | \li \c INSTANCE_COLOR -> vec4, the instance color: to be combined with \c {COLOR}. |
| 1089 | \li \c INSTANCE_DATA -> vec4, instance custom data. |
| 1090 | \li \c INSTANCE_INDEX -> int, the instance number, and index into the instancing table. |
| 1091 | \endlist |
| 1092 | |
| 1093 | \section1 Screen, depth, and other textures |
| 1094 | |
| 1095 | The rendering pipeline can expose a number of textures to the custom |
| 1096 | material shaders with content from special render passes. This applies both |
| 1097 | to shaded and unshaded custom materials. |
| 1098 | |
| 1099 | For example, a shader may want access to a depth texture that contains the |
| 1100 | depth buffer contents for the opaque objects in the scene. This is achieved |
| 1101 | by sampling \c DEPTH_TEXTURE. Such a texture is not normally generated, |
| 1102 | unless there is a real need for it. Therefore, the presence of the |
| 1103 | following keywords in the vertex or fragment shader also acts as a toggle |
| 1104 | for opting in to the - potentially expensive - passes for generating the |
| 1105 | texture in question. (of course, it could be that some of these become |
| 1106 | already enabled due to other settings, such as the ambient occlusion |
| 1107 | parameters in SceneEnvironment or due to a post-processing effect relying |
| 1108 | on the depth texture, in which case the textures in question are generated |
| 1109 | regardless of the custom material and so sampling these special textures in |
| 1110 | the material comes at no extra cost apart from the texture access itself) |
| 1111 | |
| 1112 | \list |
| 1113 | |
| 1114 | \li \c SCREEN_TEXTURE - When present, a texture (\c sampler2D or \c |
| 1115 | sampler2DArray) with the color buffer from a rendering pass containing the |
| 1116 | contents of the scene excluding any transparent materials or any materials |
| 1117 | also using the SCREEN_TEXTURE is exposed to the shader under this name. The |
| 1118 | texture can be used for techniques that require the contents of the |
| 1119 | framebuffer they are being rendered to. The SCREEN_TEXTURE texture uses the |
| 1120 | same clear mode as the View3D. The size of these textures matches the size |
| 1121 | of the View3D in pixels. For example, a fragment shader could contain the |
| 1122 | following: |
| 1123 | \badcode |
| 1124 | vec2 uv = FRAGCOORD.xy / vec2(textureSize(SCREEN_TEXTURE, 0)); |
| 1125 | vec2 displace = vec2(0.1); |
| 1126 | vec4 c = texture(SCREEN_TEXTURE, uv + displace); |
| 1127 | \endcode |
| 1128 | |
| 1129 | Be aware that using \c SCREEN_TEXTURE requires appropriate, conscious |
| 1130 | design of the scene. Objects using such materials have to be positioned |
| 1131 | carefully, typically above all other objects that are expected to be |
| 1132 | visible in the texture. Objects that employ semi-transparency in some form |
| 1133 | are never part of the \c SCREEN_TEXTURE. Often \c SCREEN_TEXTURE will be |
| 1134 | used in combination with \c BASE_COLOR in \c MAIN. For example, the |
| 1135 | following custom fragment shader applies an emboss effect, while keeping |
| 1136 | fragments not touched by opaque objects transparent. This assumes that the |
| 1137 | object with the material is placed in the front, and that it has blending |
| 1138 | enabled. \badcode |
| 1139 | void MAIN() |
| 1140 | { |
| 1141 | vec2 size = vec2(textureSize(SCREEN_TEXTURE, 0)); |
| 1142 | vec2 uv = FRAGCOORD.xy / size; |
| 1143 | |
| 1144 | // basic emboss effect |
| 1145 | vec2 d = vec2(1.0 / size.x, 1.0 / size.y); |
| 1146 | vec4 diff = texture(SCREEN_TEXTURE, uv + d) - texture(SCREEN_TEXTURE, uv - d); |
| 1147 | float c = (diff.x + diff.y + diff.z) + 0.5; |
| 1148 | |
| 1149 | float alpha = texture(SCREEN_TEXTURE, uv).a; |
| 1150 | BASE_COLOR = vec4(vec3(c), alpha); |
| 1151 | } |
| 1152 | \endcode |
| 1153 | With \l{Multiview Rendering}{multiview rendering}, \c SCREEN_TEXTURE is a \c |
| 1154 | sampler2DArray. Use \c VIEW_INDEX to select the layer to use. For VR/AR |
| 1155 | applications that wish to support both types of rendering, the portable |
| 1156 | approach is the following: |
| 1157 | \badcode |
| 1158 | #if QSHADER_VIEW_COUNT >= 2 |
| 1159 | vec4 c = texture(SCREEN_TEXTURE, vec3(uv, VIEW_INDEX)); |
| 1160 | #else |
| 1161 | vec4 c = texture(SCREEN_TEXTURE, uv); |
| 1162 | #endif |
| 1163 | \endcode |
| 1164 | |
| 1165 | \li \c SCREEN_MIP_TEXTURE - Identical to \c SCREEN_TEXTURE in most ways, |
| 1166 | the difference being that this texture has mipmaps generated. This can be |
| 1167 | an expensive feature performance-wise, depending on the screen size, and |
| 1168 | due to having to generate the mipmaps every time the scene is rendered. |
| 1169 | Therefore, prefer using \c SCREEN_TEXTURE always, unless a technique |
| 1170 | relying on the texture mip levels (e.g. using \c textureLod in the shader) |
| 1171 | is implemented by the custom material. |
| 1172 | |
| 1173 | \li \c DEPTH_TEXTURE - When present, a texture (\c sampler2D or \c |
| 1174 | sampler2DArray) with the (non-linearized) depth buffer contents is exposed |
| 1175 | to the shader under this name. Only opaque objects are included. |
| 1176 | For example, a fragment shader could contain the following: \badcode |
| 1177 | ivec2 dtSize = textureSize(DEPTH_TEXTURE, 0); |
| 1178 | vec2 dtUV = (FRAGCOORD.xy) / vec2(dtSize); |
| 1179 | vec4 depthSample = texture(DEPTH_TEXTURE, dtUV); |
| 1180 | float zNear = CAMERA_PROPERTIES.x; |
| 1181 | float zFar = CAMERA_PROPERTIES.y; |
| 1182 | float zRange = zFar - zNear; |
| 1183 | float z_n = 2.0 * depthSample.r - 1.0; |
| 1184 | float d = 2.0 * zNear * zFar / (zFar + zNear - z_n * zRange); |
| 1185 | d /= zFar; |
| 1186 | \endcode |
| 1187 | With \l{Multiview Rendering}{multiview rendering}, \c DEPTH_TEXTURE is a \c |
| 1188 | sampler2DArray. Use \c VIEW_INDEX to select the layer to use. For VR/AR |
| 1189 | applications that wish to support both types of rendering, the portable |
| 1190 | approach is the following: |
| 1191 | \badcode |
| 1192 | #if QSHADER_VIEW_COUNT >= 2 |
| 1193 | vec4 depthSample = texture(DEPTH_TEXTURE, vec3(uv, VIEW_INDEX)); |
| 1194 | #else |
| 1195 | vec4 depthSample = texture(DEPTH_TEXTURE, uv); |
| 1196 | #endif |
| 1197 | \endcode |
| 1198 | |
| 1199 | \li \c AO_TEXTURE - When present and screen space ambient occlusion is |
| 1200 | enabled (meaning when the AO strength and distance are both non-zero) in |
| 1201 | SceneEnvironment, the SSAO texture (\c sampler2D or \c sampler2DArray) is |
| 1202 | exposed to the shader under this name. Sampling this texture can be useful |
| 1203 | in unshaded materials. Shaded materials have ambient occlusion support built |
| 1204 | in. This means that the ambient occlusion factor is taken into account |
| 1205 | automatically. Whereas in a fragment shader for an unshaded material one |
| 1206 | could write the following to achieve the same: \badcode |
| 1207 | ivec2 aoSize = textureSize(AO_TEXTURE, 0); |
| 1208 | vec2 aoUV = (FRAGCOORD.xy) / vec2(aoSize); |
| 1209 | float aoFactor = texture(AO_TEXTURE, aoUV).x; |
| 1210 | \endcode |
| 1211 | With \l{Multiview Rendering}{multiview rendering}, \c AO_TEXTURE is a \c |
| 1212 | sampler2DArray. Use \c VIEW_INDEX to select the layer to use. For VR/AR |
| 1213 | applications that wish to support both types of rendering, the portable |
| 1214 | approach is the following: |
| 1215 | \badcode |
| 1216 | #if QSHADER_VIEW_COUNT >= 2 |
| 1217 | ivec2 aoSize = textureSize(AO_TEXTURE, 0).xy; |
| 1218 | vec2 aoUV = (FRAGCOORD.xy) / vec2(aoSize); |
| 1219 | float aoFactor = texture(AO_TEXTURE, vec3(aoUV, VIEW_INDEX)).x; |
| 1220 | #else |
| 1221 | ivec2 aoSize = textureSize(AO_TEXTURE, 0); |
| 1222 | vec2 aoUV = (FRAGCOORD.xy) / vec2(aoSize); |
| 1223 | float aoFactor = texture(AO_TEXTURE, aoUV).x; |
| 1224 | #endif |
| 1225 | \endcode |
| 1226 | |
| 1227 | \li \c IBL_TEXTURE - It will not enable any special rendering pass, but it can |
| 1228 | be used when the material has \l {Material::lightProbe} or the model is in the scope of |
| 1229 | \l {SceneEnvironment::lightProbe}. |
| 1230 | |
| 1231 | \badcode |
| 1232 | void IBL_PROBE() |
| 1233 | { |
| 1234 | DIFFUSE += AO_FACTOR * BASE_COLOR.rgb * textureLod(IBL_TEXTURE, NORMAL, IBL_MAXMIPMAP).rgb; |
| 1235 | } |
| 1236 | \endcode |
| 1237 | |
| 1238 | \li \c VIEW_INDEX - When used in the custom shader code, this is a |
| 1239 | (non-interpolated) uint variable. When \l{Multiview Rendering}{multiview |
| 1240 | rendering} is not used, the value is always 0. With multiview rendering, the |
| 1241 | value is the current view index (e.g., gl_ViewIndex). Useful in particular |
| 1242 | in combination with \c DEPTH_TEXTURE and similar when multiview rendering is |
| 1243 | enabled. |
| 1244 | |
| 1245 | \endlist |
| 1246 | |
| 1247 | \sa {Qt Quick 3D - Custom Shaders Example}, {Qt Quick 3D - Custom Materials Example}, {Programmable Materials, Effects, Geometry, and Texture data} |
| 1248 | */ |
| 1249 | |
| 1250 | /*! |
| 1251 | \qmlproperty url CustomMaterial::vertexShader |
| 1252 | |
| 1253 | Specfies the file with the snippet of custom vertex shader code. |
| 1254 | |
| 1255 | The value is a URL and must either be a local file or use the qrc scheme to |
| 1256 | access files embedded via the Qt resource system. Relative file paths |
| 1257 | (without a scheme) are also accepted, in which case the file is treated as |
| 1258 | relative to the component (the \c{.qml} file). |
| 1259 | |
| 1260 | \sa fragmentShader |
| 1261 | */ |
| 1262 | |
| 1263 | /*! |
| 1264 | \qmlproperty url CustomMaterial::fragmentShader |
| 1265 | |
| 1266 | Specfies the file with the snippet of custom fragment shader code. |
| 1267 | |
| 1268 | The value is a URL and must either be a local file or use the qrc scheme to |
| 1269 | access files embedded via the Qt resource system. Relative file paths |
| 1270 | (without a scheme) are also accepted, in which case the file is treated as |
| 1271 | relative to the component (the \c{.qml} file). |
| 1272 | |
| 1273 | \sa vertexShader |
| 1274 | */ |
| 1275 | |
| 1276 | /*! |
| 1277 | \qmlproperty enumeration CustomMaterial::shadingMode |
| 1278 | Specifies the type of the material. The default value is Shaded. |
| 1279 | |
| 1280 | \value CustomMaterial.Unshaded |
| 1281 | \value CustomMaterial.Shaded |
| 1282 | */ |
| 1283 | |
| 1284 | /*! |
| 1285 | \qmlproperty bool CustomMaterial::alwaysDirty |
| 1286 | Specifies that the material state is always dirty, which indicates that the material needs |
| 1287 | to be refreshed every time it is used by the QtQuick3D. |
| 1288 | */ |
| 1289 | |
| 1290 | /*! |
| 1291 | \qmlproperty enumeration CustomMaterial::sourceBlend |
| 1292 | |
| 1293 | Specifies the source blend factor. The default value is \c |
| 1294 | CustomMaterial.NoBlend. |
| 1295 | |
| 1296 | \value CustomMaterial.NoBlend |
| 1297 | \value CustomMaterial.Zero |
| 1298 | \value CustomMaterial.One |
| 1299 | \value CustomMaterial.SrcColor |
| 1300 | \value CustomMaterial.OneMinusSrcColor |
| 1301 | \value CustomMaterial.DstColor |
| 1302 | \value CustomMaterial.OneMinusDstColor |
| 1303 | \value CustomMaterial.SrcAlpha |
| 1304 | \value CustomMaterial.OneMinusSrcAlpha |
| 1305 | \value CustomMaterial.DstAlpha |
| 1306 | \value CustomMaterial.OneMinusDstAlpha |
| 1307 | \value CustomMaterial.ConstantColor |
| 1308 | \value CustomMaterial.OneMinusConstantColor |
| 1309 | \value CustomMaterial.ConstantAlpha |
| 1310 | \value CustomMaterial.OneMinusConstantAlpha |
| 1311 | \value CustomMaterial.SrcAlphaSaturate |
| 1312 | |
| 1313 | \note Both \l sourceBlend and \l destinationBlend needs to be set to a non-default |
| 1314 | value before blending is enabled. |
| 1315 | |
| 1316 | \sa destinationBlend |
| 1317 | */ |
| 1318 | |
| 1319 | /*! |
| 1320 | \qmlproperty enumeration CustomMaterial::destinationBlend |
| 1321 | |
| 1322 | Specifies the destination blend factor. The default value is \c |
| 1323 | CustomMaterial.NoBlend. |
| 1324 | |
| 1325 | \value CustomMaterial.NoBlend |
| 1326 | \value CustomMaterial.Zero |
| 1327 | \value CustomMaterial.One |
| 1328 | \value CustomMaterial.SrcColor |
| 1329 | \value CustomMaterial.OneMinusSrcColor |
| 1330 | \value CustomMaterial.DstColor |
| 1331 | \value CustomMaterial.OneMinusDstColor |
| 1332 | \value CustomMaterial.SrcAlpha |
| 1333 | \value CustomMaterial.OneMinusSrcAlpha |
| 1334 | \value CustomMaterial.DstAlpha |
| 1335 | \value CustomMaterial.OneMinusDstAlpha |
| 1336 | \value CustomMaterial.ConstantColor |
| 1337 | \value CustomMaterial.OneMinusConstantColor |
| 1338 | \value CustomMaterial.ConstantAlpha |
| 1339 | \value CustomMaterial.OneMinusConstantAlpha |
| 1340 | \value CustomMaterial.SrcAlphaSaturate |
| 1341 | |
| 1342 | \note Both \l sourceBlend and \l destinationBlend needs to be set to a non-default |
| 1343 | value before blending is enabled. |
| 1344 | |
| 1345 | \sa sourceBlend |
| 1346 | */ |
| 1347 | |
| 1348 | /*! |
| 1349 | \qmlproperty real CustomMaterial::lineWidth |
| 1350 | |
| 1351 | This property determines the width of the lines rendered, when the geometry |
| 1352 | is using a primitive type of lines or line strips. The default value is |
| 1353 | 1.0. This property is not relevant when rendering other types of geometry, |
| 1354 | such as, triangle meshes. |
| 1355 | |
| 1356 | \warning Line widths other than 1 may not be suported at run time, |
| 1357 | depending on the underlying graphics API. When that is the case, the |
| 1358 | request to change the width is ignored. For example, none of the following |
| 1359 | can be expected to support wide lines: Direct3D, Metal, OpenGL with core |
| 1360 | profile contexts. |
| 1361 | |
| 1362 | \note Unlike the line width, the value of which is part of the graphics |
| 1363 | pipeline object, the point size for geometries with a topology of points is |
| 1364 | controlled by the vertex shader (when supported), and has therefore no |
| 1365 | corresponding QML property. |
| 1366 | */ |
| 1367 | |
| 1368 | /*! |
| 1369 | \qmlproperty enumeration CustomMaterial::sourceAlphaBlend |
| 1370 | \since 6.7 |
| 1371 | |
| 1372 | Specifies the source alpha blend factor. The default value is \c |
| 1373 | CustomMaterial.NoBlend. This value is only actively used if \l sourceBlend and |
| 1374 | \l destinationBlend is set to a non-default value. |
| 1375 | |
| 1376 | \value CustomMaterial.NoBlend |
| 1377 | \value CustomMaterial.Zero |
| 1378 | \value CustomMaterial.One |
| 1379 | \value CustomMaterial.SrcColor |
| 1380 | \value CustomMaterial.OneMinusSrcColor |
| 1381 | \value CustomMaterial.DstColor |
| 1382 | \value CustomMaterial.OneMinusDstColor |
| 1383 | \value CustomMaterial.SrcAlpha |
| 1384 | \value CustomMaterial.OneMinusSrcAlpha |
| 1385 | \value CustomMaterial.DstAlpha |
| 1386 | \value CustomMaterial.OneMinusDstAlpha |
| 1387 | \value CustomMaterial.ConstantColor |
| 1388 | \value CustomMaterial.OneMinusConstantColor |
| 1389 | \value CustomMaterial.ConstantAlpha |
| 1390 | \value CustomMaterial.OneMinusConstantAlpha |
| 1391 | \value CustomMaterial.SrcAlphaSaturate |
| 1392 | |
| 1393 | \note For backwards compatibility purposes, when left to its default value, |
| 1394 | will be assigned the same value as \l sourceBlend when \l sourceBlend and |
| 1395 | \l destinationBlend is set to non-default values. |
| 1396 | |
| 1397 | \sa sourceBlend |
| 1398 | */ |
| 1399 | |
| 1400 | /*! |
| 1401 | \qmlproperty enumeration CustomMaterial::destinationAlphaBlend |
| 1402 | \since 6.7 |
| 1403 | |
| 1404 | Specifies the destination alpha blend factor. The default value is \c |
| 1405 | CustomMaterial.NoBlend. This value is only actively used if \l sourceBlend and |
| 1406 | \l destinationBlend is set to a non-default value. |
| 1407 | |
| 1408 | \value CustomMaterial.NoBlend |
| 1409 | \value CustomMaterial.Zero |
| 1410 | \value CustomMaterial.One |
| 1411 | \value CustomMaterial.SrcColor |
| 1412 | \value CustomMaterial.OneMinusSrcColor |
| 1413 | \value CustomMaterial.DstColor |
| 1414 | \value CustomMaterial.OneMinusDstColor |
| 1415 | \value CustomMaterial.SrcAlpha |
| 1416 | \value CustomMaterial.OneMinusSrcAlpha |
| 1417 | \value CustomMaterial.DstAlpha |
| 1418 | \value CustomMaterial.OneMinusDstAlpha |
| 1419 | \value CustomMaterial.ConstantColor |
| 1420 | \value CustomMaterial.OneMinusConstantColor |
| 1421 | \value CustomMaterial.ConstantAlpha |
| 1422 | \value CustomMaterial.OneMinusConstantAlpha |
| 1423 | \value CustomMaterial.SrcAlphaSaturate |
| 1424 | |
| 1425 | \note For backwards compatibility purposes, when left to its default value, |
| 1426 | will be assigned the same value as \l destinationBlend when \l sourceBlend and |
| 1427 | \l destinationBlend is set to non-default values. |
| 1428 | |
| 1429 | \sa destinationBlend |
| 1430 | */ |
| 1431 | |
| 1432 | static inline QRhiGraphicsPipeline::BlendFactor toRhiBlendFactor(QQuick3DCustomMaterial::BlendMode mode) |
| 1433 | { |
| 1434 | switch (mode) { |
| 1435 | case QQuick3DCustomMaterial::BlendMode::Zero: |
| 1436 | return QRhiGraphicsPipeline::Zero; |
| 1437 | case QQuick3DCustomMaterial::BlendMode::One: |
| 1438 | return QRhiGraphicsPipeline::One; |
| 1439 | case QQuick3DCustomMaterial::BlendMode::SrcColor: |
| 1440 | return QRhiGraphicsPipeline::SrcColor; |
| 1441 | case QQuick3DCustomMaterial::BlendMode::OneMinusSrcColor: |
| 1442 | return QRhiGraphicsPipeline::OneMinusSrcColor; |
| 1443 | case QQuick3DCustomMaterial::BlendMode::DstColor: |
| 1444 | return QRhiGraphicsPipeline::DstColor; |
| 1445 | case QQuick3DCustomMaterial::BlendMode::OneMinusDstColor: |
| 1446 | return QRhiGraphicsPipeline::OneMinusDstColor; |
| 1447 | case QQuick3DCustomMaterial::BlendMode::SrcAlpha: |
| 1448 | return QRhiGraphicsPipeline::SrcAlpha; |
| 1449 | case QQuick3DCustomMaterial::BlendMode::OneMinusSrcAlpha: |
| 1450 | return QRhiGraphicsPipeline::OneMinusSrcAlpha; |
| 1451 | case QQuick3DCustomMaterial::BlendMode::DstAlpha: |
| 1452 | return QRhiGraphicsPipeline::DstAlpha; |
| 1453 | case QQuick3DCustomMaterial::BlendMode::OneMinusDstAlpha: |
| 1454 | return QRhiGraphicsPipeline::OneMinusDstAlpha; |
| 1455 | case QQuick3DCustomMaterial::BlendMode::ConstantColor: |
| 1456 | return QRhiGraphicsPipeline::ConstantColor; |
| 1457 | case QQuick3DCustomMaterial::BlendMode::OneMinusConstantColor: |
| 1458 | return QRhiGraphicsPipeline::OneMinusConstantColor; |
| 1459 | case QQuick3DCustomMaterial::BlendMode::ConstantAlpha: |
| 1460 | return QRhiGraphicsPipeline::ConstantAlpha; |
| 1461 | case QQuick3DCustomMaterial::BlendMode::OneMinusConstantAlpha: |
| 1462 | return QRhiGraphicsPipeline::OneMinusConstantAlpha; |
| 1463 | case QQuick3DCustomMaterial::BlendMode::SrcAlphaSaturate: |
| 1464 | return QRhiGraphicsPipeline::SrcAlphaSaturate; |
| 1465 | default: |
| 1466 | return QRhiGraphicsPipeline::One; |
| 1467 | } |
| 1468 | } |
| 1469 | |
| 1470 | QQuick3DCustomMaterial::QQuick3DCustomMaterial(QQuick3DObject *parent) |
| 1471 | : QQuick3DMaterial(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::CustomMaterial)), parent) |
| 1472 | { |
| 1473 | } |
| 1474 | |
| 1475 | QQuick3DCustomMaterial::~QQuick3DCustomMaterial() {} |
| 1476 | |
| 1477 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::srcBlend() const |
| 1478 | { |
| 1479 | return m_srcBlend; |
| 1480 | } |
| 1481 | |
| 1482 | void QQuick3DCustomMaterial::setSrcBlend(BlendMode mode) |
| 1483 | { |
| 1484 | if (m_srcBlend == mode) |
| 1485 | return; |
| 1486 | |
| 1487 | m_srcBlend = mode; |
| 1488 | update(); |
| 1489 | emit srcBlendChanged(); |
| 1490 | } |
| 1491 | |
| 1492 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::dstBlend() const |
| 1493 | { |
| 1494 | return m_dstBlend; |
| 1495 | } |
| 1496 | |
| 1497 | void QQuick3DCustomMaterial::setDstBlend(BlendMode mode) |
| 1498 | { |
| 1499 | if (m_dstBlend == mode) |
| 1500 | return; |
| 1501 | |
| 1502 | m_dstBlend = mode; |
| 1503 | update(); |
| 1504 | emit dstBlendChanged(); |
| 1505 | } |
| 1506 | |
| 1507 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::srcAlphaBlend() const |
| 1508 | { |
| 1509 | return m_srcAlphaBlend; |
| 1510 | } |
| 1511 | |
| 1512 | void QQuick3DCustomMaterial::setSrcAlphaBlend(QQuick3DCustomMaterial::BlendMode mode) |
| 1513 | { |
| 1514 | if (m_srcAlphaBlend == mode) |
| 1515 | return; |
| 1516 | |
| 1517 | m_srcAlphaBlend = mode; |
| 1518 | update(); |
| 1519 | emit srcAlphaBlendChanged(); |
| 1520 | } |
| 1521 | |
| 1522 | QQuick3DCustomMaterial::BlendMode QQuick3DCustomMaterial::dstAlphaBlend() const |
| 1523 | { |
| 1524 | return m_dstAlphaBlend; |
| 1525 | } |
| 1526 | |
| 1527 | void QQuick3DCustomMaterial::setDstAlphaBlend(QQuick3DCustomMaterial::BlendMode mode) |
| 1528 | { |
| 1529 | if (m_dstAlphaBlend == mode) |
| 1530 | return; |
| 1531 | |
| 1532 | m_dstAlphaBlend = mode; |
| 1533 | update(); |
| 1534 | emit dstAlphaBlendChanged(); |
| 1535 | } |
| 1536 | |
| 1537 | QQuick3DCustomMaterial::ShadingMode QQuick3DCustomMaterial::shadingMode() const |
| 1538 | { |
| 1539 | return m_shadingMode; |
| 1540 | } |
| 1541 | |
| 1542 | void QQuick3DCustomMaterial::setShadingMode(ShadingMode mode) |
| 1543 | { |
| 1544 | if (m_shadingMode == mode) |
| 1545 | return; |
| 1546 | |
| 1547 | m_shadingMode = mode; |
| 1548 | markDirty(that&: *this, type: Dirty::ShaderSettingsDirty); |
| 1549 | emit shadingModeChanged(); |
| 1550 | } |
| 1551 | |
| 1552 | QUrl QQuick3DCustomMaterial::vertexShader() const |
| 1553 | { |
| 1554 | return m_vertexShader; |
| 1555 | } |
| 1556 | |
| 1557 | void QQuick3DCustomMaterial::setVertexShader(const QUrl &url) |
| 1558 | { |
| 1559 | if (m_vertexShader == url) |
| 1560 | return; |
| 1561 | |
| 1562 | m_vertexShader = url; |
| 1563 | markDirty(that&: *this, type: Dirty::ShaderSettingsDirty); |
| 1564 | emit vertexShaderChanged(); |
| 1565 | } |
| 1566 | |
| 1567 | QUrl QQuick3DCustomMaterial::fragmentShader() const |
| 1568 | { |
| 1569 | return m_fragmentShader; |
| 1570 | } |
| 1571 | |
| 1572 | void QQuick3DCustomMaterial::setFragmentShader(const QUrl &url) |
| 1573 | { |
| 1574 | if (m_fragmentShader == url) |
| 1575 | return; |
| 1576 | |
| 1577 | m_fragmentShader = url; |
| 1578 | markDirty(that&: *this, type: Dirty::ShaderSettingsDirty); |
| 1579 | emit fragmentShaderChanged(); |
| 1580 | } |
| 1581 | |
| 1582 | |
| 1583 | QString QQuick3DCustomMaterial::vertexShaderCode() const |
| 1584 | { |
| 1585 | return m_vertexShaderCode; |
| 1586 | } |
| 1587 | |
| 1588 | void QQuick3DCustomMaterial::setVertexShaderCode(const QString &code) |
| 1589 | { |
| 1590 | if (m_vertexShaderCode == code) |
| 1591 | return; |
| 1592 | |
| 1593 | m_vertexShaderCode = code; |
| 1594 | markDirty(that&: *this, type: Dirty::ShaderSettingsDirty); |
| 1595 | emit vertexShaderCodeChanged(); |
| 1596 | } |
| 1597 | |
| 1598 | QString QQuick3DCustomMaterial::fragmentShaderCode() const |
| 1599 | { |
| 1600 | return m_fragmentShaderCode; |
| 1601 | } |
| 1602 | |
| 1603 | void QQuick3DCustomMaterial::setFragmentShaderCode(const QString &code) |
| 1604 | { |
| 1605 | if (m_fragmentShaderCode == code) |
| 1606 | return; |
| 1607 | |
| 1608 | m_fragmentShaderCode = code; |
| 1609 | markDirty(that&: *this, type: Dirty::ShaderSettingsDirty); |
| 1610 | emit fragmentShaderCodeChanged(); |
| 1611 | } |
| 1612 | |
| 1613 | float QQuick3DCustomMaterial::lineWidth() const |
| 1614 | { |
| 1615 | return m_lineWidth; |
| 1616 | } |
| 1617 | |
| 1618 | void QQuick3DCustomMaterial::setLineWidth(float width) |
| 1619 | { |
| 1620 | if (qFuzzyCompare(p1: m_lineWidth, p2: width)) |
| 1621 | return; |
| 1622 | m_lineWidth = width; |
| 1623 | update(); |
| 1624 | emit lineWidthChanged(); |
| 1625 | } |
| 1626 | |
| 1627 | void QQuick3DCustomMaterial::markAllDirty() |
| 1628 | { |
| 1629 | m_dirtyAttributes |= Dirty::AllDirty; |
| 1630 | QQuick3DMaterial::markAllDirty(); |
| 1631 | } |
| 1632 | |
| 1633 | void QQuick3DCustomMaterial::markDirty(QQuick3DCustomMaterial &that, Dirty type) |
| 1634 | { |
| 1635 | if (!(that.m_dirtyAttributes & quint32(type))) { |
| 1636 | that.m_dirtyAttributes |= quint32(type); |
| 1637 | that.update(); |
| 1638 | } |
| 1639 | } |
| 1640 | |
| 1641 | bool QQuick3DCustomMaterial::alwaysDirty() const |
| 1642 | { |
| 1643 | return m_alwaysDirty; |
| 1644 | } |
| 1645 | |
| 1646 | void QQuick3DCustomMaterial::setAlwaysDirty(bool alwaysDirty) |
| 1647 | { |
| 1648 | if (m_alwaysDirty == alwaysDirty) |
| 1649 | return; |
| 1650 | |
| 1651 | m_alwaysDirty = alwaysDirty; |
| 1652 | update(); |
| 1653 | emit alwaysDirtyChanged(); |
| 1654 | } |
| 1655 | |
| 1656 | static void setCustomMaterialFlagsFromShader(QSSGRenderCustomMaterial *material, const QSSGCustomShaderMetaData &meta) |
| 1657 | { |
| 1658 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesScreenTexture)) |
| 1659 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenTexture, on: true); |
| 1660 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesScreenMipTexture)) |
| 1661 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture, on: true); |
| 1662 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesDepthTexture)) |
| 1663 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::DepthTexture, on: true); |
| 1664 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesAoTexture)) |
| 1665 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::AoTexture, on: true); |
| 1666 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesProjectionMatrix)) |
| 1667 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ProjectionMatrix, on: true); |
| 1668 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesInverseProjectionMatrix)) |
| 1669 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::InverseProjectionMatrix, on: true); |
| 1670 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesVarColor)) |
| 1671 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::VarColor, on: true); |
| 1672 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesIblOrientation)) |
| 1673 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::IblOrientation, on: true); |
| 1674 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesLightmap)) |
| 1675 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Lightmap, on: true); |
| 1676 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesSkinning)) |
| 1677 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Skinning, on: true); |
| 1678 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesMorphing)) |
| 1679 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Morphing, on: true); |
| 1680 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesViewIndex)) |
| 1681 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ViewIndex, on: true); |
| 1682 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesClearcoat)) |
| 1683 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Clearcoat, on: true); |
| 1684 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesClearcoatFresnelScaleBias)) |
| 1685 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ClearcoatFresnelScaleBias, on: true); |
| 1686 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesFresnelScaleBias)) |
| 1687 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::FresnelScaleBias, on: true); |
| 1688 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesTransmission)) { |
| 1689 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Transmission, on: true); |
| 1690 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenTexture, on: true); |
| 1691 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture, on: true); |
| 1692 | } |
| 1693 | |
| 1694 | // vertex only |
| 1695 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::OverridesPosition)) |
| 1696 | material->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::OverridesPosition, on: true); |
| 1697 | |
| 1698 | // fragment only |
| 1699 | if (meta.flags.testFlag(flag: QSSGCustomShaderMetaData::UsesSharedVars)) |
| 1700 | material->m_usesSharedVariables = true; |
| 1701 | } |
| 1702 | |
| 1703 | static QByteArray prepareCustomShader(QSSGRenderCustomMaterial *customMaterial, |
| 1704 | const QSSGShaderCustomMaterialAdapter::StringPairList &uniforms, |
| 1705 | const QByteArray &snippet, |
| 1706 | QSSGShaderCache::ShaderType shaderType, |
| 1707 | QSSGCustomShaderMetaData &meta, |
| 1708 | bool multiViewCompatible) |
| 1709 | { |
| 1710 | if (snippet.isEmpty()) |
| 1711 | return QByteArray(); |
| 1712 | |
| 1713 | QByteArray sourceCode = snippet; |
| 1714 | QByteArray buf; |
| 1715 | auto result = QSSGShaderCustomMaterialAdapter::prepareCustomShader(dst&: buf, |
| 1716 | shaderCode: sourceCode, |
| 1717 | type: shaderType, |
| 1718 | baseUniforms: uniforms, |
| 1719 | baseInputs: {}, |
| 1720 | baseOutputs: {}, |
| 1721 | multiViewCompatible); |
| 1722 | sourceCode = result.first; |
| 1723 | sourceCode.append(a: buf); |
| 1724 | meta = result.second; |
| 1725 | setCustomMaterialFlagsFromShader(material: customMaterial, meta); |
| 1726 | return sourceCode; |
| 1727 | } |
| 1728 | |
| 1729 | QSSGRenderGraphObject *QQuick3DCustomMaterial::updateSpatialNode(QSSGRenderGraphObject *node) |
| 1730 | { |
| 1731 | using namespace QSSGShaderUtils; |
| 1732 | |
| 1733 | const auto &renderContext = QQuick3DObjectPrivate::get(item: this)->sceneManager->wattached->rci(); |
| 1734 | if (!renderContext) { |
| 1735 | qWarning(msg: "QQuick3DCustomMaterial: No render context interface?" ); |
| 1736 | return nullptr; |
| 1737 | } |
| 1738 | |
| 1739 | QSSGShaderCustomMaterialAdapter::StringPairList uniforms; |
| 1740 | QSSGRenderCustomMaterial *customMaterial = static_cast<QSSGRenderCustomMaterial *>(node); |
| 1741 | bool newBackendNode = false; |
| 1742 | bool shadersMayChange = false; |
| 1743 | if (!customMaterial) { |
| 1744 | customMaterial = new QSSGRenderCustomMaterial; |
| 1745 | newBackendNode = true; |
| 1746 | } else if (m_dirtyAttributes & ShaderSettingsDirty) { |
| 1747 | shadersMayChange = true; |
| 1748 | } |
| 1749 | |
| 1750 | if (newBackendNode || shadersMayChange) { |
| 1751 | markAllDirty(); |
| 1752 | |
| 1753 | customMaterial->m_properties.clear(); |
| 1754 | customMaterial->m_textureProperties.clear(); |
| 1755 | |
| 1756 | customMaterial->m_shadingMode = QSSGRenderCustomMaterial::ShadingMode(int(m_shadingMode)); |
| 1757 | |
| 1758 | QMetaMethod propertyDirtyMethod; |
| 1759 | const int idx = metaObject()->indexOfSlot(slot: "onPropertyDirty()" ); |
| 1760 | if (idx != -1) |
| 1761 | propertyDirtyMethod = metaObject()->method(index: idx); |
| 1762 | |
| 1763 | const int propCount = metaObject()->propertyCount(); |
| 1764 | int propOffset = metaObject()->propertyOffset(); |
| 1765 | |
| 1766 | // Custom materials can have multilayered inheritance structure, so find the actual propOffset |
| 1767 | const QMetaObject *superClass = metaObject()->superClass(); |
| 1768 | while (superClass && qstrcmp(str1: superClass->className(), str2: "QQuick3DCustomMaterial" ) != 0) { |
| 1769 | propOffset = superClass->propertyOffset(); |
| 1770 | superClass = superClass->superClass(); |
| 1771 | } |
| 1772 | |
| 1773 | using TextureInputProperty = QPair<QQuick3DShaderUtilsTextureInput *, const char *>; |
| 1774 | QVector<TextureInputProperty> textureProperties; // We'll deal with these later |
| 1775 | |
| 1776 | for (int i = propOffset; i != propCount; ++i) { |
| 1777 | const auto property = metaObject()->property(index: i); |
| 1778 | if (Q_UNLIKELY(!property.isValid())) |
| 1779 | continue; |
| 1780 | |
| 1781 | const auto name = property.name(); |
| 1782 | QMetaType propType = property.metaType(); |
| 1783 | QVariant propValue = property.read(obj: this); |
| 1784 | if (propType == QMetaType(QMetaType::QVariant)) |
| 1785 | propType = propValue.metaType(); |
| 1786 | |
| 1787 | if (propType.id() >= QMetaType::User) { |
| 1788 | if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) { |
| 1789 | if (QQuick3DShaderUtilsTextureInput *texture = property.read(obj: this).value<QQuick3DShaderUtilsTextureInput *>()) |
| 1790 | textureProperties.push_back(t: {texture, name}); |
| 1791 | } |
| 1792 | } else if (propType == QMetaType(QMetaType::QObjectStar)) { |
| 1793 | if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(object: propValue.value<QObject *>())) |
| 1794 | textureProperties.push_back(t: {texture, name}); |
| 1795 | } else { |
| 1796 | const auto type = uniformType(type: propType); |
| 1797 | if (type != QSSGRenderShaderValue::Unknown) { |
| 1798 | uniforms.append(t: { uniformTypeName(type: propType), name }); |
| 1799 | customMaterial->m_properties.push_back(t: { name, propValue, uniformType(type: propType), i}); |
| 1800 | if (newBackendNode) { |
| 1801 | // Track the property changes |
| 1802 | if (property.hasNotifySignal() && propertyDirtyMethod.isValid()) |
| 1803 | connect(sender: this, signal: property.notifySignal(), receiver: this, method: propertyDirtyMethod); |
| 1804 | } // else already connected |
| 1805 | } else { |
| 1806 | // ### figure out how _not_ to warn when there are no dynamic |
| 1807 | // properties defined (because warnings like Blah blah objectName etc. are not helpful) |
| 1808 | //qWarning("No known uniform conversion found for effect property %s. Skipping", property.name()); |
| 1809 | } |
| 1810 | } |
| 1811 | } |
| 1812 | |
| 1813 | const auto processTextureProperty = [&](QQuick3DShaderUtilsTextureInput &texture, const QByteArray &name) { |
| 1814 | texture.name = name; |
| 1815 | |
| 1816 | QSSGRenderCustomMaterial::TextureProperty textureData; |
| 1817 | textureData.texInput = &texture; |
| 1818 | textureData.name = name; |
| 1819 | textureData.shaderDataType = QSSGRenderShaderValue::Texture; |
| 1820 | |
| 1821 | if (newBackendNode) { |
| 1822 | connect(sender: &texture, signal: &QQuick3DShaderUtilsTextureInput::enabledChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 1823 | connect(sender: &texture, signal: &QQuick3DShaderUtilsTextureInput::textureChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 1824 | } // else already connected |
| 1825 | |
| 1826 | QQuick3DTexture *tex = texture.texture(); // may be null if the TextureInput has no 'texture' set |
| 1827 | if (tex && QQuick3DObjectPrivate::get(item: tex)->type == QQuick3DObjectPrivate::Type::ImageCube) |
| 1828 | uniforms.append(t: { QByteArrayLiteral("samplerCube" ), textureData.name }); |
| 1829 | else if (tex && tex->textureData() && tex->textureData()->depth() > 0) |
| 1830 | uniforms.append(t: { QByteArrayLiteral("sampler3D" ), textureData.name }); |
| 1831 | else |
| 1832 | uniforms.append(t: { QByteArrayLiteral("sampler2D" ), textureData.name }); |
| 1833 | |
| 1834 | customMaterial->m_textureProperties.push_back(t: textureData); |
| 1835 | }; |
| 1836 | |
| 1837 | for (const auto &textureProperty : std::as_const(t&: textureProperties)) |
| 1838 | processTextureProperty(*textureProperty.first, textureProperty.second); |
| 1839 | |
| 1840 | if (customMaterial->incompleteBuildTimeObject || (m_dirtyAttributes & DynamicPropertiesDirty)) { // This object came from the shadergen tool |
| 1841 | const auto names = dynamicPropertyNames(); |
| 1842 | for (const auto &name : names) { |
| 1843 | QVariant propValue = property(name: name.constData()); |
| 1844 | QMetaType propType = propValue.metaType(); |
| 1845 | if (propType == QMetaType(QMetaType::QVariant)) |
| 1846 | propType = propValue.metaType(); |
| 1847 | |
| 1848 | if (propType.id() >= QMetaType::User) { |
| 1849 | if (propType.id() == qMetaTypeId<QQuick3DShaderUtilsTextureInput *>()) { |
| 1850 | if (QQuick3DShaderUtilsTextureInput *texture = propValue.value<QQuick3DShaderUtilsTextureInput *>()) |
| 1851 | textureProperties.push_back(t: {texture, name}); |
| 1852 | } |
| 1853 | } else if (propType.id() == QMetaType::QObjectStar) { |
| 1854 | if (QQuick3DShaderUtilsTextureInput *texture = qobject_cast<QQuick3DShaderUtilsTextureInput *>(object: propValue.value<QObject *>())) |
| 1855 | textureProperties.push_back(t: {texture, name}); |
| 1856 | } else { |
| 1857 | const auto type = uniformType(type: propType); |
| 1858 | if (type != QSSGRenderShaderValue::Unknown) { |
| 1859 | uniforms.append(t: { uniformTypeName(type: propType), name }); |
| 1860 | customMaterial->m_properties.push_back(t: { name, propValue, |
| 1861 | uniformType(type: propType), -1 /* aka. dynamic property */}); |
| 1862 | // We don't need to track property changes |
| 1863 | } else { |
| 1864 | // ### figure out how _not_ to warn when there are no dynamic |
| 1865 | // properties defined (because warnings like Blah blah objectName etc. are not helpful) |
| 1866 | qWarning(msg: "No known uniform conversion found for custom material property %s. Skipping" , name.constData()); |
| 1867 | } |
| 1868 | } |
| 1869 | } |
| 1870 | |
| 1871 | for (const auto &property : std::as_const(t&: textureProperties)) |
| 1872 | processTextureProperty(*property.first, property.second); |
| 1873 | } |
| 1874 | |
| 1875 | const QQmlContext *context = qmlContext(this); |
| 1876 | QByteArray vertex; |
| 1877 | QByteArray fragment; |
| 1878 | QByteArray vertexProcessed[2]; |
| 1879 | QSSGCustomShaderMetaData vertexMeta; |
| 1880 | QByteArray fragmentProcessed[2]; |
| 1881 | QSSGCustomShaderMetaData fragmentMeta; |
| 1882 | QByteArray shaderPathKey("custom material --" ); |
| 1883 | |
| 1884 | customMaterial->m_renderFlags = {}; |
| 1885 | |
| 1886 | if (!m_vertexShader.isEmpty()) |
| 1887 | vertex = QSSGShaderUtils::resolveShader(fileUrl: m_vertexShader, context, shaderPathKey); |
| 1888 | else if (!m_vertexShaderCode.isEmpty()) |
| 1889 | vertex = m_vertexShaderCode.toLatin1(); |
| 1890 | |
| 1891 | if (!m_fragmentShader.isEmpty()) |
| 1892 | fragment = QSSGShaderUtils::resolveShader(fileUrl: m_fragmentShader, context, shaderPathKey); |
| 1893 | else if (!m_fragmentShaderCode.isEmpty()) |
| 1894 | fragment = m_fragmentShaderCode.toLatin1(); |
| 1895 | |
| 1896 | // Multiview is a problem, because we will get a dedicated snippet after |
| 1897 | // preparation (the one that has [qt_viewIndex] added where it matters). |
| 1898 | // But at least the view count plays no role here on this level. So one |
| 1899 | // normal and one multiview "variant" is good enough. |
| 1900 | |
| 1901 | vertexProcessed[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex] = |
| 1902 | prepareCustomShader(customMaterial, uniforms, snippet: vertex, shaderType: QSSGShaderCache::ShaderType::Vertex, meta&: vertexMeta, multiViewCompatible: false); |
| 1903 | fragmentProcessed[QSSGRenderCustomMaterial::RegularShaderPathKeyIndex] = |
| 1904 | prepareCustomShader(customMaterial, uniforms, snippet: fragment, shaderType: QSSGShaderCache::ShaderType::Fragment, meta&: fragmentMeta, multiViewCompatible: false); |
| 1905 | |
| 1906 | vertexProcessed[QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex] = |
| 1907 | prepareCustomShader(customMaterial, uniforms, snippet: vertex, shaderType: QSSGShaderCache::ShaderType::Vertex, meta&: vertexMeta, multiViewCompatible: true); |
| 1908 | fragmentProcessed[QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex] = |
| 1909 | prepareCustomShader(customMaterial, uniforms, snippet: fragment, shaderType: QSSGShaderCache::ShaderType::Fragment, meta&: fragmentMeta, multiViewCompatible: true); |
| 1910 | |
| 1911 | // At this point we have snippets that look like this: |
| 1912 | // - the original code, with VARYING ... lines removed |
| 1913 | // - followed by QQ3D_SHADER_META block for uniforms |
| 1914 | // - followed by QQ3D_SHADER_META block for inputs/outputs |
| 1915 | |
| 1916 | customMaterial->m_customShaderPresence = {}; |
| 1917 | for (int i : { QSSGRenderCustomMaterial::RegularShaderPathKeyIndex, QSSGRenderCustomMaterial::MultiViewShaderPathKeyIndex }) { |
| 1918 | if (vertexProcessed[i].isEmpty() && fragmentProcessed[i].isEmpty()) |
| 1919 | continue; |
| 1920 | |
| 1921 | const QByteArray key = shaderPathKey + ':' + QCryptographicHash::hash(data: QByteArray(vertexProcessed[i] + fragmentProcessed[i]), method: QCryptographicHash::Algorithm::Sha1).toHex(); |
| 1922 | // the processed snippet code is different for regular and multiview, so 'key' reflects that already |
| 1923 | customMaterial->m_shaderPathKey[i] = key; |
| 1924 | if (!vertexProcessed[i].isEmpty()) { |
| 1925 | customMaterial->m_customShaderPresence.setFlag(flag: QSSGRenderCustomMaterial::CustomShaderPresenceFlag::Vertex); |
| 1926 | renderContext->shaderLibraryManager()->setShaderSource(inShaderPathKey: key, type: QSSGShaderCache::ShaderType::Vertex, inSource: vertexProcessed[i], meta: vertexMeta); |
| 1927 | } |
| 1928 | if (!fragmentProcessed[i].isEmpty()) { |
| 1929 | customMaterial->m_customShaderPresence.setFlag(flag: QSSGRenderCustomMaterial::CustomShaderPresenceFlag::Fragment); |
| 1930 | renderContext->shaderLibraryManager()->setShaderSource(inShaderPathKey: key, type: QSSGShaderCache::ShaderType::Fragment, inSource: fragmentProcessed[i], meta: fragmentMeta); |
| 1931 | } |
| 1932 | } |
| 1933 | } |
| 1934 | |
| 1935 | customMaterial->setAlwaysDirty(m_alwaysDirty); |
| 1936 | if (m_srcBlend != BlendMode::NoBlend && m_dstBlend != BlendMode::NoBlend) { // both must be set to something other than NoBlend |
| 1937 | customMaterial->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Blending, on: true); |
| 1938 | customMaterial->m_srcBlend = toRhiBlendFactor(mode: m_srcBlend); |
| 1939 | customMaterial->m_dstBlend = toRhiBlendFactor(mode: m_dstBlend); |
| 1940 | // alpha blending is only active if rgb blending is |
| 1941 | if (m_srcAlphaBlend != BlendMode::NoBlend && m_dstAlphaBlend != BlendMode::NoBlend) { |
| 1942 | customMaterial->m_srcAlphaBlend = toRhiBlendFactor(mode: m_srcAlphaBlend); |
| 1943 | customMaterial->m_dstAlphaBlend = toRhiBlendFactor(mode: m_dstAlphaBlend); |
| 1944 | } else { |
| 1945 | customMaterial->m_srcAlphaBlend = customMaterial->m_srcBlend; |
| 1946 | customMaterial->m_dstAlphaBlend = customMaterial->m_dstBlend; |
| 1947 | } |
| 1948 | } else { |
| 1949 | customMaterial->m_renderFlags.setFlag(flag: QSSGRenderCustomMaterial::RenderFlag::Blending, on: false); |
| 1950 | } |
| 1951 | customMaterial->m_lineWidth = m_lineWidth; |
| 1952 | |
| 1953 | QQuick3DMaterial::updateSpatialNode(node: customMaterial); |
| 1954 | |
| 1955 | if (m_dirtyAttributes & Dirty::PropertyDirty) { |
| 1956 | for (auto &prop : customMaterial->m_properties) { |
| 1957 | auto p = metaObject()->property(index: prop.pid); |
| 1958 | if (Q_LIKELY(p.isValid())) |
| 1959 | prop.value = p.read(obj: this); |
| 1960 | } |
| 1961 | } |
| 1962 | |
| 1963 | if (m_dirtyAttributes & Dirty::TextureDirty) { |
| 1964 | for (QSSGRenderCustomMaterial::TextureProperty &prop : customMaterial->m_textureProperties) { |
| 1965 | QQuick3DTexture *tex = prop.texInput->texture(); |
| 1966 | if (tex) { |
| 1967 | if (prop.texInput->enabled) |
| 1968 | prop.texImage = tex->getRenderImage(); |
| 1969 | else |
| 1970 | prop.texImage = nullptr; |
| 1971 | prop.minFilterType = tex->minFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest |
| 1972 | : QSSGRenderTextureFilterOp::Linear; |
| 1973 | prop.magFilterType = tex->magFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest |
| 1974 | : QSSGRenderTextureFilterOp::Linear; |
| 1975 | prop.mipFilterType = tex->generateMipmaps() ? (tex->mipFilter() == QQuick3DTexture::Nearest ? QSSGRenderTextureFilterOp::Nearest |
| 1976 | : QSSGRenderTextureFilterOp::Linear) |
| 1977 | : QSSGRenderTextureFilterOp::None; |
| 1978 | prop.horizontalClampType = tex->horizontalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat |
| 1979 | : (tex->horizontalTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge |
| 1980 | : QSSGRenderTextureCoordOp::MirroredRepeat; |
| 1981 | prop.verticalClampType = tex->verticalTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat |
| 1982 | : (tex->verticalTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge |
| 1983 | : QSSGRenderTextureCoordOp::MirroredRepeat; |
| 1984 | prop.zClampType = tex->depthTiling() == QQuick3DTexture::Repeat ? QSSGRenderTextureCoordOp::Repeat |
| 1985 | : (tex->depthTiling() == QQuick3DTexture::ClampToEdge) ? QSSGRenderTextureCoordOp::ClampToEdge |
| 1986 | : QSSGRenderTextureCoordOp::MirroredRepeat; |
| 1987 | } else { |
| 1988 | prop.texImage = nullptr; |
| 1989 | } |
| 1990 | |
| 1991 | if (tex != prop.lastConnectedTexture) { |
| 1992 | prop.lastConnectedTexture = tex; |
| 1993 | disconnect(prop.minFilterChangedConn); |
| 1994 | disconnect(prop.magFilterChangedConn); |
| 1995 | disconnect(prop.mipFilterChangedConn); |
| 1996 | disconnect(prop.horizontalTilingChangedConn); |
| 1997 | disconnect(prop.verticalTilingChangedConn); |
| 1998 | disconnect(prop.depthTilingChangedConn); |
| 1999 | if (tex) { |
| 2000 | prop.minFilterChangedConn = connect(sender: tex, signal: &QQuick3DTexture::minFilterChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 2001 | prop.magFilterChangedConn = connect(sender: tex, signal: &QQuick3DTexture::magFilterChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 2002 | prop.mipFilterChangedConn = connect(sender: tex, signal: &QQuick3DTexture::mipFilterChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 2003 | prop.horizontalTilingChangedConn = connect(sender: tex, signal: &QQuick3DTexture::horizontalTilingChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 2004 | prop.verticalTilingChangedConn = connect(sender: tex, signal: &QQuick3DTexture::verticalTilingChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 2005 | prop.depthTilingChangedConn = connect(sender: tex, signal: &QQuick3DTexture::depthTilingChanged, context: this, slot: &QQuick3DCustomMaterial::onTextureDirty); |
| 2006 | } |
| 2007 | } |
| 2008 | } |
| 2009 | } |
| 2010 | |
| 2011 | m_dirtyAttributes = 0; |
| 2012 | |
| 2013 | return customMaterial; |
| 2014 | } |
| 2015 | |
| 2016 | void QQuick3DCustomMaterial::itemChange(QQuick3DObject::ItemChange change, const QQuick3DObject::ItemChangeData &value) |
| 2017 | { |
| 2018 | QQuick3DMaterial::itemChange(change, value); |
| 2019 | if (change == QQuick3DObject::ItemSceneChange) { |
| 2020 | if (auto sceneManager = value.sceneManager) { |
| 2021 | for (const auto &it : std::as_const(t&: m_dynamicTextureMaps)) { |
| 2022 | if (auto tex = it->texture()) |
| 2023 | QQuick3DObjectPrivate::refSceneManager(obj: tex, mgr&: *sceneManager); |
| 2024 | } |
| 2025 | } else { |
| 2026 | for (const auto &it : std::as_const(t&: m_dynamicTextureMaps)) { |
| 2027 | if (auto tex = it->texture()) |
| 2028 | QQuick3DObjectPrivate::derefSceneManager(obj: tex); |
| 2029 | } |
| 2030 | } |
| 2031 | } |
| 2032 | } |
| 2033 | |
| 2034 | void QQuick3DCustomMaterial::onPropertyDirty() |
| 2035 | { |
| 2036 | markDirty(that&: *this, type: Dirty::PropertyDirty); |
| 2037 | update(); |
| 2038 | } |
| 2039 | |
| 2040 | void QQuick3DCustomMaterial::onTextureDirty() |
| 2041 | { |
| 2042 | markDirty(that&: *this, type: Dirty::TextureDirty); |
| 2043 | update(); |
| 2044 | } |
| 2045 | |
| 2046 | void QQuick3DCustomMaterial::setDynamicTextureMap(QQuick3DShaderUtilsTextureInput *textureMap) |
| 2047 | { |
| 2048 | // There can only be one texture input per property, as the texture input is a combination |
| 2049 | // of the texture used and the uniform name! |
| 2050 | auto it = m_dynamicTextureMaps.constFind(value: textureMap); |
| 2051 | |
| 2052 | if (it == m_dynamicTextureMaps.constEnd()) { |
| 2053 | // Track the object, if it's destroyed we need to remove it from our table. |
| 2054 | connect(sender: textureMap, signal: &QQuick3DShaderUtilsTextureInput::destroyed, context: this, slot: [this, textureMap]() { |
| 2055 | auto it = m_dynamicTextureMaps.constFind(value: textureMap); |
| 2056 | if (it != m_dynamicTextureMaps.constEnd()) |
| 2057 | m_dynamicTextureMaps.erase(i: it); |
| 2058 | }); |
| 2059 | m_dynamicTextureMaps.insert(value: textureMap); |
| 2060 | |
| 2061 | update(); |
| 2062 | } |
| 2063 | } |
| 2064 | |
| 2065 | QT_END_NAMESPACE |
| 2066 | |