├── Malt
├── __init__.py
├── Pipelines
│ ├── NPR_Pipeline
│ │ ├── Defaults
│ │ │ └── defaults.blend
│ │ ├── Shaders
│ │ │ ├── NPR_Intellisense.glsl
│ │ │ ├── NPR_ScreenShader.glsl
│ │ │ └── NPR_LightShader.glsl
│ │ ├── Nodes
│ │ │ ├── Render
│ │ │ │ └── ScreenPass.py
│ │ │ └── RenderLayer
│ │ │ │ └── MainPass.py
│ │ └── NPR_LightShaders.py
│ └── MiniPipeline
│ │ └── MiniPipeline.py
├── Shaders
│ ├── Node Utils 2
│ │ ├── Filter.glsl
│ │ ├── Mat4.glsl
│ │ ├── Bool.glsl
│ │ ├── Color.glsl
│ │ ├── node_utils_2.glsl
│ │ ├── Parameters.glsl
│ │ ├── Int.glsl
│ │ └── Vector.glsl
│ ├── Passes
│ │ ├── BlendTexture.glsl
│ │ ├── JumpFlood.glsl
│ │ ├── DepthToBlenderDepth.glsl
│ │ ├── Unpack8bitTextures.glsl
│ │ ├── sRGBConversion.glsl
│ │ ├── CopyTextures.glsl
│ │ ├── LineComposite.glsl
│ │ └── BlendTransparency.glsl
│ ├── Node Utils
│ │ ├── bool.glsl
│ │ ├── packing.glsl
│ │ ├── properties.glsl
│ │ ├── sampler.glsl
│ │ ├── node_utils.glsl
│ │ ├── int.glsl
│ │ ├── common.glsl
│ │ ├── vec2.glsl
│ │ ├── float.glsl
│ │ ├── vec4.glsl
│ │ └── vec3.glsl
│ ├── Procedural
│ │ ├── Fractal_Noise.inl
│ │ ├── Bayer.glsl
│ │ ├── Cell_Noise.inl
│ │ ├── Noise.inl
│ │ ├── Noise.glsl
│ │ └── Fractal_Noise.glsl
│ ├── Common
│ │ ├── Hash.glsl
│ │ ├── Matrix.glsl
│ │ ├── Quaternion.glsl
│ │ └── Mapping.glsl
│ ├── Filters
│ │ ├── StructureTensor.glsl
│ │ ├── JumpFlood.glsl
│ │ ├── Sharpen.glsl
│ │ ├── Curvature.glsl
│ │ ├── AO.glsl
│ │ └── Bevel.glsl
│ └── readme.md
├── Render
│ ├── readme.md
│ ├── DepthToCompositeDepth.py
│ ├── Sampling.py
│ └── Common.py
├── GL
│ ├── GLSLParser
│ │ ├── build.py
│ │ ├── CMakeLists.txt
│ │ └── external
│ │ │ ├── PEGTL-LICENSE
│ │ │ └── CMakeLists.txt
│ ├── GLSLEval.py
│ ├── GL.py
│ └── readme.md
├── Nodes
│ ├── SuperSamplingAA.py
│ ├── SceneFilter.py
│ └── LineRender.py
├── PipelinePlugin.py
├── Scene.py
└── PipelineNode.py
├── docs
├── overrides
│ └── partials
│ │ └── _footer.html
├── images
│ ├── become_a_patron.png
│ ├── creativeshrimp_logo.png
│ └── cube-solid.svg
├── From-Nodes-To-Code
│ ├── lighting.png
│ ├── textures.png
│ ├── all-together.png
│ ├── flat-color.png
│ └── starting-point.png
├── Documentation
│ ├── 2022-02-16-16-45-47.png
│ ├── 2022-02-16-17-05-31.png
│ ├── 2022-02-16-17-08-23.png
│ ├── 2022-02-16-17-20-18.png
│ ├── 2022-02-23-16-58-56.png
│ ├── 2022-02-23-17-03-18.png
│ ├── 2022-02-23-17-11-03.png
│ ├── 2022-02-25-19-56-34.png
│ ├── 2022-02-25-20-18-44.png
│ ├── 2022-03-21-15-36-42.png
│ ├── 2022-03-21-15-58-04.png
│ ├── 2022-03-21-18-05-53.png
│ ├── 2022-03-21-18-11-27.png
│ ├── 2022-03-21-18-12-34.png
│ ├── 2022-03-21-18-17-30.png
│ ├── 2022-03-21-19-27-33.png
│ ├── 2022-03-21-19-36-40.png
│ ├── 2022-04-02-17-34-35.png
│ ├── 2022-04-02-17-35-20.png
│ ├── 2022-04-02-17-36-17.png
│ ├── 2022-04-02-18-02-24.png
│ ├── parameter-override.gif
│ ├── Plugins.md
│ ├── Tooling.md
│ └── Getting Started.md
├── extra
│ ├── extra.js
│ └── extra.css
├── Setup-BlenderMalt-for-Development.md
├── FAQ.md
├── reference
│ ├── settings.md
│ ├── Render Layer-graph.md
│ └── Render-graph.md
└── index.md
├── mkdocs
├── requirements.txt
├── netlify.toml
└── mkdocs.yml
├── LICENSE - DEPENDENCIES (FULL TEXT)
├── scripts
├── PatchDependencies
│ ├── mcpp-Darwin
│ ├── mcpp-Linux
│ ├── mcpp-Windows.exe
│ ├── python-gpu-310.exe
│ ├── python-gpu-37.exe
│ ├── python-gpu-39.exe
│ └── python-gpu.c
├── settings.json
├── generate_license_dependencies_full.py
├── print_pixel_formats.py
├── get_glslang.py
├── install_dependencies.py
├── format.py
├── generate_conversion_nodes.py
└── setup_blender_addon.py
├── .gitignore
├── plugins
├── PluginExample
│ ├── Shaders
│ │ └── PluginExample.glsl
│ └── __init__.py
└── Experimental
│ ├── __init__.py
│ └── RenderLayer
│ └── OpaqueLayer.py
├── Bridge
├── Mesh.py
├── renderdoc
│ ├── CMakeLists.txt
│ ├── __init__.py
│ ├── build.py
│ └── renderdoc_wrapper.c
├── ipc
│ ├── CMakeLists.txt
│ ├── build.py
│ └── ipc.c
├── Texture.py
├── __init__.py
├── Proxys.py
└── Material.py
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ └── bug_report.yml
└── FUNDING.yml
├── BlenderMalt
├── CBlenderMalt
│ ├── CMakeLists.txt
│ ├── build.py
│ ├── blender_dna
│ │ ├── DNA_session_uuid_types.h
│ │ ├── DNA_listBase.h
│ │ └── BLI_sys_types.h
│ └── __init__.py
├── MaltNodes
│ ├── _init_.py
│ └── Nodes
│ │ ├── MaltStructNode.py
│ │ ├── MaltArrayIndexNode.py
│ │ ├── MaltFunctionSubCategory.py
│ │ └── MaltInlineNode.py
├── readme.md
├── MaltLights.py
└── MaltTextures.py
├── __init__.py
├── LICENSE
├── LICENSE - DEPENDENCIES
└── README.md
/Malt/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/overrides/partials/_footer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mkdocs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs-material==8.2.8
2 |
--------------------------------------------------------------------------------
/docs/images/become_a_patron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/images/become_a_patron.png
--------------------------------------------------------------------------------
/LICENSE - DEPENDENCIES (FULL TEXT):
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/LICENSE - DEPENDENCIES (FULL TEXT)
--------------------------------------------------------------------------------
/docs/images/creativeshrimp_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/images/creativeshrimp_logo.png
--------------------------------------------------------------------------------
/docs/From-Nodes-To-Code/lighting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/From-Nodes-To-Code/lighting.png
--------------------------------------------------------------------------------
/docs/From-Nodes-To-Code/textures.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/From-Nodes-To-Code/textures.png
--------------------------------------------------------------------------------
/scripts/PatchDependencies/mcpp-Darwin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/scripts/PatchDependencies/mcpp-Darwin
--------------------------------------------------------------------------------
/scripts/PatchDependencies/mcpp-Linux:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/scripts/PatchDependencies/mcpp-Linux
--------------------------------------------------------------------------------
/docs/From-Nodes-To-Code/all-together.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/From-Nodes-To-Code/all-together.png
--------------------------------------------------------------------------------
/docs/From-Nodes-To-Code/flat-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/From-Nodes-To-Code/flat-color.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-16-16-45-47.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-16-16-45-47.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-16-17-05-31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-16-17-05-31.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-16-17-08-23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-16-17-08-23.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-16-17-20-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-16-17-20-18.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-23-16-58-56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-23-16-58-56.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-23-17-03-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-23-17-03-18.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-23-17-11-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-23-17-11-03.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-25-19-56-34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-25-19-56-34.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-02-25-20-18-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-02-25-20-18-44.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-15-36-42.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-15-36-42.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-15-58-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-15-58-04.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-18-05-53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-18-05-53.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-18-11-27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-18-11-27.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-18-12-34.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-18-12-34.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-18-17-30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-18-17-30.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-19-27-33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-19-27-33.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-03-21-19-36-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-03-21-19-36-40.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-04-02-17-34-35.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-04-02-17-34-35.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-04-02-17-35-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-04-02-17-35-20.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-04-02-17-36-17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-04-02-17-36-17.png
--------------------------------------------------------------------------------
/docs/Documentation/2022-04-02-18-02-24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/2022-04-02-18-02-24.png
--------------------------------------------------------------------------------
/docs/Documentation/parameter-override.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/Documentation/parameter-override.gif
--------------------------------------------------------------------------------
/docs/From-Nodes-To-Code/starting-point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/docs/From-Nodes-To-Code/starting-point.png
--------------------------------------------------------------------------------
/scripts/PatchDependencies/mcpp-Windows.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/scripts/PatchDependencies/mcpp-Windows.exe
--------------------------------------------------------------------------------
/scripts/PatchDependencies/python-gpu-310.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/scripts/PatchDependencies/python-gpu-310.exe
--------------------------------------------------------------------------------
/scripts/PatchDependencies/python-gpu-37.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/scripts/PatchDependencies/python-gpu-37.exe
--------------------------------------------------------------------------------
/scripts/PatchDependencies/python-gpu-39.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/scripts/PatchDependencies/python-gpu-39.exe
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/Defaults/defaults.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underdoggit4/Malt/HEAD/Malt/Pipelines/NPR_Pipeline/Defaults/defaults.blend
--------------------------------------------------------------------------------
/mkdocs/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "site/"
3 | command = "mkdocs build"
4 | ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../docs/"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .*
2 | !.gitignore
3 | !.gitattributes
4 | !.gitmodules
5 |
6 | !.github
7 |
8 | __pycache__
9 |
10 | *.lib
11 | *.dll
12 | *.so
13 |
14 | *.log
15 |
16 |
--------------------------------------------------------------------------------
/plugins/PluginExample/Shaders/PluginExample.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 | #include "Common/Hash.glsl"
3 |
4 | vec4 instance_random_color()
5 | {
6 | return hash(ID[0]);
7 | }
8 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Filter.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_FILTER_GLSL
2 | #define NODE_UTILS_2_FILTER_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Filter;
6 | */
7 |
8 | #endif //NODE_UTILS_2_FILTER_GLSL
9 |
--------------------------------------------------------------------------------
/docs/extra/extra.js:
--------------------------------------------------------------------------------
1 | /* https://github.com/squidfunk/mkdocs-material/issues/1635#issuecomment-811058692 */
2 |
3 | document.querySelectorAll('.zoom').forEach(item => {
4 | item.addEventListener('click', function () {
5 | this.classList.toggle('image-zoom-large');
6 | })
7 | });
--------------------------------------------------------------------------------
/Bridge/Mesh.py:
--------------------------------------------------------------------------------
1 | MESHES = {}
2 |
3 | def load_mesh(pipeline, msg):
4 | name = msg['name']
5 | data = msg['data']
6 |
7 | MESHES[name] = pipeline.load_mesh(
8 | position = data['positions'],
9 | indices = data['indices'],
10 | normal = data['normals'],
11 | tangent = data['tangents'],
12 | uvs = data['uvs'],
13 | colors = data['colors']
14 | )
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
3 | contact_links:
4 | - name: Q&A
5 | url: https://github.com/bnpr/Malt/discussions/categories/q-a
6 | about: Ask your *How to* questions.
7 |
8 | - name: Feedback & Feature Requests
9 | url: https://github.com/bnpr/Malt/discussions/categories/feedback-feature-requests
10 | about: Post your feedback and feature requests.
11 |
12 |
--------------------------------------------------------------------------------
/scripts/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "*.pyx": "python",
4 | "*.glsl": "cpp"
5 | },
6 | "cmake.configureOnOpen": false,
7 | "C_Cpp.default.includePath": ["Malt\\Shaders"],
8 | "C_Cpp.autoAddFileAssociations": true,
9 | "C_Cpp.default.cppStandard": "c++03",
10 | "C_Cpp.default.compilerPath": "",
11 | "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true,
12 | "C_Cpp.errorSquiggles": "Disabled",
13 | }
--------------------------------------------------------------------------------
/Bridge/renderdoc/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 |
3 | # set(CMAKE_GENERATOR_PLATFORM x64)
4 |
5 | project(RenderDocWrapper)
6 |
7 | SET(CMAKE_BUILD_TYPE Release)
8 | SET(BUILD_SHARED_LIBS ON)
9 |
10 | add_library(renderdoc_wrapper renderdoc_wrapper.c)
11 |
12 | if(NOT WIN32)
13 | target_link_libraries(renderdoc_wrapper dl)
14 | endif()
15 |
16 | install(TARGETS renderdoc_wrapper CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR})
17 |
18 |
--------------------------------------------------------------------------------
/Bridge/ipc/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 |
3 | # set(CMAKE_GENERATOR_PLATFORM x64)
4 |
5 | project(Ipc)
6 |
7 | SET(CMAKE_BUILD_TYPE Release)
8 | SET(BUILD_SHARED_LIBS ON)
9 |
10 | add_library(Ipc ipc.c)
11 |
12 | if(NOT WIN32)
13 | target_link_libraries(Ipc pthread)
14 | if(NOT APPLE)
15 | target_link_libraries(Ipc rt)
16 | endif()
17 | endif()
18 |
19 | install(TARGETS Ipc CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR})
20 |
21 |
--------------------------------------------------------------------------------
/BlenderMalt/CBlenderMalt/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 |
3 | SET(CMAKE_CXX_STANDARD 17)
4 | # set(CMAKE_GENERATOR_PLATFORM x64)
5 |
6 | project(CBlenderMalt)
7 |
8 | SET(CMAKE_BUILD_TYPE Release)
9 | SET(BUILD_SHARED_LIBS ON)
10 |
11 | add_library(CBlenderMalt CBlenderMalt.cpp)
12 | target_include_directories(CBlenderMalt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/blender_dna)
13 |
14 | install(TARGETS CBlenderMalt CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR})
15 |
16 |
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Intellisense.glsl:
--------------------------------------------------------------------------------
1 | //This is just for getting text editor autocompletion on user shaders.
2 | //It doesn't have any effect at runtime
3 | #ifdef __INTELLISENSE__
4 |
5 | #define VERTEX_SHADER
6 | #define PIXEL_SHADER
7 |
8 | #define IS_MESH_SHADER
9 |
10 | #define CUSTOM_VERTEX_DISPLACEMENT
11 | #define SHADOW_PASS
12 | #define PRE_PASS
13 | #define MAIN_PASS
14 |
15 | #define IS_LIGHT_SHADER
16 |
17 | #define IS_SCREEN_SHADER
18 |
19 | #endif //__INTELLISENSE__
20 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/BlendTexture.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | uniform sampler2D blend_texture;
13 |
14 | layout (location = 0) out vec4 OUT_COLOR;
15 |
16 | void main()
17 | {
18 | PIXEL_SETUP_INPUT();
19 |
20 | vec4 color = texture(blend_texture, UV[0]);
21 | color.rgb *= color.a;
22 | OUT_COLOR = color;
23 | }
24 |
25 | #endif //PIXEL_SHADER
26 |
--------------------------------------------------------------------------------
/plugins/Experimental/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from Malt.PipelinePlugin import PipelinePlugin, isinstance_str
3 |
4 | class ExperimentalNodes(PipelinePlugin):
5 |
6 | @classmethod
7 | def poll_pipeline(self, pipeline):
8 | return isinstance_str(pipeline, 'NPR_Pipeline')
9 |
10 | @classmethod
11 | def register_graph_libraries(self, graphs):
12 | root = os.path.dirname(__file__)
13 | graphs['Render Layer'].add_library(os.path.join(root, 'RenderLayer'))
14 |
15 | PLUGIN = ExperimentalNodes
16 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/bool.glsl:
--------------------------------------------------------------------------------
1 | #ifndef BOOL_GLSL
2 | #define BOOL_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | bool bool_and(bool a, bool b) { return a && b; }
9 | bool bool_or(bool a, bool b) { return a || b; }
10 | bool bool_not(bool b) { return !b; }
11 |
12 | bool bool_equal(bool a, bool b){ return a == b; }
13 | bool bool_not_equal(bool a, bool b){ return a != b; }
14 |
15 | bool if_else(bool condition, bool if_true, bool if_false){ return condition ? if_true : if_false; }
16 |
17 | #endif //BOOL_GLSL
18 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/JumpFlood.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | #include "Filters/JumpFlood.glsl"
13 |
14 | uniform sampler2D input_texture;
15 | uniform float width;
16 |
17 | layout (location = 0) out vec4 OUT_RESULT;
18 |
19 | void main()
20 | {
21 | PIXEL_SETUP_INPUT();
22 |
23 | vec2 uv = screen_uv();
24 | OUT_RESULT = jump_flood(input_texture, uv, width, true);
25 | }
26 |
27 | #endif //PIXEL_SHADER
28 |
--------------------------------------------------------------------------------
/scripts/PatchDependencies/python-gpu.c:
--------------------------------------------------------------------------------
1 | /* Minimal main program -- everything is loaded from the library */
2 |
3 | #include "Python.h"
4 |
5 | #ifdef MS_WINDOWS
6 | typedef unsigned long DWORD;
7 | _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
8 | _declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0x00000001;
9 | int
10 | wmain(int argc, wchar_t **argv)
11 | {
12 | printf("Python GPU\n");
13 | return Py_Main(argc, argv);
14 | }
15 | #else
16 | int
17 | main(int argc, char **argv)
18 | {
19 | return _Py_UnixMain(argc, argv);
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/Bridge/renderdoc/__init__.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import ctypes
4 |
5 | import platform
6 |
7 | src_dir = os.path.abspath(os.path.dirname(__file__))
8 |
9 | library = 'librenderdoc_wrapper.so'
10 | if platform.system() == 'Windows': library = 'renderdoc_wrapper.dll'
11 | if platform.system() == 'Darwin': library = 'librenderdoc_wrapper.dylib'
12 |
13 | renderdoc = ctypes.CDLL(os.path.join(src_dir, library))
14 |
15 | capture_start = renderdoc['capture_start']
16 | capture_start.restype = None
17 |
18 | capture_end = renderdoc['capture_end']
19 | capture_end.restype = None
20 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Mat4.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_MAT4_GLSL
2 | #define NODE_UTILS_2_MAT4_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Math; subcategory=Matrix;
6 | */
7 |
8 | /* META
9 | @meta: label=Inverse;
10 | @a: label=Matrix; default=mat4(1);
11 | */
12 | mat4 Mat4_inverse(mat4 a)
13 | {
14 | return inverse(a);
15 | }
16 |
17 | /* META
18 | @meta: label=Multiply;
19 | @a: default=mat4(1);
20 | @b: default=mat4(1);
21 | */
22 | mat4 Mat4_multiply(mat4 a, mat4 b)
23 | {
24 | return a * b;
25 | }
26 |
27 | #endif //NODE_UTILS_2_MAT4_GLSL
28 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/DepthToBlenderDepth.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | uniform sampler2D DEPTH_TEXTURE;
13 | uniform int DEPTH_CHANNEL;
14 |
15 | layout (location = 0) out vec4 OUT_RESULT;
16 |
17 | void main()
18 | {
19 | PIXEL_SETUP_INPUT();
20 |
21 | float depth = texture(DEPTH_TEXTURE, UV[0])[DEPTH_CHANNEL];
22 | vec3 camera = screen_to_camera(UV[0], depth);
23 | OUT_RESULT.r = -camera.z;
24 | }
25 |
26 | #endif //PIXEL_SHADER
27 |
--------------------------------------------------------------------------------
/plugins/PluginExample/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from Malt.PipelinePlugin import PipelinePlugin, isinstance_str
3 |
4 | class PluginExample(PipelinePlugin):
5 |
6 | @classmethod
7 | def poll_pipeline(self, pipeline):
8 | return isinstance_str(pipeline, 'NPR_Pipeline')
9 |
10 | @classmethod
11 | def register_graph_libraries(self, graphs):
12 | library_path = os.path.join(os.path.dirname(__file__), 'Shaders')
13 | for graph in graphs.values():
14 | if graph.language == 'GLSL':
15 | graph.add_library(library_path)
16 |
17 | PLUGIN = PluginExample
18 |
--------------------------------------------------------------------------------
/docs/images/cube-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/packing.glsl:
--------------------------------------------------------------------------------
1 | #ifndef PACKING_GLSL
2 | #define PACKING_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | uvec4 pack_8bit(vec4 a, vec4 b, vec4 c, vec4 d)
9 | {
10 | return uvec4(packUnorm4x8(a), packUnorm4x8(b), packUnorm4x8(c), packUnorm4x8(d));
11 | }
12 |
13 | void unpack_8bit(uvec4 packed_vector, out vec4 a, out vec4 b, out vec4 c, out vec4 d)
14 | {
15 | a = unpackUnorm4x8(packed_vector.x);
16 | b = unpackUnorm4x8(packed_vector.y);
17 | c = unpackUnorm4x8(packed_vector.z);
18 | d = unpackUnorm4x8(packed_vector.w);
19 | }
20 |
21 | #endif //PACKING_GLSL
22 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/properties.glsl:
--------------------------------------------------------------------------------
1 | #ifndef PROPERTIES_GLSL
2 | #define PROPERTIES_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | bool bool_property(bool b) { return b; }
9 |
10 | float float_property(float f) { return f; }
11 |
12 | int int_property(int i) { return i; }
13 |
14 | vec2 vec2_property(vec2 v) { return v; }
15 |
16 | /* META @v: subtype=Vector;*/
17 | vec3 vec3_property(vec3 v) { return v; }
18 | /* META @v: subtype=Vector;*/
19 | vec4 vec4_property(vec4 v) { return v; }
20 |
21 | vec3 vec3_color_property(vec3 v) { return v; }
22 | vec4 vec4_color_property(vec4 v) { return v; }
23 |
24 | #endif //PROPERTIES_GLSL
25 |
--------------------------------------------------------------------------------
/Malt/Render/readme.md:
--------------------------------------------------------------------------------
1 | # Render Library
2 |
3 | Any *Pipeline* agnostic render utility that can't be implemented only as *GLSL* code should go here.
4 | From simple *Uniform Buffer Objects* ([Common.py](Common.py)) and pure *Python* utilities ([Sampling.py](Sampling.py)) to more complex features like Lighting and Shadowmaps ([Lighting.py](Lighting.py)).
5 |
6 | *Render* modules don't follow any specific API and while similar features share a similar interface, they are free to implement the most appropriate for their needs.
7 |
8 | As more features are implemented and common patterns arise, a *Pipeline Plugins* API can be designed so using this utilities requires less manual communication from the *Pipeline* side.
9 |
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # pragma37
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/Bridge/ipc/build.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import platform
4 |
5 | src_dir = os.path.abspath(os.path.dirname(__file__))
6 | build_dir = os.path.join(src_dir, '.build')
7 |
8 | try: os.mkdir(build_dir)
9 | except: pass
10 |
11 | if platform.system() == 'Windows': #Multi-config generators, like Visual Studio
12 | subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)
13 | subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)
14 | else: #Single-config generators
15 | subprocess.check_call(['cmake', '..'], cwd=build_dir)
16 | subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)
17 |
18 | subprocess.check_call(['cmake', '--install', '.'], cwd=build_dir)
19 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # Some people are used to download the zipped repo from Github to install Blender addons.
2 | # This file is just to redirect those users to the right path. It's not distributed in the actual addon.
3 |
4 | bl_info = {
5 | "name": "Oops! You downloaded the wrong BlenderMalt file.",
6 | "description" : "Please, read the install intructions on Github or malt3d.com",
7 | "author" : "Miguel Pozo",
8 | "version": (1,0,0),
9 | "blender" : (3, 0, 0),
10 | "doc_url": "https://malt3d.com/documentation/getting started/#install",
11 | "tracker_url": "https://github.com/bnpr/Malt#install",
12 | "category": "Render"
13 | }
14 |
15 | def register():
16 | pass
17 |
18 | def unregister():
19 | pass
20 |
--------------------------------------------------------------------------------
/Bridge/renderdoc/build.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import platform
4 |
5 | src_dir = os.path.abspath(os.path.dirname(__file__))
6 | build_dir = os.path.join(src_dir, '.build')
7 |
8 | try: os.mkdir(build_dir)
9 | except: pass
10 |
11 | if platform.system() == 'Windows': #Multi-config generators, like Visual Studio
12 | subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)
13 | subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)
14 | else: #Single-config generators
15 | subprocess.check_call(['cmake', '..'], cwd=build_dir)
16 | subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)
17 |
18 | subprocess.check_call(['cmake', '--install', '.'], cwd=build_dir)
19 |
--------------------------------------------------------------------------------
/BlenderMalt/CBlenderMalt/build.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import platform
4 |
5 | src_dir = os.path.abspath(os.path.dirname(__file__))
6 | build_dir = os.path.join(src_dir, '.build')
7 |
8 | try: os.mkdir(build_dir)
9 | except: pass
10 |
11 | if platform.system() == 'Windows': #Multi-config generators, like Visual Studio
12 | subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)
13 | subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)
14 | else: #Single-config generators
15 | subprocess.check_call(['cmake', '..'], cwd=build_dir)
16 | subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)
17 |
18 | subprocess.check_call(['cmake', '--install', '.'], cwd=build_dir)
19 |
--------------------------------------------------------------------------------
/docs/Documentation/Plugins.md:
--------------------------------------------------------------------------------
1 | # Plugins
2 |
3 | Render pipelines can be customized through plugins.
4 | Plugins can:
5 |
6 | - Add new node libraries to *Pipeline Graphs*.
7 | - Add new *Pipeline Parameters*.
8 | - Add new *PipelineGraph types*.
9 |
10 | Plugins can be installed globally in the *Addon Settings* or per world in the *World Panel*.
11 |
12 | A basic example can be found in [Development/plugins](https://github.com/bnpr/Malt/tree/Development/plugins)
13 |
14 | > The Plugins path should point to the plugins parent folder, then any plugin that you drop into that folder should be automatically loaded.
15 |
16 | > For example, the Plugins path shouldn't point to `C:/Malt-plugins/My-plugin-example/`, it should point to `C:/Malt-plugins/`
17 |
--------------------------------------------------------------------------------
/docs/extra/extra.css:
--------------------------------------------------------------------------------
1 | /* https://github.com/squidfunk/mkdocs-material/issues/1635#issuecomment-811058692 */
2 |
3 | [data-md-color-scheme="slate"] {
4 | --md-default-bg-color: #24272e;
5 | }
6 |
7 | /*Disable the edit button*/
8 | .md-content__button {
9 | display: none;
10 | }
11 |
12 | .zoom {
13 | /*transition: transform ease-in-out 0.5s;*/
14 | cursor: zoom-in;
15 | }
16 |
17 | .image-zoom-large {
18 | width: auto;
19 | max-width: 100vw;
20 | max-height: 100vh;
21 | /*transform: scale(1.5);*/
22 | cursor: zoom-out;
23 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
24 | z-index: 100;
25 | position: absolute;
26 | left: 50%;
27 | transform: translate(-50%, 0);
28 | }
--------------------------------------------------------------------------------
/Malt/GL/GLSLParser/build.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import platform
4 |
5 | current_dir = os.path.abspath(os.path.dirname(__file__))
6 | build_dir = os.path.join(current_dir, '.build')
7 |
8 | try: os.mkdir(build_dir)
9 | except: pass
10 |
11 | if platform.system() == 'Windows': #Multi-config generators, like Visual Studio
12 | subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir)
13 | subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir)
14 | else: #Single-config generators
15 | subprocess.check_call(['cmake', '..'], cwd=build_dir)
16 | subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir)
17 |
18 | #subprocess.check_call(['cmake', '--install', '.', '--prefix', '.'], cwd=build_dir)
19 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/Unpack8bitTextures.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | uniform usampler2D IN_PACKED;
13 |
14 | layout (location = 0) out vec4 OUT_A;
15 | layout (location = 1) out vec4 OUT_B;
16 | layout (location = 2) out vec4 OUT_C;
17 | layout (location = 3) out vec4 OUT_D;
18 |
19 | void main()
20 | {
21 | PIXEL_SETUP_INPUT();
22 |
23 | uvec4 packed_pixel = texture(IN_PACKED, UV[0]);
24 | OUT_A = unpackUnorm4x8(packed_pixel.r);
25 | OUT_B = unpackUnorm4x8(packed_pixel.g);
26 | OUT_C = unpackUnorm4x8(packed_pixel.b);
27 | OUT_D = unpackUnorm4x8(packed_pixel.a);
28 | }
29 |
30 | #endif //PIXEL_SHADER
31 |
--------------------------------------------------------------------------------
/Malt/GL/GLSLParser/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | project(GLSLParser)
3 | set(CMAKE_CXX_STANDARD 17)
4 |
5 | add_subdirectory(external)
6 |
7 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.bin")
8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
9 |
10 | file(GLOB_RECURSE src_glob "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
11 |
12 | add_executable(${PROJECT_NAME} ${src_glob})
13 |
14 | target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
15 |
16 | #target_precompile_headers(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/pch.h)
17 |
18 | target_link_libraries(${PROJECT_NAME} pegtl)
19 | target_include_directories(${PROJECT_NAME} PUBLIC ${RAPIDJSON_INCLUDE_DIR})
20 |
21 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/sRGBConversion.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | uniform sampler2D input_texture;
13 | uniform bool to_srgb;
14 | uniform bool convert = true;
15 |
16 | layout (location = 0) out vec4 OUT_COLOR;
17 |
18 | void main()
19 | {
20 | PIXEL_SETUP_INPUT();
21 |
22 | vec4 color = texture(input_texture, UV[0]);
23 | if(convert)
24 | {
25 | if(to_srgb)
26 | {
27 | color.rgb = linear_to_srgb(color.rgb);
28 | }
29 | else
30 | {
31 | color.rgb = srgb_to_linear(color.rgb);
32 | }
33 | }
34 | OUT_COLOR = color;
35 | }
36 |
37 | #endif //PIXEL_SHADER
38 |
--------------------------------------------------------------------------------
/Malt/Shaders/Procedural/Fractal_Noise.inl:
--------------------------------------------------------------------------------
1 | //Should define TILE
2 | //Should declare coord and tile_size (if TILE)
3 |
4 | vec4 color = vec4(0);
5 | float weight = 1.0;
6 | float total_weight = 0.0;
7 | detail_balance = clamp(detail_balance, 0.00001, 1.0);
8 | detail = max(1.0, detail);
9 |
10 | for (int i = 0; i < ceil(detail); i++)
11 | {
12 | #if TILE != 0
13 | vec4 noise = noise(coord, tile_size);
14 | #else
15 | vec4 noise = noise(coord);
16 | #endif
17 | float octave_weight = (i + 1 > floor(detail))? mod(detail, 1.0) : 1.0;
18 | weight *= detail_balance * 2 * octave_weight;
19 | color += weight * noise;
20 | total_weight += weight;
21 | coord *= 2.0;
22 | #if TILE != 0
23 | tile_size *= 2;
24 | #endif
25 | }
26 |
27 | color /= total_weight;
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Bool.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_BOOL_GLSL
2 | #define NODE_UTILS_2_BOOL_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Math; subcategory=Boolean Logic;
6 | */
7 |
8 | /*META @meta: label=And;*/
9 | bool Bool_and(bool a, bool b) { return a && b; }
10 | /*META @meta: label=Or;*/
11 | bool Bool_or(bool a, bool b) { return a || b; }
12 | /*META @meta: label=Not;*/
13 | bool Bool_not(bool b) { return !b; }
14 | /*META @meta: label=Equal;*/
15 | bool Bool_equal(bool a, bool b){ return a == b; }
16 | /*META @meta: label=Not Equal;*/
17 | bool Bool_not_equal(bool a, bool b){ return a != b; }
18 | /*META @meta: label=If Else; @a: label=If True; @b: label=If False; */
19 | bool Bool_if_else(bool condition, bool a, bool b){ return condition ? a : b; }
20 |
21 | #endif //NODE_UTILS_2_BOOL_GLSL
22 |
--------------------------------------------------------------------------------
/Bridge/ipc/ipc.c:
--------------------------------------------------------------------------------
1 | #ifdef _WIN32
2 | #define EXPORT __declspec( dllexport )
3 | #else
4 | #define EXPORT __attribute__ ((visibility ("default")))
5 | #endif
6 |
7 | #define IPC_IMPLEMENTATION
8 | #include "ipc.h"
9 |
10 | EXPORT int create_shared_memory(char* name, size_t size, ipc_sharedmemory* mem)
11 | {
12 | ipc_mem_init(mem, name, size);
13 | if (ipc_mem_create(mem) != 0) {
14 | return errno;
15 | }
16 | return 0;
17 | }
18 |
19 | EXPORT int open_shared_memory(char* name, size_t size, ipc_sharedmemory* mem)
20 | {
21 | ipc_mem_init(mem, name, size);
22 | if (ipc_mem_open_existing(mem) != 0) {
23 | return errno;
24 | }
25 | return 0;
26 | }
27 |
28 | EXPORT void close_shared_memory(ipc_sharedmemory mem, bool release)
29 | {
30 | ipc_mem_close(&mem, release);
31 | }
32 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/sampler.glsl:
--------------------------------------------------------------------------------
1 | #ifndef SAMPLER_GLSL
2 | #define SAMPLER_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | vec4 sampler1D_sample(sampler1D t, float u) { return texture(t, u); }
9 | int sampler1D_size(sampler1D t) { return textureSize(t, 0); }
10 | vec4 sampler1D_textel_fetch(sampler1D t, int u) { return texelFetch(t, u, 0); }
11 |
12 | /* META @uv: default=UV[0];*/
13 | vec4 sampler2D_sample(sampler2D t, vec2 uv) { return texture(t, uv); }
14 | /* META @uv: default=UV[0];*/
15 | vec4 sampler2D_sample_nearest(sampler2D t, vec2 uv){ return texelFetch(t, ivec2(textureSize(t, 0) * uv), 0); }
16 | ivec2 sampler2D_size(sampler2D t) { return textureSize(t, 0); }
17 | vec4 sampler2D_textel_fetch(sampler2D t, ivec2 uv) { return texelFetch(t, uv, 0); }
18 |
19 | #endif //SAMPLER_GLSL
20 |
--------------------------------------------------------------------------------
/scripts/generate_license_dependencies_full.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | TOP_DIR = os.path.join(os.path.dirname(__file__), os.pardir)
4 |
5 | dependencies = open(os.path.join(TOP_DIR, 'LICENSE - DEPENDENCIES')).read()
6 |
7 | result = ''
8 |
9 | for dependency in dependencies.split('\n\n'):
10 | lines = dependency.splitlines()
11 | for i, line in enumerate(lines):
12 | if i < 3:
13 | result += line
14 | else:
15 | url = line.replace('github.com', 'raw.githubusercontent.com').replace('blob/','')
16 | from urllib.request import urlopen
17 | content = urlopen(url).read().decode('utf-8')
18 | result+=content
19 | result+='\n'
20 | result += '*'*80
21 | result+='\n'
22 |
23 | open(os.path.join(TOP_DIR, 'LICENSE - DEPENDENCIES (FULL TEXT)'), 'w').write(result)
24 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/node_utils.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_GLSL
2 | #define NODE_UTILS_GLSL
3 |
4 | #include "Node Utils/common.glsl"
5 | #include "Node Utils/bool.glsl"
6 | #include "Node Utils/float.glsl"
7 | #include "Node Utils/int.glsl"
8 | #include "Node Utils/packing.glsl"
9 | #include "Node Utils/properties.glsl"
10 | #include "Node Utils/sampler.glsl"
11 | #include "Node Utils/vec2.glsl"
12 | #include "Node Utils/vec3.glsl"
13 | #include "Node Utils/vec4.glsl"
14 |
15 | // Basic common API
16 | #include "Common.glsl"
17 | #include "Filters/AO.glsl"
18 | #include "Filters/Bevel.glsl"
19 | #include "Filters/Blur.glsl"
20 | #include "Filters/Curvature.glsl"
21 | #include "Filters/Line.glsl"
22 | #include "Procedural/Noise.glsl"
23 | #include "Procedural/Cell_Noise.glsl"
24 | #include "Procedural/Fractal_Noise.glsl"
25 | #include "Shading/ShadingModels.glsl"
26 |
27 | #endif //NODE_UTILS_GLSL
28 |
--------------------------------------------------------------------------------
/BlenderMalt/CBlenderMalt/blender_dna/DNA_session_uuid_types.h:
--------------------------------------------------------------------------------
1 | /* SPDX-License-Identifier: GPL-2.0-or-later */
2 |
3 | /** \file
4 | * \ingroup DNA
5 | */
6 |
7 | #pragma once
8 |
9 | #include "BLI_sys_types.h"
10 |
11 | #ifdef UNDEFINED
12 | extern "C" {
13 | #endif
14 |
15 | /* Is a structure because of the following considerations:
16 | *
17 | * - It is not possible to use custom types in DNA members: makesdna does not recognize them.
18 | * - It allows to add more bits, more than standard fixed-size types can store. For example, if
19 | * we ever need to go 128 bits, it is as simple as adding extra 64bit field.
20 | */
21 | typedef struct SessionUUID {
22 | /* Never access directly, as it might cause a headache when more bits are needed: if the field
23 | * is used directly it will not be easy to find all places where partial access is used. */
24 | uint64_t uuid_;
25 | } SessionUUID;
26 |
27 | #ifdef UNDEFINED
28 | }
29 | #endif
30 |
--------------------------------------------------------------------------------
/Malt/Shaders/Common/Hash.glsl:
--------------------------------------------------------------------------------
1 | #ifndef COMMON_HASH_GLSL
2 | #define COMMON_HASH_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Math; internal=True;
6 | */
7 |
8 | uvec4 _pcg4d(uvec4 v)
9 | {
10 | //http://www.jcgt.org/published/0009/03/02/
11 | v = v * 1664525u + 1013904223u;
12 | v.x += v.y*v.w;
13 | v.y += v.z*v.x;
14 | v.z += v.x*v.y;
15 | v.w += v.y*v.z;
16 | v ^= v >> 16u;
17 | v.x += v.y*v.w;
18 | v.y += v.z*v.x;
19 | v.z += v.x*v.y;
20 | v.w += v.y*v.z;
21 | return v;
22 | }
23 |
24 | vec4 _pcg4d(vec4 v)
25 | {
26 | uvec4 u = _pcg4d(floatBitsToUint(v));
27 | return vec4(u) / float(0xffffffffU);
28 | }
29 |
30 | vec4 hash(float v){ return _pcg4d(vec4(v,0,0,0)); }
31 | vec4 hash(vec2 v){ return _pcg4d(vec4(v,0,0)); }
32 | vec4 hash(vec3 v){ return _pcg4d(vec4(v,0)); }
33 | /* META @meta: internal=false; @v:subtype=Data; */
34 | vec4 hash(vec4 v){ return _pcg4d(v); }
35 |
36 | #endif // COMMON_HASH_GLSL
37 |
--------------------------------------------------------------------------------
/plugins/Experimental/RenderLayer/OpaqueLayer.py:
--------------------------------------------------------------------------------
1 | from Malt.PipelineNode import PipelineNode
2 | from Malt.PipelineParameters import Parameter, Type
3 |
4 | class OpaqueLayer(PipelineNode):
5 |
6 | def __init__(self, pipeline):
7 | PipelineNode.__init__(self, pipeline)
8 |
9 | @classmethod
10 | def reflect_inputs(cls):
11 | return {
12 | 'Output Name' : Parameter('', Type.STRING)
13 | }
14 |
15 | @classmethod
16 | def reflect_outputs(cls):
17 | return {
18 | 'Opaque Layer' : Parameter('', Type.TEXTURE)
19 | }
20 |
21 | def execute(self, parameters):
22 | inputs = parameters['IN']
23 | outputs = parameters['OUT']
24 |
25 | output_name = inputs['Output Name']
26 | render_layers_node = parameters['__GLOBALS__']['__RENDER_LAYERS__']
27 | outputs['Opaque Layer'] = render_layers_node.opaque_targets.get(output_name)
28 |
29 | NODE = OpaqueLayer
30 |
--------------------------------------------------------------------------------
/docs/Setup-BlenderMalt-for-Development.md:
--------------------------------------------------------------------------------
1 | # How to Setup BlenderMalt for Development
2 |
3 | - Clone the repository.
4 | `git clone https://github.com/bnpr/Malt.git`
5 | - Set a user scripts folder for Blender if you don't have one.
6 | [*Blender > Preferences > File Paths > Scripts*](https://docs.blender.org/manual/en/latest/editors/preferences/file_paths.html)
7 | - Locate the *Python* executable from your *Blender* installation.
8 | For example: ```"C:\Program Files\Blender Foundation\Blender 2.93\2.93\python\bin\python.exe"```
9 | - Run: ``` /scripts/setup_blender_addon.py --scripts-folder ```
10 |
11 | > The setup script will try to compile the CBlenderMalt and the Bridge ipc libraries using CMake,
12 | > if you don't have the required toolchain you can just copy them from the Github release.
13 |
14 | Now if you restart *Blender* and go to *Preferences > Add-ons*, you should be able to enable *BlenderMalt*.
15 |
16 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Color.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_COLOR_GLSL
2 | #define NODE_UTILS_2_COLOR_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Color;
6 | */
7 |
8 | /* META
9 | @meta: label=Gradient; subcategory=Color Gradient;
10 | @Coord: label=U; subtype=Slider; min=0.0; max=1.0;
11 | */
12 | vec4 Color_Gradient_1d(sampler1D Color_Ramp, float Coord) { return texture(Color_Ramp, Coord); }
13 |
14 | /* META
15 | @meta: label=RGB Gradient; subcategory=Color Gradient;
16 | @Coord: label=UVW; subtype=Slider; min=0.0; max=1.0;
17 | */
18 | vec3 Color_Gradient_3d(sampler1D Color_Ramp, vec3 Coord) { return rgb_gradient(Color_Ramp, Coord); }
19 |
20 | /* META
21 | @meta: label=RGBA Gradient; subcategory=Color Gradient;
22 | @Coord: label=UVWX; subtype=Slider; min=0.0; max=1.0;
23 | */
24 | vec4 Color_Gradient_4d(sampler1D Color_Ramp, vec4 Coord) { return rgba_gradient(Color_Ramp, Coord); }
25 |
26 | #include "Node Utils 2/ColorBlend.glsl"
27 |
28 | #endif // NODE_UTILS_2_COLOR_GLSL
29 |
--------------------------------------------------------------------------------
/scripts/print_pixel_formats.py:
--------------------------------------------------------------------------------
1 | GL_ENUMS = {}
2 | GL_NAMES = {}
3 |
4 | if True: #create new scope to import OpenGL
5 | from OpenGL import GL
6 | for e in dir(GL):
7 | if e.startswith('GL_'):
8 | GL_ENUMS[getattr(GL, e)] = e
9 | GL_NAMES[e] = getattr(GL, e)
10 |
11 | from OpenGL.GL import *
12 |
13 | def print_format_prop(format, prop):
14 | read = glGetInternalformativ(GL_TEXTURE_2D, format, prop, 1)
15 | print(GL_ENUMS[format], GL_ENUMS[prop], GL_ENUMS[read])
16 |
17 | def print_format_props(format):
18 | print_format_prop(format, GL_READ_PIXELS)
19 | print_format_prop(format, GL_READ_PIXELS_FORMAT)
20 | print_format_prop(format, GL_READ_PIXELS_TYPE)
21 | print_format_prop(format, GL_TEXTURE_IMAGE_FORMAT)
22 | print_format_prop(format, GL_TEXTURE_IMAGE_TYPE)
23 |
24 | print_format_props(GL_RGB8)
25 | print_format_props(GL_RGBA8)
26 | print_format_props(GL_RGB16F)
27 | print_format_props(GL_RGBA16F)
28 | print_format_props(GL_RGB32F)
29 | print_format_props(GL_RGBA32F)
30 |
--------------------------------------------------------------------------------
/BlenderMalt/CBlenderMalt/blender_dna/DNA_listBase.h:
--------------------------------------------------------------------------------
1 | /* SPDX-License-Identifier: GPL-2.0-or-later
2 | * Copyright 2001-2002 NaN Holding BV. All rights reserved. */
3 |
4 | /** \file
5 | * \ingroup DNA
6 | * \brief These structs are the foundation for all linked lists in the library system.
7 | *
8 | * Doubly-linked lists start from a ListBase and contain elements beginning
9 | * with Link.
10 | */
11 |
12 | #pragma once
13 |
14 | #ifdef UNDEFINED
15 | extern "C" {
16 | #endif
17 |
18 | /** Generic - all structs which are put into linked lists begin with this. */
19 | typedef struct Link {
20 | struct Link *next, *prev;
21 | } Link;
22 |
23 | /** Simple subclass of Link. Use this when it is not worth defining a custom one. */
24 | typedef struct LinkData {
25 | struct LinkData *next, *prev;
26 | void *data;
27 | } LinkData;
28 |
29 | /** Never change the size of this! dna_genfile.c detects pointer_size with it. */
30 | typedef struct ListBase {
31 | void *first, *last;
32 | } ListBase;
33 |
34 | /* 8 byte alignment! */
35 |
36 | #ifdef UNDEFINED
37 | }
38 | #endif
39 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/int.glsl:
--------------------------------------------------------------------------------
1 | #ifndef INT_GLSL
2 | #define INT_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | int int_add(int a, int b){ return a+b; }
9 | int int_subtract(int a, int b){ return a-b; }
10 | int int_multiply(int a, int b){ return a*b; }
11 | int int_divide(int a, int b){ return a/b; }
12 | int int_modulo(int a, int b){ return a%b; }
13 |
14 | int int_clamp(int i, int min, int max){ return clamp(i, min, max); }
15 |
16 | int int_sign(int i){ return sign(i); }
17 | int int_abs(int i){ return abs(i); }
18 | int int_min(int a, int b){ return min(a,b); }
19 | int int_max(int a, int b){ return max(a,b); }
20 |
21 | bool int_equal(int a, int b){ return a == b; }
22 | bool int_not_equal(int a, int b){ return a != b; }
23 | bool int_greater(int a, int b){ return a > b; }
24 | bool int_greater_or_equal(int a, int b){ return a >= b; }
25 | bool int_less(int a, int b){ return a < b; }
26 | bool int_less_or_equal(int a, int b){ return a <= b; }
27 |
28 | int int_if_else(bool condition, int if_true, int if_false){ return condition ? if_true : if_false; }
29 |
30 | #endif //INT_GLSL
31 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/CopyTextures.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | uniform sampler2D IN[8];
13 |
14 | uniform sampler2D IN_DEPTH;
15 |
16 | layout (location = 0) out vec4 OUT_0;
17 | layout (location = 1) out vec4 OUT_1;
18 | layout (location = 2) out vec4 OUT_2;
19 | layout (location = 3) out vec4 OUT_3;
20 | layout (location = 4) out vec4 OUT_4;
21 | layout (location = 5) out vec4 OUT_5;
22 | layout (location = 6) out vec4 OUT_6;
23 | layout (location = 7) out vec4 OUT_7;
24 |
25 | void main()
26 | {
27 | PIXEL_SETUP_INPUT();
28 |
29 | ivec2 uv = ivec2(gl_FragCoord.xy);
30 |
31 | OUT_0 = texelFetch(IN[0], uv, 0);
32 | OUT_1 = texelFetch(IN[1], uv, 0);
33 | OUT_2 = texelFetch(IN[2], uv, 0);
34 | OUT_3 = texelFetch(IN[3], uv, 0);
35 | OUT_4 = texelFetch(IN[4], uv, 0);
36 | OUT_5 = texelFetch(IN[5], uv, 0);
37 | OUT_6 = texelFetch(IN[6], uv, 0);
38 | OUT_7 = texelFetch(IN[7], uv, 0);
39 |
40 | gl_FragDepth = texelFetch(IN_DEPTH, uv, 0).x;
41 | }
42 |
43 | #endif //PIXEL_SHADER
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Malt - Copyright (c) 2020-2022 BNPR, Miguel Pozo and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/common.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_COMMON_GLSL
2 | #define NODE_UTILS_COMMON_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | vec3 surface_position() { return POSITION; }
9 | vec3 surface_normal() { return NORMAL; }
10 | vec3 surface_tangent(int index) { return get_tangent(index); }
11 | vec3 surface_bitangent(int index) { return get_bitangent(index); }
12 | vec2 surface_uv(int index) { return UV[index]; }
13 | vec4 surface_vertex_color(int index) { return COLOR[index]; }
14 |
15 | vec3 surface_original_position() { return IO_POSITION; }
16 | vec3 surface_original_normal() { return IO_NORMAL; }
17 |
18 | uvec4 object_id() { return ID; }
19 |
20 | uvec4 object_original_id() { return IO_ID; }
21 |
22 | mat4 model_matrix() { return MODEL; }
23 | mat4 camera_matrix() { return CAMERA; }
24 | mat4 projection_matrix() { return PROJECTION; }
25 |
26 | vec2 render_resolution() { return vec2(RESOLUTION); }
27 | vec2 sample_offset() { return SAMPLE_OFFSET; }
28 | int sample_count() { return SAMPLE_COUNT; }
29 |
30 | int current_frame() { return FRAME; }
31 | float current_time() { return TIME; }
32 |
33 | #endif // NODE_UTILS_COMMON_GLSL
34 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/LineComposite.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 |
3 | #ifdef VERTEX_SHADER
4 | void main()
5 | {
6 | DEFAULT_SCREEN_VERTEX_SHADER();
7 | }
8 | #endif
9 |
10 | #ifdef PIXEL_SHADER
11 |
12 | #include "Filters/Line.glsl"
13 |
14 | layout (location = 0) out vec4 OUT_RESULT;
15 |
16 | uniform sampler2D color_texture;
17 |
18 | uniform sampler2D depth_texture;
19 | uniform int depth_channel;
20 |
21 | uniform usampler2D id_texture;
22 | uniform int id_channel;
23 |
24 | uniform sampler2D line_color_texture;
25 |
26 | uniform sampler2D line_width_texture;
27 | uniform int line_width_channel;
28 | uniform float line_width_scale = 1.0;
29 |
30 | uniform int brute_force_range = 10;
31 |
32 | void main()
33 | {
34 | PIXEL_SETUP_INPUT();
35 |
36 | vec2 uv = UV[0];
37 | vec4 line_color = line_expand(
38 | uv, brute_force_range,
39 | line_color_texture, line_width_texture, line_width_channel, line_width_scale,
40 | depth_texture, depth_channel, id_texture, id_channel
41 | ).color;
42 |
43 | vec4 color = texture(color_texture, uv);
44 |
45 | OUT_RESULT = alpha_blend(color, line_color);
46 | }
47 |
48 | #endif //PIXEL_SHADER
49 |
--------------------------------------------------------------------------------
/Malt/GL/GLSLParser/external/PEGTL-LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2007-2021 Dr. Colin Hirsch and Daniel Frey
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/node_utils_2.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_GLSL
2 | #define NODE_UTILS_2_GLSL
3 |
4 | #include "Node Utils 2/conversion.glsl"
5 | #include "Node Utils 2/Bool.glsl"
6 | #include "Node Utils 2/Float.glsl"
7 | #include "Node Utils 2/Int.glsl"
8 | #include "Node Utils 2/Mat4.glsl"
9 | #include "Node Utils 2/Vec2.glsl"
10 | #include "Node Utils 2/Vec3.glsl"
11 | #include "Node Utils 2/Vec4.glsl"
12 |
13 | #include "Node Utils 2/Input.glsl"
14 | #include "Node Utils 2/Color.glsl"
15 | #include "Node Utils 2/Vector.glsl"
16 | #include "Node Utils 2/Parameters.glsl"
17 | #include "Node Utils 2/Texturing.glsl"
18 | #include "Node Utils 2/Filter.glsl"
19 |
20 | // Basic common API
21 | #include "Common.glsl"
22 | #include "Filters/AO.glsl"
23 | #include "Filters/Bevel.glsl"
24 | #include "Filters/Blur.glsl"
25 | #include "Filters/Sharpen.glsl"
26 | #include "Filters/Curvature.glsl"
27 | #include "Filters/Kuwahara.glsl"
28 | #include "Filters/Line.glsl"
29 | #include "Filters/StructureTensor.glsl"
30 | #include "Procedural/Noise.glsl"
31 | #include "Procedural/Cell_Noise.glsl"
32 | #include "Procedural/Fractal_Noise.glsl"
33 | #include "Shading/ShadingModels.glsl"
34 |
35 | #endif //NODE_UTILS_GLSL
36 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | # What's the difference between Malt and BEER?
4 |
5 | | | Malt | BEER |
6 | |---|------|------|
7 | | **Target Audience** | Advanced users | Everyone |
8 | | **Worflow** | Code & Nodes | Layers/Stacks |
9 | | **Backend** | OpenGL | Malt |
10 | | **Project Lead** | [Miguel Pozo](https://twitter.com/pragma37) | [LightBWK](https://twitter.com/Lightbwk) |
11 | | **Main Developer** | [Miguel Pozo](https://twitter.com/pragma37) | *TBD* |
12 | | **Funding** | [Patreon](https://patreon.com/pragma37) | [BEER Dev Fund](https://blendernpr.org/beer/) |
13 |
14 | # Will Malt be ported to Vulkan?
15 |
16 | The most likely replacement for *OpenGL* in *Malt* is WebGPU Native, since it's easier to use than *Vulkan* and has better *MacOS* support.
17 |
18 | # Can Malt be used for games?
19 |
20 | Not at the moment.
21 | Malt main focus is animation and illustration and it prioritizes flexibility and image quality, which usually comes at a performance cost.
22 |
23 | # How can I help?
24 |
25 | - Share your *Malt* artworks! [*#malt3d*](https://twitter.com/hashtag/malt3d)
26 | - Make tutorials.
27 | - Implement new features.
28 | - Join the [Patreon](https://www.patreon.com/pragma37).
29 |
30 |
--------------------------------------------------------------------------------
/Malt/Render/DepthToCompositeDepth.py:
--------------------------------------------------------------------------------
1 | from Malt.GL.GL import *
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.RenderTarget import RenderTarget
4 |
5 | _shader_src='''
6 | #include "Passes/DepthToBlenderDepth.glsl"
7 | '''
8 |
9 | _SHADER = None
10 |
11 | class CompositeDepth():
12 |
13 | def __init__(self):
14 | self.t = None
15 | self.fbo = None
16 |
17 | self.shader = None
18 |
19 | def render(self, pipeline, common_buffer, depth_texture, depth_channel=0):
20 | if self.t is None or self.t.resolution != depth_texture.resolution:
21 | self.t = Texture(depth_texture.resolution, GL_R32F)
22 | self.fbo = RenderTarget([self.t])
23 |
24 | if self.shader == None:
25 | global _SHADER
26 | if _SHADER is None: _SHADER = pipeline.compile_shader_from_source(_shader_src)
27 | self.shader = _SHADER
28 |
29 | self.shader.textures['DEPTH_TEXTURE'] = depth_texture
30 | self.shader.uniforms['DEPTH_CHANNEL'].set_value(depth_channel)
31 | common_buffer.shader_callback(self.shader)
32 | pipeline.draw_screen_pass(self.shader, self.fbo)
33 | return self.t
34 |
--------------------------------------------------------------------------------
/Malt/GL/GLSLParser/external/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | include(FetchContent)
2 | include(ExternalProject)
3 |
4 | FetchContent_Declare(
5 | pegtl
6 | GIT_REPOSITORY https://github.com/taocpp/PEGTL
7 | GIT_TAG 3.2.1
8 | )
9 | FetchContent_MakeAvailable(pegtl)
10 |
11 |
12 | FetchContent_Declare(
13 | rapidjson
14 | GIT_REPOSITORY https://github.com/Tencent/rapidjson
15 | GIT_TAG 00dbcf2c6e03c47d6c399338b6de060c71356464
16 | )
17 | set(RAPIDJSON_BUILD_DOC OFF CACHE INTERNAL "")
18 | set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE INTERNAL "")
19 | set(RAPIDJSON_BUILD_TESTS OFF CACHE INTERNAL "")
20 | FetchContent_MakeAvailable(rapidjson)
21 | FetchContent_GetProperties(rapidjson)
22 | set(RAPIDJSON_INCLUDE_DIR ${rapidjson_SOURCE_DIR}/include PARENT_SCOPE)
23 |
24 | #[[
25 | ExternalProject_Add(
26 | rapidjson
27 | PREFIX ${CMAKE_BINARY_DIR}/rapidjson
28 | GIT_REPOSITORY https://github.com/Tencent/rapidjson
29 | GIT_TAG 00dbcf2c6e03c47d6c399338b6de060c71356464
30 | CONFIGURE_COMMAND ""
31 | BUILD_COMMAND ""
32 | INSTALL_COMMAND ""
33 | LOG_DOWNLOAD ON
34 | )
35 | ExternalProject_Get_Property(rapidjson SOURCE_DIR)
36 | set(RAPIDJSON_INCLUDE_DIR ${SOURCE_DIR}/include PARENT_SCOPE)
37 | ]]
38 |
39 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltNodes/_init_.py:
--------------------------------------------------------------------------------
1 | def get_modules():
2 | from . import MaltNode, MaltNodeUITools, MaltNodeTree, MaltSocket, MaltCustomPasses
3 | modules = [MaltNode, MaltNodeUITools,MaltNodeTree, MaltSocket, MaltCustomPasses]
4 |
5 | import importlib, os
6 | nodes_dir = os.path.join(os.path.dirname(__file__), 'Nodes')
7 | for name in os.listdir(nodes_dir):
8 | try:
9 | if name == '__pycache__':
10 | continue
11 | if name.endswith('.py'):
12 | name = name[:-3]
13 | module = importlib.import_module(f'BlenderMalt.MaltNodes.Nodes.{name}')
14 | modules.append(module)
15 | except ModuleNotFoundError:
16 | # Ignore it. The file or dir is not a python module
17 | pass
18 | except Exception:
19 | import traceback
20 | traceback.print_exc()
21 |
22 | return modules
23 |
24 |
25 | def register():
26 | import importlib
27 | for module in get_modules():
28 | importlib.reload(module)
29 |
30 | for module in get_modules():
31 | module.register()
32 |
33 | def unregister():
34 | for module in reversed(get_modules()):
35 | module.unregister()
36 |
--------------------------------------------------------------------------------
/scripts/get_glslang.py:
--------------------------------------------------------------------------------
1 | import os, stat, platform, shutil, zipfile
2 |
3 | current_dir = os.path.dirname(os.path.realpath(__file__))
4 | gl_folder = os.path.join(current_dir, '..', 'Malt', 'GL')
5 |
6 | zip_file = os.path.join(current_dir, 'glslang.zip')
7 |
8 | glslang_url = {
9 | 'Windows' : "https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Release.zip",
10 | 'Linux' : "https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-linux-Release.zip",
11 | 'Darwin' : "https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-osx-Release.zip",
12 | }
13 | glslang_url = glslang_url[platform.system()]
14 |
15 | zipped_path = 'bin/glslangValidator'
16 | target_path = os.path.join(gl_folder, '.glslang')
17 | if platform.system() == 'Windows':
18 | zipped_path += '.exe'
19 | target_path += '.exe'
20 |
21 | import urllib.request
22 | urllib.request.urlretrieve(glslang_url, zip_file)
23 |
24 | with zipfile.ZipFile(zip_file) as z:
25 | with z.open(zipped_path) as zip, open(target_path, 'wb') as unzip:
26 | shutil.copyfileobj(zip, unzip)
27 |
28 | # Set as executable (Needed on Linux)
29 | os.chmod(target_path, os.stat(target_path).st_mode | stat.S_IEXEC)
30 |
31 | os.remove(zip_file)
32 |
--------------------------------------------------------------------------------
/Bridge/Texture.py:
--------------------------------------------------------------------------------
1 | from Malt.GL import Texture
2 | from Malt.GL.GL import *
3 |
4 | TEXTURES = {}
5 |
6 | def load_texture(msg):
7 | name = msg['name']
8 | data = msg['buffer'].buffer()
9 | resolution = msg['resolution']
10 | channels = msg['channels']
11 | sRGB = msg['sRGB']
12 |
13 | internal_formats = [
14 | GL_R32F,
15 | GL_RG32F,
16 | GL_RGB32F,
17 | GL_RGBA32F,
18 | ]
19 | pixel_formats = [
20 | GL_RED,
21 | GL_RG,
22 | GL_RGB,
23 | GL_RGBA
24 | ]
25 | internal_format = internal_formats[channels-1]
26 | pixel_format = pixel_formats[channels-1]
27 |
28 | if sRGB:
29 | if channels == 4:
30 | internal_format = GL_SRGB_ALPHA
31 | else:
32 | internal_format = GL_SRGB
33 |
34 | #Nearest + Anisotropy seems to yield the best results with temporal super sampling
35 | TEXTURES[name] = Texture.Texture(resolution, internal_format, GL_FLOAT, data, pixel_format=pixel_format,
36 | wrap=GL_REPEAT, min_filter=GL_NEAREST_MIPMAP_NEAREST, build_mipmaps=True, anisotropy=True)
37 |
38 | GRADIENTS = {}
39 |
40 | def load_gradient(name, pixels, nearest):
41 | GRADIENTS[name] = Texture.Gradient(pixels, len(pixels)/4, nearest_interpolation=nearest)
42 |
--------------------------------------------------------------------------------
/Malt/GL/GLSLEval.py:
--------------------------------------------------------------------------------
1 | def glsl_vector(convert, length, *args):
2 | unpacked_args = []
3 | for arg in args:
4 | try:
5 | unpacked_args.extend([*arg])
6 | except:
7 | unpacked_args.append(arg)
8 | unpacked_args = [convert(arg) for arg in unpacked_args]
9 | if len(unpacked_args) == 0:
10 | return (0.0)*length
11 | elif len(unpacked_args) == 1:
12 | return (unpacked_args[0],) * length
13 | else:
14 | assert(len(unpacked_args) == length)
15 | return tuple(unpacked_args)
16 |
17 | def _vec2(convert, *args): return glsl_vector(convert, 2, *args)
18 | def _vec3(convert, *args): return glsl_vector(convert, 3, *args)
19 | def _vec4(convert, *args): return glsl_vector(convert, 4, *args)
20 |
21 | def vec2(*args): return _vec2(float, *args)
22 | def vec3(*args): return _vec3(float, *args)
23 | def vec4(*args): return _vec4(float, *args)
24 |
25 | def ivec2(*args): return _vec2(int, *args)
26 | def ivec3(*args): return _vec3(int, *args)
27 | def ivec4(*args): return _vec4(int, *args)
28 |
29 | def uint(n): return max(int(n), 0)
30 |
31 | def uvec2(*args): return _vec2(uint, *args)
32 | def uvec3(*args): return _vec3(uint, *args)
33 | def uvec4(*args): return _vec4(uint, *args)
34 |
35 | def glsl_eval(str):
36 | true = True
37 | false = False
38 | return eval(str)
39 |
--------------------------------------------------------------------------------
/docs/Documentation/Tooling.md:
--------------------------------------------------------------------------------
1 | # External Tools
2 |
3 | ## VSCode
4 |
5 | *Malt* has built-in integration with [VSCode](https://code.visualstudio.com), including a workaround to provide GLSL intellisense via C++ language servers.
6 | When *Malt* is the active render engine, it will auto-setup a *VSCode* workspace on the same folder you save your *.blend* file.
7 | >*(This feature can be disabled in the Addon Preferences)*
8 |
9 |
10 |
11 |
13 |
14 |
15 | ## RenderDoc
16 |
17 | *Malt* has built-in integration with [RenderDoc](https://renderdoc.org).
18 | It can be enabled by setting path to the **renderdoccmd** executable in the addon settings.
19 | Onnce Malt has been initialized, RenderDoc can be attached to Malt through the [attach to running instance](https://renderdoc.org/docs/window/capture_connection.html) option.
20 |
21 | 
22 |
23 | Captures can be triggered directly through the Blender UI:
24 | 
25 |
--------------------------------------------------------------------------------
/Bridge/renderdoc/renderdoc_wrapper.c:
--------------------------------------------------------------------------------
1 | #include "renderdoc_app.h"
2 | #include
3 |
4 | #ifdef _WIN32
5 | #define EXPORT __declspec( dllexport )
6 | #else
7 | #define EXPORT __attribute__ ((visibility ("default")))
8 | #endif
9 |
10 | RENDERDOC_API_1_4_1* API = NULL;
11 |
12 | #ifdef _WIN32
13 | #include
14 | void init()
15 | {
16 | if (API) return;
17 | HMODULE module = GetModuleHandleA("renderdoc.dll");
18 | if (module)
19 | {
20 | pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(module, "RENDERDOC_GetAPI");
21 | int result = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void **)&API);
22 | if (result != 1) API = NULL;
23 | }
24 | }
25 | #else
26 | #include
27 | void init()
28 | {
29 | if (API) return;
30 | void* module = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
31 | if (module)
32 | {
33 | pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(module, "RENDERDOC_GetAPI");
34 | int result = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void **)&API);
35 | if (result != 1) API = NULL;
36 | }
37 | }
38 | #endif
39 |
40 | EXPORT void capture_start()
41 | {
42 | init();
43 | if (API) API->StartFrameCapture(NULL, NULL);
44 | }
45 |
46 | EXPORT void capture_end()
47 | {
48 | init();
49 | if (API) API->EndFrameCapture(NULL, NULL);
50 | }
51 |
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/Shaders/NPR_ScreenShader.glsl:
--------------------------------------------------------------------------------
1 | #define NO_UV_INPUT
2 | #define NO_VERTEX_COLOR_INPUT
3 | #define NO_MODEL_INPUT
4 |
5 | #include "NPR_Intellisense.glsl"
6 |
7 | #ifdef PIXEL_SHADER
8 | vec3 DEFERRED_TRUE_NORMAL;
9 | #define CUSTOM_TRUE_NORMAL DEFERRED_TRUE_NORMAL
10 | #endif
11 |
12 | #include "Common.glsl"
13 |
14 | #ifdef VERTEX_SHADER
15 | void main()
16 | {
17 | DEFAULT_SCREEN_VERTEX_SHADER();
18 | }
19 | #endif
20 |
21 | #ifdef PIXEL_SHADER
22 | uniform sampler2D IN_NORMAL_DEPTH;
23 | uniform usampler2D IN_ID;
24 |
25 | uniform bool RENDER_LAYER_MODE = false;
26 | uniform bool DEFERRED_MODE = false;
27 |
28 | void SCREEN_SHADER();
29 |
30 | void main()
31 | {
32 | PIXEL_SETUP_INPUT();
33 |
34 | DEFERRED_TRUE_NORMAL = reconstruct_normal(IN_NORMAL_DEPTH, 3, screen_pixel());
35 |
36 | if(RENDER_LAYER_MODE)
37 | {
38 | vec4 normal_depth = texture(IN_NORMAL_DEPTH, UV[0]);
39 | if(DEFERRED_MODE && normal_depth.w == 1.0)
40 | {
41 | discard;
42 | }
43 | POSITION = screen_to_camera(UV[0], normal_depth.w);
44 | POSITION = transform_point(inverse(CAMERA), POSITION);
45 | NORMAL = normal_depth.xyz;
46 | ID = texture(IN_ID, UV[0]);
47 | }
48 |
49 | SCREEN_SHADER();
50 | }
51 | #endif
52 |
53 | #include "NPR_Pipeline/NPR_Filters.glsl"
54 | #include "NPR_Pipeline/NPR_Shading.glsl"
55 | #include "NPR_Pipeline/NPR_Shading2.glsl"
56 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Parameters.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_PARAMETERS_GLSL
2 | #define NODE_UTILS_2_PARAMETERS_GLSL
3 |
4 | /*META GLOBAL
5 | @meta: category=Parameters;
6 | */
7 |
8 | /* META
9 | @meta: label=Boolean;
10 | */
11 | bool Bool_property(bool b) { return b; }
12 | /* META
13 | @meta: label=Float;
14 | */
15 | float Float_property(float f) { return f; }
16 | /* META
17 | @meta: label=Integer;
18 | */
19 | int Int_property(int i) { return i; }
20 | /* META
21 | @meta: label=Vector 2D;
22 | */
23 | vec2 Vec2_property(vec2 v) { return v; }
24 |
25 | /* META
26 | @meta: label=Vector 3D;
27 | @v: subtype=Vector;
28 | */
29 | vec3 Vec3_property(vec3 v) { return v; }
30 | /* META
31 | @meta: label=Vector 4D;
32 | @v: subtype=Vector;
33 | */
34 | vec4 Vec4_property(vec4 v) { return v; }
35 |
36 | /* META
37 | @meta: label=RGB Color;
38 | @v: subtype=Color;
39 | */
40 | vec3 Vec3_color_property(vec3 v) { return v; }
41 | /* META
42 | @meta: label=RGBA Color;
43 | @v: subtype=Color;
44 | */
45 | vec4 Vec4_color_property(vec4 v) { return v; }
46 |
47 | #ifdef GL_ARB_bindless_texture
48 | /* META
49 | @meta: label=Color Ramp;
50 | */
51 | sampler1D Sampler1D_property(sampler1D color_ramp) { return color_ramp; }
52 | /* META
53 | @meta: label=Image;
54 | */
55 | sampler2D Sampler2D_property(sampler2D image) { return image; }
56 | #endif
57 |
58 | #endif //NODE_UTILS_2_PARAMETERS_GLSL
59 |
--------------------------------------------------------------------------------
/Malt/Shaders/Procedural/Bayer.glsl:
--------------------------------------------------------------------------------
1 | #ifndef BAYER_GLSL
2 | #define BAYER_GLSL
3 |
4 | // Based on: https://en.wikipedia.org/wiki/Ordered_dithering
5 |
6 | /* META GLOBAL
7 | @meta: internal=true;
8 | */
9 |
10 | int _bayer_index(ivec2 texel, int size)
11 | {
12 | texel = texel % ivec2(size);
13 | return texel.x + size*texel.y;
14 | }
15 |
16 | float bayer_2x2(ivec2 texel)
17 | {
18 | int matrix[4] = int[4](
19 | 0,2,
20 | 3,1
21 | );
22 | return float(matrix[_bayer_index(texel, 2)]) / 4.0;
23 | }
24 |
25 | float bayer_3x3(ivec2 texel)
26 | {
27 | int matrix[9] = int[9](
28 | 0,7,3,
29 | 6,5,2,
30 | 4,1,8
31 | );
32 | return float(matrix[_bayer_index(texel, 3)]) / 9.0;
33 | }
34 |
35 | float bayer_4x4(ivec2 texel)
36 | {
37 | int matrix[16] = int[16](
38 | 0,8,2,10,
39 | 12,4,14,6,
40 | 3,11,1,9,
41 | 15,7,13,5
42 | );
43 | return float(matrix[_bayer_index(texel, 4)]) / 16.0;
44 | }
45 |
46 | float bayer_8x8(ivec2 texel)
47 | {
48 | int matrix[64] = int[64](
49 | 0,32,8,40,2,34,10,42,
50 | 48,16,56,24,50,18,58,26,
51 | 12,44,4,36,14,46,6,38,
52 | 60,28,53,20,62,30,54,22,
53 | 3,35,11,43,1,33,9,41,
54 | 51,19,59,27,49,17,57,25,
55 | 15,47,7,39,13,45,5,37,
56 | 63,31,55,23,61,29,53,21
57 | );
58 | return float(matrix[_bayer_index(texel, 8)]) / 64.0;
59 | }
60 |
61 | #endif //BAYER_GLSL
62 |
--------------------------------------------------------------------------------
/BlenderMalt/CBlenderMalt/__init__.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import os
3 | import ctypes
4 |
5 | import platform
6 |
7 | src_dir = os.path.abspath(os.path.dirname(__file__))
8 |
9 | library = 'libCBlenderMalt.so'
10 | if platform.system() == 'Windows': library = 'CBlenderMalt.dll'
11 | if platform.system() == 'Darwin': library = 'libCBlenderMalt.dylib'
12 |
13 | CBlenderMalt = ctypes.CDLL(os.path.join(src_dir, library))
14 |
15 | retrieve_mesh_data = CBlenderMalt['retrieve_mesh_data']
16 | retrieve_mesh_data.argtypes = [
17 | ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int,
18 | ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float),
19 | ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint32)
20 | ]
21 | retrieve_mesh_data.restype = None
22 |
23 | mesh_tangents_ptr = CBlenderMalt['mesh_tangents_ptr']
24 | mesh_tangents_ptr.argtypes = [ctypes.c_void_p]
25 | mesh_tangents_ptr.restype = ctypes.POINTER(ctypes.c_float)
26 |
27 | pack_tangents = CBlenderMalt['pack_tangents']
28 | pack_tangents.argtypes = [ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.POINTER(ctypes.c_float)]
29 | pack_tangents.restype = None
30 |
31 | has_flat_polys = CBlenderMalt['has_flat_polys']
32 | has_flat_polys.argtypes = [ctypes.c_void_p, ctypes.c_int]
33 | has_flat_polys.restype = ctypes.c_bool
34 |
35 | get_rect_ptr = CBlenderMalt['get_rect_ptr']
36 | get_rect_ptr.argtypes = [ctypes.c_void_p]
37 | get_rect_ptr.restype = ctypes.POINTER(ctypes.c_float)
38 |
--------------------------------------------------------------------------------
/Bridge/__init__.py:
--------------------------------------------------------------------------------
1 | def reload():
2 | import importlib
3 | from . import Client_API, Server, Material, Mesh, Texture
4 | for module in [ Client_API, Server, Material, Mesh, Texture ]:
5 | importlib.reload(module)
6 |
7 | def start_server(pipeline_path, viewport_bit_depth, connection_addresses,
8 | shared_dic, lock, log_path, debug_mode, renderdoc_path, plugins_paths, docs_path):
9 | import logging
10 | log_level = logging.DEBUG if debug_mode else logging.INFO
11 | logging.basicConfig(filename=log_path, level=log_level, format='Malt > %(message)s')
12 | console_logger = logging.StreamHandler()
13 | console_logger.setLevel(logging.WARNING)
14 | logging.getLogger().addHandler(console_logger)
15 |
16 | import os, sys, ctypes
17 | if sys.platform == 'win32':
18 | win = ctypes.windll.kernel32
19 | HIGH_PRIORITY_CLASS = 0x00000080
20 | PROCESS_SET_INFORMATION = 0x0200
21 | process = win.OpenProcess(PROCESS_SET_INFORMATION, 0, win.GetCurrentProcessId())
22 | win.SetPriorityClass(process, HIGH_PRIORITY_CLASS)
23 | win.CloseHandle(process)
24 | if renderdoc_path and os.path.exists(renderdoc_path):
25 | import subprocess
26 | subprocess.call([renderdoc_path, 'inject', '--PID={}'.format(os.getpid())])
27 |
28 | from . import Server
29 | try:
30 | Server.main(pipeline_path, viewport_bit_depth, connection_addresses,
31 | shared_dic, lock, log_path, debug_mode, plugins_paths, docs_path)
32 | except:
33 | import traceback
34 | logging.error(traceback.format_exc())
35 |
--------------------------------------------------------------------------------
/Malt/Shaders/Passes/BlendTransparency.glsl:
--------------------------------------------------------------------------------
1 | #include "Common.glsl"
2 | #include "Common/Color.glsl"
3 |
4 | #ifdef VERTEX_SHADER
5 | void main()
6 | {
7 | DEFAULT_SCREEN_VERTEX_SHADER();
8 | }
9 | #endif
10 |
11 | #ifdef PIXEL_SHADER
12 |
13 | uniform sampler2D IN_BACK[8];
14 | uniform sampler2D IN_FRONT[8];
15 |
16 | layout (location = 0) out vec4 OUT_RESULT_0;
17 | layout (location = 1) out vec4 OUT_RESULT_1;
18 | layout (location = 2) out vec4 OUT_RESULT_2;
19 | layout (location = 3) out vec4 OUT_RESULT_3;
20 | layout (location = 4) out vec4 OUT_RESULT_4;
21 | layout (location = 5) out vec4 OUT_RESULT_5;
22 | layout (location = 6) out vec4 OUT_RESULT_6;
23 | layout (location = 7) out vec4 OUT_RESULT_7;
24 |
25 | void main()
26 | {
27 | PIXEL_SETUP_INPUT();
28 |
29 | ivec2 uv = ivec2(gl_FragCoord.xy);
30 |
31 | OUT_RESULT_0 = alpha_blend(texelFetch(IN_BACK[0], uv, 0), texelFetch(IN_FRONT[0], uv, 0));
32 | OUT_RESULT_1 = alpha_blend(texelFetch(IN_BACK[1], uv, 0), texelFetch(IN_FRONT[1], uv, 0));
33 | OUT_RESULT_2 = alpha_blend(texelFetch(IN_BACK[2], uv, 0), texelFetch(IN_FRONT[2], uv, 0));
34 | OUT_RESULT_3 = alpha_blend(texelFetch(IN_BACK[3], uv, 0), texelFetch(IN_FRONT[3], uv, 0));
35 | OUT_RESULT_4 = alpha_blend(texelFetch(IN_BACK[4], uv, 0), texelFetch(IN_FRONT[4], uv, 0));
36 | OUT_RESULT_5 = alpha_blend(texelFetch(IN_BACK[5], uv, 0), texelFetch(IN_FRONT[5], uv, 0));
37 | OUT_RESULT_6 = alpha_blend(texelFetch(IN_BACK[6], uv, 0), texelFetch(IN_FRONT[6], uv, 0));
38 | OUT_RESULT_7 = alpha_blend(texelFetch(IN_BACK[7], uv, 0), texelFetch(IN_FRONT[7], uv, 0));
39 | }
40 |
41 | #endif //PIXEL_SHADER
42 |
--------------------------------------------------------------------------------
/Malt/Shaders/Filters/StructureTensor.glsl:
--------------------------------------------------------------------------------
1 | #ifndef STRUCTURE_TENSOR_GLSL
2 | #define STRUCTURE_TENSOR_GLSL
3 |
4 | // Described in: https://en.wikipedia.org/wiki/Structure_tensor
5 |
6 | /* META
7 | @meta: category=Filter; internal=true;
8 | @uv: default=UV[0];
9 | */
10 | vec3 structure_tensor(sampler2D tex, vec2 uv)
11 | {
12 | vec2 texel = 1.0 / textureSize(tex, 0);
13 |
14 | // Stores the texel offset in x,y and the weight in z. the cells with weight 0 are ignored.
15 | // Notice that for v, the axes of the kernel are flipped to effectively rotate the kernel.
16 | // -1 0 1
17 | // -2 0 2
18 | // -1 0 1
19 | vec3 sobel_kernel[6] = vec3[](
20 | vec3(-1, -1, -1), vec3(+1, -1, +1),
21 | vec3(-1, +0, -2), vec3(+1, +0, +2),
22 | vec3(-1, +1, -1), vec3(+1, -1, +1)
23 | );
24 |
25 | vec3 u = vec3(0.0);
26 | vec3 v = vec3(0.0);
27 | for(int i = 0; i < 6; i++)
28 | {
29 | vec3 k = sobel_kernel[i];
30 | u += texture(tex, uv + texel * k.xy).xyz * k.z;
31 | v += texture(tex, uv + texel * k.yx).xyz * k.z;
32 | }
33 | u /= 4.0;
34 | v /= 4.0;
35 |
36 | return vec3(
37 | dot(u, u),
38 | dot(v, v),
39 | dot(u, v)
40 | );
41 | }
42 |
43 | /* META @meta: internal=true; */
44 | vec3 flow_from_structure(vec3 s)
45 | {
46 | float l = 0.5 * (s.y + s.x +sqrt(s.y*s.y - 2.0*s.x*s.y + s.x*s.x + 4.0*s.z*s.z));
47 | vec2 d = vec2(s.x - l, s.z);
48 | return (length(d) > 0.0)? vec3(normalize(d), sqrt(l)) : vec3(0,1,0);
49 | }
50 |
51 | #endif //STRUCTURE_TENSOR_GLSL
52 |
--------------------------------------------------------------------------------
/Malt/Shaders/Common/Matrix.glsl:
--------------------------------------------------------------------------------
1 | #ifndef COMMON_MATRIX_GLSL
2 | #define COMMON_MATRIX_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Math; subcategory=Matrix;
6 | */
7 |
8 | #include "Common/Quaternion.glsl"
9 |
10 | /* META
11 | @meta: label=From Translation;
12 | @t: subtype=Vector;
13 | */
14 | mat4 mat4_translation(vec3 t)
15 | {
16 | mat4 m = mat4(1.0);
17 | m[3] = vec4(t, 1);
18 | return m;
19 | }
20 |
21 | /* META
22 | @meta: label=From Quaternion;
23 | @q: subtype=Quaternion; default=vec4(0,0,0,1);
24 | */
25 | mat4 mat4_rotation_from_quaternion(vec4 q)
26 | {
27 | return mat4(mat3(
28 | quaternion_transform(q, vec3(1,0,0)),
29 | quaternion_transform(q, vec3(0,1,0)),
30 | quaternion_transform(q, vec3(0,0,1))
31 | ));
32 | }
33 |
34 | /* META
35 | @meta: label=From Euler;
36 | @e: subtype=Euler;
37 | */
38 | mat4 mat4_rotation_from_euler(vec3 e)
39 | {
40 | mat4 x = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(1,0,0), e.x));
41 | mat4 y = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(0,1,0), e.y));
42 | mat4 z = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(0,0,1), e.z));
43 |
44 | return z * y * x;
45 | }
46 |
47 | /* META
48 | @meta: label=From Scale;
49 | @s: subtype=Vector; default=vec3(1);
50 | */
51 | mat4 mat4_scale(vec3 s)
52 | {
53 | return mat4(mat3(s.x,0,0, 0,s.y,0, 0,0,s.z));
54 | }
55 |
56 | /* META
57 | @meta: label=Is Orthographic;
58 | @matrix: default=mat4(1);
59 | */
60 | bool is_ortho(mat4 matrix)
61 | {
62 | return matrix[3][3] == 1.0;
63 | }
64 |
65 | #endif // COMMON_MATRIX_GLSL
66 |
--------------------------------------------------------------------------------
/Malt/Nodes/SuperSamplingAA.py:
--------------------------------------------------------------------------------
1 | from Malt.GL.GL import *
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.RenderTarget import RenderTarget
4 | from Malt.PipelineNode import PipelineNode
5 | from Malt.PipelineParameters import Parameter, Type
6 |
7 | class SuperSamplingAA(PipelineNode):
8 |
9 | """
10 | Performs anti-aliasing by accumulating multiple render samples into a single texture.
11 | """
12 |
13 | def __init__(self, pipeline):
14 | PipelineNode.__init__(self, pipeline)
15 | self.resolution = None
16 |
17 | @classmethod
18 | def reflect_inputs(cls):
19 | inputs = {}
20 | inputs['Color'] = Parameter('', Type.TEXTURE)
21 | return inputs
22 |
23 | @classmethod
24 | def reflect_outputs(cls):
25 | outputs = {}
26 | outputs['Color'] = Parameter('', Type.TEXTURE)
27 | return outputs
28 |
29 | def setup_render_targets(self, resolution):
30 | self.t_color = Texture(resolution, GL_RGBA16F)
31 | self.fbo = RenderTarget([self.t_color])
32 |
33 | def execute(self, parameters):
34 | inputs = parameters['IN']
35 | outputs = parameters['OUT']
36 |
37 | if self.pipeline.resolution != self.resolution:
38 | self.setup_render_targets(self.pipeline.resolution)
39 | self.resolution = self.pipeline.resolution
40 |
41 | if self.pipeline.is_new_frame:
42 | self.fbo.clear([(0,0,0,0)])
43 | if inputs['Color']:
44 | self.pipeline.blend_texture(inputs['Color'], self.fbo, 1.0 / (self.pipeline.sample_count + 1))
45 | outputs['Color'] = self.t_color
46 |
47 | NODE = SuperSamplingAA
48 |
--------------------------------------------------------------------------------
/Malt/Shaders/readme.md:
--------------------------------------------------------------------------------
1 | # Shader Library
2 |
3 | All *GLSL* code should go here.
4 | All shader files have their own [*include guard*](https://en.wikipedia.org/wiki/Include_guard) macro.
5 | *Include guards* are used instead of the *#pragma once* preprocessor directive to allow user code to override the Malt built-in code when needed.
6 |
7 | Whenever it makes sense, functions should be implemented in a pipeline/renderer agnostic way.
8 | Ideally it should be possible to copy-paste *Malt* functions into any other render engine with *GLSL* support.
9 |
10 | [Common.glsl](Common.glsl) contains all the code assumed to be shared by all pipelines, including the vertex attributes layout, the *COMMON_UNIFORMS* block and the uniform blocks needed for batch rendering.
11 |
12 | The [Common](Common) folder contains 3d math and rendering utility functions.
13 |
14 | [Intellisense](Intellisense) is autogenerated from a script ([build_intellisense_glsl.py](../../scripts/build_intellisense_glsl.py)) and enables *GLSL* autocompletion to work through *C++* autocompletion providers. It's just for code editors and has no use at runtime.
15 |
16 | [Lighting](Lighting) contains a basic lighting and shadow mapping implementation and it's intended to be extended by custom pipelines.
17 |
18 | [Shading](Shading) has an extensive collection of basic building blocks for implementing *BRDFs*.
19 |
20 | [Filters](Filters) is for texture processing functions, from *Blur* to *AO*.
21 | Standalone texture processing and other types of shaders that wouldn't benefit from a library-like interface are located in [Passes](Passes)
22 |
23 | Any pipeline specific code should go inside their own folder in [Pipelines](Pipelines).
24 |
--------------------------------------------------------------------------------
/scripts/install_dependencies.py:
--------------------------------------------------------------------------------
1 | import os, subprocess, sys, shutil, stat
2 |
3 | current_dir = os.path.dirname(os.path.realpath(__file__))
4 |
5 | malt_folder = os.path.join(current_dir, '..', 'Malt')
6 |
7 | try:
8 | subprocess.check_call([sys.executable, '-m', 'pip', '--version'])
9 | except:
10 | subprocess.check_call([sys.executable, '-m', 'ensurepip'])
11 | os.environ.pop("PIP_REQ_TRACKER", None)
12 |
13 | py_version = str(sys.version_info[0])+str(sys.version_info[1])
14 | malt_dependencies_path = os.path.join(malt_folder, '.Dependencies-{}'.format(py_version))
15 | dependencies = ['glfw', 'PyOpenGL', 'PyOpenGL_accelerate', 'Pyrr', 'xxhash']
16 | for dependency in dependencies:
17 | try:
18 | subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', dependency, '--target', malt_dependencies_path])
19 | except:
20 | print('ERROR: pip install {} failed.'.format(dependency))
21 | import traceback
22 | traceback.print_exc()
23 |
24 |
25 | from distutils.dir_util import copy_tree
26 | copy_tree(os.path.join(current_dir, 'PatchDependencies'), malt_dependencies_path)
27 |
28 | #make sure mcpp has executable permissions
29 | for str in ['Linux', 'Darwin']:
30 | mcpp = os.path.join(malt_dependencies_path, f'mcpp-{str}')
31 | os.chmod(mcpp, os.stat(mcpp).st_mode | stat.S_IEXEC)
32 |
33 | #Remove numpy since Blender already ships with it
34 | #Remove bin to avoid AVs false positives
35 | for e in os.listdir(malt_dependencies_path):
36 | if e.startswith('numpy') or e == 'bin':
37 | shutil.rmtree(os.path.join(malt_dependencies_path, e))
38 |
39 |
40 | #subprocess.check_call([sys.executable, os.path.join(current_dir, 'get_glslang.py')])
41 |
--------------------------------------------------------------------------------
/Bridge/Proxys.py:
--------------------------------------------------------------------------------
1 | from Malt.GL.Mesh import Mesh
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.Texture import Gradient
4 | from Malt.Scene import Material
5 |
6 | class MeshProxy(Mesh):
7 |
8 | def __init__(self, name, submesh_index):
9 | self.name = name
10 | self.mesh = None
11 | self.submesh_index = submesh_index
12 |
13 | def resolve(self):
14 | import Bridge.Mesh
15 | self.mesh = Bridge.Mesh.MESHES[self.name][self.submesh_index]
16 | self.__dict__.update(self.mesh.__dict__)
17 |
18 | def __del__(self):
19 | pass
20 |
21 | class TextureProxy(Texture):
22 |
23 | def __init__(self, name):
24 | self.name = name
25 | self.texture = None
26 |
27 | def resolve(self):
28 | import Bridge.Texture
29 | self.texture = Bridge.Texture.TEXTURES[self.name]
30 | self.__dict__.update(self.texture.__dict__)
31 |
32 | def __del__(self):
33 | pass
34 |
35 | class GradientProxy(Gradient):
36 |
37 | def __init__(self, name):
38 | self.name = name
39 | self.gradient = None
40 |
41 | def resolve(self):
42 | import Bridge.Texture
43 | self.gradient = Bridge.Texture.GRADIENTS[self.name]
44 | self.__dict__.update(self.gradient.__dict__)
45 |
46 | def __del__(self):
47 | pass
48 |
49 | class MaterialProxy(Material):
50 |
51 | def __init__(self, path, shader_parameters, parameters):
52 | self.path = path
53 | self.shader_parameters = shader_parameters
54 | super().__init__(None, parameters)
55 |
56 | def resolve(self):
57 | import Bridge.Material
58 | self.shader = Bridge.Material.get_shader(self.path, self.shader_parameters)
59 |
--------------------------------------------------------------------------------
/Malt/PipelinePlugin.py:
--------------------------------------------------------------------------------
1 | from Malt.Utils import isinstance_str
2 |
3 | class PipelinePlugin():
4 |
5 | @classmethod
6 | def poll_pipeline(self, pipeline):
7 | return True
8 |
9 | @classmethod
10 | def register_pipeline_parameters(self, parameters):
11 | pass
12 |
13 | @classmethod
14 | def register_pipeline_graphs(self):
15 | return []
16 |
17 | @classmethod
18 | def register_graph_libraries(self, graphs):
19 | pass
20 |
21 | @classmethod
22 | def blendermalt_register(self):
23 | pass
24 |
25 | @classmethod
26 | def blendermalt_unregister(self):
27 | pass
28 |
29 | @classmethod
30 | def blendermalt_register_nodeitems(self, MaltNodeItemClass):
31 | # Should return a dictionary where keys are category names and values are arrays of MaltNodeItems
32 | return {}
33 |
34 | def load_plugins_from_dir(dir):
35 | import sys, os, importlib
36 | if dir not in sys.path:
37 | sys.path.append(dir)
38 | plugins=[]
39 | for e in os.scandir(dir):
40 | if (e.path.startswith('.') or e.path.startswith('_') or
41 | e.is_file() and e.path.endswith('.py') == False):
42 | continue
43 | try:
44 | '''
45 | spec = importlib.util.spec_from_file_location("_dynamic_plugin_module_", e.path)
46 | module = importlib.util.module_from_spec(spec)
47 | spec.loader.exec_module(module)
48 | '''
49 | module = importlib.import_module(e.name)
50 | importlib.reload(module)
51 | plugins.append(module.PLUGIN)
52 | except:
53 | import traceback
54 | traceback.print_exc()
55 | print('FILEPATH : ', e.path)
56 | return plugins
57 |
--------------------------------------------------------------------------------
/scripts/format.py:
--------------------------------------------------------------------------------
1 | def scan_dirs(path, file_callback):
2 | import os
3 | for e in os.scandir(path):
4 | if e.is_file():
5 | extension = e.name.split('.')[-1]
6 | if extension in ('py','glsl','h','c','cpp'):
7 | file_callback(e)
8 | if e.is_dir():
9 | if e.name.startswith('.') or e.name.startswith('__'):
10 | continue
11 | scan_dirs(e, file_callback)
12 |
13 |
14 | def fix_whitespace(path):
15 | with open(path, 'r+') as f:
16 | result = ''
17 | lines = f.readlines()
18 | for i, line in enumerate(lines):
19 | new_line = ''
20 | if line.isspace():
21 | while i < len(lines) - 1:
22 | i += 1
23 | found_next_line = False
24 | if lines[i].isspace() == False:
25 | for c in lines[i]:
26 | if c.isspace():
27 | new_line += c
28 | else:
29 | break
30 | found_next_line = True
31 | else:
32 | continue
33 | if found_next_line:
34 | break
35 | else:
36 | new_line = line.rstrip()
37 | result += new_line
38 | result += '\n'
39 | result = result.splitlines(keepends=True)
40 | while len(result) > 1 and result[-1].isspace():
41 | while result[-1].isspace():
42 | result.pop()
43 | result = ''.join(result)
44 | f.seek(0)
45 | f.truncate()
46 | f.write(result)
47 |
48 |
49 | import sys
50 |
51 | scan_dirs(sys.argv[1], fix_whitespace)
52 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Int.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_INT_GLSL
2 | #define NODE_UTILS_2_INT_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Math; subcategory=Integer;
6 | */
7 |
8 | /*META @meta: label=Add;*/
9 | int Int_add(int a, int b){ return a+b; }
10 | /*META @meta: label=Subtract;*/
11 | int Int_subtract(int a, int b){ return a-b; }
12 | /*META @meta: label=Multiply;*/
13 | int Int_multiply(int a, int b){ return a*b; }
14 | /*META @meta: label=Divide;*/
15 | int Int_divide(int a, int b){ return a/b; }
16 | /*META @meta: label=Modulo;*/
17 | int Int_modulo(int a, int b){ return a%b; }
18 |
19 | /*META @meta: label=Clamp; @b: label=Min; @c: label=Max; */
20 | int Int_clamp(int a, int b, int c){ return clamp(a, b, c); }
21 |
22 | /*META @meta: label=Sign;*/
23 | int Int_sign(int a){ return sign(a); }
24 | /*META @meta: label=Absolute;*/
25 | int Int_abs(int a){ return abs(a); }
26 | /*META @meta: label=Minimum;*/
27 | int Int_min(int a, int b){ return min(a,b); }
28 | /*META @meta: label=Maximum;*/
29 | int Int_max(int a, int b){ return max(a,b); }
30 |
31 | /*META @meta: label=Equal;*/
32 | bool Int_equal(int a, int b){ return a == b; }
33 | /*META @meta: label=Not Equal;*/
34 | bool Int_not_equal(int a, int b){ return a != b; }
35 | /*META @meta: label=Greater;*/
36 | bool Int_greater(int a, int b){ return a > b; }
37 | /*META @meta: label=Greater or Equal;*/
38 | bool Int_greater_or_equal(int a, int b){ return a >= b; }
39 | /*META @meta: label=Less;*/
40 | bool Int_less(int a, int b){ return a < b; }
41 | /*META @meta: label=Less or Equal;*/
42 | bool Int_less_or_equal(int a, int b){ return a <= b; }
43 |
44 | /*META @meta: label=If Else; @a: label=If True; @b: label=If False; */
45 | int Int_if_else(bool condition, int a, int b){ return condition ? a : b; }
46 |
47 | #endif //NODE_UTILS_2_INT_GLSL
48 |
--------------------------------------------------------------------------------
/Malt/Shaders/Filters/JumpFlood.glsl:
--------------------------------------------------------------------------------
1 | #ifndef JUMP_FLOOD_GLSL
2 | #define JUMP_FLOOD_GLSL
3 |
4 | /* META
5 | @width: default=1.0;
6 | */
7 | //input should be a texture where x and y are screen space uvs.
8 | //samples with values == vec(-1,-1) are ignored
9 | //More info : https://medium.com/@bgolus/the-quest-for-very-wide-outlines-ba82ed442cd9#c5bb
10 | vec4 jump_flood(sampler2D input_texture, vec2 uv, float width, bool third_channel_scale)
11 | {
12 | vec4 nearest = vec4(-1,-1,-1,-1);
13 | vec2 resolution = vec2(textureSize(input_texture, 0));
14 | vec2 offset = vec2(width) / resolution;
15 |
16 | for(int x = -1; x <= 1; x++)
17 | {
18 | for(int y = -1; y <= 1; y++)
19 | {
20 | vec2 sample_uv = uv + vec2(x,y) * offset;
21 | vec4 sampled = texture(input_texture, sample_uv);
22 | if(sampled.xy != vec2(-1,-1)) //Check if it's valid
23 | {
24 | float stored_distance = distance(uv * resolution, nearest.xy * resolution);
25 | float sampled_distance = distance(uv * resolution, sampled.xy * resolution);
26 |
27 | if(third_channel_scale)
28 | {
29 | if(nearest.z > 0)
30 | {
31 | stored_distance /= nearest.z;
32 | }
33 | if(sampled.z > 0)
34 | {
35 | sampled_distance /= sampled.z;
36 | }
37 | }
38 |
39 | if(nearest.xy == vec2(-1,-1) || sampled_distance < stored_distance)
40 | {
41 | nearest = sampled;
42 | }
43 | }
44 | }
45 | }
46 |
47 | return nearest;
48 | }
49 |
50 |
51 | #endif //JUMP_FLOOD_GLSL
52 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/vec2.glsl:
--------------------------------------------------------------------------------
1 | #ifndef VEC2_GLSL
2 | #define VEC2_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | vec2 vec2_add(vec2 a, vec2 b){ return a+b; }
9 | vec2 vec2_subtract(vec2 a, vec2 b){ return a-b; }
10 | vec2 vec2_multiply(vec2 a, vec2 b){ return a*b; }
11 | vec2 vec2_divide(vec2 a, vec2 b){ return a/b; }
12 | vec2 vec2_scale(vec2 v, float s){ return v*s; }
13 | vec2 vec2_modulo(vec2 a, vec2 b){ return mod(a,b); }
14 | vec2 vec2_pow(vec2 v, vec2 e){ return pow(v, e); }
15 | vec2 vec2_sqrt(vec2 v){ return sqrt(v); }
16 |
17 | vec2 vec2_round(vec2 v){ return round(v); }
18 | vec2 vec2_fract(vec2 v){ return fract(v); }
19 | vec2 vec2_floor(vec2 v){ return floor(v); }
20 | vec2 vec2_ceil(vec2 v){ return ceil(v); }
21 |
22 | vec2 vec2_clamp(vec2 v, vec2 min, vec2 max){ return clamp(v, min, max); }
23 |
24 | vec2 vec2_sign(vec2 v){ return sign(v); }
25 | vec2 vec2_abs(vec2 v){ return abs(v); }
26 | vec2 vec2_min(vec2 a, vec2 b){ return min(a,b); }
27 | vec2 vec2_max(vec2 a, vec2 b){ return max(a,b); }
28 |
29 | vec2 vec2_mix(vec2 a, vec2 b, vec2 factor){ return mix(a,b,factor); }
30 | vec2 vec2_mix_float(vec2 a, vec2 b, float factor){ return mix(a,b,factor); }
31 |
32 | vec2 vec2_normalize(vec2 v){ return normalize(v); }
33 |
34 | float vec2_length(vec2 v){ return length(v); }
35 | float vec2_distance(vec2 a, vec2 b){ return distance(a,b); }
36 | float vec2_dot_product(vec2 a, vec2 b){ return dot(a,b); }
37 |
38 | bool vec2_equal(vec2 a, vec2 b){ return a == b; }
39 | bool vec2_not_equal(vec2 a, vec2 b){ return a != b; }
40 |
41 | vec2 vec2_if_else(bool condition, vec2 if_true, vec2 if_false){ return condition ? if_true : if_false; }
42 |
43 | vec2 vec2_join(float x, float y) { return vec2(x,y);}
44 | void vec2_split(vec2 v, out float x, out float y){ x=v.x; y=v.y; }
45 |
46 | #endif //VEC2_GLSL
47 |
--------------------------------------------------------------------------------
/Malt/Shaders/Procedural/Cell_Noise.inl:
--------------------------------------------------------------------------------
1 | //Should define T, DIMENSIONS and TILE
2 | //Should declare coord and tile_size (if TILE)
3 | T real_coord = coord;
4 | #if TILE != 0
5 | T _tile_size = round(T(tile_size));
6 | coord = mod(coord, _tile_size);
7 | #endif
8 | T real_origin = floor(real_coord) + 0.5;
9 | T origin = floor(coord) + 0.5;
10 | T delta = fract(coord);
11 |
12 | vec4 r_cell_color;
13 | T r_cell_position;
14 | float r_cell_distance = 10;
15 |
16 | #if DIMENSIONS == 4
17 | const ivec4 D = ivec4(1);
18 | #elif DIMENSIONS == 3
19 | const ivec4 D = ivec4(1,1,1,0);
20 | #elif DIMENSIONS == 2
21 | const ivec4 D = ivec4(1,1,0,0);
22 | #elif DIMENSIONS == 1
23 | const ivec4 D = ivec4(1,0,0,0);
24 | #endif
25 |
26 | for(int w = -D.w; w <= D.w; w++)
27 | {
28 | for(int z = -D.z; z <= D.z; z++)
29 | {
30 | for(int y = -D.y; y <= D.y; y++)
31 | {
32 | for(int x = -D.x; x <= D.x; x++)
33 | {
34 | T offset = T(vec4(x,y,z,w));
35 | T real_corner = real_origin + offset;
36 | T corner = origin + offset;
37 | #if TILE != 0
38 | corner = mod(corner, _tile_size);
39 | #endif
40 | vec4 corner_hash = hash(corner);
41 | T real_cell_position = real_corner + (T(corner_hash) - 0.5);
42 | T cell_position = corner + (T(corner_hash) - 0.5);
43 | float cell_distance = distance(real_coord, real_cell_position);
44 | if(cell_distance < r_cell_distance)
45 | {
46 | r_cell_color = corner_hash;
47 | r_cell_position = real_cell_position;
48 | r_cell_distance = cell_distance;
49 | }
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Reports
2 | description: Open a new issue
3 | body:
4 |
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Before opening a new issue:
9 | - Read the [readme](https://github.com/bnpr/Malt#malt), check that you meet the minimum requirements and have installed Malt properly.
10 | - Make sure you are running the latest Malt version.
11 | - Search [existing issues](https://github.com/bnpr/Malt/issues) to ensure it has not already been reported.
12 |
13 | - type: input
14 | attributes:
15 | label: Malt version
16 | placeholder: Release | Development
17 | validations:
18 | required: true
19 |
20 | - type: input
21 | attributes:
22 | label: Blender version
23 | placeholder: Blender 3.0.0
24 | validations:
25 | required: true
26 |
27 | - type: input
28 | attributes:
29 | label: OS
30 | placeholder: Windows 10 64 bits
31 | validations:
32 | required: true
33 |
34 | - type: input
35 | attributes:
36 | label: Hardware info
37 | placeholder: Intel i7-4790K | 16GB RAM | Nvidia GTX 1060 6GB
38 | validations:
39 | required: true
40 |
41 | - type: textarea
42 | attributes:
43 | label: Issue description and reproduction steps
44 | validations:
45 | required: true
46 |
47 | - type: textarea
48 | attributes:
49 | label: Attachments
50 | description: |
51 | - The **Log file** from the Blender session where the bug happened.
52 | You can find it in *Preferences > Addons > BlenderMalt > Open Session Log* or search for it in your system temporary folder.
53 | - A zipped **.blend file** with a minimal example to reproduce the error. *(Unless the error is not file dependent)*
54 | > Don't copy/paste the *Log* text. Drag and drop the files to upload them.
55 | validations:
56 | required: true
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/LICENSE - DEPENDENCIES:
--------------------------------------------------------------------------------
1 | Malt:
2 |
3 | PyOpenGL
4 | BSD License
5 | http://pyopengl.sourceforge.net
6 | https://github.com/mcfletch/pyopengl/blob/master/license.txt
7 |
8 | pyGLFW
9 | MIT License
10 | https://github.com/FlorianRhiem/pyGLFW
11 | https://github.com/FlorianRhiem/pyGLFW/blob/master/LICENSE.txt
12 |
13 | GLFW
14 | zlib License
15 | https://www.glfw.org/
16 | https://github.com/glfw/glfw/blob/master/LICENSE.md
17 |
18 | mcpp
19 | MIT License
20 | https://github.com/pragma37/mcpp
21 | https://github.com/pragma37/mcpp/blob/master/LICENSE
22 |
23 | multipledispatch
24 | BSD License
25 | http://github.com/mrocklin/multipledispatch/
26 | https://github.com/mrocklin/multipledispatch/blob/master/LICENSE.txt
27 |
28 | numpy
29 | BSD License
30 | https://www.numpy.org
31 | https://github.com/numpy/numpy/blob/main/LICENSE.txt
32 | https://github.com/numpy/numpy/blob/main/LICENSES_bundled.txt
33 |
34 | pyrr
35 | BSD License
36 | https://github.com/adamlwgriffiths/Pyrr
37 | https://github.com/adamlwgriffiths/Pyrr/blob/master/LICENSE
38 |
39 | six
40 | MIT License
41 | https://github.com/benjaminp/six
42 | https://github.com/benjaminp/six/blob/master/LICENSE
43 |
44 | GLSLParser:
45 |
46 | PEGTL
47 | Boost Software License
48 | https://github.com/taocpp/PEGTL
49 | https://github.com/taocpp/PEGTL/blob/main/LICENSE_1_0.txt
50 |
51 | rapidjson
52 | MIT License
53 | https://github.com/Tencent/rapidjson
54 | https://github.com/Tencent/rapidjson/blob/master/license.txt
55 |
56 | Bridge:
57 |
58 | ipc
59 | The Unlicense
60 | https://github.com/jarikomppa/ipc
61 | https://github.com/jarikomppa/ipc/blob/master/LICENSE
62 |
63 | renderdoc (renderdoc_app.h)
64 | MIT License
65 | https://renderdoc.org/
66 | https://github.com/baldurk/renderdoc/blob/v1.x/LICENSE.md
67 |
68 | BlenderMalt:
69 |
70 | blender
71 | GNU GPL
72 | https://blender.org
73 | https://github.com/blender/blender/blob/master/doc/license/GPL3-license.txt
74 |
75 |
--------------------------------------------------------------------------------
/Malt/Shaders/Common/Quaternion.glsl:
--------------------------------------------------------------------------------
1 | #ifndef COMMON_QUATERNION_GLSL
2 | #define COMMON_QUATERNION_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Math; subcategory=Quaternion;
6 | */
7 |
8 |
9 | /* META
10 | @meta: label=From Axis Angle;
11 | @axis: subtype=Normal;
12 | @angle: subtype=Angle; default=0;
13 | */
14 | vec4 quaternion_from_axis_angle(vec3 axis, float angle)
15 | {
16 | return vec4(axis * sin(0.5 * angle), cos(0.5 * angle));
17 | }
18 |
19 | /* META
20 | @meta: label=From Vector Delta;
21 | @from: subtype=Normal;
22 | @to: subtype=Normal;
23 | */
24 | vec4 quaternion_from_vector_delta(vec3 from, vec3 to)
25 | {
26 | return normalize(vec4(cross(from, to), 1.0 + dot(from, to)));
27 | }
28 |
29 | /* META
30 | @meta: label=Inverted;
31 | @a: label=Q; subtype=Quaternion; default=vec4(0,0,0,1);
32 | */
33 | vec4 quaternion_inverted(vec4 a)
34 | {
35 | return vec4(-a.xyz, a.w);
36 | }
37 |
38 | /* META
39 | @meta: label=Multiply;
40 | @a: subtype=Quaternion; default=vec4(0,0,0,1);
41 | @b: subtype=Quaternion; default=vec4(0,0,0,1);
42 | */
43 | vec4 quaternion_multiply(vec4 a, vec4 b)
44 | {
45 | return vec4
46 | (
47 | a.xyz * b.w + b.xyz * a.w + cross(a.xyz, b.xyz),
48 | a.w * b.w - dot(a.xyz, b.xyz)
49 | );
50 | }
51 |
52 | /* META
53 | @meta: label=Transform;
54 | @a: label=Q; subtype=Quaternion; default=vec4(0,0,0,1);
55 | @vector: subtype=Vector; default=vec3(0);
56 | */
57 | vec3 quaternion_transform(vec4 a, vec3 vector)
58 | {
59 | vec3 t = cross(a.xyz, vector) * 2.0;
60 | return vector + t * a.w + cross(a.xyz, t);
61 | }
62 |
63 | /* META
64 | @meta: label=Mix;
65 | @a: subtype=Quaternion; default=vec4(0,0,0,1);
66 | @b: subtype=Quaternion; default=vec4(0,0,0,1);
67 | @factor: subtype=Slider; default=0.5; min=0; max=1;
68 | */
69 | vec4 quaternion_mix(vec4 a, vec4 b, float factor)
70 | {
71 | return normalize(mix(a, b, factor));
72 | }
73 |
74 | #endif //COMMON_QUATERION_GLSL
75 |
--------------------------------------------------------------------------------
/Malt/Render/Sampling.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 | import random
4 | #Don't share state
5 | random = random.Random()
6 |
7 | #Rotated Grid Super Sampling pattern
8 | #https://en.wikipedia.org/wiki/Supersampling#Rotated_grid
9 | def get_RGSS_samples(grid_size, width=1.0):
10 | samples = []
11 | for x in range(0, grid_size):
12 | for y in range(0, grid_size):
13 | _x = (x / grid_size) * 2.0 - 1.0 #(-1 ... +1 range)
14 | _y = (y / grid_size) * 2.0 - 1.0 #(-1 ... +1 range)
15 |
16 | angle = math.atan(1/2)
17 | sin = math.sin(angle)
18 | cos = math.cos(angle)
19 | r_x = _x * cos - _y * sin
20 | r_y = _x * sin + _y * cos
21 |
22 | scale = math.sqrt(5)/2
23 | r_x *= scale
24 |
25 | #discard samples where radius > 1
26 | if r_x * r_x + r_y * r_y <= 1:
27 | r_x *= width
28 | r_y *= width
29 | samples.append((r_x,r_y))
30 |
31 | random.seed(0)
32 | #randomize the sampling order to get better early results
33 | samples = sorted(samples, key=lambda k: random.random())
34 |
35 | #Make sure there's at least one sample
36 | if len(samples) == 0:
37 | samples = [(0,0)]
38 |
39 | return samples
40 |
41 |
42 | #Random sampling. Best results at high sample counts
43 | def get_random_samples(grid_size, width=1.0):
44 | random.seed(0)
45 | samples = []
46 | for i in range(0, grid_size * grid_size):
47 | x = 2
48 | y = 2
49 |
50 | #discard samples where radius > 1
51 | while x*x + y*y > 1.0:
52 | x = random.random() * 2.0 - 1.0 #(-1 ... +1 range)
53 | y = random.random() * 2.0 - 1.0 #(-1 ... +1 range)
54 |
55 | x *= width
56 | y *= width
57 |
58 | samples.append((x,y))
59 |
60 | #Make sure there's at least one sample
61 | if len(samples) == 0:
62 | samples = [(0,0)]
63 |
64 | return samples
65 |
--------------------------------------------------------------------------------
/BlenderMalt/CBlenderMalt/blender_dna/BLI_sys_types.h:
--------------------------------------------------------------------------------
1 | /* SPDX-License-Identifier: GPL-2.0-or-later
2 | * Copyright 2001-2002 NaN Holding BV. All rights reserved. */
3 |
4 | /** \file
5 | * \ingroup bli
6 | *
7 | * A platform-independent definition of [u]intXX_t
8 | * Plus the accompanying header include for htonl/ntohl
9 | *
10 | * This file includes to define [u]intXX_t types, where
11 | * XX can be 8, 16, 32 or 64. Unfortunately, not all systems have this
12 | * file.
13 | * - Windows uses __intXX compiler-builtin types. These are signed,
14 | * so we have to flip the signs.
15 | * For these rogue platforms, we make the typedefs ourselves.
16 | */
17 |
18 | #pragma once
19 |
20 | #ifdef UNDEFINED
21 | extern "C" {
22 | #endif
23 |
24 | #if defined(__linux__) || defined(__GNU__) || defined(__NetBSD__) || defined(__OpenBSD__) || \
25 | defined(__FreeBSD_kernel__) || defined(__HAIKU__)
26 |
27 | /* Linux-i386, Linux-Alpha, Linux-PPC */
28 | # include
29 |
30 | /* XXX */
31 | # ifndef UINT64_MAX
32 | # define UINT64_MAX 18446744073709551615
33 | typedef uint8_t u_int8_t;
34 | typedef uint16_t u_int16_t;
35 | typedef uint32_t u_int32_t;
36 | typedef uint64_t u_int64_t;
37 | # endif
38 |
39 | #elif defined(__APPLE__)
40 |
41 | # include
42 |
43 | /* MSVC >= 2010 */
44 | #elif defined(_MSC_VER)
45 | # include
46 |
47 | #else
48 |
49 | /* FreeBSD, Solaris */
50 | # include
51 | # include
52 |
53 | #endif /* ifdef platform for types */
54 |
55 | #include
56 | #include /* size_t define */
57 |
58 | #ifndef UNDEFINED
59 | /* The standard header is missing on some systems. */
60 | # if defined(__APPLE__) || defined(__NetBSD__)
61 | typedef unsigned int char32_t;
62 | # else
63 | # include
64 | # endif
65 | #endif
66 |
67 | typedef unsigned int uint;
68 | typedef unsigned short ushort;
69 | typedef unsigned long ulong;
70 | typedef unsigned char uchar;
71 |
72 | #ifdef UNDEFINED
73 | }
74 | #endif
75 |
--------------------------------------------------------------------------------
/Bridge/Material.py:
--------------------------------------------------------------------------------
1 | from Malt.PipelineParameters import Parameter
2 |
3 | MATERIAL_SHADERS = {}
4 |
5 | class Material():
6 |
7 | def __init__(self, path, pipeline, search_paths=[], custom_passes={}):
8 | self.path = path
9 | self.parameters = {}
10 | self.compiler_error = ''
11 |
12 | compiled_material = pipeline.compile_material(path, search_paths)#, custom_passes)
13 |
14 | if isinstance(compiled_material, str):
15 | self.compiler_error = compiled_material
16 | else:
17 | for pass_name, shader in compiled_material.items():
18 | for uniform_name, uniform in shader.uniforms.items():
19 | self.parameters[uniform_name] = Parameter.from_uniform(uniform)
20 | if shader.error:
21 | self.compiler_error += pass_name + " : " + shader.error
22 | if shader.validator:
23 | self.compiler_error += pass_name + " : " + shader.validator
24 |
25 | if self.compiler_error == '':
26 | global MATERIAL_SHADERS
27 | MATERIAL_SHADERS[self.path] = compiled_material
28 | else:
29 | MATERIAL_SHADERS[self.path] = {}
30 |
31 |
32 | def get_shader(path, parameters):
33 | if path not in MATERIAL_SHADERS.keys():
34 | return {}
35 | shaders = MATERIAL_SHADERS[path]
36 | new_shader = {}
37 |
38 | for pass_name, pass_shader in shaders.items():
39 | if pass_shader.error:
40 | new_shader = {}
41 | break
42 |
43 | pass_shader_copy = pass_shader.copy()
44 | new_shader[pass_name] = pass_shader_copy
45 |
46 | for name, parameter in parameters.items():
47 | if name in pass_shader_copy.textures.keys():
48 | pass_shader_copy.textures[name] = parameter
49 | elif name in pass_shader_copy.uniforms.keys():
50 | pass_shader_copy.uniforms[name].set_value(parameter)
51 |
52 | return new_shader
53 |
--------------------------------------------------------------------------------
/Malt/Render/Common.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 |
3 | from Malt.GL.Shader import UBO
4 |
5 | class C_CommonBuffer(ctypes.Structure):
6 | _fields_ = [
7 | ('CAMERA', ctypes.c_float*16),
8 | ('PROJECTION', ctypes.c_float*16),
9 | ('RESOLUTION', ctypes.c_int*2),
10 | ('SAMPLE_OFFSET', ctypes.c_float*2),
11 | ('SAMPLE_COUNT', ctypes.c_int),
12 | ('FRAME', ctypes.c_int),
13 | ('TIME', ctypes.c_float),
14 | ('__padding', ctypes.c_int),
15 | ]
16 |
17 | def bake_sample_offset(projection_matrix, sample_offset, resolution):
18 | offset_x = sample_offset[0] / resolution[0]
19 | offset_y = sample_offset[1] / resolution[1]
20 | if projection_matrix[-1] == 1.0:
21 | #Orthographic camera
22 | projection_matrix[12] += offset_x
23 | projection_matrix[13] += offset_y
24 | else:
25 | #Perspective camera
26 | projection_matrix[8] += offset_x
27 | projection_matrix[9] += offset_y
28 |
29 | class CommonBuffer():
30 |
31 | def __init__(self):
32 | self.data = C_CommonBuffer()
33 | self.UBO = UBO()
34 |
35 | def load(self, scene, resolution, sample_offset=(0,0), sample_count=0, camera=None, projection = None):
36 | self.data.CAMERA = tuple(camera if camera else scene.camera.camera_matrix)
37 | self.data.PROJECTION = tuple(projection if projection else scene.camera.projection_matrix)
38 | self.data.RESOLUTION = resolution
39 | self.data.SAMPLE_OFFSET = sample_offset
40 | self.data.SAMPLE_COUNT = sample_count
41 | self.data.FRAME = scene.frame
42 | self.data.TIME = scene.time
43 |
44 | bake_sample_offset(self.data.PROJECTION, sample_offset, resolution)
45 |
46 | self.UBO.load_data(self.data)
47 |
48 | def bind(self, block):
49 | self.UBO.bind(block)
50 |
51 | def shader_callback(self, shader):
52 | if 'COMMON_UNIFORMS' in shader.uniform_blocks:
53 | self.bind(shader.uniform_blocks['COMMON_UNIFORMS'])
54 |
--------------------------------------------------------------------------------
/Malt/Scene.py:
--------------------------------------------------------------------------------
1 | class Camera():
2 |
3 | def __init__(self, camera_matrix, projection_matrix, parameters={}):
4 | self.camera_matrix = camera_matrix
5 | self.projection_matrix = projection_matrix
6 | self.parameters = parameters
7 |
8 | class Material():
9 |
10 | def __init__(self, shader, parameters={}):
11 | self.shader = shader
12 | self.parameters = parameters
13 |
14 | class Mesh():
15 |
16 | def __init__(self, mesh, parameters={}):
17 | self.mesh = mesh
18 | self.parameters = parameters
19 |
20 | class Object():
21 |
22 | def __init__(self, matrix, mesh, material, parameters={}, mirror_scale=False, tags=[]):
23 | self.matrix = matrix
24 | self.mesh = mesh
25 | self.material = material
26 | self.parameters = parameters
27 | self.mirror_scale = mirror_scale
28 | self.tags = tags
29 |
30 | class Light():
31 |
32 | def __init__(self):
33 | self.type = 0
34 | self.color = (0,0,0)
35 | self.parameters = {}
36 | self.position = (0,0,0)
37 | self.direction = (0,0,0)
38 | self.sun_max_distance = 0
39 | self.spot_angle = 0
40 | self.spot_blend = 0
41 | self.radius = 0
42 | self.matrix = None
43 |
44 | class Scene():
45 |
46 | def __init__(self):
47 | self.camera = None
48 | self.objects = []
49 | self.lights = []
50 | self.parameters = {}
51 | self.world_parameters = {}
52 | self.frame = 0
53 | self.time = 0
54 |
55 | self.batches = None
56 | self.shader_resources = {}
57 |
58 | class ShaderResource():
59 |
60 | def shader_callback(self, shader):
61 | pass
62 |
63 | class TextureShaderResource():
64 |
65 | def __init__(self, name, texture):
66 | self.name = name
67 | self.texture = texture
68 |
69 | def shader_callback(self, shader):
70 | if self.name in shader.textures.keys():
71 | shader.textures[self.name] = self.texture
72 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/float.glsl:
--------------------------------------------------------------------------------
1 | #ifndef FLOAT_GLSL
2 | #define FLOAT_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | float float_add(float a, float b){ return a+b; }
9 | float float_subtract(float a, float b){ return a-b; }
10 | float float_multiply(float a, float b){ return a*b; }
11 | float float_divide(float a, float b){ return a/b; }
12 | float float_modulo(float a, float b){ return mod(a,b); }
13 | float float_pow(float f, float e){ return pow(f, e); }
14 | float float_sqrt(float f){ return sqrt(f); }
15 |
16 | float float_round(float f){ return round(f); }
17 | float float_fract(float f){ return fract(f); }
18 | float float_floor(float f){ return floor(f); }
19 | float float_ceil(float f){ return ceil(f); }
20 |
21 | float float_clamp(float f, float min, float max){ return clamp(f, min, max); }
22 |
23 | float float_sign(float f){ return sign(f); }
24 | float float_abs(float f){ return abs(f); }
25 | float float_min(float a, float b){ return min(a,b); }
26 | float float_max(float a, float b){ return max(a,b); }
27 |
28 | float float_mix(float a, float b, float factor){ return mix(a,b,factor); }
29 |
30 | float float_sin(float f) { return sin(f); }
31 | float float_cos(float f) { return cos(f); }
32 | float float_tan(float f) { return tan(f); }
33 | float float_asin(float f) { return asin(f); }
34 | float float_acos(float f) { return acos(f); }
35 | float float_atan(float f) { return atan(f); }
36 |
37 | float float_degrees(float r) { return degrees(r); }
38 | float float_radians(float d) { return radians(d); }
39 |
40 | bool float_equal(float a, float b){ return a == b; }
41 | bool float_not_equal(float a, float b){ return a != b; }
42 | bool float_greater(float a, float b){ return a > b; }
43 | bool float_greater_or_equal(float a, float b){ return a >= b; }
44 | bool float_less(float a, float b){ return a < b; }
45 | bool float_less_or_equal(float a, float b){ return a <= b; }
46 |
47 | float float_if_else(bool condition, float if_true, float if_false){ return condition ? if_true : if_false; }
48 |
49 | #endif //FLOAT_GLSL
50 |
--------------------------------------------------------------------------------
/Malt/Shaders/Filters/Sharpen.glsl:
--------------------------------------------------------------------------------
1 | #ifndef SHARPEN_GLSL
2 | #define SHARPEN_GLSL
3 |
4 | #include "Filters/Blur.glsl"
5 |
6 | /* META GLOBAL
7 | @meta: category=Filter; subcategory=Sharpen;
8 | */
9 |
10 | vec4 _sharpen_common(sampler2D tex, vec2 uv, vec4 blurred, float sharpness)
11 | {
12 | vec4 base = texture(tex, uv);
13 | float scaler = 10.0; // Use scalar to put the range of the sharpness value mostly between 0-1 for convenience
14 | return base + (base - blurred) * sharpness * scaler;
15 | }
16 |
17 | /* META
18 | @meta: label=Box;
19 | @tex: label=Texture;
20 | @uv: label=UV; default = UV[0];
21 | @radius: default=1.0; min=0.0;
22 | @sharpness: default = 0.3; min=0.0;
23 | */
24 | vec4 box_sharpen(sampler2D tex, vec2 uv, float radius, bool circular, float sharpness)
25 | {
26 | vec4 blurred = box_blur(tex, uv, radius, circular);
27 | return _sharpen_common(tex, uv, blurred, sharpness);
28 | }
29 |
30 | /* META
31 | @meta: label=Gaussian;
32 | @tex: label=Texture;
33 | @uv: label=UV; default = UV[0];
34 | @radius: default=1.0; min=0.0;
35 | @sigma: default=1.0;
36 | @sharpness: default = 0.3; min=0.0;
37 | */
38 | vec4 gaussian_sharpen(sampler2D tex, vec2 uv, float radius, float sigma, float sharpness)
39 | {
40 | vec4 blurred = gaussian_blur(tex, uv, radius, sigma);
41 | return _sharpen_common(tex, uv, blurred, sharpness);
42 | }
43 |
44 | /* META
45 | @meta: label=Jitter;
46 | @tex: label=Texture;
47 | @uv: label=UV; default = UV[0];
48 | @radius: default=1.0; min=0.0;
49 | @distribution_exponent: default=5.0;
50 | @samples: default=8; min=1;
51 | @sharpness: default = 0.3; min=0.0;
52 | */
53 | vec4 jitter_sharpen(sampler2D tex, vec2 uv, float radius, float distribution_exponent, int samples, float sharpness)
54 | {
55 | vec4 blurred = jitter_blur(tex, uv, radius, distribution_exponent, samples);
56 | return _sharpen_common(tex, uv, blurred, sharpness);
57 | }
58 |
59 |
60 | #endif //SHARPEN_GLSL
61 |
--------------------------------------------------------------------------------
/Malt/Shaders/Filters/Curvature.glsl:
--------------------------------------------------------------------------------
1 | #ifndef CURVATURE_GLSL
2 | #define CURVATURE_GLSL
3 |
4 | #include "Common/Math.glsl"
5 |
6 | /* META GLOBAL
7 | @meta: category=Filter;
8 | */
9 |
10 | /* META
11 | @uv: label=UV; default=UV[0];
12 | @width: default=1.0;
13 | @x: subtype=Normal; default=vec3(1,0,0);
14 | @y: subtype=Normal; default=vec3(0,1,0);
15 | */
16 | float curvature(sampler2D normal_texture, vec2 uv, float width, vec3 x, vec3 y)
17 | {
18 | // x and y must be the screen x and y axis in the same coordinate space as the texture normals
19 | vec2 offset = vec2(width) / vec2(textureSize(normal_texture, 0));
20 |
21 | vec3 l = texture(normal_texture, uv + vec2(-offset.x,0)).xyz;
22 | vec3 r = texture(normal_texture, uv + vec2( offset.x,0)).xyz;
23 | vec3 d = texture(normal_texture, uv + vec2(0,-offset.y)).xyz;
24 | vec3 u = texture(normal_texture, uv + vec2(0, offset.y)).xyz;
25 |
26 | if(width != 1.0)
27 | {
28 | l = normalize(l);
29 | r = normalize(r);
30 | d = normalize(d);
31 | u = normalize(u);
32 | }
33 |
34 | float curvature = (dot(u,y) - dot(d,y)) + (dot(r,x) - dot(l,x));
35 |
36 | return map_range_clamped(curvature, -1, 1, 0, 1);
37 | }
38 |
39 | #include "Filters/Line.glsl"
40 |
41 | // Like curvature, but discard depth discontinuities
42 | /* META
43 | @meta: internal=true;
44 | @uv: default=UV[0];
45 | @width: default=1.0;
46 | @x: subtype=Normal; default=vec3(1,0,0);
47 | @y: subtype=Normal; default=vec3(0,1,0);
48 | @depth_range: default=0.1;
49 | */
50 | float surface_curvature(sampler2D normal_texture, sampler2D depth_texture, int depth_channel,
51 | vec2 uv, float width, vec3 x, vec3 y, float depth_range)
52 | {
53 | float curvature = curvature(normal_texture, uv, width, x, y);
54 |
55 | float delta_depth = _line_detection_depth(depth_texture, depth_channel, uv, width, LINE_DEPTH_MODE_ANY);
56 |
57 | delta_depth /= depth_range;
58 | delta_depth = clamp(delta_depth, 0, 1);
59 |
60 | return mix(curvature, 0.5, delta_depth);
61 | }
62 |
63 | #endif //CURVATURE_GLSL
64 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltNodes/Nodes/MaltStructNode.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from BlenderMalt.MaltNodes.MaltNode import MaltNode
3 |
4 |
5 | class MaltStructNode(bpy.types.Node, MaltNode):
6 |
7 | bl_label = "Struct Node"
8 |
9 | def malt_setup(self, copy=None):
10 | inputs = {}
11 | inputs[self.struct_type] = {'type' : self.struct_type}
12 | outputs = {}
13 | outputs[self.struct_type] = {'type' : self.struct_type}
14 |
15 | self.setup_sockets(inputs, outputs, copy=copy)
16 |
17 | struct_type : bpy.props.StringProperty(update=MaltNode.setup,
18 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
19 |
20 | def get_struct(self):
21 | graph = self.id_data.get_pipeline_graph()
22 | if self.struct_type in graph.structs:
23 | return graph.structs[self.struct_type]
24 | else:
25 | return self.id_data.get_library()['structs'][self.struct_type]
26 |
27 | def get_source_socket_reference(self, socket):
28 | if socket.name == self.struct_type:
29 | return self.get_source_name()
30 | else:
31 | return socket.name.replace(self.struct_type, self.get_source_name())
32 |
33 | def struct_input_is_linked(self):
34 | return self.inputs[self.struct_type].get_linked() is not None
35 |
36 | def get_source_code(self, transpiler):
37 | code = ''
38 |
39 | for input in self.inputs:
40 | initialization = input.get_source_initialization()
41 | if input.is_struct_member():
42 | if initialization:
43 | code += transpiler.asignment(input.get_source_reference(), initialization)
44 | else:
45 | code += transpiler.declaration(input.data_type, 0, self.get_source_name(), initialization)
46 |
47 | return code
48 |
49 |
50 | classes = [
51 | MaltStructNode,
52 | ]
53 |
54 | def register():
55 | for _class in classes: bpy.utils.register_class(_class)
56 |
57 | def unregister():
58 | for _class in reversed(classes): bpy.utils.unregister_class(_class)
59 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltNodes/Nodes/MaltArrayIndexNode.py:
--------------------------------------------------------------------------------
1 | from Malt.PipelineParameters import Parameter, Type
2 | import bpy
3 | from BlenderMalt.MaltNodes.MaltNode import MaltNode
4 |
5 |
6 | class MaltArrayIndexNode(bpy.types.Node, MaltNode):
7 |
8 | bl_label = "Array Index Node"
9 |
10 | def malt_init(self):
11 | self.setup()
12 |
13 | def malt_setup(self, copy=None):
14 | self.setup_sockets({ 'array' : {'type': '', 'size': 1}, 'index' : {'type': Parameter(0, Type.INT) }},
15 | {'element' : {'type': ''} }, copy=copy)
16 |
17 | def malt_update(self):
18 | inputs = {
19 | 'array' : {'type': '', 'size': 1},
20 | 'index' : {'type': 'int', 'meta':{'value':'0'} }
21 | }
22 | outputs = { 'element' : {'type': ''} }
23 |
24 | linked = self.inputs['array'].get_linked()
25 | if linked and linked.array_size > 0:
26 | inputs['array']['type'] = linked.data_type
27 | inputs['array']['size'] = linked.array_size
28 | outputs['element']['type'] = linked.data_type
29 |
30 | self.setup_sockets(inputs, outputs)
31 |
32 | def get_source_socket_reference(self, socket):
33 | return '{}_0_{}'.format(self.get_source_name(), socket.name)
34 |
35 | def get_source_code(self, transpiler):
36 | array = self.inputs['array']
37 | index = self.inputs['index']
38 | element = self.outputs['element']
39 | element_reference = index.get_source_global_reference()
40 | if index.get_linked():
41 | element_reference = index.get_linked().get_source_reference()
42 | initialization = '{}[{}]'.format(array.get_linked().get_source_reference(), element_reference)
43 | return transpiler.declaration(element.data_type, element.array_size, element.get_source_reference(), initialization)
44 |
45 |
46 | classes = [
47 | MaltArrayIndexNode,
48 | ]
49 |
50 | def register():
51 | for _class in classes: bpy.utils.register_class(_class)
52 |
53 | def unregister():
54 | for _class in reversed(classes): bpy.utils.unregister_class(_class)
55 |
--------------------------------------------------------------------------------
/docs/Documentation/Getting Started.md:
--------------------------------------------------------------------------------
1 | # Gettting Started
2 |
3 | ## Requirements
4 |
5 | - OpenGL 4.5 support.
6 | - Latest Blender stable release.
7 |
8 | > A dedicated Nvidia or AMD graphics card is highly recomended.
9 |
10 | ## Install
11 |
12 | - Go to [the latest Release page](https://github.com/bnpr/Malt/releases/tag/Release-latest).
13 | - Download the *BlenderMalt* version that matches your OS.
14 | - Open Blender. Go to *Preferences > Addons*, click on the *Install...* button and select *BlenderMalt.zip* from your downloads. *(This will take a few seconds)*
15 | - Tick the box in the *BlenderMalt* panel to enable it.
16 |
17 | > Altenatively, you can download the [Development version](https://github.com/bnpr/Malt/releases/tag/Development-latest) to test the latest features.
18 |
19 | ## Uninstall
20 |
21 | - Untick the box in *Preferences > Addons > BlenderMalt* to disable the addon.
22 | - Restart *Blender*.
23 | - Go back to *Preferences > Addons > BlenderMalt*, expand the panel and click the *Remove* button.
24 |
25 | ## Enable Malt
26 |
27 | 
28 |
29 | *Malt* is a separate render engine, just like *Cycles* and *EEVEE*.
30 | To enable it, select *Malt* in *Properties Panel > Render Properties > Render Engine*.
31 |
32 | > When *Malt* is enabled, a tiny black window will pop up. This is the process where the renderer runs.
33 | 
34 | Feel free to ignore it, it's only there because hiding it can lower the process priority and impact the render performance.
35 |
36 | ## Sample Files
37 |
38 | Sample files can be found at [Github](https://github.com/bnpr/Malt/discussions/94).
39 |
40 | 
41 |
42 | ## Pipelines
43 |
44 | *Malt* allows implementing custom *render pipelines* for advanced use cases.
45 |
46 | However, the built-in *NPR Pipeline* is a fully featured and highly customizable pipeline designed to cover most use cases.
47 |
48 | Most parts of this documentation apply only to the *NPR Pipeline*.
49 |
50 | > For building custom *render pipelines*, see the [Developer Documentation](https://github.com/bnpr/Malt/tree/Development/Malt#malt).
51 |
--------------------------------------------------------------------------------
/BlenderMalt/readme.md:
--------------------------------------------------------------------------------
1 | # BlenderMalt
2 |
3 | ## Introduction
4 |
5 | *BlenderMalt* is a [*Malt Host*](../Malt#malt-pipelines) for *Blender*.
6 | It handles all the *Scene* data loading and syncronization and exposes a minimal UI suitable for a code-centric workflow.
7 | *BlenderMalt* communicates with *Malt* through a [*Bridge*](../Bridge) instance.
8 |
9 | ## RenderEngine
10 |
11 | [*MaltRenderEngine.py*](MaltRenderEngine.py) implements the *Blender* [*RenderEngine*](https://docs.blender.org/api/current/bpy.types.RenderEngine.html) interface.
12 |
13 | It takes care of generating a *Malt Scene* from the *Blender* [*DepsGraph*](https://docs.blender.org/api/current/bpy.types.Depsgraph.html), send it to *Bridge* for rendering and pass the result back to *Blender*.
14 |
15 | ## Pipeline
16 |
17 | [*MaltPipeline.py*](MaltPipeline.py) handles *Bridge* instances, makes sure all *Blender* objects have their respective *Pipeline Parameters* registered as *MaltPropertyGroups*, and handles *Meshes* and *Textures* updates.
18 |
19 | ## Pipeline Properties
20 |
21 | [*MaltPropertyGroups*](MaltProperties.py) store *Malt* *Pipeline Parameters* as native *Blender* [*PropertyGroups*](https://docs.blender.org/api/current/bpy.types.PropertyGroup.html) and convert them to *Malt* *Scene* parameters on request.
22 | They also can handle their own UI rendering automatically, all that is needed is to pass a *Blender* [*UI Layout*](https://docs.blender.org/api/current/bpy.types.UILayout.html) to their *draw_ui* method.
23 |
24 | ## Materials
25 |
26 | [*MaltMaterial.py*](MaltMaterial.py) handles the compilation (including automatic hot-reloading), UI rendering and storage as native *Blender* materials of *Malt* *Pipeline* materials.
27 |
28 | ## Meshes
29 |
30 | [*MaltMeshes.py*](MaltMeshes.py) retrieves any *Blender* geometry as vertex and index buffers and sends it to *Bridge*.
31 | This module is highly optimized to allow real-time mesh editing and (when possible) retrieves vertex data directly from *Blender* internal C data through the [*CBlenderMalt*](CBlenderMalt) library.
32 |
33 | ## Textures
34 |
35 | [*MaltTextures.py*](MaltTextures.py) sends 1D and 2D textures pixel data to *Bridge*.
36 |
37 |
--------------------------------------------------------------------------------
/Malt/Nodes/SceneFilter.py:
--------------------------------------------------------------------------------
1 | from Malt.GL.GL import *
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.RenderTarget import RenderTarget
4 | from Malt.PipelineNode import PipelineNode
5 | from Malt.PipelineParameters import Parameter, Type
6 | from copy import copy, deepcopy
7 |
8 | class SceneFilter(PipelineNode):
9 | """
10 | Filters a Scene based on object tags.
11 | """
12 |
13 | def __init__(self, pipeline):
14 | PipelineNode.__init__(self, pipeline)
15 | self.last_scene = None
16 | self.matches = None
17 | self.non_matches = None
18 |
19 | @classmethod
20 | def reflect_inputs(cls):
21 | return {
22 | 'Scene' : Parameter('Scene', Type.OTHER),
23 | 'Filter' : Parameter('', Type.STRING)
24 | }
25 |
26 | @classmethod
27 | def reflect_outputs(cls):
28 | return {
29 | 'Matches' : Parameter('Scene', Type.OTHER),
30 | 'Non Matches' : Parameter('Scene', Type.OTHER)
31 | }
32 |
33 | def execute(self, parameters):
34 | inputs = parameters['IN']
35 | outputs = parameters['OUT']
36 |
37 | scene = inputs['Scene']
38 | tag = inputs['Filter']
39 |
40 | if scene != self.last_scene:
41 | self.last_scene = scene
42 | self.matches = copy(scene)
43 | self.matches.objects = []
44 | self.matches.shader_resources = copy(self.matches.shader_resources)
45 | self.non_matches = copy(scene)
46 | self.non_matches.objects = []
47 | self.non_matches.shader_resources = copy(self.non_matches.shader_resources)
48 | for obj in scene.objects:
49 | if tag in obj.tags:
50 | self.matches.objects.append(obj)
51 | else:
52 | self.non_matches.objects.append(obj)
53 | self.matches.batches = self.pipeline.build_scene_batches(self.matches.objects)
54 | self.non_matches.batches = self.pipeline.build_scene_batches(self.non_matches.objects)
55 |
56 | outputs['Matches'] = self.matches
57 | outputs['Non Matches'] = self.non_matches
58 |
59 |
60 | NODE = SceneFilter
61 |
--------------------------------------------------------------------------------
/Malt/Shaders/Filters/AO.glsl:
--------------------------------------------------------------------------------
1 | #ifndef AO_GLSL
2 | #define AO_GLSL
3 |
4 | #include "Common/Math.glsl"
5 | #include "Common/Transform.glsl"
6 |
7 | /* META
8 | @meta: internal=true;
9 | @position: subtype=Vector; default=POSITION;
10 | @normal: subtype=Normal; default=NORMAL;
11 | @samples: default=8;
12 | @radius: default=1.0;
13 | @distribution_exponent: default=5.0;
14 | @bias: default=0.01;
15 | */
16 | float ao(sampler2D depth_texture, int depth_channel, vec3 position, vec3 normal, int samples, float radius, float distribution_exponent, float bias)
17 | {
18 | //Loosely based on https://learnopengl.com/Advanced-Lighting/SSAO
19 | float occlusion = 0;
20 |
21 | if(samples <= 0 || radius <= 0.0)
22 | {
23 | return 1.0;
24 | }
25 |
26 | for(int i = 0; i < samples; i++)
27 | {
28 | // Generate a random TBN matrix
29 | vec3 random_vec = random_per_pixel(i).xyz;
30 | random_vec.xyz = random_vec.xyz * 2.0 - 1.0;
31 |
32 | vec3 normal = transform_normal(CAMERA, normal);
33 | vec3 tangent = normalize(random_vec - normal * dot(random_vec, normal));
34 | vec3 bitangent = cross(normal, tangent);
35 | mat3 TBN = mat3(tangent, bitangent, normal);
36 |
37 | vec3 random_offset = random_per_pixel(samples+i).xyz;
38 | // Make samples close to the center more likely, based on distribution exponent
39 | random_offset = normalize(random_offset) * pow(length(random_offset), distribution_exponent);
40 | random_offset *= radius;
41 |
42 | vec3 sample_offset = TBN * random_offset;
43 | vec3 sample_position = transform_point(CAMERA, position) + sample_offset;
44 |
45 | vec3 sample_uv = project_point_to_screen_coordinates(PROJECTION, sample_position);
46 |
47 | float sampled_depth = texture(depth_texture, sample_uv.xy)[depth_channel];
48 | sampled_depth = screen_to_camera(sample_uv.xy, sampled_depth).z;
49 |
50 | float range_check = smoothstep(0.0, 1.0, radius / abs(sample_position.z - sampled_depth));
51 |
52 | occlusion += (sampled_depth >= sample_position.z + bias ? 1.0 : 0.0) * range_check;
53 | }
54 |
55 | return 1.0 - (occlusion / samples);
56 | }
57 |
58 | #endif //AO_GLSL
59 |
--------------------------------------------------------------------------------
/Malt/PipelineNode.py:
--------------------------------------------------------------------------------
1 | class PipelineNode():
2 |
3 | def __init__(self, pipeline):
4 | self.pipeline = pipeline
5 |
6 | @staticmethod
7 | def get_pass_type():
8 | return None
9 |
10 | @classmethod
11 | def static_reflect(cls, name, inputs, outputs):
12 | meta = {}
13 | if cls.__doc__:
14 | meta['doc'] = cls.__doc__
15 | dictionary = {
16 | 'name' : name,
17 | 'type' : 'void',
18 | 'file' : 'Render',
19 | 'parameters' : [],
20 | 'pass_type' : cls.get_pass_type(),
21 | 'meta' : meta,
22 | }
23 | for name, input in inputs.items():
24 | meta = {}
25 | if input.doc:
26 | meta['doc'] = input.doc
27 | if input.subtype:
28 | meta['subtype'] = input.subtype
29 | if input.default_value is not None and input.default_value != input.type_string():
30 | meta['default'] = input.default_value
31 | dictionary['parameters'].append({
32 | 'name' : name,
33 | 'type' : input,
34 | 'size' : input.size,
35 | 'io' : 'in',
36 | 'meta' : meta,
37 | })
38 | for name, output in outputs.items():
39 | meta = {}
40 | if output.doc:
41 | meta['doc'] = output.doc
42 | if output.subtype:
43 | meta['subtype'] = output.subtype
44 | if output.default_value is not None and output.default_value != output.type_string():
45 | meta['default'] = output.default_value
46 | dictionary['parameters'].append({
47 | 'name' : name,
48 | 'type' : output,
49 | 'size' : output.size,
50 | 'io' : 'out',
51 | 'meta' : {},
52 | })
53 | return dictionary
54 |
55 | @classmethod
56 | def reflect(cls):
57 | return cls.static_reflect(cls.__name__, cls.reflect_inputs(), cls.reflect_outputs())
58 |
59 | @classmethod
60 | def reflect_inputs(cls):
61 | return {}
62 |
63 | @classmethod
64 | def reflect_outputs(cls):
65 | return {}
66 |
67 | def execute(self, parameters):
68 | pass
69 |
--------------------------------------------------------------------------------
/Malt/Pipelines/MiniPipeline/MiniPipeline.py:
--------------------------------------------------------------------------------
1 | from os import path
2 |
3 | from Malt.GL.GL import *
4 | from Malt.GL.Mesh import Mesh
5 | from Malt.GL.RenderTarget import RenderTarget
6 | from Malt.GL.Shader import Shader, UBO
7 | from Malt.GL.Texture import Texture
8 |
9 | from Malt.Pipeline import *
10 |
11 | class MiniPipeline(Pipeline):
12 |
13 | DEFAULT_SHADER = None
14 |
15 | def __init__(self, plugins=[]):
16 | super().__init__(plugins)
17 |
18 | self.parameters.world['Background Color'] = Parameter((0.5,0.5,0.5,1), Type.FLOAT, 4)
19 |
20 | if MiniPipeline.DEFAULT_SHADER is None:
21 | source = '''
22 | #include "Common.glsl"
23 |
24 | #ifdef VERTEX_SHADER
25 | void main()
26 | {
27 | DEFAULT_VERTEX_SHADER();
28 | }
29 | #endif
30 |
31 | #ifdef PIXEL_SHADER
32 | layout (location = 0) out vec4 RESULT;
33 | void main()
34 | {
35 | PIXEL_SETUP_INPUT();
36 | RESULT = vec4(1);
37 | }
38 | #endif
39 | '''
40 | MiniPipeline.DEFAULT_SHADER = self.compile_material_from_source('mesh', source)
41 |
42 | self.default_shader = MiniPipeline.DEFAULT_SHADER
43 |
44 | def compile_material_from_source(self, material_type, source, include_paths=[]):
45 | return {
46 | 'MAIN_PASS' : self.compile_shader_from_source(
47 | source, include_paths, ['MAIN_PASS']
48 | )
49 | }
50 |
51 | def setup_render_targets(self, resolution):
52 | self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F)
53 | self.t_main_color = Texture(resolution, GL_RGBA32F)
54 | self.fbo_main = RenderTarget([self.t_main_color], self.t_depth)
55 |
56 | def do_render(self, resolution, scene, is_final_render, is_new_frame):
57 | shader_resources = { 'COMMON_UNIFORMS' : self.common_buffer }
58 |
59 | self.fbo_main.clear([scene.world_parameters['Background Color']], 1)
60 |
61 | self.draw_scene_pass(self.fbo_main, scene.batches, 'MAIN_PASS', self.default_shader['MAIN_PASS'], shader_resources)
62 |
63 | return { 'COLOR' : self.t_main_color }
64 |
65 |
66 | PIPELINE = MiniPipeline
67 |
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/Shaders/NPR_LightShader.glsl:
--------------------------------------------------------------------------------
1 | #define NO_NORMAL_INPUT
2 | #define NO_UV_INPUT
3 | #define NO_VERTEX_COLOR_INPUT
4 | #define NO_MODEL_INPUT
5 | #define NO_ID_INPUT
6 |
7 | #include "NPR_Intellisense.glsl"
8 | #include "Common.glsl"
9 | #include "Lighting/Lighting.glsl"
10 |
11 | uniform int LIGHT_INDEX;
12 |
13 | #ifdef VERTEX_SHADER
14 | void main()
15 | {
16 | DEFAULT_SCREEN_VERTEX_SHADER();
17 | }
18 | #endif
19 |
20 | #ifdef PIXEL_SHADER
21 |
22 | uniform sampler2D IN_DEPTH;
23 |
24 | layout (location = 0) out vec3 RESULT;
25 |
26 | void LIGHT_SHADER(vec3 relative_coordinates, vec3 uvw, inout vec3 color, inout float attenuation);
27 |
28 | void main()
29 | {
30 | PIXEL_SETUP_INPUT();
31 |
32 | float depth = texelFetch(IN_DEPTH, screen_pixel(), 0).x;
33 | POSITION = screen_to_camera(screen_uv(), depth);
34 | POSITION = transform_point(inverse(CAMERA), POSITION);
35 |
36 | Light L = LIGHTS.lights[LIGHT_INDEX];
37 | LitSurface LS = lit_surface(POSITION, vec3(0), L, false);
38 |
39 | vec3 light_space = vec3(0);
40 | vec3 uvw = vec3(0);
41 |
42 | if(L.type == LIGHT_SPOT)
43 | {
44 | light_space = project_point(LIGHTS.spot_matrices[L.type_index], POSITION);
45 | uvw.xy = light_space.xy * 0.5 + 0.5;
46 | }
47 | if(L.type == LIGHT_SUN)
48 | {
49 | vec3 z = L.direction;
50 | vec3 c = vec3(0,0,1);
51 | if(abs(dot(z, c)) < 1.0)
52 | {
53 | vec3 x = normalize(cross(c, z));
54 | vec3 y = normalize(cross(x, z));
55 | mat3 rotation = mat3(x,y,z);
56 | mat4 m = mat4_translation(L.position) * mat4(rotation);
57 | m = inverse(m);
58 |
59 | light_space = transform_point(m, POSITION);
60 | }
61 | else
62 | {
63 | light_space = POSITION;
64 | light_space -= L.position;
65 | }
66 |
67 | uvw.xy = light_space.xy;
68 | }
69 | if(L.type == LIGHT_POINT)
70 | {
71 | light_space = POSITION - L.position;
72 | light_space /= L.radius;
73 |
74 | uvw = normalize(light_space);
75 | }
76 |
77 | vec3 color = L.color;
78 | float attenuation = LS.P;
79 |
80 | LIGHT_SHADER(light_space, uvw, color, attenuation);
81 |
82 | RESULT = color * attenuation;
83 | }
84 |
85 | #endif //PIXEL_SHADER
86 |
--------------------------------------------------------------------------------
/scripts/generate_conversion_nodes.py:
--------------------------------------------------------------------------------
1 | result = ''
2 |
3 | base_types = ('float','int','uint','bool')
4 | vector_types = ('vec','ivec','uvec','bvec')
5 |
6 | for to_base_type in base_types:
7 | result += f'//{to_base_type}\n'
8 | for from_base_type in base_types:
9 | if from_base_type == to_base_type:
10 | continue
11 | result += '/* META @meta: internal=true; */\n'
12 | param = from_base_type[:1]
13 | result += f'{to_base_type} {to_base_type}_from_{from_base_type}({from_base_type} {param}) {{ return {to_base_type}({param}); }}\n'
14 |
15 | result += '\n'
16 |
17 | for to_vector_type in vector_types:
18 | for to_vector_len in range(2,5):
19 | to_vector = f'{to_vector_type}{to_vector_len}'
20 |
21 | result += f'//{to_vector}\n'
22 |
23 | for base_type in base_types:
24 | result += '/* META @meta: internal=true; */\n'
25 | param = base_type[:1]
26 | conversion = f'{to_vector}({param})'
27 | if to_vector_len == 4 and to_vector_type != 'bvec':
28 | conversion = f'{to_vector}({param}, {param}, {param}, 1)'
29 | result += f'{to_vector} {to_vector}_from_{base_type}({base_type} {param}) {{ return {conversion}; }}\n'
30 |
31 | for from_vector_type in vector_types:
32 | for from_vector_len in range(2,5):
33 | if from_vector_type == to_vector_type and from_vector_len == to_vector_len:
34 | continue
35 | from_vector = f'{from_vector_type}{from_vector_len}'
36 |
37 | param = 'v'
38 | if to_vector_len < from_vector_len:
39 | param += '.xyzw'[:to_vector_len+1]
40 | if to_vector_len > from_vector_len:
41 | for i in range(from_vector_len, to_vector_len):
42 | if to_vector_type != 'bvec' and to_vector_len == 4 and i == to_vector_len - 1:
43 | param += ', 1'
44 | else:
45 | param += ', 0'
46 | conversion = f'{to_vector}({param})'
47 |
48 | result += '/* META @meta: internal=true; */\n'
49 | result += f'{to_vector} {to_vector}_from_{from_vector}({from_vector} v) {{ return {conversion}; }}\n'
50 |
51 | result += '\n'
52 |
53 |
54 | print(result)
55 |
--------------------------------------------------------------------------------
/docs/reference/settings.md:
--------------------------------------------------------------------------------
1 | # Malt Settings
2 | ## World
3 | - **Material**
4 | - **Default** *: ( Material )** = Malt - Default Mesh Material*
5 | >The default material, used for objects with no material assigned.
6 | - **Override** *: ( Material )** = None*
7 | >When set, overrides all scene materials with this one.
8 | - **Viewport**
9 | - **Resolution Scale** *: ( Float )** = 1.0*
10 | >A multiplier for the viewport resolution.
11 | It can be lowered to improve viewport performance or for specific styles, like *pixel art*.
12 | - **Smooth Interpolation** *: ( Bool )** = True*
13 | >The interpolation mode used when *Resolution Scale* is not 1.
14 | Toggles between *Nearest/Bilinear* interpolation.
15 | - **Samples**
16 | - **Grid Size** *: ( Int )** = 8*
17 | >The number of render samples per side in the sampling grid.
18 | The total number of samples is the square of this value minus the samples that fall outside the sampling radius.
19 | Higher values will provide cleaner renders at the cost of increased render times.
20 | - **Width** *: ( Float )** = 1.0*
21 | >The width (and height) of the sampling grid.
22 | Larger values will result in smoother/blurrier images while lower values will result in sharper/more aliased ones.
23 | Keep it withing the 1-2 range for best results.
24 | - **Render** *: ( Graph )** = Default Render*
25 | >The *Render Node Tree* used to render the scene.
26 | See [Render & Render Layers](#Render & Render Layers) for more info.
27 | ## Material
28 | - **Light Groups**
29 | - **Light** *: ( Int )** = [1, 0, 0, 0]*
30 | >The *Light Groups* (up to 4) that lit this material.
31 | - **Shadow** *: ( Int )** = [1, 0, 0, 0]*
32 | >The *Light Groups* (up to 4) that this material casts shadows on.
33 | ## Mesh
34 | - **double_sided** *: ( Bool )** = False*
35 | >Disables backface culling, so geometry is rendered from both sides.
36 | - **precomputed_tangents** *: ( Bool )** = False*
37 | >Load precomputed mesh tangents *(needed for improving normal mapping quality on low poly meshes)*.
38 | It's disabled by default since it slows down mesh loading in Blender.
39 | When disabled, the *tangents* are calculated on the fly from the *pixel shader*.
40 | ## Light
41 | - **Light Group** *: ( Int )** = 1*
42 | >Lights only affect materials with a matching *Light Group* value.
43 | - **Shader** *: ( Material )** = None*
44 | >When set, the *Material* with a custom *Light Shader* or *Light Node Tree* that will be used to render this light.
45 |
--------------------------------------------------------------------------------
/docs/reference/Render Layer-graph.md:
--------------------------------------------------------------------------------
1 | # Render Layer Graph Reference
2 | ---
3 | ## Render
4 | ---
5 | ### **LineRender**
6 | Expands the line up to the width especified in the *Line Width* texture
7 | and composites it on top of the *Color* texture.
8 |
9 | - **Inputs**
10 | - **Color** *: ( Texture )*
11 | - **Line Color** *: ( Texture )*
12 | - **Line Width** *: ( Texture )*
13 | - **Max Width** *: ( Int ) - default = 10*
14 | >The maximum width the shader can render.
15 | Increasing the value lowers the render performance.
16 | - **Line Scale** *: ( Float ) - default = 1.0*
17 | >Scale all Line Width values with this one.
18 | *(Useful for rendering at different resolutions)*
19 | - **Normal Depth** *: ( Texture )*
20 | - **ID** *: ( Texture )*
21 | - **Outputs**
22 | - **Color** *: ( Texture )*
23 | ---
24 | ### **SuperSamplingAA**
25 | Performs anti-aliasing by accumulating multiple render samples into a single texture.
26 |
27 | - **Inputs**
28 | - **Color** *: ( Texture )*
29 | - **Outputs**
30 | - **Color** *: ( Texture )*
31 | ---
32 | ### **MainPass**
33 | >Graph Type / Pass : *Mesh / MAIN_PASS_PIXEL_SHADER*
34 |
35 | Renders the scene geometry using the *Mesh Main Pass*.
36 | The node sockets are dynamic, based on the *Main Pass Custom IO*.
37 | If *Normal Depth/ID* is empty, the *Pre Pass* *Normal Depth/ID* will be used.
38 |
39 | - **Inputs**
40 | - **Scene** *: ( Scene )*
41 | - **Normal Depth** *: ( Texture )*
42 | - **ID** *: ( Texture )*
43 | ---
44 | ### **PrePass**
45 | >Graph Type / Pass : *Mesh / PRE_PASS_PIXEL_SHADER*
46 |
47 | Renders the scene geometry using the *Mesh Pre Pass*.
48 | The node sockets are dynamic, based on the *Pre Pass Custom IO*.
49 | If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used.
50 |
51 | - **Inputs**
52 | - **Scene** *: ( Scene )*
53 | - **Outputs**
54 | - **Scene** *: ( Scene )*
55 | - **Normal Depth** *: ( Texture )*
56 | - **ID** *: ( Texture )*
57 | ---
58 | ### **ScreenPass**
59 | >Graph Type / Pass : *Screen / SCREEN_SHADER*
60 |
61 | Renders a full screen shader pass.
62 | The node sockets are dynamic, based on the shader selected.
63 | If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used.
64 |
65 | - **Inputs**
66 | - **Layer Only** *: ( Bool ) - default = True*
67 | >Draw only on top of the current layer geometry,
68 | to avoid accidentally covering previous layers.
69 | - **Scene** *: ( Scene )*
70 | - **Normal Depth** *: ( Texture )*
71 | - **ID** *: ( Texture )*
72 |
--------------------------------------------------------------------------------
/scripts/setup_blender_addon.py:
--------------------------------------------------------------------------------
1 | import os, sys, platform, subprocess, shutil, pathlib
2 |
3 | import argparse
4 | parser = argparse.ArgumentParser()
5 | parser.add_argument('--scripts-folder', type=pathlib.Path, help='Create a symlink pointing to the addon.')
6 | parser.add_argument('--copy-modules', action='store_true', help='Copy Malt and Bridge instead of making a symlink. (Needed for zipping in Linux)')
7 | ARGS = parser.parse_args()
8 |
9 | current_dir = os.path.dirname(os.path.realpath(__file__))
10 | main_dir = os.path.realpath(os.path.join(current_dir, '..'))
11 |
12 | blender_malt_folder = os.path.join(main_dir, 'BlenderMalt')
13 | bridge_folder = os.path.join(main_dir, 'Bridge')
14 | malt_folder = os.path.join(main_dir, 'Malt')
15 |
16 | def build_lib(path):
17 | subprocess.check_call([sys.executable, 'build.py'], cwd=path)
18 | import stat
19 | def delete_read_only(func, path, exc_info):
20 | os.chmod(path, stat.S_IWRITE)
21 | os.remove(path)
22 | shutil.rmtree(os.path.join(path, '.build'), onerror=delete_read_only)
23 |
24 | build_lib(os.path.join(blender_malt_folder, 'CBlenderMalt'))
25 | build_lib(os.path.join(malt_folder, 'GL', 'GLSLParser'))
26 | build_lib(os.path.join(bridge_folder, 'ipc'))
27 | build_lib(os.path.join(bridge_folder, 'renderdoc'))
28 |
29 | subprocess.check_call([sys.executable, os.path.join(current_dir, 'install_dependencies.py')])
30 |
31 | def ensure_dir(path):
32 | if os.path.exists(path) == False:
33 | os.mkdir(path)
34 |
35 | def make_link(point_from, point_to):
36 | if os.path.exists(point_from):
37 | print('Already linked:', point_from, '<--->', point_to)
38 | return
39 | if platform.system() == 'Windows':
40 | import _winapi
41 | _winapi.CreateJunction(point_to, point_from)
42 | else:
43 | os.symlink(point_to, point_from, True)
44 |
45 | def make_copy(copy_to, copy_from):
46 | from distutils.dir_util import copy_tree
47 | copy_tree(copy_from, copy_to)
48 |
49 | import_path = os.path.join(blender_malt_folder, '.MaltPath')
50 | ensure_dir(import_path)
51 |
52 | setup_modules = make_link
53 | if ARGS.copy_modules:
54 | setup_modules = make_copy
55 |
56 | setup_modules(os.path.join(import_path, 'Malt'), os.path.join(main_dir, 'Malt'))
57 | setup_modules(os.path.join(import_path, 'Bridge'), os.path.join(main_dir, 'Bridge'))
58 |
59 | if ARGS.scripts_folder:
60 | addons_folder = os.path.join(ARGS.scripts_folder, 'addons')
61 | ensure_dir(addons_folder)
62 | make_link(os.path.join(addons_folder, 'BlenderMalt'), blender_malt_folder)
63 |
--------------------------------------------------------------------------------
/mkdocs/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Malt
2 |
3 | site_url: https://malt3d.com
4 | site_author: Miguel Pozo
5 | site_description: Malt Render Engine
6 | repo_name: bnpr/Malt
7 | repo_url: https://github.com/bnpr/Malt
8 | docs_dir: ../docs/
9 | #edit_uri: ""
10 |
11 | nav:
12 | - 'index.md'
13 | - 'Documentation':
14 | - 'Documentation/Getting Started.md'
15 | - 'Documentation/Settings.md'
16 | - 'Documentation/Graphs.md'
17 | - 'Reference':
18 | - 'reference/settings.md'
19 | - 'reference/Mesh-graph.md'
20 | - 'reference/Screen-graph.md'
21 | - 'reference/Light-graph.md'
22 | - 'reference/Render-graph.md'
23 | - 'reference/Render Layer-graph.md'
24 | - 'Documentation/Plugins.md'
25 | - 'Documentation/Tooling.md'
26 | #- 'Developer Documentation' : 'https://github.com/bnpr/Malt/tree/Development/Malt#malt'
27 | - 'Discussions - Q&A' : 'https://github.com/bnpr/Malt/discussions'
28 | - 'Patreon' : 'https://patreon.com/pragma37'
29 | - 'Download' : 'https://github.com/bnpr/Malt/releases/tag/Release-latest'
30 |
31 | theme:
32 | name: material
33 | custom_dir: ../docs/overrides
34 | logo: images/cube-solid.svg
35 | favicon: images/cube-solid.svg
36 | features:
37 | - navigation.instant
38 | #- navigation.tracking
39 | - navigation.tabs
40 | #- toc.integrate
41 | palette:
42 | - scheme: slate
43 | primary: teal
44 | toggle:
45 | icon: material/toggle-switch-off-outline
46 | name: Switch to light mode
47 | - scheme: default
48 | primary: teal
49 | toggle:
50 | icon: material/toggle-switch
51 | name: Switch to dark mode
52 |
53 | extra_css:
54 | - extra/extra.css
55 |
56 | extra_javascript:
57 | - extra/extra.js
58 |
59 | markdown_extensions:
60 | - attr_list
61 | - pymdownx.highlight:
62 | use_pygments: true
63 | anchor_linenums: true
64 | - pymdownx.inlinehilite
65 | - pymdownx.snippets
66 | - pymdownx.superfences
67 | - pymdownx.superfences:
68 | custom_fences:
69 | - name: mermaid
70 | class: mermaid
71 | format: !!python/name:pymdownx.superfences.fence_code_format
72 |
73 | extra:
74 | generator: false
75 | social:
76 | - icon: fontawesome/brands/github
77 | link: https://github.com/bnpr/Malt
78 | - icon: fontawesome/brands/youtube
79 | link: https://www.youtube.com/channel/UCNjoz44PbgbQPqFrqy7Y4mg
80 | - icon: fontawesome/brands/twitter
81 | link: https://twitter.com/pragma37
82 | - icon: fontawesome/brands/patreon
83 | link: https://www.patreon.com/pragma37
84 |
85 |
--------------------------------------------------------------------------------
/Malt/Shaders/Procedural/Noise.inl:
--------------------------------------------------------------------------------
1 | //Should define T, DIMENSIONS and TILE
2 | //Should declare coord and tile_size (if TILE)
3 | #if TILE != 0
4 | T _tile_size = round(tile_size);
5 | coord = mod(coord, _tile_size);
6 | #endif
7 | T origin = floor(coord);
8 | T delta = fract(coord);
9 | //quintic interpolation factor
10 | T factor = delta*delta*delta*(delta*(delta*6.0-15.0)+10.0);
11 |
12 | #if DIMENSIONS == 4
13 | const ivec4 D = ivec4(1);
14 | #elif DIMENSIONS == 3
15 | const ivec4 D = ivec4(1,1,1,0);
16 | #elif DIMENSIONS == 2
17 | const ivec4 D = ivec4(1,1,0,0);
18 | #elif DIMENSIONS == 1
19 | const ivec4 D = ivec4(1,0,0,0);
20 | #endif
21 |
22 | vec4 w_results[2];
23 | vec4 z_results[2];
24 | vec4 y_results[2];
25 | vec4 x_results[2];
26 |
27 | for(int w = 0; w <= D.w; w++)
28 | {
29 | for(int z = 0; z <= D.z; z++)
30 | {
31 | for(int y = 0; y <= D.y; y++)
32 | {
33 | for(int x = 0; x <= D.x; x++)
34 | {
35 | T offset = T(vec4(x,y,z,w));
36 | T corner = origin + offset;
37 | #if TILE != 0
38 | corner = mod(corner, _tile_size);
39 | #endif
40 | T corner_hash_r = T(hash(corner)) * 2.0 - 1.0; //(-1|+1) range
41 | T corner_hash_g = T(hash(corner_hash_r)) * 2.0 - 1.0;
42 | T corner_hash_b = T(hash(corner_hash_g)) * 2.0 - 1.0;
43 | T corner_hash_a = T(hash(corner_hash_b)) * 2.0 - 1.0;
44 | x_results[x].r = dot(corner_hash_r, delta-offset);
45 | x_results[x].g = dot(corner_hash_g, delta-offset);
46 | x_results[x].b = dot(corner_hash_b, delta-offset);
47 | x_results[x].a = dot(corner_hash_a, delta-offset);
48 | }
49 | #if DIMENSIONS >= 2
50 | y_results[y] = mix(x_results[0], x_results[1], factor.x);
51 | #endif
52 | }
53 | #if DIMENSIONS >= 3
54 | z_results[z] = mix(y_results[0], y_results[1], factor.y);
55 | #endif
56 | }
57 | #if DIMENSIONS >= 4
58 | w_results[w] = mix(z_results[0], z_results[1], factor.z);
59 | #endif
60 | }
61 |
62 | vec4 color;
63 | #if DIMENSIONS == 4
64 | color = mix(w_results[0], w_results[1], factor.w) * 0.5 + 0.5;
65 | #elif DIMENSIONS == 3
66 | color = mix(z_results[0], z_results[1], factor.z) * 0.5 + 0.5;
67 | #elif DIMENSIONS == 2
68 | color = mix(y_results[0], y_results[1], factor.y) * 0.5 + 0.5;
69 | #elif DIMENSIONS == 1
70 | color = mix(x_results[0], x_results[1], factor) * 0.5 + 0.5;
71 | #endif
72 |
73 |
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/Nodes/Render/ScreenPass.py:
--------------------------------------------------------------------------------
1 | from Malt.GL import GL
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.RenderTarget import RenderTarget
4 | from Malt.PipelineNode import PipelineNode
5 | from Malt.PipelineParameters import Parameter, Type
6 | from Malt.Scene import TextureShaderResource
7 |
8 |
9 | class ScreenPass(PipelineNode):
10 | """
11 | Renders a full screen shader pass.
12 | The node sockets are dynamic, based on the shader selected.
13 | """
14 |
15 | def __init__(self, pipeline):
16 | PipelineNode.__init__(self, pipeline)
17 | self.resolution = None
18 | self.texture_targets = {}
19 | self.render_target = None
20 | self.custom_io = []
21 |
22 | @staticmethod
23 | def get_pass_type():
24 | return 'Screen.SCREEN_SHADER'
25 |
26 | def execute(self, parameters):
27 | inputs = parameters['IN']
28 | outputs = parameters['OUT']
29 | material = parameters['PASS_MATERIAL']
30 | custom_io = parameters['CUSTOM_IO']
31 |
32 | if self.pipeline.resolution != self.resolution or self.custom_io != custom_io:
33 | self.texture_targets = {}
34 | for io in custom_io:
35 | if io['io'] == 'out':
36 | if io['type'] == 'Texture':#TODO
37 | self.texture_targets[io['name']] = Texture(self.pipeline.resolution, GL.GL_RGBA16F)
38 | self.render_target = RenderTarget([*self.texture_targets.values()])
39 | self.resolution = self.pipeline.resolution
40 | self.custom_io = custom_io
41 |
42 | self.render_target.clear([(0,0,0,0)]*len(self.texture_targets))
43 |
44 | if material and material.shader and 'SHADER' in material.shader:
45 | shader = material.shader['SHADER']
46 | for io in custom_io:
47 | if io['io'] == 'in':
48 | if io['type'] == 'Texture':#TODO
49 | from Malt.SourceTranspiler import GLSLTranspiler
50 | glsl_name = GLSLTranspiler.custom_io_reference('IN', 'SCREEN_SHADER', io['name'])
51 | shader.textures[glsl_name] = inputs[io['name']]
52 | self.pipeline.common_buffer.shader_callback(shader)
53 | shader.uniforms['RENDER_LAYER_MODE'].set_value(False)
54 | self.pipeline.draw_screen_pass(shader, self.render_target)
55 |
56 | for io in custom_io:
57 | if io['io'] == 'out':
58 | if io['type'] == 'Texture':#TODO
59 | outputs[io['name']] = self.texture_targets[io['name']]
60 |
61 |
62 | NODE = ScreenPass
63 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltLights.py:
--------------------------------------------------------------------------------
1 | import math
2 | import bpy
3 |
4 | class MaltLight(bpy.types.PropertyGroup):
5 |
6 | def sync_data(self, context):
7 | light = self.id_data
8 | #light.shadow_soft_size = self.radius
9 | if light.type == 'SPOT':
10 | light.cutoff_distance = self.radius
11 | light.spot_size = self.spot_angle
12 | light.spot_blend = self.spot_blend_angle / self.spot_angle
13 |
14 | strength : bpy.props.FloatProperty(name='Strength', default=1,
15 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
16 |
17 | override_global_settings : bpy.props.BoolProperty(name='Override Global Settings', default=False,
18 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
19 | max_distance : bpy.props.FloatProperty(name='Max Distance', default=100,
20 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
21 |
22 | radius : bpy.props.FloatProperty(
23 | name='Radius',
24 | default=5,
25 | update=sync_data,
26 | options={'LIBRARY_EDITABLE'},
27 | override={'LIBRARY_OVERRIDABLE'}
28 | )
29 | spot_angle : bpy.props.FloatProperty(
30 | name='Angle',
31 | default=1,
32 | subtype='ANGLE',
33 | min=0,
34 | max=math.pi,
35 | update=sync_data,
36 | options={'LIBRARY_EDITABLE'},
37 | override={'LIBRARY_OVERRIDABLE'}
38 | )
39 | spot_blend_angle : bpy.props.FloatProperty(
40 | name='Blend',
41 | default=0.1,
42 | subtype='ANGLE',
43 | min=0,
44 | max=math.pi,
45 | update=sync_data,
46 | options={'LIBRARY_EDITABLE'},
47 | override={'LIBRARY_OVERRIDABLE'}
48 | )
49 |
50 | def draw_ui(self, layout):
51 | layout.prop(self.id_data, 'color')
52 | layout.prop(self, 'strength')
53 | if self.id_data.type == 'SUN':
54 | layout.prop(self, 'override_global_settings')
55 | if self.override_global_settings:
56 | layout.prop(self, 'max_distance')
57 | if self.id_data.type != 'SUN':
58 | layout.prop(self, 'radius')
59 | if self.id_data.type == 'SPOT':
60 | layout.prop(self, 'spot_angle')
61 | layout.prop(self, 'spot_blend_angle')
62 |
63 | classes = [
64 | MaltLight
65 | ]
66 |
67 | def register():
68 | for _class in classes: bpy.utils.register_class(_class)
69 | bpy.types.Light.malt = bpy.props.PointerProperty(type=MaltLight,
70 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
71 |
72 | def unregister():
73 | for _class in reversed(classes): bpy.utils.unregister_class(_class)
74 | del bpy.types.Light.malt
75 |
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/NPR_LightShaders.py:
--------------------------------------------------------------------------------
1 |
2 | from Malt.GL.GL import *
3 | from Malt.GL.Shader import UBO
4 | from Malt.GL.Texture import TextureArray
5 | from Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget
6 |
7 | from Malt.Render import Lighting
8 |
9 | from Malt.PipelineGraph import *
10 |
11 | import ctypes
12 |
13 | class NPR_LightShaders():
14 |
15 | def __init__(self):
16 | class C_NPR_LightShadersBuffer(ctypes.Structure):
17 | _fields_ = [
18 | ('custom_shading_index', ctypes.c_int*Lighting.MAX_LIGHTS),
19 | ]
20 | self.data = C_NPR_LightShadersBuffer()
21 | self.custom_shading_count = 0
22 | self.UBO = UBO()
23 |
24 | self.texture = None
25 | self.fbos = None
26 |
27 | def load(self, pipeline, depth_texture, scene):
28 | self.custom_shading_count = 0
29 | for i, light in enumerate(scene.lights):
30 | custom_shading_index = -1
31 | if light.parameters['Shader'] is not None:
32 | custom_shading_index = self.custom_shading_count
33 | self.custom_shading_count += 1
34 | self.data.custom_shading_index[i] = custom_shading_index
35 |
36 | self.UBO.load_data(self.data)
37 |
38 | if self.custom_shading_count == 0:
39 | return
40 |
41 | lights = [l for l in scene.lights if l.parameters['Shader'] is not None]
42 | tex = self.texture
43 | if tex is None or tex.resolution != depth_texture.resolution or tex.length < len(lights):
44 | self.texture = TextureArray(depth_texture.resolution, len(lights), GL_RGB32F)
45 | self.fbos = []
46 | for i in range(len(lights)):
47 | self.fbos.append(RenderTarget([ArrayLayerTarget(self.texture, i)]))
48 |
49 | for i, light in enumerate(lights):
50 | material = light.parameters['Shader']
51 | if material.shader and 'SHADER' in material.shader.keys():
52 | shader = material.shader['SHADER']
53 | for resource in scene.shader_resources.values():
54 | resource.shader_callback(shader)
55 | shader.textures['IN_DEPTH'] = depth_texture
56 | if 'LIGHT_INDEX' in shader.uniforms:
57 | light_index = scene.lights.index(light)
58 | shader.uniforms['LIGHT_INDEX'].set_value(light_index)
59 | pipeline.draw_screen_pass(shader, self.fbos[i])
60 |
61 | def shader_callback(self, shader):
62 | if 'LIGHTS_CUSTOM_SHADING' in shader.uniform_blocks:
63 | self.UBO.bind(shader.uniform_blocks['LIGHTS_CUSTOM_SHADING'])
64 | shader.textures['IN_LIGHT_CUSTOM_SHADING'] = self.texture
65 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | hide:
3 | #- toc
4 | #- navigation
5 | ---
6 |
7 | # Malt
8 |
9 |
10 | ***Malt*** is a fully ***customizable real-time rendering*** framework for animation and illustration.
11 | It's aimed at artists who want more control over their workflow and/or their art style, with special care put into the needs of ***stylized non photorealistic rendering***.
12 |
13 | Designed as a community effort to expand the possibility space of 3d rendering, it provides graphics programmers and technical artist an enjoyable *“shadertoy-like”* workflow inside ***Blender***, while still allowing to effortlessly share their work with non technical artists through ***Python*** and ***GLSL*** plugins.
14 |
15 | ---
16 |
17 |
18 |
21 |
22 | @pragma37
23 |
24 |
25 |
26 | ---
27 |
28 | - Free and **Open Source**. *MIT License*
29 | - **Real Time Rendering**
30 | - Full **Blender** integration
31 | - Built-in Pipeline for **Stylized Non Photorealistic Rendering**
32 | - Stylized shading models
33 | - Light Groups
34 | - Line Rendering
35 | - Fully customizable through nodes *(Materials, Light shaders, Screen shaders and even the render pipeline itself)*
36 | - Code as a *First Class Citizen*
37 | - *Auto-reloading* for everything
38 | - **VSCode** (including **GLSL** *intellisense*) and **Renderdoc** integration
39 | - Automatic generation of nodes from plain GLSL functions
40 | - Automatic UI for Shader and Pipeline parameters
41 | - 100% customizable Python Render Pipelines
42 |
43 | ---
44 |
45 |
46 |
49 |
50 | @pragma37
51 |
52 |
53 |
54 | ---
55 |
56 |
57 |
60 |
61 | @Renato3xl
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Malt/GL/GL.py:
--------------------------------------------------------------------------------
1 | import collections
2 |
3 | import OpenGL
4 | #OpenGL.ERROR_LOGGING = False
5 | #OpenGL.FULL_LOGGING = False
6 | #OpenGL.ERROR_ON_COPY = False
7 | from OpenGL.GL import *
8 | from OpenGL.extensions import hasGLExtension
9 |
10 | if True:
11 | #For some reason PyOpenGL doesnt support the most common depth/stencil buffer by default ???
12 | #https://sourceforge.net/p/pyopengl/bugs/223/
13 | from OpenGL import images
14 | images.TYPE_TO_ARRAYTYPE[GL_UNSIGNED_INT_24_8] = GL_UNSIGNED_INT
15 | images.TIGHT_PACK_FORMATS[GL_UNSIGNED_INT_24_8] = 4
16 | images.TYPE_TO_ARRAYTYPE[GL_HALF_FLOAT] = GL_HALF_FLOAT
17 | from OpenGL import arrays
18 | if arrays.ADT:
19 | arrays.GL_CONSTANT_TO_ARRAY_TYPE[GL_HALF_FLOAT] = arrays.ADT(GL_HALF_FLOAT, GLhalfARB)
20 | else:
21 | class GLhalfFloatArray(ArrayDatatype, ctypes.POINTER(GLhalfARB)):
22 | baseType = GLhalfARB
23 | typeConstant = GL_HALF_FLOAT
24 | arrays.GL_CONSTANT_TO_ARRAY_TYPE[GL_HALF_FLOAT] = GLhalfFloatArray
25 |
26 | NULL = None
27 | GL_ENUMS = {}
28 | GL_NAMES = {}
29 |
30 | if True: #create new scope to import OpenGL
31 | from OpenGL import GL
32 | for e in dir(GL):
33 | if e.startswith('GL_'):
34 | GL_ENUMS[getattr(GL, e)] = e
35 | GL_NAMES[e] = getattr(GL, e)
36 |
37 | class DrawQuery():
38 |
39 | def __init__(self, query_type=GL_ANY_SAMPLES_PASSED):
40 | self.query = None
41 | self.query_type = query_type
42 |
43 | def begin_query(self):
44 | if self.query:
45 | glDeleteQueries(1, self.query)
46 | self.query = gl_buffer(GL_UNSIGNED_INT, 1)
47 | glGenQueries(1, self.query)
48 | glBeginQuery(self.query_type, self.query[0])
49 |
50 | def end_query(self):
51 | glEndQuery(self.query_type)
52 |
53 | def begin_conditional_draw(self, wait_mode=GL_QUERY_WAIT):
54 | glBeginConditionalRender(self.query[0], wait_mode)
55 |
56 | def end_conditional_draw(self):
57 | glEndConditionalRender()
58 |
59 |
60 | def gl_buffer(type, size, data=None):
61 | types = {
62 | GL_BYTE : GLbyte,
63 | GL_UNSIGNED_BYTE : GLubyte,
64 | GL_SHORT : GLshort,
65 | GL_UNSIGNED_SHORT : GLushort,
66 | GL_INT : GLint,
67 | GL_UNSIGNED_INT : GLuint,
68 | #GL_HALF_FLOAT : GLhalfARB,
69 | GL_HALF_FLOAT : GLfloat,
70 | GL_FLOAT : GLfloat,
71 | GL_DOUBLE : GLdouble,
72 | GL_BOOL : GLboolean,
73 | }
74 | gl_type = (types[type] * size)
75 | if data:
76 | try:
77 | return gl_type(*data)
78 | except:
79 | return gl_type(data)
80 | else:
81 | return gl_type()
82 |
83 |
84 | def buffer_to_string(buffer):
85 | chars = []
86 | for char in list(buffer):
87 | if chr(char) == '\0':
88 | break
89 | chars.append(chr(char))
90 | return ''.join(chars)
91 |
--------------------------------------------------------------------------------
/Malt/Nodes/LineRender.py:
--------------------------------------------------------------------------------
1 | from Malt.GL.GL import *
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.RenderTarget import RenderTarget
4 | from Malt.PipelineNode import PipelineNode
5 | from Malt.PipelineParameters import Parameter, Type
6 |
7 | _SHADER = None
8 |
9 | class LineRender(PipelineNode):
10 | """
11 | Expands the line up to the width especified in the *Line Width* texture
12 | and composites it on top of the *Color* texture.
13 | """
14 |
15 | def __init__(self, pipeline):
16 | PipelineNode.__init__(self, pipeline)
17 | self.resolution = None
18 |
19 | @classmethod
20 | def reflect_inputs(cls):
21 | inputs = {}
22 | inputs['Color'] = Parameter('', Type.TEXTURE)
23 | inputs['Line Color'] = Parameter('', Type.TEXTURE)
24 | inputs['Line Width'] = Parameter('', Type.TEXTURE)
25 | inputs['Max Width'] = Parameter(10, Type.INT, doc="""
26 | The maximum width the shader can render.
27 | Increasing the value lowers the render performance.""")
28 | inputs['Line Scale'] = Parameter(1.0, Type.FLOAT, doc="""
29 | Scale all Line Width values with this one.
30 | *(Useful for rendering at different resolutions)*""")
31 | inputs['Normal Depth'] = Parameter('', Type.TEXTURE)
32 | inputs['ID'] = Parameter('', Type.TEXTURE)
33 | return inputs
34 |
35 | @classmethod
36 | def reflect_outputs(cls):
37 | outputs = {}
38 | outputs['Color'] = Parameter('', Type.TEXTURE)
39 | return outputs
40 |
41 | def setup_render_targets(self, resolution):
42 | self.t_color = Texture(resolution, GL_RGBA16F)
43 | self.fbo_color = RenderTarget([self.t_color])
44 |
45 | def execute(self, parameters):
46 | inputs = parameters['IN']
47 | outputs = parameters['OUT']
48 |
49 | if self.pipeline.resolution != self.resolution:
50 | self.setup_render_targets(self.pipeline.resolution)
51 | self.resolution = self.pipeline.resolution
52 |
53 | global _SHADER
54 | if _SHADER is None:
55 | _SHADER = self.pipeline.compile_shader_from_source('#include "Passes/LineComposite.glsl"')
56 |
57 | _SHADER.textures['color_texture'] = inputs['Color']
58 | _SHADER.textures['depth_texture'] = inputs['Normal Depth']
59 | _SHADER.uniforms['depth_channel'].set_value(3)
60 | _SHADER.textures['id_texture'] = inputs['ID']
61 | _SHADER.textures['line_color_texture'] = inputs['Line Color']
62 | _SHADER.textures['line_width_texture'] = inputs['Line Width']
63 | _SHADER.uniforms['line_width_scale'].set_value(inputs['Line Scale'])
64 | _SHADER.uniforms['brute_force_range'].set_value(inputs['Max Width'])
65 |
66 | self.pipeline.common_buffer.shader_callback(_SHADER)
67 | self.pipeline.draw_screen_pass(_SHADER, self.fbo_color)
68 |
69 | outputs['Color'] = self.t_color
70 |
71 | NODE = LineRender
72 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltTextures.py:
--------------------------------------------------------------------------------
1 | import ctypes
2 | from . import MaltPipeline
3 |
4 | __TEXTURES = {}
5 |
6 | def get_texture(texture):
7 | name = texture.name_full
8 | if name not in __TEXTURES or __TEXTURES[name] is None:
9 | __TEXTURES[name] = __load_texture(texture)
10 | return __TEXTURES[name]
11 |
12 |
13 | def __load_texture(texture):
14 | w,h = texture.size
15 | channels = int(texture.channels)
16 | size = w*h*channels
17 | sRGB = texture.colorspace_settings.name == 'sRGB' and texture.is_float == False
18 | if size == 0:
19 | return False
20 |
21 | buffer = MaltPipeline.get_bridge().get_shared_buffer(ctypes.c_float, size)
22 | texture.pixels.foreach_get(buffer.as_np_array())
23 |
24 | MaltPipeline.get_bridge().load_texture(texture.name_full, buffer, (w,h), channels, sRGB)
25 |
26 | from Bridge.Proxys import TextureProxy
27 | return TextureProxy(texture.name_full)
28 |
29 | __GRADIENTS = {}
30 | __GRADIENT_RESOLUTION = 256
31 | # Blender doesn't trigger depsgraph updates for newly created textures,
32 | # so we always reload gradients until it's been succesfully unloaded once (TODO)
33 | __GRADIENTS_WORKAROUND = []
34 | def add_gradient_workaround(texture):
35 | __GRADIENTS_WORKAROUND.append(texture.name_full)
36 |
37 | def get_gradient(texture):
38 | name = texture.name_full
39 | if name not in __GRADIENTS or __GRADIENTS[name] is None or name in __GRADIENTS_WORKAROUND:
40 | __GRADIENTS[name] = __load_gradient(texture)
41 | return __GRADIENTS[name]
42 |
43 | def __load_gradient(texture):
44 | pixels = []
45 | for i in range(0, __GRADIENT_RESOLUTION):
46 | pixel = texture.color_ramp.evaluate( i*(1.0 / __GRADIENT_RESOLUTION))
47 | pixels.extend(pixel)
48 | nearest = texture.color_ramp.interpolation == 'CONSTANT'
49 | MaltPipeline.get_bridge().load_gradient(texture.name_full, pixels, nearest)
50 | from Bridge.Proxys import GradientProxy
51 | return GradientProxy(texture.name_full)
52 |
53 | def copy_color_ramp(old, new):
54 | new.color_mode = old.color_mode
55 | new.hue_interpolation = old.hue_interpolation
56 | new.interpolation = old.interpolation
57 | while len(new.elements) > len(old.elements):
58 | new.elements.remove(new.elements[len(new.elements)-1])
59 | for i, o in enumerate(old.elements):
60 | n = new.elements[i] if i < len(new.elements) else new.elements.new(o.position)
61 | n.position = o.position
62 | n.color = o.color[:]
63 | n.alpha = o.alpha
64 |
65 | def reset_textures():
66 | global __TEXTURES
67 | __TEXTURES = {}
68 | global __GRADIENTS
69 | __GRADIENTS = {}
70 |
71 | def unload_texture(texture):
72 | __TEXTURES[texture.name_full] = None
73 |
74 | def unload_gradients(texture):
75 | __GRADIENTS[texture.name_full] = None
76 | if texture.name_full in __GRADIENTS_WORKAROUND:
77 | __GRADIENTS_WORKAROUND.remove(texture.name_full)
78 |
79 | def register():
80 | pass
81 |
82 | def unregister():
83 | pass
84 |
--------------------------------------------------------------------------------
/docs/reference/Render-graph.md:
--------------------------------------------------------------------------------
1 | # Render Graph Reference
2 | ---
3 | ## Render
4 | ---
5 | ### **LineRender**
6 | Expands the line up to the width especified in the *Line Width* texture
7 | and composites it on top of the *Color* texture.
8 |
9 | - **Inputs**
10 | - **Color** *: ( Texture )*
11 | - **Line Color** *: ( Texture )*
12 | - **Line Width** *: ( Texture )*
13 | - **Max Width** *: ( Int ) - default = 10*
14 | >The maximum width the shader can render.
15 | Increasing the value lowers the render performance.
16 | - **Line Scale** *: ( Float ) - default = 1.0*
17 | >Scale all Line Width values with this one.
18 | *(Useful for rendering at different resolutions)*
19 | - **Normal Depth** *: ( Texture )*
20 | - **ID** *: ( Texture )*
21 | - **Outputs**
22 | - **Color** *: ( Texture )*
23 | ---
24 | ### **SuperSamplingAA**
25 | Performs anti-aliasing by accumulating multiple render samples into a single texture.
26 |
27 | - **Inputs**
28 | - **Color** *: ( Texture )*
29 | - **Outputs**
30 | - **Color** *: ( Texture )*
31 | ---
32 | ### **RenderLayers**
33 | >Graph Type / Pass : *Render Layer / Render Layer*
34 |
35 | Renders the scene geometry, using multiple *depth peeling* layers for transparent objects.
36 | The node sockets are dynamic, based on the graph selected.
37 |
38 | - **Inputs**
39 | - **Scene** *: ( Scene )*
40 | - **Transparent Layers** *: ( Int ) - default = 4*
41 | >The maximum number of overlapping transparency layers.
42 | Incresing this values lowers performance.
43 | ---
44 | ### **SceneLighting**
45 | Renders the shadow maps and attaches them along the scene lights data to the *Scene* shader resources.
46 |
47 | - **Inputs**
48 | - **Scene** *: ( Scene )*
49 | - **Point Resolution** *: ( Int ) - default = 2048*
50 | >Shadowmap resolution for point lights *(for each cubemap side)*.
51 | - **Spot Resolution** *: ( Int ) - default = 2048*
52 | >Shadowmap resolution for spot lights.
53 | - **Sun Resolution** *: ( Int ) - default = 2048*
54 | >Shadowmap resolution for sun light lights *(for each cascade side)*.
55 | - **Sun Max Distance** *: ( Float ) - default = 100*
56 | >The maximum distance from the view origin at which objects will still cast shadows.
57 | The lower the value, the higher the perceived resolution.
58 | - **Sun CSM Count** *: ( Int ) - default = 4*
59 | >The number of [Shadow Cascades](https://docs.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps#cascaded-shadow-maps-and-perspective-aliasing) for sun lights.
60 | - **Sun CSM Distribution** *: ( Float ) - default = 0.9*
61 | >Interpolates the cascades distribution along the view distance between linear distribution *(at 0)* and logarithmic distribution *(at 1)*.
62 | The appropriate value depends on camera FOV and scene characteristics.
63 | - **Outputs**
64 | - **Scene** *: ( Scene )*
65 | ---
66 | ### **ScreenPass**
67 | >Graph Type / Pass : *Screen / SCREEN_SHADER*
68 |
69 | Renders a full screen shader pass.
70 | The node sockets are dynamic, based on the shader selected.
71 |
72 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/vec4.glsl:
--------------------------------------------------------------------------------
1 | #ifndef VEC4_GLSL
2 | #define VEC4_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
9 | vec4 vec4_add(vec4 a, vec4 b){ return a+b; }
10 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
11 | vec4 vec4_subtract(vec4 a, vec4 b){ return a-b; }
12 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
13 | vec4 vec4_multiply(vec4 a, vec4 b){ return a*b; }
14 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
15 | vec4 vec4_divide(vec4 a, vec4 b){ return a/b; }
16 | /*META @v: subtype=Vector;*/
17 | vec4 vec4_scale(vec4 v, float s){ return v*s; }
18 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
19 | vec4 vec4_modulo(vec4 a, vec4 b){ return mod(a,b); }
20 | /*META @v: subtype=Vector; @e: subtype=Vector;*/
21 | vec4 vec4_pow(vec4 v, vec4 e){ return pow(v, e); }
22 | /*META @v: subtype=Vector;*/
23 | vec4 vec4_sqrt(vec4 v){ return sqrt(v); }
24 |
25 | /*META @v: subtype=Vector;*/
26 | vec4 vec4_round(vec4 v){ return round(v); }
27 | /*META @v: subtype=Vector;*/
28 | vec4 vec4_fract(vec4 v){ return fract(v); }
29 | /*META @v: subtype=Vector;*/
30 | vec4 vec4_floor(vec4 v){ return floor(v); }
31 | /*META @v: subtype=Vector;*/
32 | vec4 vec4_ceil(vec4 v){ return ceil(v); }
33 |
34 | /*META @v: subtype=Vector; @min: subtype=Vector; @max: subtype=Vector;*/
35 | vec4 vec4_clamp(vec4 v, vec4 min, vec4 max){ return clamp(v, min, max); }
36 |
37 | /*META @v: subtype=Vector;*/
38 | vec4 vec4_sign(vec4 v){ return sign(v); }
39 | /*META @v: subtype=Vector;*/
40 | vec4 vec4_abs(vec4 v){ return abs(v); }
41 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
42 | vec4 vec4_min(vec4 a, vec4 b){ return min(a,b); }
43 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
44 | vec4 vec4_max(vec4 a, vec4 b){ return max(a,b); }
45 |
46 | /*META @a: subtype=Vector; @b: subtype=Vector; @factor: subtype=Vector;*/
47 | vec4 vec4_mix(vec4 a, vec4 b, vec4 factor){ return mix(a,b,factor); }
48 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
49 | vec4 vec4_mix_float(vec4 a, vec4 b, float factor){ return mix(a,b,factor); }
50 |
51 | /*META @v: subtype=Vector;*/
52 | vec4 vec4_normalize(vec4 v){ return normalize(v); }
53 |
54 | /*META @v: subtype=Vector;*/
55 | float vec4_length(vec4 v){ return length(v); }
56 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
57 | float vec4_distance(vec4 a, vec4 b){ return distance(a,b); }
58 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
59 | float vec4_dot_product(vec4 a, vec4 b){ return dot(a,b); }
60 |
61 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
62 | bool vec4_equal(vec4 a, vec4 b){ return a == b; }
63 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
64 | bool vec4_not_equal(vec4 a, vec4 b){ return a != b; }
65 |
66 | /*META @if_true: subtype=Vector; @if_false: subtype=Vector;*/
67 | vec4 vec4_if_else(bool condition, vec4 if_true, vec4 if_false){ return condition ? if_true : if_false; }
68 |
69 | vec4 vec4_join(float r, float g, float b, float a) { return vec4(r,g,b,a);}
70 | /*META @v: subtype=Vector;*/
71 | void vec4_split(vec4 v, out float r, out float g, out float b, out float a){ r=v.r; g=v.g; b=v.b; a=v.a; }
72 |
73 | #endif //VEC4_GLSL
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Malt
2 |
3 | Malt is a fully customizable real-time rendering framework for animation and illustration.
4 | It's aimed at advanced users and technical artists who want more control over their workflow and/or their art style, with special care put into the needs of stylized non photorealistic rendering.
5 |
6 | [Download](#install) | [Docs](https://malt3d.com) | [Forums & Support](https://github.com/bnpr/Malt/discussions) | [Bug Reports](https://github.com/bnpr/Malt/issues) | [Twitter](https://twitter.com/pragma37) | [Patreon](https://patreon.com/pragma37)
7 |
8 | ## Features
9 |
10 | - **Free and Open Source**. MIT License.
11 | - **Real Time Rendering**.
12 | - **Complete *Blender* integration**.
13 | - **Built-in Pipeline for Stylized Non Photorealistic Rendering**.
14 | - **Code as a First Class Citizen**
15 | - Automatic reloading.
16 | - *VSCode* integration, including *GLSL* autocompletion.
17 | - Automatic generation of nodes from *GLSL* functions.
18 | - Automatic UI for *Shader* and *Pipeline* parameters.
19 | - 100% customizable *Python* Render Pipelines.
20 |
21 | ## Current State
22 |
23 | We've been working on a full redesign of the default nodes before the 1.0 Release.
24 | [Give it try and leave your feedback](https://github.com/bnpr/Malt/discussions/382).
25 |
26 | Malt is software agnostic, but Blender is the only integration planned right now.
27 |
28 | ## Requirements
29 |
30 | - OpenGL 4.5
31 | - Latest Blender stable release.
32 | - Windows or Linux
33 |
34 | > A dedicated Nvidia or AMD graphics card is highly recomended.
35 |
36 | ## Install
37 |
38 | - Go to [the latest Release page](https://github.com/bnpr/Malt/releases/tag/Release-latest).
39 | - Download the *BlenderMalt* version that matches your OS.
40 | - Open Blender. Go to *Preferences > Addons*, click on the *Install...* button and select *BlenderMalt.zip* from your downloads. *(It will take a few seconds)*
41 | - Tick the box in the *BlenderMalt* panel to enable it.
42 |
43 | > Altenatively, you can download the [Development version](https://github.com/bnpr/Malt/releases/tag/Development-latest) to test the latest features.
44 |
45 | ## Uninstall
46 |
47 | - Untick the box in *Preferences > Addons > BlenderMalt* to disable the addon.
48 | - Restart *Blender*.
49 | - Go back to *Preferences > Addons > BlenderMalt*, expand the panel and click the *Remove* button.
50 |
51 | ## First steps
52 |
53 | To learn how to use *Malt*, check the [Docs](https://malt3d.com/Documentation/Getting%20Started/), this [playlist](https://www.youtube.com/playlist?list=PLiN2BGdwwlLqbks8h5MohvH0Xd0Zql_Sg) and the [Sample Files](https://github.com/bnpr/Malt/discussions/94).
54 | The [Q&A section](https://github.com/bnpr/Malt/discussions/categories/q-a) is full of info as well.
55 |
56 | ## Developer Documentation
57 |
58 | [How to setup BlenderMalt for Development.](docs/Setup-BlenderMalt-for-Development.md)
59 |
60 | Developer documentation is best viewed directly in [Github](https://github.com/bnpr/Malt/tree/Development#developer-documentation), most folders in the source code have relevant documentation.
61 | The [Malt folder documentation](Malt#malt) is a good starting point.
62 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils/vec3.glsl:
--------------------------------------------------------------------------------
1 | #ifndef VEC3_GLSL
2 | #define VEC3_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: internal = true;
6 | */
7 |
8 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
9 | vec3 vec3_add(vec3 a, vec3 b){ return a+b; }
10 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
11 | vec3 vec3_subtract(vec3 a, vec3 b){ return a-b; }
12 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
13 | vec3 vec3_multiply(vec3 a, vec3 b){ return a*b; }
14 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
15 | vec3 vec3_divide(vec3 a, vec3 b){ return a/b; }
16 | /*META @v: subtype=Vector;*/
17 | vec3 vec3_scale(vec3 v, float s){ return v*s; }
18 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
19 | vec3 vec3_modulo(vec3 a, vec3 b){ return mod(a,b); }
20 | /*META @v: subtype=Vector; @e: subtype=Vector;*/
21 | vec3 vec3_pow(vec3 v, vec3 e){ return pow(v, e); }
22 | /*META @v: subtype=Vector;*/
23 | vec3 vec3_sqrt(vec3 v){ return sqrt(v); }
24 |
25 | /*META @v: subtype=Vector;*/
26 | vec3 vec3_round(vec3 v){ return round(v); }
27 | /*META @v: subtype=Vector;*/
28 | vec3 vec3_fract(vec3 v){ return fract(v); }
29 | /*META @v: subtype=Vector;*/
30 | vec3 vec3_floor(vec3 v){ return floor(v); }
31 | /*META @v: subtype=Vector;*/
32 | vec3 vec3_ceil(vec3 v){ return ceil(v); }
33 |
34 | /*META @v: subtype=Vector; @min: subtype=Vector; @max: subtype=Vector;*/
35 | vec3 vec3_clamp(vec3 v, vec3 min, vec3 max){ return clamp(v, min, max); }
36 |
37 | /*META @v: subtype=Vector;*/
38 | vec3 vec3_sign(vec3 v){ return sign(v); }
39 | /*META @v: subtype=Vector;*/
40 | vec3 vec3_abs(vec3 v){ return abs(v); }
41 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
42 | vec3 vec3_min(vec3 a, vec3 b){ return min(a,b); }
43 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
44 | vec3 vec3_max(vec3 a, vec3 b){ return max(a,b); }
45 |
46 | /*META @a: subtype=Vector; @b: subtype=Vector; @factor: subtype=Vector;*/
47 | vec3 vec3_mix(vec3 a, vec3 b, vec3 factor){ return mix(a,b,factor); }
48 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
49 | vec3 vec3_mix_float(vec3 a, vec3 b, float factor){ return mix(a,b,factor); }
50 |
51 | /*META @v: subtype=Vector;*/
52 | vec3 vec3_normalize(vec3 v){ return normalize(v); }
53 |
54 | /*META @v: subtype=Vector;*/
55 | float vec3_length(vec3 v){ return length(v); }
56 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
57 | float vec3_distance(vec3 a, vec3 b){ return distance(a,b); }
58 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
59 | float vec3_dot_product(vec3 a, vec3 b){ return dot(a,b); }
60 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
61 | vec3 vec3_cross_product(vec3 a, vec3 b){ return cross(a,b); }
62 |
63 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
64 | bool vec3_equal(vec3 a, vec3 b){ return a == b; }
65 | /*META @a: subtype=Vector; @b: subtype=Vector;*/
66 | bool vec3_not_equal(vec3 a, vec3 b){ return a != b; }
67 |
68 | /*META @if_true: subtype=Vector; @if_false: subtype=Vector;*/
69 | vec3 vec3_if_else(bool condition, vec3 if_true, vec3 if_false){ return condition ? if_true : if_false; }
70 |
71 | vec3 vec3_join(float x, float y, float z) { return vec3(x,y,z);}
72 | /*META @v: subtype=Vector;*/
73 | void vec3_split(vec3 v, out float x, out float y, out float z){ x=v.x; y=v.y; z=v.z; }
74 |
75 | #endif //VEC3_GLSL
76 |
--------------------------------------------------------------------------------
/Malt/Shaders/Procedural/Noise.glsl:
--------------------------------------------------------------------------------
1 | #ifndef PROCEDURAL_NOISE_GLSL
2 | #define PROCEDURAL_NOISE_GLSL
3 |
4 | #include "Common/Hash.glsl"
5 |
6 | /* META GLOBAL
7 | @meta: internal=true;
8 | */
9 |
10 | /* META
11 | @coord: subtype=Vector; default=vec4(POSITION,0);
12 | @tile_size: subtype=Vector; default=vec4(1);
13 | */
14 | vec4 noise_ex(vec4 coord, bool tile, vec4 tile_size)
15 | {
16 | //Kept for backward compatibility
17 | #define DIMENSIONS 4
18 | #define T vec4
19 | if(tile)
20 | {
21 | #define TILE 1
22 | #include "Noise.inl"
23 | return color;
24 | }
25 | else
26 | {
27 | #define TILE 0
28 | #include "Noise.inl"
29 | return color;
30 | }
31 | }
32 |
33 | /* META
34 | @coord: subtype=Vector; default=vec4(POSITION,0);
35 | @tile_size: subtype=Vector; default=ivec4(5);
36 | */
37 | vec4 noise(vec4 coord, ivec4 tile_size)
38 | {
39 | #define DIMENSIONS 4
40 | #define T vec4
41 | #define TILE 1
42 | #include "Noise.inl"
43 | return color;
44 | }
45 |
46 | /* META
47 | @coord: subtype=Vector; default=vec4(POSITION,0);
48 | */
49 | vec4 noise(vec4 coord)
50 | {
51 | #define DIMENSIONS 4
52 | #define T vec4
53 | #define TILE 0
54 | #include "Noise.inl"
55 | return color;
56 | }
57 |
58 | /* META
59 | @coord: subtype=Vector; default=POSITION;
60 | @tile_size: subtype=Vector; default=ivec3(5);
61 | */
62 | vec4 noise(vec3 coord, ivec3 tile_size)
63 | {
64 | #define DIMENSIONS 3
65 | #define T vec3
66 | #define TILE 1
67 | #include "Noise.inl"
68 | return color;
69 | }
70 |
71 | /* META
72 | @coord: subtype=Vector; default=POSITION;
73 | */
74 | vec4 noise(vec3 coord)
75 | {
76 | #define DIMENSIONS 3
77 | #define T vec3
78 | #define TILE 0
79 | #include "Noise.inl"
80 | return color;
81 | }
82 |
83 | /* META
84 | @coord: subtype=Vector; default=POSITION.xy;
85 | @tile_size: subtype=Vector; default=ivec2(5);
86 | */
87 | vec4 noise(vec2 coord, ivec2 tile_size)
88 | {
89 | #define DIMENSIONS 2
90 | #define T vec2
91 | #define TILE 1
92 | #include "Noise.inl"
93 | return color;
94 | }
95 |
96 | /* META
97 | @coord: subtype=Vector; default=POSITION.xy;
98 | */
99 | vec4 noise(vec2 coord)
100 | {
101 | #define DIMENSIONS 2
102 | #define T vec2
103 | #define TILE 0
104 | #include "Noise.inl"
105 | return color;
106 | }
107 |
108 | /* META
109 | @coord: subtype=Vector; default=POSITION.x;
110 | @tile_size: subtype=Vector; default=5;
111 | */
112 | vec4 noise(float coord, int tile_size)
113 | {
114 | #define DIMENSIONS 1
115 | #define T float
116 | #define TILE 1
117 | #include "Noise.inl"
118 | return color;
119 | }
120 |
121 | /* META
122 | @coord: subtype=Vector; default=POSITION.x;
123 | */
124 | vec4 noise(float coord)
125 | {
126 | #define DIMENSIONS 1
127 | #define T float
128 | #define TILE 0
129 | #include "Noise.inl"
130 | return color;
131 | }
132 |
133 | #undef DIMENSIONS
134 | #undef T
135 | #undef TILE
136 |
137 | #endif // PROCEDURAL_NOISE_GLSL
138 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltNodes/Nodes/MaltFunctionSubCategory.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from BlenderMalt.MaltNodes.Nodes.MaltFunctionNode import MaltFunctionNodeBase
3 |
4 | class MaltFunctionSubCategoryNode(bpy.types.Node, MaltFunctionNodeBase):
5 |
6 | bl_label = "Function SubCategory Node"
7 |
8 | subcategory : bpy.props.StringProperty(options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
9 |
10 | def get_function_enums(self, context=None):
11 | library = self.id_data.get_full_library()
12 | items = []
13 | for key in library['subcategories'][self.subcategory]:
14 | function = library['functions'].get(key)
15 | if function is None or function['meta'].get('internal'):
16 | continue
17 | label = function['meta'].get('label')
18 | if label is None:
19 | label = function['name'].replace('_',' ').title()
20 | items.append((key, label, label))
21 | return items
22 |
23 | def update_function_enum(self, context=None):
24 | self.function_type = self.function_enum
25 | if self.disable_updates == False:
26 | self.id_data.update_ext(force_update=True)
27 |
28 | function_enum : bpy.props.EnumProperty(name='', items=get_function_enums, update=update_function_enum,
29 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
30 |
31 | def malt_setup(self, copy=None):
32 | #Keep the correct function when the subcategory list changes
33 | if self.function_type != '' and self.function_type != self.function_enum:
34 | try: self.function_enum = self.function_type
35 | except:
36 | try:
37 | self.function_type = self.function_enum
38 | except:
39 | self.function_type = self.get_function_enums()[0][0]
40 | return super().malt_setup(copy=copy)
41 |
42 | def should_delete_outdated_links(self):
43 | return True
44 |
45 | def draw_buttons(self, context, layout):
46 | r = layout.row(align=True)
47 | r.context_pointer_set('active_node', self)
48 | r.prop(self, 'function_enum')
49 | if context.preferences.addons['BlenderMalt'].preferences.use_subfunction_cycling:
50 | r.operator('wm.malt_cycle_sub_categories', text='', icon='COLLAPSEMENU')
51 | return super().draw_buttons(context, layout)
52 |
53 | def calc_node_width(self, point_size, dpi) -> float:
54 | import blf
55 | blf.size(0, point_size, dpi)
56 | max_width = super().calc_node_width(point_size, dpi)
57 | layout_padding = 70 # account for the spaces on both sides of the enum dropdown
58 | label = next(enum[1] for enum in self.get_function_enums() if enum[0]==self.function_enum)
59 | return max(max_width, blf.dimensions(0, label)[0] + layout_padding)
60 |
61 | def draw_label(self):
62 | label = super().draw_label()
63 | if self.hide:
64 | label += ' - ' + self.get_function()['meta'].get('label', None)
65 | return label
66 |
67 |
68 | def register():
69 | bpy.utils.register_class(MaltFunctionSubCategoryNode)
70 |
71 | def unregister():
72 | bpy.utils.unregister_class(MaltFunctionSubCategoryNode)
73 |
--------------------------------------------------------------------------------
/Malt/Shaders/Common/Mapping.glsl:
--------------------------------------------------------------------------------
1 | #ifndef COMMON_MAPPING_GLSL
2 | #define COMMON_MAPPING_GLSL
3 |
4 | #include "Common.glsl"
5 |
6 | /* META GLOBAL
7 | @meta: category=Vector; internal=True;
8 | */
9 |
10 | vec3 view_direction();
11 | vec3 transform_normal(mat4 matrix, vec3 normal);
12 |
13 | /* META
14 | @meta: label=Matcap UV;
15 | @normal: subtype=Normal; default=NORMAL;
16 | */
17 | vec2 matcap_uv(vec3 normal)
18 | {
19 | vec3 N = transform_normal(CAMERA, normal);
20 | vec3 I = transform_normal(CAMERA, -view_direction());
21 |
22 | vec3 x = vec3(1,0,0);
23 | vec3 tangent = normalize(x - I * dot(x, I));
24 | vec3 y = vec3(0,1,0);
25 | vec3 bitangent = normalize(y - I * dot(y, I));
26 |
27 | vec3 screen_normal = vec3
28 | (
29 | dot(N, tangent),
30 | dot(N, bitangent),
31 | dot(N, I)
32 | );
33 |
34 | screen_normal = normalize(screen_normal);
35 |
36 | return screen_normal.xy * 0.499 + 0.5;
37 | }
38 |
39 | /* META
40 | @meta: category=Texturing;
41 | @normal: subtype=Normal; default=NORMAL;
42 | */
43 | vec4 sample_matcap(sampler2D matcap, vec3 normal)
44 | {
45 | return textureLod(matcap, matcap_uv(normal), 0);
46 | }
47 |
48 | /* META
49 | @meta: label=HDRI UV;
50 | @normal: subtype=Normal; default=NORMAL;
51 | */
52 | vec2 hdri_uv(vec3 normal)
53 | {
54 | vec2 uv = vec2(atan(normal.y, normal.x), asin(normal.z));
55 | vec2 inverse_atan = vec2(0.1591, 0.3183);
56 | return uv * inverse_atan + 0.5;
57 | }
58 |
59 | /* META
60 | @meta: category=Texturing; label=Sample HDRI;
61 | @normal: subtype=Normal; default=NORMAL;
62 | */
63 | vec4 sample_hdri(sampler2D hdri, vec3 normal)
64 | {
65 | return textureLod(hdri, hdri_uv(normal), 0);
66 | }
67 |
68 | vec2 curve_view_mapping(vec2 uv, vec3 normal, vec3 tangent, vec3 incoming)
69 | {
70 | vec3 screen_bitangent = transform_normal(CAMERA, cross(incoming, tangent));
71 | vec3 screen_normal = transform_normal(CAMERA, normal);
72 | float y_grad = dot(screen_bitangent, screen_normal);
73 | return vec2(uv.x, (y_grad + 1) * 0.5);
74 | }
75 |
76 | vec4 sample_flipbook(sampler2D tex, vec2 uv, ivec2 dimensions, int page, bool top_left)
77 | {
78 | int page_count = dimensions.x * dimensions.y;
79 | page = int(mod(page, page_count));
80 | vec2 offset = vec2(
81 | mod(page, dimensions.x),
82 | floor(top_left ? dimensions.y - 1 - (page / dimensions.y) : (page / dimensions.y))
83 | );
84 | uv = (uv + offset) / vec2(dimensions);
85 | return texture(tex, uv);
86 | }
87 |
88 | float pingpong(float a, float b);
89 |
90 | vec4 flowmap(sampler2D tex, vec2 uv, vec2 flow, float progression, int samples)
91 | {
92 | vec4 result;
93 | float fraction = 1.0 / float(samples);
94 | for(int i = 0; i < samples; i++)
95 | {
96 | float flow_scale = fract(progression - i * fraction);
97 | vec4 color = texture(tex, uv - flow * flow_scale);
98 |
99 | float range = fract(progression - (float(i) / float(samples))) * samples;
100 | float p = pingpong(range, 1.0);
101 | float bounds = (range > 0.0 && range < 2.0)? 1.0 : 0.0;
102 | result += color * p * bounds;
103 | }
104 | return result;
105 | }
106 |
107 | #endif //COMMON_MAPPING_GLSL
108 |
--------------------------------------------------------------------------------
/Malt/Shaders/Node Utils 2/Vector.glsl:
--------------------------------------------------------------------------------
1 | #ifndef NODE_UTILS_2_VECTOR_GLSL
2 | #define NODE_UTILS_2_VECTOR_GLSL
3 |
4 | /* META GLOBAL
5 | @meta: category=Vector;
6 | */
7 |
8 | mat4 TRANSFORM_CONVERSION_TABLE[3*3] = mat4[3*3](
9 | mat4(1), MODEL, CAMERA*MODEL,
10 | inverse(MODEL), mat4(1), CAMERA,
11 | inverse(CAMERA*MODEL), inverse(CAMERA), mat4(1)
12 | );
13 |
14 | /* META
15 | @Type: subtype=ENUM(Point,Vector,Normal);
16 | @From: subtype=ENUM(Object,World,Camera);
17 | @To: subtype=ENUM(Object,World,Camera,Screen);
18 | @Vector: subtype=Vector;
19 | */
20 | void Transform(
21 | int Type,
22 | int From,
23 | int To,
24 | inout vec3 Vector
25 | )
26 | {
27 | mat4 m = TRANSFORM_CONVERSION_TABLE[clamp(From,0,2)*3 + clamp(To,0,2)];
28 | bool project = To == 3;
29 | if(Type==0)//Point
30 | {
31 | if(project)
32 | {
33 | m = PROJECTION * m;
34 | Vector = project_point_to_screen_coordinates(m, Vector);
35 | }
36 | else
37 | {
38 | Vector = transform_point(m, Vector);
39 | }
40 | }
41 | else
42 | {
43 | if(Type==1)//Vector
44 | {
45 | Vector = transform_direction(m, Vector);
46 | }
47 | if(Type==2)//Normal
48 | {
49 | Vector = transform_normal(m, Vector);
50 | }
51 | if (project)
52 | {
53 | Vector = camera_direction_to_screen_space(Vector);
54 | }
55 | }
56 | }
57 |
58 | /* META
59 | @meta: subcategory=Mapping; label=Point;
60 | @vector: subtype=Vector; default='vec3(0.0)';
61 | @location: subtype=Vector;
62 | @rotation: subtype=Euler;
63 | @scale: subtype=Vector; default=vec3(1.0);
64 | */
65 | vec3 mapping_point(vec3 vector, vec3 location, vec3 rotation, vec3 scale)
66 | {
67 | vec3 result = vector * scale;
68 | result = transform_point(mat4_rotation_from_euler(rotation), result);
69 | return result + location;
70 | }
71 |
72 | /* META
73 | @meta: subcategory=Mapping; label=Texture;
74 | @vector: subtype=Vector; default='vec3(0.0)';
75 | @location: subtype=Vector;
76 | @rotation: subtype=Euler;
77 | @scale: subtype=Vector; default=vec3(1.0);
78 | */
79 | vec3 mapping_texture(vec3 vector, vec3 location, vec3 rotation, vec3 scale)
80 | {
81 | vec3 result = vector - location;
82 | result = transform_point(inverse(mat4_rotation_from_euler(rotation)), result);
83 | return result / scale;
84 | }
85 |
86 | /* META
87 | @meta: subcategory=Mapping; label=Vector;
88 | @vector: subtype=Vector; default='vec3(0.0)';
89 | @rotation: subtype=Euler;
90 | @scale: subtype=Vector; default=vec3(1.0);
91 | */
92 | vec3 mapping_vector(vec3 vector, vec3 rotation, vec3 scale)
93 | {
94 | return transform_direction(mat4_rotation_from_euler(rotation), vector * scale);
95 | }
96 |
97 | /* META
98 | @meta: subcategory=Mapping; label=Normal;
99 | @vector: subtype=Vector; default='vec3(0.0)';
100 | @rotation: subtype=Euler;
101 | @scale: subtype=Vector; default=vec3(1.0);
102 | */
103 |
104 | vec3 mapping_normal(vec3 vector, vec3 rotation, vec3 scale)
105 | {
106 | return normalize(mapping_vector(vector, rotation, scale));
107 | }
108 |
109 | #endif // COMMON_VECTOR_GLSL
110 |
--------------------------------------------------------------------------------
/BlenderMalt/MaltNodes/Nodes/MaltInlineNode.py:
--------------------------------------------------------------------------------
1 | import bpy
2 | from BlenderMalt.MaltNodes.MaltNode import MaltNode
3 |
4 |
5 | class MaltInlineNode(bpy.types.Node, MaltNode):
6 |
7 | bl_label = "Inline Code Node"
8 |
9 | def code_update(self, context):
10 | #update the node tree
11 | self.id_data.update_ext(force_update=True)
12 |
13 | code : bpy.props.StringProperty(update=code_update,
14 | options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
15 |
16 | def on_socket_update(self, socket):
17 | self.update()
18 | self.id_data.update_ext(force_update=True)
19 |
20 | def malt_init(self):
21 | self.setup()
22 |
23 | def malt_update(self):
24 | last = 0
25 | for i, input in enumerate(self.inputs):
26 | if input.data_type != '' or input.get_linked():
27 | last = i + 1
28 | variables = 'abcdefgh'[:min(last+1,8)]
29 |
30 | inputs = {}
31 | for var in variables:
32 | inputs[var] = {'type': ''}
33 | if var in self.inputs:
34 | input = self.inputs[var]
35 | linked = self.inputs[var].get_linked()
36 | if linked and linked.data_type != '':
37 | inputs[var] = {'type': linked.data_type, 'size': linked.array_size}
38 | else:
39 | inputs[var] = {'type': input.data_type, 'size': input.array_size}
40 |
41 | outputs = { 'result' : {'type': ''} }
42 | if 'result' in self.outputs:
43 | out = self.outputs['result'].get_linked()
44 | if out:
45 | outputs['result'] = {'type': out.data_type, 'size': out.array_size}
46 |
47 | self.setup_sockets(inputs, outputs)
48 |
49 | def draw_buttons(self, context, layout):
50 | layout.prop(self, 'code', text='')
51 |
52 | def draw_socket(self, context, layout, socket, text):
53 | if socket.is_output == False:
54 | MaltNode.draw_socket(self, context, layout, socket, socket.name)
55 | layout.prop(socket, 'data_type', text='')
56 | else:
57 | MaltNode.draw_socket(self, context, layout, socket, socket.name)
58 |
59 | def get_source_socket_reference(self, socket):
60 | return '{}_0_{}'.format(self.get_source_name(), socket.name)
61 |
62 | def get_source_code(self, transpiler):
63 | code = ''
64 | result_socket = self.outputs['result']
65 | code += transpiler.declaration(result_socket.data_type, result_socket.array_size, result_socket.get_source_reference())
66 |
67 | scoped_code = ''
68 | for input in self.inputs:
69 | if input.data_type != '':
70 | initialization = input.get_source_initialization()
71 | scoped_code += transpiler.declaration(input.data_type, input.array_size, input.name, initialization)
72 | if self.code != '':
73 | scoped_code += transpiler.asignment(self.outputs['result'].get_source_reference(), self.code)
74 |
75 | return code + transpiler.scoped(scoped_code)
76 |
77 |
78 | classes = [
79 | MaltInlineNode,
80 | ]
81 |
82 | def register():
83 | for _class in classes: bpy.utils.register_class(_class)
84 |
85 | def unregister():
86 | for _class in reversed(classes): bpy.utils.unregister_class(_class)
87 |
--------------------------------------------------------------------------------
/Malt/Shaders/Filters/Bevel.glsl:
--------------------------------------------------------------------------------
1 | #ifndef FILTERS_BEVEL_GLSL
2 | #define FILTERS_BEVEL_GLSL
3 |
4 | #include "Common/Math.glsl"
5 | #include "Common/Transform.glsl"
6 |
7 | /* META
8 | @meta: internal=true;
9 | @id: default=ID[0];
10 | @samples: default=8;
11 | @radius: default=1.0;
12 | @distribution_exponent: default=5.0;
13 | @hard_bevel_max_dot: default=0.5;
14 | */
15 | vec3 bevel
16 | (
17 | sampler2D normal_texture, sampler2D depth_texture, int depth_channel,
18 | uint id, bool filter_by_id, usampler2D id_texture, int id_channel,
19 | int samples, float radius, float distribution_exponent,
20 | bool hard_bevel, float hard_bevel_max_dot
21 | )
22 | {
23 | vec2 uv = screen_uv();
24 |
25 | vec3 pixel_normal = texture(normal_texture, uv).xyz;
26 | float pixel_depth = texture(depth_texture, uv)[depth_channel];
27 | vec3 pixel_position = screen_to_camera(uv, pixel_depth);
28 |
29 | float closest_distance = radius;
30 |
31 | vec3 normal = pixel_normal;
32 |
33 | float screen_radius = radius / pixel_world_size_at(pixel_depth);
34 |
35 | for(int i = 0; i < samples; i++)
36 | {
37 | vec2 offset = random_per_pixel(i).xy;
38 | if(length(offset) > 1)
39 | {
40 | offset = normalize(offset);
41 | }
42 | offset = offset * 2.0 - 1.0;
43 | if(distribution_exponent > 1)
44 | {
45 | offset = pow(abs(offset), vec2(distribution_exponent)) * sign(offset);
46 | }
47 | offset *= screen_radius;
48 |
49 | vec2 offset_uv = uv + (offset / vec2(RESOLUTION));
50 | ivec2 offset_texel = ivec2(RESOLUTION * offset_uv);
51 |
52 | if(filter_by_id)
53 | {
54 | uint offset_id = texelFetch(id_texture, offset_texel, 0)[id_channel];
55 | if (offset_id != id)
56 | {
57 | continue;
58 | }
59 | }
60 |
61 | vec3 offset_normal = texelFetch(normal_texture, offset_texel, 0).xyz;
62 | float offset_depth = texelFetch(depth_texture, offset_texel, 0)[depth_channel];
63 |
64 | vec3 offset_position = screen_to_camera(offset_uv, offset_depth);
65 | float offset_distance = distance(pixel_position, offset_position);
66 |
67 | if(offset_distance < radius)
68 | {
69 | if(hard_bevel)
70 | {
71 | float offset_dot = dot(pixel_normal, offset_normal);
72 | if(offset_dot <= hard_bevel_max_dot)
73 | {
74 | if(offset_distance < closest_distance)
75 | {
76 | closest_distance = offset_distance;
77 | normal = offset_normal;
78 | }
79 | }
80 | }
81 | else
82 | {
83 | normal += offset_normal;
84 | }
85 | }
86 | }
87 |
88 | if(hard_bevel)
89 | {
90 | float mix_factor = 1.0 - (closest_distance / screen_radius);
91 | mix_factor = saturate(pow(mix_factor, distribution_exponent) * distribution_exponent);
92 | return normalize(mix(NORMAL, normalize(NORMAL + normalize(normal)), mix_factor));
93 | }
94 | else
95 | {
96 | return normalize(normal);
97 | }
98 | }
99 |
100 | #endif //FILTERS_BEVEL_GLSL
101 |
--------------------------------------------------------------------------------
/Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/MainPass.py:
--------------------------------------------------------------------------------
1 | from Malt.GL.GL import *
2 | from Malt.GL.Texture import Texture
3 | from Malt.GL.RenderTarget import RenderTarget
4 | from Malt.PipelineNode import PipelineNode
5 | from Malt.PipelineParameters import Parameter, Type
6 | from Malt.Scene import TextureShaderResource
7 |
8 | class MainPass(PipelineNode):
9 | """
10 | Renders the scene geometry using the *Mesh Main Pass*.
11 | The node sockets are dynamic, based on the *Main Pass Custom IO*.
12 | If *Normal Depth/ID* is empty, the *Pre Pass* *Normal Depth/ID* will be used.
13 | """
14 |
15 | def __init__(self, pipeline):
16 | PipelineNode.__init__(self, pipeline)
17 | self.resolution = None
18 | self.t_depth = None
19 |
20 | @staticmethod
21 | def get_pass_type():
22 | return 'Mesh.MAIN_PASS_PIXEL_SHADER'
23 |
24 | @classmethod
25 | def reflect_inputs(cls):
26 | inputs = {}
27 | inputs['Scene'] = Parameter('Scene', Type.OTHER)
28 | inputs['Normal Depth'] = Parameter('', Type.TEXTURE)
29 | inputs['ID'] = Parameter('', Type.TEXTURE)
30 | return inputs
31 |
32 | @classmethod
33 | def reflect_outputs(cls):
34 | outputs = {}
35 | return outputs
36 |
37 | def setup_render_targets(self, resolution, t_depth, custom_io):
38 | self.custom_targets = {}
39 | for io in custom_io:
40 | if io['io'] == 'out' and io['type'] == 'Texture':#TODO
41 | formats = {
42 | 'float' : GL.GL_R16F,
43 | 'vec2' : GL.GL_RG16F,
44 | 'vec3' : GL.GL_RGB16F,
45 | 'vec4' : GL.GL_RGBA16F,
46 | }
47 | self.custom_targets[io['name']] = Texture(resolution, formats[io['subtype']])
48 | self.t_depth = t_depth
49 | self.fbo = RenderTarget([*self.custom_targets.values()], self.t_depth)
50 |
51 | def execute(self, parameters):
52 | inputs = parameters['IN']
53 | outputs = parameters['OUT']
54 | custom_io = parameters['CUSTOM_IO']
55 |
56 | scene = inputs['Scene']
57 | if scene is None:
58 | return
59 | t_normal_depth = inputs['Normal Depth']
60 | t_id = inputs['ID']
61 |
62 | shader_resources = scene.shader_resources.copy()
63 | if t_normal_depth:
64 | shader_resources['IN_NORMAL_DEPTH'] = TextureShaderResource('IN_NORMAL_DEPTH', t_normal_depth)
65 | if t_id:
66 | shader_resources['IN_ID'] = TextureShaderResource('IN_ID', t_id)
67 |
68 | t_depth = shader_resources['T_DEPTH'].texture
69 | if self.pipeline.resolution != self.resolution or self.custom_io != custom_io or t_depth != self.t_depth:
70 | self.setup_render_targets(self.pipeline.resolution, t_depth, custom_io)
71 | self.resolution = self.pipeline.resolution
72 | self.custom_io = custom_io
73 |
74 | for io in custom_io:
75 | if io['io'] == 'in':
76 | if io['type'] == 'Texture':#TODO
77 | from Malt.SourceTranspiler import GLSLTranspiler
78 | glsl_name = GLSLTranspiler.custom_io_reference('IN', 'MAIN_PASS_PIXEL_SHADER', io['name'])
79 | shader_resources['CUSTOM_IO'+glsl_name] = TextureShaderResource(glsl_name, inputs[io['name']])
80 |
81 | self.fbo.clear([(0,0,0,0)] * len(self.fbo.targets))
82 | self.pipeline.draw_scene_pass(self.fbo, scene.batches, 'MAIN_PASS', self.pipeline.default_shader['MAIN_PASS'],
83 | shader_resources, GL_EQUAL)
84 |
85 | outputs.update(self.custom_targets)
86 |
87 | NODE = MainPass
88 |
--------------------------------------------------------------------------------
/Malt/GL/readme.md:
--------------------------------------------------------------------------------
1 | # GL
2 |
3 | The GL folder contains a series of modules that abstracts commonly needed OpenGL functionality.
4 | It doesn't try to be a complete abstraction, so it's meant to be used alongside raw OpenGL calls.
5 |
6 | ## [GL.py](GL.py)
7 | Loads OpenGL functions via [PyOpenGL](https://pypi.org/project/PyOpenGL/), provides OpenGL enums reflection dictionaries and implements a Python to OpenGL types conversion function via the *gl_buffer function*.
8 |
9 | ## [Mesh.py](Mesh.py)
10 |
11 | Builds VAOs with the same layout used by [Common.glsl](Malt/Shaders/Common.glsl).
12 | Each parameter expects a flat 1D iterable with all the per-vertex data.
13 | Tangents, UVs and Vertex Colors buffers must be passed inside a list, even if you pass only one. Four of each are allowed at most.
14 |
15 | The MeshCustomLoad class doesn't do any loading by itself. It's reserved for cases where special opimizations are needed, like [BlenderMalt/MaltMeshes.py](BlenderMalt/MaltMeshes.py).
16 |
17 | * [OpenGL Wiki - Vertex Array Object](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Array_Object)
18 | * [LearnOpenGL - Hello Triangle](https://learnopengl.com/Getting-started/Hello-Triangle)
19 |
20 | ## [Shader.py](Shader.py)
21 |
22 | Builds OpenGL programs from GLSL vertex and fragment shader source code and provides an interface for reflection and configuration of shader parameters.
23 |
24 | The *shader_preprocessor* function parses source code with a C preprocessor to provide support for *#include directives* in glsl shaders.
25 |
26 | * [OpenGL Wiki - GLSL Objects](https://www.khronos.org/opengl/wiki/GLSL_Object)
27 | * [Learn OpenGL - Shaders](https://learnopengl.com/Getting-started/Shaders)
28 |
29 | ## [Texture.py](Texture.py)
30 |
31 | Loads 1D textures (*Gradient*), 2D textures (*Texture*), 2D texture arrays (*TextureArray*), cube maps (*CubeMap*) and cube map arrays (*CubeMapArrays*) under a simple and mostly shared interface.
32 | The *internal_format* parameter is the [OpenGL image format](https://www.khronos.org/opengl/wiki/Image_Format) the texture will be stored inside the GPU.
33 |
34 | * [OpenGL Wiki - Texture](https://www.khronos.org/opengl/wiki/Texture)
35 | * [Learn OpenGL - Textures](https://learnopengl.com/Getting-started/Textures)
36 |
37 | ## [RenderTarget.py](RenderTarget.py)
38 |
39 | Builds FBOs from Textures. It accepts an arbitrary number of color targets and a single depth/stencil target.
40 | Color target Textures must be passed inside a list, even if you pass only one.
41 |
42 | For rendering to other types of targets (like Cube Maps and Texture Arrays) you can pass an object with a custom attach method (See the *ArrayLayerTarget* class for an example).
43 |
44 | * [OpenGL Wiki - Framebuffer Objects](https://www.khronos.org/opengl/wiki/Framebuffer_Object)
45 | * [Learn OpenGL - Framebuffers](https://learnopengl.com/Advanced-OpenGL/Framebuffers)
46 |
47 | ## Basic Example (draw a full screen quad)
48 |
49 | ```python
50 | positions=[
51 | 1.0, 1.0, 0.0,
52 | 1.0, -1.0, 0.0,
53 | -1.0, -1.0, 0.0,
54 | -1.0, 1.0, 0.0,
55 | ]
56 | indices=[
57 | 0, 1, 3,
58 | 1, 2, 3,
59 | ]
60 |
61 | mesh = Mesh(positions, indices)
62 |
63 | vertex_source='''
64 | layout (location = 0) in vec3 POSITION;
65 | void main()
66 | {
67 | gl_Position = vec4(POSITION, 1);
68 | }
69 | '''
70 |
71 | pixel_source='''
72 | layout (location = 0) out vec4 RESULT;
73 | uniform vec4 color = vec4(1,0,0,1);
74 | void main()
75 | {
76 | RESULT = color;
77 | }
78 | '''
79 |
80 | shader = Shader(vertex_source, pixel_source)
81 |
82 | shader.uniforms['color'].set_value((0,1,0,1))
83 |
84 | result_texture = Texture((1024, 1024), GL_RGBA32F)
85 |
86 | result_target = RenderTarget([result_texture])
87 |
88 | result_target.bind()
89 | shader.bind()
90 | mesh.draw()
91 | ```
92 |
--------------------------------------------------------------------------------
/Malt/Shaders/Procedural/Fractal_Noise.glsl:
--------------------------------------------------------------------------------
1 | #ifndef PROCEDURAL_FRACTAL_NOISE_GLSL
2 | #define PROCEDURAL_FRACTAL_NOISE_GLSL
3 |
4 | #include "Noise.glsl"
5 |
6 | /* META GLOBAL
7 | @meta: category=Texturing; @meta: internal=true;
8 | */
9 |
10 | /* META
11 | @coord: subtype=Vector; default=vec4(POSITION,0);
12 | @detail: default=3.0; min=1.0;
13 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
14 | @tile_size: default=ivec4(5);
15 | */
16 | vec4 fractal_noise(vec4 coord, float detail, float detail_balance, ivec4 tile_size)
17 | {
18 | #define TILE 1
19 | #include "Fractal_Noise.inl"
20 | return color;
21 | }
22 |
23 | /* META
24 | @coord: subtype=Vector; default=vec4(POSITION,0);
25 | @detail: default=3.0; min=1.0;
26 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
27 | */
28 | vec4 fractal_noise(vec4 coord, float detail, float detail_balance)
29 | {
30 | #define TILE 0
31 | #include "Fractal_Noise.inl"
32 | return color;
33 | }
34 |
35 | /* META
36 | @coord: subtype=Vector; default=POSITION;
37 | @detail: default=3.0; min=1.0;
38 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
39 | @tile_size: default=ivec3(5);
40 | */
41 | vec4 fractal_noise(vec3 coord, float detail, float detail_balance, ivec3 tile_size)
42 | {
43 | #define TILE 1
44 | #include "Fractal_Noise.inl"
45 | return color;
46 | }
47 |
48 | /* META
49 | @coord: subtype=Vector; default=POSITION;
50 | @detail: default=3.0; min=1.0;
51 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
52 | */
53 | vec4 fractal_noise(vec3 coord, float detail, float detail_balance)
54 | {
55 | #define TILE 0
56 | #include "Fractal_Noise.inl"
57 | return color;
58 | }
59 |
60 | /* META
61 | @coord: subtype=Vector; default=POSITION.xy;
62 | @detail: default=3.0; min=1.0;
63 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
64 | @tile_size: default=ivec2(5);
65 | */
66 | vec4 fractal_noise(vec2 coord, float detail, float detail_balance, ivec2 tile_size)
67 | {
68 | #define TILE 1
69 | #include "Fractal_Noise.inl"
70 | return color;
71 | }
72 |
73 | /* META
74 | @coord: subtype=Vector; default=POSITION.xy;
75 | @detail: default=3.0; min=1.0;
76 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
77 | */
78 | vec4 fractal_noise(vec2 coord, float detail, float detail_balance)
79 | {
80 | #define TILE 0
81 | #include "Fractal_Noise.inl"
82 | return color;
83 | }
84 |
85 | /* META
86 | @coord: subtype=Vector; default=POSITION.x;
87 | @detail: default=3.0; min=1.0;
88 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
89 | @tile_size: default=5;
90 | */
91 | vec4 fractal_noise(float coord, float detail, float detail_balance, int tile_size)
92 | {
93 | #define TILE 1
94 | #include "Fractal_Noise.inl"
95 | return color;
96 | }
97 |
98 | /* META
99 | @coord: subtype=Vector; default=POSITION.x;
100 | @detail: default=3.0; min=1.0;
101 | @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5;
102 | */
103 | vec4 fractal_noise(float coord, float detail, float detail_balance)
104 | {
105 | #define TILE 0
106 | #include "Fractal_Noise.inl"
107 | return color;
108 | }
109 |
110 | #undef TILE
111 |
112 | // Keep for backward compatibility
113 | vec4 fractal_noise_ex(vec4 coord, int octaves, bool tile, vec4 tile_size)
114 | {
115 | if(tile)
116 | {
117 | return fractal_noise(coord, float(octaves), 0.5, ivec4(tile_size));
118 | }
119 | else
120 | {
121 | return fractal_noise(coord, float(octaves), 0.5);
122 | }
123 | }
124 | vec4 fractal_noise(vec4 coord, int octaves)
125 | {
126 | return fractal_noise(coord, float(octaves), 0.5);
127 | }
128 |
129 |
130 | #endif // PROCEDURAL_FRACTAL_NOISE_GLSL
131 |
--------------------------------------------------------------------------------