Repository: BlenderNPR/BEER Branch: Development Commit: b5ba80553ded Files: 208 Total size: 1.1 MB Directory structure: gitextract_fmgxqm5w/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── config.yml │ └── workflows/ │ └── BlenderMalt.yml ├── .gitignore ├── BlenderMalt/ │ ├── CBlenderMalt/ │ │ ├── CBlenderMalt.cpp │ │ ├── CMakeLists.txt │ │ ├── __init__.py │ │ ├── build.py │ │ ├── mikktspace.c │ │ └── mikktspace.h │ ├── MaltLights.py │ ├── MaltMaterial.py │ ├── MaltMeshes.py │ ├── MaltNodes/ │ │ ├── MaltCustomPasses.py │ │ ├── MaltNode.py │ │ ├── MaltNodeTree.py │ │ ├── MaltNodeUITools.py │ │ ├── MaltSocket.py │ │ ├── Nodes/ │ │ │ ├── MaltArrayIndexNode.py │ │ │ ├── MaltFunctionNode.py │ │ │ ├── MaltFunctionSubCategory.py │ │ │ ├── MaltIONode.py │ │ │ ├── MaltInlineNode.py │ │ │ └── MaltStructNode.py │ │ └── _init_.py │ ├── MaltPipeline.py │ ├── MaltProperties.py │ ├── MaltRenderEngine.py │ ├── MaltTextures.py │ ├── MaltUtils.py │ ├── __init__.py │ └── readme.md ├── Bridge/ │ ├── Client_API.py │ ├── Docs.py │ ├── Material.py │ ├── Mesh.py │ ├── Proxys.py │ ├── Server.py │ ├── Texture.py │ ├── __init__.py │ ├── ipc/ │ │ ├── CMakeLists.txt │ │ ├── __init__.py │ │ ├── build.py │ │ ├── ipc.c │ │ └── ipc.h │ └── renderdoc/ │ ├── CMakeLists.txt │ ├── __init__.py │ ├── build.py │ ├── renderdoc_app.h │ └── renderdoc_wrapper.c ├── LICENSE ├── LICENSE - DEPENDENCIES ├── LICENSE - DEPENDENCIES (FULL TEXT) ├── Malt/ │ ├── GL/ │ │ ├── GL.py │ │ ├── GLSLEval.py │ │ ├── GLSLParser/ │ │ │ ├── CMakeLists.txt │ │ │ ├── build.py │ │ │ ├── external/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── PEGTL-LICENSE │ │ │ │ └── rapidjson-LICENSE │ │ │ └── src/ │ │ │ └── main.cpp │ │ ├── Mesh.py │ │ ├── RenderTarget.py │ │ ├── Shader.py │ │ ├── Texture.py │ │ └── readme.md │ ├── Nodes/ │ │ ├── LineRender.py │ │ ├── SceneFilter.py │ │ └── SuperSamplingAA.py │ ├── Pipeline.py │ ├── PipelineGraph.py │ ├── PipelineNode.py │ ├── PipelineParameters.py │ ├── PipelinePlugin.py │ ├── Pipelines/ │ │ ├── MiniPipeline/ │ │ │ └── MiniPipeline.py │ │ └── NPR_Pipeline/ │ │ ├── Defaults/ │ │ │ └── defaults.blend │ │ ├── NPR_LightShaders.py │ │ ├── NPR_Lighting.py │ │ ├── NPR_Pipeline.py │ │ ├── Nodes/ │ │ │ ├── Render/ │ │ │ │ ├── RenderLayers.py │ │ │ │ ├── SceneLighting.py │ │ │ │ └── ScreenPass.py │ │ │ └── RenderLayer/ │ │ │ ├── MainPass.py │ │ │ ├── PrePass.py │ │ │ └── ScreenPass.py │ │ └── Shaders/ │ │ ├── NPR_Intellisense.glsl │ │ ├── NPR_LightShader.glsl │ │ ├── NPR_MeshShader.glsl │ │ ├── NPR_Pipeline/ │ │ │ ├── NPR_Filters.glsl │ │ │ ├── NPR_Lighting.glsl │ │ │ ├── NPR_Mesh.glsl │ │ │ ├── NPR_Shading.glsl │ │ │ └── NPR_Shading2.glsl │ │ └── NPR_ScreenShader.glsl │ ├── Render/ │ │ ├── Common.py │ │ ├── DepthToCompositeDepth.py │ │ ├── Lighting.py │ │ ├── Sampling.py │ │ └── readme.md │ ├── Scene.py │ ├── Shaders/ │ │ ├── Common/ │ │ │ ├── Color.glsl │ │ │ ├── Hash.glsl │ │ │ ├── Mapping.glsl │ │ │ ├── Math.glsl │ │ │ ├── Matrix.glsl │ │ │ ├── Normal.glsl │ │ │ ├── Quaternion.glsl │ │ │ └── Transform.glsl │ │ ├── Common.glsl │ │ ├── Filters/ │ │ │ ├── AO.glsl │ │ │ ├── Bevel.glsl │ │ │ ├── Blur.glsl │ │ │ ├── Curvature.glsl │ │ │ ├── JumpFlood.glsl │ │ │ ├── Kuwahara.glsl │ │ │ ├── Line.glsl │ │ │ ├── Sharpen.glsl │ │ │ └── StructureTensor.glsl │ │ ├── Intellisense/ │ │ │ └── intellisense.glsl │ │ ├── Lighting/ │ │ │ └── Lighting.glsl │ │ ├── Node Utils/ │ │ │ ├── bool.glsl │ │ │ ├── common.glsl │ │ │ ├── float.glsl │ │ │ ├── int.glsl │ │ │ ├── node_utils.glsl │ │ │ ├── packing.glsl │ │ │ ├── properties.glsl │ │ │ ├── sampler.glsl │ │ │ ├── vec2.glsl │ │ │ ├── vec3.glsl │ │ │ └── vec4.glsl │ │ ├── Node Utils 2/ │ │ │ ├── Bool.glsl │ │ │ ├── Color.glsl │ │ │ ├── ColorBlend.glsl │ │ │ ├── Filter.glsl │ │ │ ├── Float.glsl │ │ │ ├── Input.glsl │ │ │ ├── Int.glsl │ │ │ ├── Mat4.glsl │ │ │ ├── Parameters.glsl │ │ │ ├── Texturing.glsl │ │ │ ├── Vec2.glsl │ │ │ ├── Vec3.glsl │ │ │ ├── Vec4.glsl │ │ │ ├── Vector.glsl │ │ │ ├── conversion.glsl │ │ │ └── node_utils_2.glsl │ │ ├── Passes/ │ │ │ ├── BlendTexture.glsl │ │ │ ├── BlendTransparency.glsl │ │ │ ├── CopyTextures.glsl │ │ │ ├── DepthToBlenderDepth.glsl │ │ │ ├── JumpFlood.glsl │ │ │ ├── LineComposite.glsl │ │ │ ├── Unpack8bitTextures.glsl │ │ │ └── sRGBConversion.glsl │ │ ├── Procedural/ │ │ │ ├── Bayer.glsl │ │ │ ├── Cell_Noise.glsl │ │ │ ├── Cell_Noise.inl │ │ │ ├── Fractal_Noise.glsl │ │ │ ├── Fractal_Noise.inl │ │ │ ├── Noise.glsl │ │ │ └── Noise.inl │ │ ├── SDF/ │ │ │ └── SDF.glsl │ │ ├── Shading/ │ │ │ ├── BRDF.glsl │ │ │ └── ShadingModels.glsl │ │ └── readme.md │ ├── SourceTranspiler.py │ ├── Utils.py │ ├── __init__.py │ └── readme.md ├── README.md ├── __init__.py ├── docs/ │ ├── Documentation/ │ │ ├── Getting Started.md │ │ ├── Graphs.md │ │ ├── Plugins.md │ │ ├── Settings.md │ │ └── Tooling.md │ ├── FAQ.md │ ├── From-Nodes-To-Code/ │ │ └── From-Nodes-To-Code.md │ ├── Setup-BlenderMalt-for-Development.md │ ├── extra/ │ │ ├── extra.css │ │ └── extra.js │ ├── index.md │ ├── overrides/ │ │ └── partials/ │ │ └── _footer.html │ └── reference/ │ ├── Light-graph.md │ ├── Mesh-graph.md │ ├── Render Layer-graph.md │ ├── Render-graph.md │ ├── Screen-graph.md │ └── settings.md ├── mkdocs/ │ ├── mkdocs.yml │ ├── netlify.toml │ └── requirements.txt ├── plugins/ │ ├── Experimental/ │ │ ├── RenderLayer/ │ │ │ └── OpaqueLayer.py │ │ └── __init__.py │ └── PluginExample/ │ ├── Shaders/ │ │ └── PluginExample.glsl │ └── __init__.py └── scripts/ ├── PatchDependencies/ │ ├── mcpp-Darwin │ └── mcpp-Linux ├── build_intellisense_glsl.py ├── format.py ├── generate_conversion_nodes.py ├── generate_license_dependencies_full.py ├── get_glslang.py ├── install_dependencies.py ├── print_pixel_formats.py ├── settings.json └── setup_blender_addon.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug Reports description: Open a new issue body: - type: markdown attributes: value: | Before opening a new issue: - Read the [readme](https://github.com/bnpr/Malt#malt), check that you meet the minimum requirements and have installed Malt properly. - Make sure you are running the latest Malt version. - Search [existing issues](https://github.com/bnpr/Malt/issues) to ensure it has not already been reported. - type: input attributes: label: Malt version placeholder: Release | Development validations: required: true - type: input attributes: label: Blender version placeholder: Blender 3.0.0 validations: required: true - type: input attributes: label: OS placeholder: Windows 10 64 bits validations: required: true - type: input attributes: label: Hardware info placeholder: Intel i7-4790K | 16GB RAM | Nvidia GTX 1060 6GB validations: required: true - type: textarea attributes: label: Issue description and reproduction steps validations: required: true - type: textarea attributes: label: Attachments description: | - The **Log file** from the Blender session where the bug happened. You can find it in *Preferences > Addons > BlenderMalt > Open Session Log* or search for it in your system temporary folder. - A zipped **.blend file** with a minimal example to reproduce the error. *(Unless the error is not file dependent)* > Don't copy/paste the *Log* text. Drag and drop the files to upload them. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Q&A url: https://github.com/bnpr/Malt/discussions/categories/q-a about: Ask your *How to* questions. - name: Feedback & Feature Requests url: https://github.com/bnpr/Malt/discussions/categories/feedback-feature-requests about: Post your feedback and feature requests. ================================================ FILE: .github/workflows/BlenderMalt.yml ================================================ name: BLENDERMALT_PACKAGE_AND_RELEASE on: push: jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: nelonoel/branch-name@v1.0.1 - name: Rollback Release uses: author/action-rollback@stable continue-on-error: true with: tag: ${{ env.BRANCH_NAME }}-latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: ${{ github.ref_type == 'branch' }} - uses: ncipollo/release-action@v1 id: create_release if: ${{ github.ref_type == 'branch' }} with: token: ${{ secrets.GITHUB_TOKEN }} allowUpdates: true tag: ${{ env.BRANCH_NAME }}-latest commit: ${{ github.sha }} prerelease: ${{ env.BRANCH_NAME != 'Release' }} body: | [**BlenderMalt-Windows.zip**](https://github.com/${{github.repository}}/releases/download/${{env.BRANCH_NAME}}-latest/BlenderMalt-Windows.zip) [**BlenderMalt-Linux.zip**](https://github.com/${{github.repository}}/releases/download/${{env.BRANCH_NAME}}-latest/BlenderMalt-Linux.zip) - name: Rollback Tagged Release uses: author/action-rollback@stable continue-on-error: true with: tag: ${{ github.action_ref }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: ${{ github.ref_type == 'tag' }} - uses: ncipollo/release-action@v1 id: create_tagged_release if: ${{ github.ref_type == 'tag' }} with: token: ${{ secrets.GITHUB_TOKEN }} commit: ${{ github.sha }} allowUpdates: true prerelease: ${{ env.BRANCH_NAME != 'Release' }} body: | [**BlenderMalt-Windows.zip**](https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/BlenderMalt-Windows.zip) [**BlenderMalt-Linux.zip**](https://github.com/${{github.repository}}/releases/download/${{github.ref_name}}/BlenderMalt-Linux.zip) outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} release_id: ${{ steps.create_release.outputs.id }} tagged_upload_url: ${{ steps.create_tagged_release.outputs.upload_url }} tagged_release_id: ${{ steps.create_tagged_release.outputs.id }} package-blender: needs: [release] runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [windows-latest, ubuntu-latest] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v6 with: python-version: '3.13' - name: setup_blender_addon.py run: python setup_blender_addon.py --copy-modules working-directory: ${{ github.workspace }}/scripts - name: Zip Addon shell: python run: | import shutil shutil.make_archive('BlenderMalt', 'zip', '.', 'BlenderMalt') - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: BlenderMalt-${{ runner.os }} path: ${{ github.workspace }}/BlenderMalt.zip - name: Remove Old Files uses: flcdrg/remove-release-asset-action@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: release_id: ${{ needs.release.outputs.release_id }} asset_name: BlenderMalt-${{ runner.os }}.zip continue-on-error: true if: ${{ github.ref_type == 'branch' }} - name: Release Addon uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ needs.release.outputs.upload_url }} asset_path: ${{ github.workspace }}/BlenderMalt.zip asset_name: BlenderMalt-${{ runner.os }}.zip asset_content_type: application/zip if: ${{ github.ref_type == 'branch' }} - name: Remove Old Tagged Files uses: flcdrg/remove-release-asset-action@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: release_id: ${{ needs.release.outputs.tagged_release_id }} asset_name: BlenderMalt-${{ runner.os }}.zip continue-on-error: true if: ${{ github.ref_type == 'tag' }} - name: Release Tagged Addon uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ needs.release.outputs.tagged_upload_url }} asset_path: ${{ github.workspace }}/BlenderMalt.zip asset_name: BlenderMalt-${{ runner.os }}.zip asset_content_type: application/zip if: ${{ github.ref_type == 'tag' }} ================================================ FILE: .gitignore ================================================ .* !.gitignore !.gitattributes !.gitmodules !.github __pycache__ *.lib *.dll *.so *.log ================================================ FILE: BlenderMalt/CBlenderMalt/CBlenderMalt.cpp ================================================ #include "stdio.h" #include "mikktspace.h" #ifdef _WIN32 #define EXPORT extern "C" __declspec( dllexport ) #else #define EXPORT extern "C" __attribute__ ((visibility ("default"))) #endif EXPORT void retrieve_mesh_data( float* in_positions, int* in_loop_verts, int loop_count, int* in_loop_tris, int* in_loop_tri_polys, int loop_tri_count, int* in_mat_indices, float* out_positions, unsigned int** out_indices, unsigned int* out_index_lengths) { for(int i = 0; i < loop_count; i++) { out_positions[i*3+0] = in_positions[in_loop_verts[i]*3+0]; out_positions[i*3+1] = in_positions[in_loop_verts[i]*3+1]; out_positions[i*3+2] = in_positions[in_loop_verts[i]*3+2]; } unsigned int* mat_i = out_index_lengths; for(int i = 0; i < loop_tri_count; i++) { int mat = in_mat_indices ? in_mat_indices[in_loop_tri_polys[i]] : 0; out_indices[mat][mat_i[mat]++] = in_loop_tris[i*3+0]; out_indices[mat][mat_i[mat]++] = in_loop_tris[i*3+1]; out_indices[mat][mat_i[mat]++] = in_loop_tris[i*3+2]; } } EXPORT bool mesh_tangents( int* in_indices, int index_len, float* in_positions, float* in_normals, float* in_uvs, float* out_tangents) { struct MData { int* indices; int index_len; float* positions; float* normals; float* uvs; float* tangents; }; MData data = { in_indices, index_len, in_positions, in_normals, in_uvs, out_tangents, }; SMikkTSpaceInterface mti = {0}; mti.m_getNumFaces = [](const SMikkTSpaceContext * pContext){ return ((MData*)pContext->m_pUserData)->index_len / 3; }; mti.m_getNumVerticesOfFace = [](const SMikkTSpaceContext * pContext, const int iFace){ return 3; }; mti.m_getPosition = [](const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert){ MData* m = (MData*)pContext->m_pUserData; int i = iFace * 3 + iVert; fvPosOut[0] = m->positions[m->indices[i] * 3 + 0]; fvPosOut[1] = m->positions[m->indices[i] * 3 + 1]; fvPosOut[2] = m->positions[m->indices[i] * 3 + 2]; }; mti.m_getNormal = [](const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert){ MData* m = (MData*)pContext->m_pUserData; int i = iFace * 3 + iVert; fvNormOut[0] = m->normals[m->indices[i] * 3 + 0]; fvNormOut[1] = m->normals[m->indices[i] * 3 + 1]; fvNormOut[2] = m->normals[m->indices[i] * 3 + 2]; }; mti.m_getTexCoord = [](const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert){ MData* m = (MData*)pContext->m_pUserData; int i = iFace * 3 + iVert; fvTexcOut[0] = m->uvs[m->indices[i] * 2 + 0]; fvTexcOut[1] = m->uvs[m->indices[i] * 2 + 1]; }; mti.m_setTSpaceBasic = [](const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert){ MData* m = (MData*)pContext->m_pUserData; int i = iFace * 3 + iVert; m->tangents[m->indices[i] * 4 + 0] = fvTangent[0]; m->tangents[m->indices[i] * 4 + 1] = fvTangent[1]; m->tangents[m->indices[i] * 4 + 2] = fvTangent[2]; m->tangents[m->indices[i] * 4 + 3] = fSign; }; SMikkTSpaceContext mtc; mtc.m_pInterface = &mti; mtc.m_pUserData = &data; return genTangSpaceDefault(&mtc); } ================================================ FILE: BlenderMalt/CBlenderMalt/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) SET(CMAKE_CXX_STANDARD 17) # set(CMAKE_GENERATOR_PLATFORM x64) project(CBlenderMalt) SET(CMAKE_BUILD_TYPE Release) SET(BUILD_SHARED_LIBS ON) add_library(CBlenderMalt CBlenderMalt.cpp mikktspace.c) target_include_directories(CBlenderMalt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/blender_dna) install(TARGETS CBlenderMalt CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR}) ================================================ FILE: BlenderMalt/CBlenderMalt/__init__.py ================================================ import subprocess import os import ctypes import platform src_dir = os.path.abspath(os.path.dirname(__file__)) library = 'libCBlenderMalt.so' if platform.system() == 'Windows': library = 'CBlenderMalt.dll' if platform.system() == 'Darwin': library = 'libCBlenderMalt.dylib' CBlenderMalt = ctypes.CDLL(os.path.join(src_dir, library)) retrieve_mesh_data = CBlenderMalt['retrieve_mesh_data'] retrieve_mesh_data.argtypes = [ ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.c_uint32) ] retrieve_mesh_data.restype = None mesh_tangents = CBlenderMalt['mesh_tangents'] mesh_tangents.argtypes = [ ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_float) ] mesh_tangents.restype = ctypes.c_bool ================================================ FILE: BlenderMalt/CBlenderMalt/build.py ================================================ import subprocess import os import platform src_dir = os.path.abspath(os.path.dirname(__file__)) build_dir = os.path.join(src_dir, '.build') try: os.mkdir(build_dir) except: pass if platform.system() == 'Windows': #Multi-config generators, like Visual Studio subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir) else: #Single-config generators subprocess.check_call(['cmake', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir) subprocess.check_call(['cmake', '--install', '.'], cwd=build_dir) ================================================ FILE: BlenderMalt/CBlenderMalt/mikktspace.c ================================================ /** \file mikktspace/mikktspace.c * \ingroup mikktspace */ /** * Copyright (C) 2011 by Morten S. Mikkelsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #include #include #include #include #include #include #include "mikktspace.h" #define TFALSE 0 #define TTRUE 1 #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 #endif #define INTERNAL_RND_SORT_SEED 39871946 // internal structure typedef struct { float x, y, z; } SVec3; static tbool veq( const SVec3 v1, const SVec3 v2 ) { return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); } static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) { SVec3 vRes; vRes.x = v1.x + v2.x; vRes.y = v1.y + v2.y; vRes.z = v1.z + v2.z; return vRes; } static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) { SVec3 vRes; vRes.x = v1.x - v2.x; vRes.y = v1.y - v2.y; vRes.z = v1.z - v2.z; return vRes; } static SVec3 vscale(const float fS, const SVec3 v) { SVec3 vRes; vRes.x = fS * v.x; vRes.y = fS * v.y; vRes.z = fS * v.z; return vRes; } static float LengthSquared( const SVec3 v ) { return v.x*v.x + v.y*v.y + v.z*v.z; } static float Length( const SVec3 v ) { return sqrtf(LengthSquared(v)); } static SVec3 Normalize( const SVec3 v ) { return vscale(1 / Length(v), v); } static float vdot( const SVec3 v1, const SVec3 v2) { return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; } static tbool NotZero(const float fX) { // could possibly use FLT_EPSILON instead return fabsf(fX) > FLT_MIN; } static tbool VNotZero(const SVec3 v) { // might change this to an epsilon based test return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); } typedef struct { int iNrFaces; int * pTriMembers; } SSubGroup; typedef struct { int iNrFaces; int * pFaceIndices; int iVertexRepresentitive; tbool bOrientPreservering; } SGroup; // #define MARK_DEGENERATE 1 #define QUAD_ONE_DEGEN_TRI 2 #define GROUP_WITH_ANY 4 #define ORIENT_PRESERVING 8 typedef struct { int FaceNeighbors[3]; SGroup * AssignedGroup[3]; // normalized first order face derivatives SVec3 vOs, vOt; float fMagS, fMagT; // original magnitudes // determines if the current and the next triangle are a quad. int iOrgFaceNumber; int iFlag, iTSpacesOffs; unsigned char vert_num[4]; } STriInfo; typedef struct { SVec3 vOs; float fMagS; SVec3 vOt; float fMagT; int iCounter; // this is to average back into quads. tbool bOrient; } STSpace; static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, const SMikkTSpaceContext * pContext); static int MakeIndex(const int iFace, const int iVert) { assert(iVert>=0 && iVert<4 && iFace>=0); return (iFace<<2) | (iVert&0x3); } static void IndexToData(int * piFace, int * piVert, const int iIndexIn) { piVert[0] = iIndexIn&0x3; piFace[0] = iIndexIn>>2; } static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) { STSpace ts_res; // this if is important. Due to floating point precision // averaging when ts0==ts1 will cause a slight difference // which results in tangent space splits later on if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) { ts_res.fMagS = pTS0->fMagS; ts_res.fMagT = pTS0->fMagT; ts_res.vOs = pTS0->vOs; ts_res.vOt = pTS0->vOt; } else { ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); } return ts_res; } static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); // degen triangles static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) { return genTangSpace(pContext, 180.0f); } tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) { // count nr_triangles int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; STriInfo * pTriInfos = NULL; SGroup * pGroups = NULL; STSpace * psTspace = NULL; int iNrTrianglesIn = 0, f=0, t=0, i=0; int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; int iNrActiveGroups = 0, index = 0; const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); tbool bRes = TFALSE; const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); // verify all call-backs have been set if ( pContext->m_pInterface->m_getNumFaces==NULL || pContext->m_pInterface->m_getNumVerticesOfFace==NULL || pContext->m_pInterface->m_getPosition==NULL || pContext->m_pInterface->m_getNormal==NULL || pContext->m_pInterface->m_getTexCoord==NULL ) return TFALSE; // count triangles on supported faces for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts==3) ++iNrTrianglesIn; else if (verts==4) iNrTrianglesIn += 2; } if (iNrTrianglesIn<=0) return TFALSE; // allocate memory for an index list piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); if (piTriListIn==NULL || pTriInfos==NULL) { if (piTriListIn!=NULL) free(piTriListIn); if (pTriInfos!=NULL) free(pTriInfos); return TFALSE; } // make an initial triangle --> face index list iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); // make a welded index list of identical positions and attributes (pos, norm, texc) //printf("gen welded index list begin\n"); GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); //printf("gen welded index list end\n"); // Mark all degenerate triangles iTotTris = iNrTrianglesIn; iDegenTriangles = 0; for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts!=3 && verts!=4) continue; // I've decided to let degenerate triangles and group-with-anythings // vary between left/right hand coordinate systems at the vertices. // All healthy triangles on the other hand are built to always be either or. /*// force the coordinate system orientation to be uniform for every face. // (this is already the case for good triangles but not for // degenerate ones and those with bGroupWithAnything==true) bool bOrient = psTspace[index].bOrient; if (psTspace[index].iCounter == 0) // tspace was not derived from a group { // look for a space created in GenerateTSpaces() by iCounter>0 bool bNotFound = true; int i=1; while (i 0) bNotFound=false; else ++i; } if (!bNotFound) bOrient = psTspace[index+i].bOrient; }*/ // set data for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; if (pContext->m_pInterface->m_setTSpace!=NULL) pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); ++index; } } free(psTspace); return TTRUE; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef struct { float vert[3]; int index; } STmpVert; static const int g_iCells = 2048; #ifdef _MSC_VER # define NOINLINE __declspec(noinline) #else # define NOINLINE __attribute__ ((noinline)) #endif // it is IMPORTANT that this function is called to evaluate the hash since // inlining could potentially reorder instructions and generate different // results for the same effective input value fVal. static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) { const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); const int iIndex = (int)fIndex; return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); } static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) { // Generate bounding box int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; STmpVert * pTmpVert = NULL; int i=0, iChannel=0, k=0, e=0; int iMaxCount=0; SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; float fMin, fMax; for (i=1; i<(iNrTrianglesIn*3); i++) { const int index = piTriList_in_and_out[i]; const SVec3 vP = GetPosition(pContext, index); if (vMin.x > vP.x) vMin.x = vP.x; else if (vMax.x < vP.x) vMax.x = vP.x; if (vMin.y > vP.y) vMin.y = vP.y; else if (vMax.y < vP.y) vMax.y = vP.y; if (vMin.z > vP.z) vMin.z = vP.z; else if (vMax.z < vP.z) vMax.z = vP.z; } vDim = vsub(vMax,vMin); iChannel = 0; fMin = vMin.x; fMax=vMax.x; if (vDim.y>vDim.x && vDim.y>vDim.z) { iChannel=1; fMin = vMin.y; fMax = vMax.y; } else if (vDim.z>vDim.x) { iChannel=2; fMin = vMin.z; fMax = vMax.z; } // make allocations piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); piHashCount = (int *) malloc(sizeof(int)*g_iCells); piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) { if (piHashTable!=NULL) free(piHashTable); if (piHashCount!=NULL) free(piHashCount); if (piHashOffsets!=NULL) free(piHashOffsets); if (piHashCount2!=NULL) free(piHashCount2); GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); return; } memset(piHashCount, 0, sizeof(int)*g_iCells); memset(piHashCount2, 0, sizeof(int)*g_iCells); // count amount of elements in each cell unit for (i=0; i<(iNrTrianglesIn*3); i++) { const int index = piTriList_in_and_out[i]; const SVec3 vP = GetPosition(pContext, index); const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); const int iCell = FindGridCell(fMin, fMax, fVal); ++piHashCount[iCell]; } // evaluate start index of each cell. piHashOffsets[0]=0; for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; if (fvMax[c]dx && dy>dz) channel=1; else if (dz>dx) channel=2; fSep = 0.5f*(fvMax[channel]+fvMin[channel]); // stop if all vertices are NaNs if (!isfinite(fSep)) return; // terminate recursion when the separation/average value // is no longer strictly between fMin and fMax values. if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) { // complete the weld for (l=iL_in; l<=iR_in; l++) { int i = pTmpVert[l].index; const int index = piTriList_in_and_out[i]; const SVec3 vP = GetPosition(pContext, index); const SVec3 vN = GetNormal(pContext, index); const SVec3 vT = GetTexCoord(pContext, index); tbool bNotFound = TTRUE; int l2=iL_in, i2rec=-1; while (l20); // at least 2 entries // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] while (iL < iR) { tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) { const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts!=3 && verts!=4) continue; pTriInfos[iDstTriIndex].iOrgFaceNumber = f; pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; if (verts==3) { unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); ++iDstTriIndex; // next } else { { pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; } { // need an order independent way to evaluate // tspace on quads. This is done by splitting // along the shortest diagonal. const int i0 = MakeIndex(f, 0); const int i1 = MakeIndex(f, 1); const int i2 = MakeIndex(f, 2); const int i3 = MakeIndex(f, 3); const SVec3 T0 = GetTexCoord(pContext, i0); const SVec3 T1 = GetTexCoord(pContext, i1); const SVec3 T2 = GetTexCoord(pContext, i2); const SVec3 T3 = GetTexCoord(pContext, i3); const float distSQ_02 = LengthSquared(vsub(T2,T0)); const float distSQ_13 = LengthSquared(vsub(T3,T1)); tbool bQuadDiagIs_02; if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; return res; } static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) { int iF, iI; SVec3 res; float norm[3]; IndexToData(&iF, &iI, index); pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; return res; } static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) { int iF, iI; SVec3 res; float texc[2]; IndexToData(&iF, &iI, index); pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); res.x=texc[0]; res.y=texc[1]; res.z=1.0f; return res; } ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// typedef union { struct { int i0, i1, f; }; int array[3]; } SEdge; static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); // returns the texture area times 2 static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) { const SVec3 t1 = GetTexCoord(pContext, indices[0]); const SVec3 t2 = GetTexCoord(pContext, indices[1]); const SVec3 t3 = GetTexCoord(pContext, indices[2]); const float t21x = t2.x-t1.x; const float t21y = t2.y-t1.y; const float t31x = t3.x-t1.x; const float t31y = t3.y-t1.y; const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; } static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) { int f=0, i=0, t=0; // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. // generate neighbor info list for (f=0; f0 ? ORIENT_PRESERVING : 0); if ( NotZero(fSignedAreaSTx2) ) { const float fAbsArea = fabsf(fSignedAreaSTx2); const float fLenOs = Length(vOs); const float fLenOt = Length(vOt); const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); // evaluate magnitudes prior to normalization of vOs and vOt pTriInfos[f].fMagS = fLenOs / fAbsArea; pTriInfos[f].fMagT = fLenOt / fAbsArea; // if this is a good triangle if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); } } // force otherwise healthy quads to a fixed orientation while (t<(iNrTrianglesIn-1)) { const int iFO_a = pTriInfos[t].iOrgFaceNumber; const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; if (iFO_a==iFO_b) // this is a quad { const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; // bad triangles should already have been removed by // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false if ((bIsDeg_a||bIsDeg_b)==TFALSE) { const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; // if this happens the quad has extremely bad mapping!! if (bOrientA!=bOrientB) { //printf("found quad with bad mapping\n"); tbool bChooseOrientFirstTri = TFALSE; if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) bChooseOrientFirstTri = TTRUE; // force match { const int t0 = bChooseOrientFirstTri ? t : (t+1); const int t1 = bChooseOrientFirstTri ? (t+1) : t; pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit } } } t += 2; } else ++t; } // match up edge pairs { SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); if (pEdges==NULL) BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); else { BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); free(pEdges); } } } ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) { const int iNrMaxGroups = iNrTrianglesIn*3; int iNrActiveGroups = 0; int iOffset = 0, f=0, i=0; (void)iNrMaxGroups; /* quiet warnings in non debug mode */ for (f=0; fiVertexRepresentitive = vert_index; pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; ++iNrActiveGroups; AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; neigh_indexL = pTriInfos[f].FaceNeighbors[i]; neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; if (neigh_indexL>=0) // neighbor { const tbool bAnswer = AssignRecur(piTriListIn, pTriInfos, neigh_indexL, pTriInfos[f].AssignedGroup[i] ); const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; assert(bAnswer || bDiff); (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ } if (neigh_indexR>=0) // neighbor { const tbool bAnswer = AssignRecur(piTriListIn, pTriInfos, neigh_indexR, pTriInfos[f].AssignedGroup[i] ); const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; assert(bAnswer || bDiff); (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ } // update offset iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; // since the groups are disjoint a triangle can never // belong to more than 3 groups. Subsequently something // is completely screwed if this assertion ever hits. assert(iOffset <= iNrMaxGroups); } } } return iNrActiveGroups; } static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) { pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; ++pGroup->iNrFaces; } static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup) { STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; // track down vertex const int iVertRep = pGroup->iVertexRepresentitive; const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; int i=-1; if (pVerts[0]==iVertRep) i=0; else if (pVerts[1]==iVertRep) i=1; else if (pVerts[2]==iVertRep) i=2; assert(i>=0 && i<3); // early out if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) { // first to group with a group-with-anything triangle // determines it's orientation. // This is the only existing order dependency in the code!! if ( pMyTriInfo->AssignedGroup[0] == NULL && pMyTriInfo->AssignedGroup[1] == NULL && pMyTriInfo->AssignedGroup[2] == NULL ) { pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); } } { const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; if (bOrient != pGroup->bOrientPreservering) return TFALSE; } AddTriToGroup(pGroup, iMyTriIndex); pMyTriInfo->AssignedGroup[i] = pGroup; { const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; if (neigh_indexL>=0) AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); if (neigh_indexR>=0) AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); } return TTRUE; } ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, const SMikkTSpaceContext * pContext) { STSpace * pSubGroupTspace = NULL; SSubGroup * pUniSubGroups = NULL; int * pTmpMembers = NULL; int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; for (g=0; giNrFaces; i++) // triangles { const int f = pGroup->pFaceIndices[i]; // triangle number int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; SSubGroup tmp_group; tbool bFound; SVec3 n, vOs, vOt; if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; assert(index>=0 && index<3); iVertIndex = piTriListIn[f*3+index]; assert(iVertIndex==pGroup->iVertexRepresentitive); // is normalized already n = GetNormal(pContext, iVertIndex); // project vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); if ( VNotZero(vOs) ) vOs = Normalize(vOs); if ( VNotZero(vOt) ) vOt = Normalize(vOt); // original face number iOF_1 = pTriInfos[f].iOrgFaceNumber; iMembers = 0; for (j=0; jiNrFaces; j++) { const int t = pGroup->pFaceIndices[j]; // triangle number const int iOF_2 = pTriInfos[t].iOrgFaceNumber; // project SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); { const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; // make sure triangles which belong to the same quad are joined. const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; const float fCosS = vdot(vOs,vOs2); const float fCosT = vdot(vOt,vOt2); assert(f!=t || bSameOrgFace); // sanity check if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) pTmpMembers[iMembers++] = t; } } // sort pTmpMembers tmp_group.iNrFaces = iMembers; tmp_group.pTriMembers = pTmpMembers; if (iMembers>1) { unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? QuickSort(pTmpMembers, 0, iMembers-1, uSeed); } // look for an existing match bFound = TFALSE; l=0; while (liVertexRepresentitive); ++iUniqueSubGroups; } // output tspace { const int iOffs = pTriInfos[f].iTSpacesOffs; const int iVert = pTriInfos[f].vert_num[index]; STSpace * pTS_out = &psTspace[iOffs+iVert]; assert(pTS_out->iCounter<2); assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); if (pTS_out->iCounter==1) { *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); pTS_out->iCounter = 2; // update counter pTS_out->bOrient = pGroup->bOrientPreservering; } else { assert(pTS_out->iCounter==0); *pTS_out = pSubGroupTspace[l]; pTS_out->iCounter = 1; // update counter pTS_out->bOrient = pGroup->bOrientPreservering; } } } // clean up and offset iUniqueTspaces for (s=0; s=0 && i<3); // project index = piTriListIn[3*f+i]; n = GetNormal(pContext, index); vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); if ( VNotZero(vOs) ) vOs = Normalize(vOs); if ( VNotZero(vOt) ) vOt = Normalize(vOt); i2 = piTriListIn[3*f + (i<2?(i+1):0)]; i1 = piTriListIn[3*f + i]; i0 = piTriListIn[3*f + (i>0?(i-1):2)]; p0 = GetPosition(pContext, i0); p1 = GetPosition(pContext, i1); p2 = GetPosition(pContext, i2); v1 = vsub(p0,p1); v2 = vsub(p2,p1); // project v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); // weight contribution by the angle // between the two edge vectors fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); fAngle = (float) acos(fCos); fMagS = pTriInfos[f].fMagS; fMagT = pTriInfos[f].fMagT; res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); res.fMagS+=(fAngle*fMagS); res.fMagT+=(fAngle*fMagT); fAngleSum += fAngle; } } // normalize if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); if (fAngleSum>0) { res.fMagS /= fAngleSum; res.fMagT /= fAngleSum; } return res; } static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) { tbool bStillSame=TTRUE; int i=0; if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; while (iiNrFaces && bStillSame) { bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; if (bStillSame) ++i; } return bStillSame; } static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) { int iL, iR, n, index, iMid, iTmp; // Random unsigned int t=uSeed&31; t=(uSeed<>(32-t)); uSeed=uSeed+t+3; // Random end iL=iLeft; iR=iRight; n = (iR-iL)+1; assert(n>=0); index = (int) (uSeed%n); iMid=pSortBuffer[index + iL]; do { while (pSortBuffer[iL] < iMid) ++iL; while (pSortBuffer[iR] > iMid) --iR; if (iL <= iR) { iTmp = pSortBuffer[iL]; pSortBuffer[iL] = pSortBuffer[iR]; pSortBuffer[iR] = iTmp; ++iL; --iR; } } while (iL <= iR); if (iLeft < iR) QuickSort(pSortBuffer, iLeft, iR, uSeed); if (iL < iRight) QuickSort(pSortBuffer, iL, iRight, uSeed); } ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) { // build array of edges unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? int iEntries=0, iCurStartIndex=-1, f=0, i=0; for (f=0; f pSortBuffer[iRight].array[channel]) { sTmp = pSortBuffer[iLeft]; pSortBuffer[iLeft] = pSortBuffer[iRight]; pSortBuffer[iRight] = sTmp; } return; } // Random t=uSeed&31; t=(uSeed<>(32-t)); uSeed=uSeed+t+3; // Random end iL = iLeft; iR = iRight; n = (iR-iL)+1; assert(n>=0); index = (int) (uSeed%n); iMid=pSortBuffer[index + iL].array[channel]; do { while (pSortBuffer[iL].array[channel] < iMid) ++iL; while (pSortBuffer[iR].array[channel] > iMid) --iR; if (iL <= iR) { sTmp = pSortBuffer[iL]; pSortBuffer[iL] = pSortBuffer[iR]; pSortBuffer[iR] = sTmp; ++iL; --iR; } } while (iL <= iR); if (iLeft < iR) QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); if (iL < iRight) QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); } // resolve ordering and edge number static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) { *edgenum_out = -1; // test if first index is on the edge if (indices[0]==i0_in || indices[0]==i1_in) { // test if second index is on the edge if (indices[1]==i0_in || indices[1]==i1_in) { edgenum_out[0]=0; // first edge i0_out[0]=indices[0]; i1_out[0]=indices[1]; } else { edgenum_out[0]=2; // third edge i0_out[0]=indices[2]; i1_out[0]=indices[0]; } } else { // only second and third index is on the edge edgenum_out[0]=1; // second edge i0_out[0]=indices[1]; i1_out[0]=indices[2]; } } ///////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// Degenerate triangles //////////////////////////////////// static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) { int iNextGoodTriangleSearchIndex=-1; tbool bStillFindingGoodOnes; // locate quads with only one good triangle int t=0; while (t<(iTotTris-1)) { const int iFO_a = pTriInfos[t].iOrgFaceNumber; const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; if (iFO_a==iFO_b) // this is a quad { const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; if ((bIsDeg_a^bIsDeg_b)!=0) { pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; } t += 2; } else ++t; } // reorder list so all degen triangles are moved to the back // without reordering the good triangles iNextGoodTriangleSearchIndex = 1; t=0; bStillFindingGoodOnes = TTRUE; while (t (t+1)); // swap triangle t0 and t1 if (!bJustADegenerate) { int i=0; for (i=0; i<3; i++) { const int index = piTriList_out[t0*3+i]; piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; piTriList_out[t1*3+i] = index; } { const STriInfo tri_info = pTriInfos[t0]; pTriInfos[t0] = pTriInfos[t1]; pTriInfos[t1] = tri_info; } } else bStillFindingGoodOnes = TFALSE; // this is not supposed to happen } if (bStillFindingGoodOnes) ++t; } assert(bStillFindingGoodOnes); // code will still work. assert(iNrTrianglesIn == t); } static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) { int t=0, i=0; // deal with degenerate triangles // punishment for degenerate triangles is O(N^2) for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf * Note that though the tangent spaces at the vertices are generated in an order-independent way, * by this implementation, the interpolated tangent space is still affected by which diagonal is * chosen to split each quad. A sensible solution is to have your tools pipeline always * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. * If these have the same length then compare the diagonals defined by the texture coordinates. * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin * and also quad triangulator plugin. */ typedef int tbool; typedef struct SMikkTSpaceContext SMikkTSpaceContext; typedef struct { // Returns the number of faces (triangles/quads) on the mesh to be processed. int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); // Returns the number of vertices on face number iFace // iFace is a number in the range {0, 1, ..., getNumFaces()-1} int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); // returns the position/normal/texcoord of the referenced face of vertex number iVert. // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); // either (or both) of the two setTSpace callbacks can be set. // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. // This function is used to return the tangent and fSign to the application. // fvTangent is a unit length vector. // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. // bitangent = fSign * cross(vN, tangent); // Note that the results are returned unindexed. It is possible to generate a new index list // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. // DO NOT! use an already existing index list. void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); // This function is used to return tangent space results to the application. // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their // true magnitudes which can be used for relief mapping effects. // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. // However, both are perpendicular to the vertex normal. // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); // bitangent = fSign * cross(vN, tangent); // Note that the results are returned unindexed. It is possible to generate a new index list // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. // DO NOT! use an already existing index list. void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, const tbool bIsOrientationPreserving, const int iFace, const int iVert); } SMikkTSpaceInterface; struct SMikkTSpaceContext { SMikkTSpaceInterface * m_pInterface; // initialized with callback functions void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) }; // these are both thread safe! tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); // To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the // normal map sampler must use the exact inverse of the pixel shader transformation. // The most efficient transformation we can possibly do in the pixel shader is // achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. // pixel shader (fast transform out) // vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); // where vNt is the tangent space normal. The normal map sampler must likewise use the // interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. // sampler does (exact inverse of pixel shader): // float3 row0 = cross(vB, vN); // float3 row1 = cross(vN, vT); // float3 row2 = cross(vT, vB); // float fSign = dot(vT, row0)<0 ? -1 : 1; // vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); // where vNout is the sampled normal in some chosen 3D space. // // Should you choose to reconstruct the bitangent in the pixel shader instead // of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. // Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of // quads as your renderer then problems will occur since the interpolated tangent spaces will differ // eventhough the vertex level tangent spaces match. This can be solved either by triangulating before // sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. // However, this must be used both by the sampler and your tools/rendering pipeline. #ifdef __cplusplus } #endif #endif ================================================ FILE: BlenderMalt/MaltLights.py ================================================ import math import bpy class MaltLight(bpy.types.PropertyGroup): def sync_data(self, context): light = self.id_data #light.shadow_soft_size = self.radius if light.type == 'SPOT': light.cutoff_distance = self.radius light.spot_size = self.spot_angle light.spot_blend = self.spot_blend_angle / self.spot_angle strength : bpy.props.FloatProperty(name='Strength', default=1, options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'}) override_global_settings : bpy.props.BoolProperty(name='Override Global Settings', default=False, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) max_distance : bpy.props.FloatProperty(name='Max Distance', default=100, options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'}) radius : bpy.props.FloatProperty( name='Radius', default=5, update=sync_data, options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'} ) spot_angle : bpy.props.FloatProperty( name='Angle', default=1, subtype='ANGLE', min=0, max=math.pi, update=sync_data, options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'} ) spot_blend_angle : bpy.props.FloatProperty( name='Blend', default=0.1, subtype='ANGLE', min=0, max=math.pi, update=sync_data, options={'LIBRARY_EDITABLE', 'ANIMATABLE'}, override={'LIBRARY_OVERRIDABLE'} ) def draw_ui(self, layout): layout.prop(self.id_data, 'color') layout.prop(self, 'strength') if self.id_data.type == 'SUN': layout.prop(self, 'override_global_settings') if self.override_global_settings: layout.prop(self, 'max_distance') if self.id_data.type != 'SUN': layout.prop(self, 'radius') if self.id_data.type == 'SPOT': layout.prop(self, 'spot_angle') layout.prop(self, 'spot_blend_angle') classes = [ MaltLight ] def register(): for _class in classes: bpy.utils.register_class(_class) bpy.types.Light.malt = bpy.props.PointerProperty(type=MaltLight, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) del bpy.types.Light.malt ================================================ FILE: BlenderMalt/MaltMaterial.py ================================================ import os import bpy from BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform from . MaltProperties import MaltPropertyGroup _MATERIALS = {} class MaltMaterial(bpy.types.PropertyGroup): def update_source(self, context): path = self.get_source_path() if path in _MATERIALS.keys() and _MATERIALS[path]: self.compiler_error = _MATERIALS[path].compiler_error self.parameters.setup(_MATERIALS[path].parameters) else: self.compiler_error = '' self.parameters.setup({}) track_shader_changes() def update_nodes(self, context): self.parameters.parent = self.shader_nodes self.update_source(context) def poll_tree(self, object): return object.bl_idname == 'MaltTree' and (object.graph_type == self.material_type or self.material_type == '') material_type : bpy.props.StringProperty(name='Type', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) shader_source : bpy.props.StringProperty(name="Shader Source", subtype='FILE_PATH', update=update_source, set_transform=malt_path_set_transform, get_transform=malt_path_get_transform, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) shader_nodes : bpy.props.PointerProperty(name="Node Tree", type=bpy.types.NodeTree, update=update_nodes, poll=poll_tree, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) compiler_error : bpy.props.StringProperty(name="Compiler Error", options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) parameters : bpy.props.PointerProperty(type=MaltPropertyGroup, name="Shader Parameters", options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_source_path(self): path = None if self.shader_nodes: if self.shader_nodes.is_active(): path = self.shader_nodes.get_generated_source_path() else: path = bpy.path.abspath(self.shader_source, library=self.id_data.library) if path: return path else: return '' def draw_ui(self, layout, extension, material_parameters): layout.active = self.id_data.library is None #only local data can be edited layout.prop_search(self, 'material_type', bpy.context.scene.world.malt, 'material_types') row = layout.row() row.active = self.shader_nodes is None row.prop(self, 'shader_source') def nodes_add_or_duplicate(): if self.shader_nodes: self.shader_nodes = self.shader_nodes.get_copy() else: self.shader_nodes = bpy.data.node_groups.new(f'{self.id_data.name} Node Tree', 'MaltTree') self.shader_nodes.graph_type = self.material_type self.id_data.update_tag() self.shader_nodes.update_tag() row = layout.row(align=True) row.template_ID(self, "shader_nodes") if self.shader_nodes: row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(nodes_add_or_duplicate, 'Duplicate') from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set( lambda: set_node_tree(bpy.context, self.shader_nodes) ) else: row.operator('wm.malt_callback', text='New', icon='ADD').callback.set(nodes_add_or_duplicate, 'New') source_path = self.get_source_path() if source_path != '' and source_path.endswith(extension) == False: box = layout.box() box.label(text='Wrong shader extension, should be '+extension+'.', icon='ERROR') return if self.compiler_error != '': layout.operator("wm.malt_print_error", icon='ERROR').message = self.compiler_error box = layout.box() lines = self.compiler_error.splitlines() for line in lines: box.label(text=line) material_parameters.draw_ui(layout, filter=extension) self.parameters.draw_ui(layout) class MALT_PT_MaterialSettings(bpy.types.Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "material" bl_label = "Malt Settings" COMPAT_ENGINES = {'MALT'} @classmethod def poll(cls, context): return context.scene.render.engine == 'MALT' and context.object is not None def draw(self, context): layout = self.layout ob = context.object slot = context.material_slot if ob: is_sortable = len(ob.material_slots) > 1 rows = 3 if is_sortable: rows = 5 row = layout.row() row.template_list("MATERIAL_UL_matslots", "", ob, "material_slots", ob, "active_material_index", rows=rows) col = row.column(align=True) col.operator("object.material_slot_add", icon='ADD', text="") col.operator("object.material_slot_remove", icon='REMOVE', text="") col.separator() col.menu("MATERIAL_MT_context_menu", icon='DOWNARROW_HLT', text="") if is_sortable: col.separator() col.operator("object.material_slot_move", icon='TRIA_UP', text="").direction = 'UP' col.operator("object.material_slot_move", icon='TRIA_DOWN', text="").direction = 'DOWN' row = layout.row(align=True) if ob: row.template_ID(ob, "active_material") def add_or_duplicate(): nonlocal ob, slot name = f'{ob.data.name} Material' material = None if slot and slot.material: material = slot.material.copy() else: material = bpy.data.materials.new(name) material.malt.material_type = 'Mesh' if slot: slot.material = material else: ob.data.materials.append(material) ob.data.update_tag() material.update_tag() if slot and slot.material: row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set( add_or_duplicate, 'Duplicate') else: row.operator('wm.malt_callback', text='New', icon='ADD').callback.set( add_or_duplicate, 'New') if slot: icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA' row.prop(slot, "link", icon=icon_link, icon_only=True) if ob.mode == 'EDIT': row = layout.row(align=True) row.operator("object.material_slot_assign", text="Assign") row.operator("object.material_slot_select", text="Select") row.operator("object.material_slot_deselect", text="Deselect") if context.material: context.material.malt.draw_ui(layout, '.mesh.glsl', context.material.malt_parameters) classes = ( MaltMaterial, MALT_PT_MaterialSettings ) def reset_materials(): global _MATERIALS _MATERIALS = {} import time __TIMESTAMP = time.time() INITIALIZED = False def track_shader_changes(force_update=False, async_compilation=True): from BlenderMalt import MaltPipeline if MaltPipeline.is_malt_active() == False and force_update == False: return 1 global INITIALIZED global __TIMESTAMP global _MATERIALS try: start_time = time.time() needs_update = [] for material in bpy.data.materials: path = material.malt.get_source_path() if path not in needs_update: if os.path.exists(path): stats = os.stat(path) if path not in _MATERIALS.keys() or stats.st_mtime > __TIMESTAMP: if path not in _MATERIALS: _MATERIALS[path] = None needs_update.append(path) compiled_materials = {} from . import MaltPipeline if len(needs_update) > 0: compiled_materials = MaltPipeline.get_bridge().compile_materials(needs_update, async_compilation=async_compilation) else: compiled_materials = MaltPipeline.get_bridge().receive_async_compilation_materials() if len(compiled_materials) > 0: for key, value in compiled_materials.items(): _MATERIALS[key] = value for material in bpy.data.materials: path = material.malt.get_source_path() if path in compiled_materials.keys(): material.malt.compiler_error = compiled_materials[path].compiler_error #TODO: Use parent parameters as defaults for materials with nodes material.malt.parameters.setup(compiled_materials[path].parameters) for screen in bpy.data.screens: for area in screen.areas: area.tag_redraw() __TIMESTAMP = start_time INITIALIZED = True except: import traceback traceback.print_exc() return 0.1 #Track again in 0.1 second def register(): for _class in classes: bpy.utils.register_class(_class) bpy.types.Material.malt = bpy.props.PointerProperty(type=MaltMaterial, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.app.timers.register(track_shader_changes, persistent=True) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) del bpy.types.Material.malt bpy.app.timers.unregister(track_shader_changes) ================================================ FILE: BlenderMalt/MaltMeshes.py ================================================ import ctypes import bpy MESHES = {} def get_mesh_name(object): name = object.name_full if len(object.modifiers) == 0 and object.data: name = object.type + '_' + object.data.name_full return name def get_mesh(object): key = get_mesh_name(object) if key not in MESHES.keys() or MESHES[key] is None: MESHES[key] = load_mesh(object, key) return MESHES[key] def load_mesh(object, name): from . import CBlenderMalt m = object.data if object.type != 'MESH' or object.mode == 'EDIT': m = object.to_mesh() if m is None or len(m.polygons) == 0: return None m.calc_loop_triangles() def to_ptr(ptr, ctype): return (ctype * 1).from_address(ptr) def attribute_ptr(name, ctype): ptr = 0 if name in m.attributes: ptr = m.attributes[name].data[0].as_pointer() return to_ptr(ptr, ctype) mesh_ptr = ctypes.c_void_p(m.as_pointer()) loop_count = len(m.loops) loop_tri_count = len(m.loop_triangles) material_count = max(1, len(m.materials)) positions = get_load_buffer('positions', ctypes.c_float, (loop_count * 3)) indices = [] indices_ptrs = (ctypes.c_void_p * material_count)() for i in range(material_count): indices.append(get_load_buffer('indices'+str(i), ctypes.c_uint32, (loop_tri_count * 3))) indices_ptrs[i] = ctypes.cast(indices[i].buffer(), ctypes.c_void_p) indices_lengths = (ctypes.c_uint32 * material_count)() CBlenderMalt.retrieve_mesh_data( attribute_ptr("position", ctypes.c_float), attribute_ptr(".corner_vert", ctypes.c_int), loop_count, to_ptr(m.loop_triangles[0].as_pointer(), ctypes.c_int), to_ptr(m.loop_triangle_polygons[0].as_pointer(), ctypes.c_int), loop_tri_count, attribute_ptr("material_index", ctypes.c_int), positions.buffer(), indices_ptrs, indices_lengths) for i in range(material_count): indices[i]._size = indices_lengths[i] normals = get_load_buffer('normals', ctypes.c_float, (loop_count * 3)) ctypes.memmove(normals.buffer(), m.corner_normals[0].as_pointer(), normals.size_in_bytes()) uvs_list = [] tangents_buffer = None for i, uv_layer in enumerate(m.uv_layers): if i >= 4: break uvs = (ctypes.c_float * (loop_count * 2)).from_address(uv_layer.data[0].as_pointer()) uv_buffer = get_load_buffer('uv'+str(i), ctypes.c_float, loop_count * 2) ctypes.memmove(uv_buffer.buffer(), uvs, uv_buffer.size_in_bytes()) uvs_list.append(uv_buffer) if i == 0 and object.original.data.malt_parameters.bools['precomputed_tangents'].boolean: tangents_buffer = get_load_buffer('tangents'+str(i), ctypes.c_float, (loop_count * 4)) CBlenderMalt.mesh_tangents( to_ptr(m.loop_triangles[0].as_pointer(), ctypes.c_int), loop_tri_count * 3, positions.buffer(), normals.buffer(), uv_buffer.buffer(), tangents_buffer.buffer()) colors_list = [None]*4 if object.type == 'MESH': for i in range(4): override = getattr(object.original.data, f'malt_vertex_color_override_{i}') attribute = m.color_attributes.get(override) #if attribute is None and i < len(m.color_attributes): # attribute = m.color_attributes[i] if attribute and attribute.domain == 'CORNER': type = None if attribute.data_type == 'BYTE_COLOR': type = ctypes.c_uint8 elif attribute.data_type == 'FLOAT_COLOR': type = ctypes.c_float else: continue color = (type * (loop_count * 4)).from_address(attribute.data[0].as_pointer()) color_buffer = get_load_buffer('colors'+str(i), type, loop_count*4) ctypes.memmove(color_buffer.buffer(), color, color_buffer.size_in_bytes()) colors_list[i] = color_buffer mesh_data = { 'positions': positions, 'indices': indices, 'normals': normals, 'uvs': uvs_list, 'tangents': tangents_buffer, 'colors': colors_list, } from . import MaltPipeline MaltPipeline.get_bridge().load_mesh(name, mesh_data) from Bridge.Proxys import MeshProxy return [MeshProxy(name, i) for i in range(material_count)] def get_load_buffer(name, ctype, size): from . import MaltPipeline return MaltPipeline.get_bridge().get_shared_buffer(ctype, size) def unload_mesh(object): MESHES[get_mesh_name(object)] = None def reset_meshes(): global MESHES MESHES = {} def draw_vertex_color_overrides(self, context): if context.scene.render.engine != 'MALT': return mesh = context.object.data self.layout.use_property_split = True self.layout.label(text='Malt Vertex Colors') def draw_color_override(key): value = getattr(mesh, key) layout = self.layout if value in mesh.color_attributes and mesh.color_attributes[value].domain != 'CORNER': layout = self.layout.box() layout.label(text='Only Face Corner attributes are supported', icon='ERROR') layout.prop_search(mesh, key, mesh, 'color_attributes') draw_color_override('malt_vertex_color_override_0') draw_color_override('malt_vertex_color_override_1') draw_color_override('malt_vertex_color_override_2') draw_color_override('malt_vertex_color_override_3') def register(): bpy.types.Mesh.malt_vertex_color_override_0 = bpy.props.StringProperty(name='0', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Mesh.malt_vertex_color_override_1 = bpy.props.StringProperty(name='1', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Mesh.malt_vertex_color_override_2 = bpy.props.StringProperty(name='2', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Mesh.malt_vertex_color_override_3 = bpy.props.StringProperty(name='3', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.DATA_PT_vertex_colors.append(draw_vertex_color_overrides) def unregister(): bpy.types.DATA_PT_vertex_colors.remove(draw_vertex_color_overrides) del bpy.types.Mesh.malt_vertex_color_override_0 del bpy.types.Mesh.malt_vertex_color_override_1 del bpy.types.Mesh.malt_vertex_color_override_2 del bpy.types.Mesh.malt_vertex_color_override_3 ================================================ FILE: BlenderMalt/MaltNodes/MaltCustomPasses.py ================================================ import bpy from BlenderMalt import MaltPipeline class MaltIOParameter(bpy.types.PropertyGroup): def get_parameter_enums(self, context=None): types = ['None'] bridge = MaltPipeline.get_bridge() if bridge and self.graph_type in bridge.graphs: graph = bridge.graphs[self.graph_type] if self.io_type in graph.graph_io.keys(): if self.is_output: types = graph.graph_io[self.io_type].dynamic_output_types else: types = graph.graph_io[self.io_type].dynamic_input_types return [(type, type, type) for type in types] def get_parameter(self): try: return self.get_parameter_enums().index(tuple(self['PARAMETER'])) except: return 0 def set_parameter(self, value): self['PARAMETER'] = self.get_parameter_enums()[value] graph_type : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) io_type : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) is_output : bpy.props.BoolProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) parameter : bpy.props.EnumProperty(items=get_parameter_enums, get=get_parameter, set=set_parameter, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def draw(self, context, layout, owner): layout.label(text='', icon='DOT') layout.prop(self, 'name', text='') layout.prop(self, 'parameter', text='') class MaltCustomIO(bpy.types.PropertyGroup): inputs : bpy.props.CollectionProperty(type=MaltIOParameter, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) inputs_index : bpy.props.IntProperty() outputs : bpy.props.CollectionProperty(type=MaltIOParameter, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) outputs_index : bpy.props.IntProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) class MaltCustomPasses(bpy.types.PropertyGroup): io : bpy.props.CollectionProperty(type=MaltCustomIO, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) class MaltGraphType(bpy.types.PropertyGroup): custom_passes : bpy.props.CollectionProperty(type=MaltCustomPasses, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) def setup_default_passes(graphs, world=None): if world is None: world = bpy.context.scene.world for name, graph in graphs.items(): if graph.graph_type == graph.SCENE_GRAPH: if name not in world.malt_graph_types: world.malt_graph_types.add().name = name graph_type = world.malt_graph_types[name] if 'Default' not in graph_type.custom_passes: graph_type.custom_passes.add().name = 'Default' for custom_pass in graph_type.custom_passes: for name, io in graph.graph_io.items(): is_new = False if name not in custom_pass.io: custom_pass.io.add().name = name is_new = True custom_io = custom_pass.io[name] if is_new: def add_io_parameter(name, type, is_output): parameters = custom_io.outputs if is_output else custom_io.inputs if name not in parameters: parameters.add().name = name parameters[name].graph_type = graph_type.name parameters[name].io_type = io.name parameters[name].is_output = is_output parameters[name].parameter = type for key, type in io.default_dynamic_inputs.items(): add_io_parameter(key, type, False) for key, type in io.default_dynamic_outputs.items(): add_io_parameter(key, type, True) class OT_MaltAddCustomPass(bpy.types.Operator): bl_idname = "wm.malt_add_custom_pass" bl_label = "Malt Add Cusstom Pass" bl_options = {'INTERNAL'} graph_type : bpy.props.StringProperty() name : bpy.props.StringProperty(default='Custom Pass') def draw(self, context): self.layout.prop(self,'name') def execute(self, context): new_pass = context.scene.world.malt_graph_types[self.graph_type].custom_passes.add() new_pass.name = self.name for name in MaltPipeline.get_bridge().graphs[self.graph_type].graph_io.keys(): new_pass.io.add().name = name return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) classes = [ MaltIOParameter, MaltCustomIO, MaltCustomPasses, MaltGraphType, OT_MaltAddCustomPass, ] def register(): for _class in classes: bpy.utils.register_class(_class) bpy.types.World.malt_graph_types = bpy.props.CollectionProperty(type=MaltGraphType, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) def unregister(): del bpy.types.World.malt_graph_types for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/MaltNode.py ================================================ from Malt.PipelineParameters import Parameter, Type import bpy from BlenderMalt.MaltProperties import MaltPropertyGroup COLUMN_TYPES = ['Texture','sampler','Material'] class MaltNode(): malt_parameters : bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) disable_updates : bpy.props.BoolProperty(name="Disable Updates", default=False, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) first_setup : bpy.props.BoolProperty(default=True, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) subscribed : bpy.props.BoolProperty(name="Subscribed", default=False, options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) malt_label : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) #Used on copy() to find the correct node instance temp_id : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) internal_name : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_parameters(self, overrides, resources): parameters = self.id_data.malt_parameters.get_parameters(overrides, resources) result = {} for name, input in self.inputs.items(): if '@' in name or input.active == False: continue key = input.get_source_global_reference() key = key.replace('"','') if key in parameters: result[name] = parameters[key] return result # Blender will trigger update callbacks even before init and update has finished # So we use some wrappers to get a more sane behaviour def _disable_updates_wrapper(self, function): tree = self.id_data initial_tree_updates = tree.disable_updates tree.disable_updates = True self.disable_updates = True try: function() except: import traceback traceback.print_exc() tree.disable_updates = initial_tree_updates self.disable_updates = False def init(self, context): self._disable_updates_wrapper(self.malt_init) def setup(self, context=None): self.setup_implementation() def setup_implementation(self, copy=None): self.temp_id = str(hash(self)) if self.internal_name == '': label = self.malt_label if self.malt_label != '' else self.name self.internal_name = self.id_data.get_unique_node_id(label) self._disable_updates_wrapper(lambda: self.malt_setup(copy=copy)) self.first_setup = False if self.subscribed == False: def callback(dummy=None): self.setup_implementation() bpy.msgbus.subscribe_rna(key=self.path_resolve('name', False), owner=self, args=(None,), notify=callback) self.subscribed = True def update(self): if self.disable_updates: return self._disable_updates_wrapper(self.malt_update) def copy(self, node): if self.id_data.on_copy: return #Find the node from its node tree so we have access to the node id_data for tree in bpy.data.node_groups: if node.name in tree.nodes: if node.temp_id == tree.nodes[node.name].temp_id: node = tree.nodes[node.name] break #We can't copy a node without ID data if node.id_data is None: node = None self.subscribed = False #TODO: Is this needed??? self.internal_name = '' self.setup_implementation(copy=node) def free(self): for input in self.inputs: key = self.get_input_parameter_name(input.name) self.id_data.malt_parameters.remove_property(key) def malt_init(self): pass def malt_setup(self, copy=None): pass def malt_update(self): pass def on_socket_update(self, socket): pass def setup_sockets(self, inputs, outputs, expand_structs=True, show_in_material_panel=False, copy=None): def _expand_structs(sockets): result = {} for name, dic in sockets.items(): result[name] = dic struct_type = self.id_data.get_struct_type(dic['type']) if struct_type: for member in struct_type['members']: result[f"{name}.{member['name']}"] = member return result if expand_structs: inputs = _expand_structs(inputs) outputs = _expand_structs(outputs) def setup(current, new): remove = [] for e in current.keys(): if e not in new: remove.append(current[e]) for e in remove: if e.is_linked == False or self.should_delete_outdated_links(): current.remove(e) else: e.active = False socket_index = 0 for i, (name, dic) in enumerate(new.items()): if '@' in name: continue #Skip overrides type = dic['type'] size = dic['size'] if 'size' in dic else 0 is_new_socket = name not in current if is_new_socket: current.new('MaltSocket', name) current[name].show_in_material_panel = show_in_material_panel if isinstance(type, Parameter): current[name].data_type = type.type_string() current[name].array_size = 0 #TODO else: current[name].data_type = type current[name].array_size = size current[name].active = True current[name].default_initialization = '' try: current[name].ui_label = dic['meta']['label'] except: current[name].ui_label = name try: meta = dic['meta'] if 'default_initialization' in meta: current[name].default_initialization = meta['default_initialization'] else: default = meta['default'] if isinstance(default, str): current[name].default_initialization = default except: pass if is_new_socket: current[name].setup_shape() if current.find(name) != socket_index: current.move(current.find(name), socket_index) socket_index += 1 setup(self.inputs, inputs) setup(self.outputs, outputs) parameters = {} for name, input in inputs.items(): parameter = None type = input['type'] size = input['size'] if 'size' in input else 0 try: subtype = input['meta']['subtype'] except: subtype = None if isinstance(type, Parameter): parameter = type else: default_value = input.get('meta', {}).get('default', None) if size == 0: try: parameter = Parameter.from_glsl_type(type, subtype, default_value) except: parameter = Parameter(type, Type.OTHER) else: parameter = Parameter(type, Type.OTHER) if parameter: for k,v in input.get('meta', {}).items(): if k not in parameter.__dict__.keys(): parameter.__dict__[k] = v label = input.get('meta', {}).get('label', name) node_label = self.draw_label().replace('.', ' ') parameter.label = f'{node_label}.{label}' parameters[self.get_input_parameter_name(name)] = parameter copy_map = None copy_map = {} if copy is None: #Copy from the node parameters (backward compatibility) materials = [m for m in bpy.data.materials if m.malt.shader_nodes is self.id_data] transpiler = self.id_data.get_transpiler() #Rename old material parameters (backward compatibility) for key in self.malt_parameters.get_rna().keys(): new_name = self.get_input_parameter_name(key) if new_name in self.id_data.malt_parameters.get_rna().keys(): continue copy_map[new_name] = key old_name = transpiler.global_reference(transpiler.get_source_name(self.name), key) for material in materials: if old_name in material.malt.parameters.get_rna().keys(): material.malt.parameters.rename_property(old_name, new_name) copy = self.malt_parameters else: for key in inputs.keys(): from_name = copy.get_input_parameter_name(key) to_name = self.get_input_parameter_name(key) copy_map[to_name] = from_name copy = copy.id_data.malt_parameters self.id_data.malt_parameters.setup(parameters, skip_private=False, replace_parameters=False, copy_from=copy, copy_map=copy_map) for input in self.inputs: #Sync old nodes with the new system input.show_in_material_panel_update() if self.first_setup: self.setup_width() def get_input_parameter_name(self, key): name = key postfix = '' if ' @ ' in name: name_postfix = name.split(' @ ') name = name_postfix[0] postfix = ' @ ' + name_postfix[1] if name in self.inputs.keys() and self.inputs[name].active: name = self.inputs[name].get_source_global_reference() name += postfix name = name.replace('"','') return name def should_delete_outdated_links(self): return False def calc_node_width(self, point_size) -> float: import blf blf.size(0, point_size) header_padding = 36 # account for a little space for the arrow icon + extra padding on the side of a label socket_padding = 31 # account for little offset of the text from the node border def adjust_width(width, text, scale=1, padding=0): new_width = blf.dimensions(0, text)[0] * scale + padding result = max(width, new_width) return result max_width = adjust_width(0, self.draw_label(), padding=header_padding) for input in self.inputs.values(): scale = 2 if input.default_initialization or '.' in input.name: scale = 1 max_width = adjust_width(max_width, input.get_ui_label(), scale=scale, padding=socket_padding*scale) for output in self.outputs.values(): max_width = adjust_width(max_width, output.get_ui_label(), padding=socket_padding) return max_width def setup_width(self): point_size = bpy.context.preferences.ui_styles[0].widget.points self.width = self.calc_node_width(point_size) def get_source_name(self): return self.id_data.get_transpiler().get_source_name(self.internal_name) def get_source_code(self, transpiler): if self.id_data.get_source_language() == 'GLSL': return '/*{} not implemented*/'.format(self) elif self.id_data.get_source_language() == 'Python': return '# {} not implemented'.format(self) def get_source_socket_reference(self, socket): if self.id_data.get_source_language() == 'GLSL': return '/*{} not implemented*/'.format(socket.name) elif self.id_data.get_source_language() == 'Python': return '# {} not implemented'.format(socket.name) def sockets_to_global_parameters(self, sockets, transpiler): code = '' for socket in sockets: if socket.active == False: continue if socket.data_type != '' and socket.get_linked() is None and socket.is_struct_member() == False: code += transpiler.global_declaration(socket.data_type, socket.array_size, socket.get_source_global_reference()) return code def get_source_global_parameters(self, transpiler): return self.sockets_to_global_parameters(self.inputs, transpiler) def setup_socket_shapes(self): from itertools import chain for socket in chain(self.inputs.values(), self.outputs.values()): socket.setup_shape() def is_column_type(self, data_type): global COLUMN_TYPES for type in COLUMN_TYPES: if type in data_type: return True return False def draw_socket(self, context, layout, socket, text): draw_parameter = socket.is_output == False and socket.get_linked() is None and socket.default_initialization == '' if socket.is_struct_member() and (socket.get_struct_socket().get_linked() or socket.get_struct_socket().default_initialization != ''): draw_parameter = False if draw_parameter: column = layout.column() def get_layout(): if draw_parameter and self.is_column_type(socket.data_type): return column.column() else: return column.row() parameter_key = socket.get_source_global_reference() parameter_key = parameter_key.replace('"','') self.id_data.malt_parameters.draw_parameter(get_layout(), parameter_key, text, is_node_socket=True) rna = self.id_data.malt_parameters.get_rna() rna_keys = rna.keys() for override in ('Preview', 'Final Render'): key = f'{parameter_key} @ {override}' if key in rna_keys and rna[key].get('active'): self.id_data.malt_parameters.draw_parameter(get_layout(), key, None, is_node_socket=True) else: layout.label(text=text) @classmethod def poll(cls, ntree): return ntree.bl_idname == 'MaltTree' def draw_label(self): print_label = self.malt_label != '' if print_label: for input in self.inputs: if input.show_in_material_panel: print_label = False break if print_label: return self.malt_label else: return self.name.replace('_', ' ') classes = [] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/MaltNodeTree.py ================================================ import os, time from Malt.SourceTranspiler import GLSLTranspiler, PythonTranspiler import bpy from BlenderMalt.MaltProperties import MaltPropertyGroup from BlenderMalt import MaltPipeline from BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform from BlenderMalt.MaltNodes.MaltNode import MaltNode def get_pipeline_graph(context): if context is None or context.space_data is None or context.space_data.edit_tree is None: return None return context.space_data.edit_tree.get_pipeline_graph() class MaltTree(bpy.types.NodeTree): bl_label = "Malt Node Tree" bl_icon = 'NODETREE' on_copy : bpy.props.BoolProperty(name="On Copy", default=False, options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_copy(self): self.on_copy = True copy = self.copy() self.on_copy = False copy.on_copy = False copy.subscribed = False copy.malt_parameters.handle_duplication() copy.reload_nodes() copy.update_ext(force_update=True) return copy type : bpy.props.EnumProperty(name = 'Type', items = [("MALT", "Malt", "Malt")], options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) @classmethod def poll(cls, context): return context.scene.render.engine == 'MALT' def poll_material(self, material): return material.malt.shader_nodes is self def update_graph_type(self, context): graph = self.get_pipeline_graph() if graph and graph.default_graph_path and len(self.nodes) == 0: blend_path, tree_name = graph.default_graph_path blend_path += '.blend' if tree_name not in bpy.data.node_groups: internal_dir = 'NodeTree' bpy.ops.wm.append( filepath=os.path.join(blend_path, internal_dir, tree_name), directory=os.path.join(blend_path, internal_dir), filename=tree_name ) bpy.data.node_groups[tree_name].reload_nodes() name = self.name copy = bpy.data.node_groups[tree_name]#.get_copy() Workaround crash in 3.4 self.user_remap(copy) bpy.data.node_groups.remove(self) copy.name = name graph_type: bpy.props.StringProperty(name='Type', update=update_graph_type, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) #deprecated library_source : bpy.props.StringProperty(name="Local Library", subtype='FILE_PATH', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}, set_transform=malt_path_set_transform, get_transform=malt_path_get_transform) disable_updates : bpy.props.BoolProperty(name="Disable Updates", default=False, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) malt_parameters : bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) subscribed : bpy.props.BoolProperty(name="Subscribed", default=False, options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) links_hash : bpy.props.StringProperty(options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def is_active(self): return self.get_pipeline_graph() is not None def get_source_language(self): return self.get_pipeline_graph().language def get_transpiler(self): if self.get_source_language() == 'GLSL': return GLSLTranspiler elif self.get_source_language() == 'Python': return PythonTranspiler def get_library_path(self): if self.library_source != '': src_path = bpy.path.abspath(self.library_source, library=self.library) if os.path.exists(src_path): return src_path return None #deprecated def get_library(self): library_path = self.get_library_path() if library_path: return get_libraries()[library_path] else: return get_empty_library() def get_full_library(self): #TODO: Cache graph = self.get_pipeline_graph() library = self.get_library() if library: result = get_empty_library() result['functions'].update(graph.functions) result['structs'].update(graph.structs) result['subcategories'].update(graph.subcategories) result['functions'].update(library['functions']) result['structs'].update(library['structs']) return result else: return { 'functions' : graph.functions, 'structs' : graph.structs, 'subcategories' : graph.subcategories, } def get_pipeline_graph(self, graph_type=None): if graph_type is None: graph_type = self.graph_type bridge = MaltPipeline.get_bridge() if bridge and graph_type in bridge.graphs: return bridge.graphs[graph_type] return None def get_unique_node_id(self, base_name): base_name = base_name.lower() # Avoid ALL CAPS uniforms, since they are considered private if 'NODE_NAMES' not in self.keys(): self['NODE_NAMES'] = {} if base_name not in self['NODE_NAMES'].keys(): self['NODE_NAMES'][base_name] = 1 else: self['NODE_NAMES'][base_name] += 1 return base_name + str(self['NODE_NAMES'][base_name]) def get_custom_io(self, io_type): params = [] for node in self.nodes: if node.bl_idname == 'MaltIONode' and node.io_type == io_type: io = 'out' if node.is_output else 'in' for parameter in node.get_custom_parameters(): params.append({ 'name': parameter.name, 'type': 'Texture', #TODO 'size': 0, 'io': io, }) return params def cast(self, from_type, to_type): cast_function = f'{to_type}_from_{from_type}' lib = self.get_full_library() for function in lib['functions'].values(): if function['name'] == cast_function and len(function['parameters']) == 1: #TODO: If more than 1 parameter, check if they have default values? return function['name'] return None def get_struct_type(self, struct_type): lib = self.get_full_library() if struct_type in lib['structs']: return lib['structs'][struct_type] return None def get_generated_source_dir(self): import os, tempfile base_path = tempfile.gettempdir() if bpy.context.blend_data.is_saved: base_path = bpy.path.abspath('//') return os.path.join(base_path,'.malt-autogenerated') def get_generated_source_path(self): import os file_prefix = 'temp' if self.library: file_prefix = bpy.path.basename(self.library.filepath).split('.')[0] elif bpy.context.blend_data.is_saved: file_prefix = bpy.path.basename(bpy.context.blend_data.filepath).split('.')[0] pipeline_graph = self.get_pipeline_graph() if pipeline_graph: return os.path.join(self.get_generated_source_dir(),'{}-{}{}'.format(file_prefix, self.name, pipeline_graph.file_extension)) return None def get_generated_source(self, force_update=False): if force_update == False and self.get('source'): return self['source'] output_nodes = [] linked_nodes = [] pipeline_graph = self.get_pipeline_graph() if pipeline_graph: for node in self.nodes: #TODO: MaltNode.is_result() if node.bl_idname == 'MaltIONode' and node.is_output: output_nodes.append(node) linked_nodes.append(node) def add_node_inputs(node, list, io_type): for input in node.inputs: if input.get_linked(): new_node = input.get_linked().node if new_node.bl_idname == 'MaltIONode' and new_node.io_type != io_type: input.links[0].is_muted = True continue if new_node not in list: add_node_inputs(new_node, list, io_type) list.append(new_node) if new_node not in linked_nodes: linked_nodes.append(new_node) transpiler = self.get_transpiler() def get_source(output): nodes = [] add_node_inputs(output, nodes, output.io_type) code = '' for node in nodes: if hasattr(node, 'get_source_code'): code += node.get_source_code(transpiler) + '\n' code += output.get_source_code(transpiler) return code shader ={} for output in output_nodes: shader[output.io_type] = get_source(output) shader['GLOBAL'] = '' library_path = self.get_library_path() if library_path: shader['GLOBAL'] += '#include "{}"\n'.format(library_path) for node in linked_nodes: if hasattr(node, 'get_source_global_parameters'): shader['GLOBAL'] += node.get_source_global_parameters(transpiler) self['source'] = pipeline_graph.generate_source(shader) return self['source'] def reload_nodes(self): self.disable_updates = True try: for node in self.nodes: if hasattr(node, 'setup'): node.setup() for node in self.nodes: if hasattr(node, 'update'): node.update() except: import traceback traceback.print_exc() self.disable_updates = False def update(self): if self.is_active(): self.update_ext() def update_ext(self, force_track_shader_changes=True, force_update=False): if self.disable_updates: return if self.get_pipeline_graph() is None: return if self.subscribed == False: bpy.msgbus.subscribe_rna(key=self.path_resolve('name', False), owner=self, args=(None,), notify=lambda _ : self.update_ext(force_update=True)) self.subscribed = True links_str = '' for link in self.links: try: b = link.to_socket a = b.get_linked(ignore_muted=False) links_str += str(a) + str(b) except: pass #Reroute Node links_hash = str(hash(links_str)) if force_update == False and links_hash == self.links_hash: return self.links_hash = links_hash self.disable_updates = True try: for link in self.links: try: b = link.to_socket a = b.get_linked(ignore_muted=False) if (a.array_size != b.array_size or (a.data_type != b.data_type and self.cast(a.data_type, b.data_type) is None)): link.is_muted = True else: link.is_muted = False except: pass source = self.get_generated_source(force_update=True) source_dir = self.get_generated_source_dir() source_path = self.get_generated_source_path() import pathlib pathlib.Path(source_dir).mkdir(parents=True, exist_ok=True) with open(source_path,'w') as f: f.write(source) if force_track_shader_changes: from BlenderMalt import MaltMaterial MaltMaterial.track_shader_changes() except: import traceback traceback.print_exc() self.disable_updates = False # Force a depsgraph update. # Otherwise these will be outddated in scene_eval self.update_tag() def setup_node_trees(): graphs = MaltPipeline.get_bridge().graphs for name, graph in graphs.items(): preload_menus(graph.structs, graph.functions, graph) track_library_changes(force_update=True, is_initial_setup=True) for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree' and tree.is_active(): tree.reload_nodes() tree.update_ext(force_track_shader_changes=False, force_update=True) from BlenderMalt import MaltMaterial MaltMaterial.track_shader_changes() #SKIP_SAVE doesn't work def manual_skip_save(): for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree': tree.subscribed = False tree.links_hash = '' for node in tree.nodes: if hasattr(node, 'subscribed'): node.subscribed = False __LIBRARIES = {} def get_libraries(): return __LIBRARIES def get_empty_library(): return { 'structs':{}, 'functions':{}, 'subcategories':{}, 'paths':[], } __TIMESTAMP = time.time() def track_library_changes(force_update=False, is_initial_setup=False): from BlenderMalt import MaltPipeline if MaltPipeline.is_malt_active() == False and force_update == False: return 1 bridge = MaltPipeline.get_bridge() graphs = MaltPipeline.get_bridge().graphs updated_graphs = [] if is_initial_setup == False: for name, graph in graphs.items(): if graph.needs_reload(): updated_graphs.append(name) if len(updated_graphs) > 0: bridge.reload_graphs(updated_graphs) for graph_name in updated_graphs: graph = graphs[graph_name] preload_menus(graph.structs, graph.functions, graphs[graph_name]) global __LIBRARIES global __TIMESTAMP start_time = time.time() #purge unused libraries new_dic = {} for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree' and tree.is_active(): src_path = tree.get_library_path() if src_path: if src_path in __LIBRARIES: new_dic[src_path] = __LIBRARIES[src_path] else: new_dic[src_path] = None __LIBRARIES = new_dic needs_update = set() for path, library in __LIBRARIES.items(): root_dir = os.path.dirname(path) if os.path.exists(path): if library is None: needs_update.add(path) else: for sub_path in library['paths']: sub_path = os.path.join(root_dir, sub_path) if os.path.exists(sub_path): # Don't track individual files granularly since macros can completely change them if os.stat(sub_path).st_mtime > __TIMESTAMP: needs_update.add(path) break if len(needs_update) > 0: results = MaltPipeline.get_bridge().reflect_source_libraries(needs_update) for path, reflection in results.items(): __LIBRARIES[path] = reflection preload_menus(reflection['structs'], reflection['functions']) if is_initial_setup == False and max(len(needs_update), len(updated_graphs)) > 0: for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree' and tree.is_active(): src_path = tree.get_library_path() if tree.graph_type in updated_graphs or (src_path and src_path in needs_update): tree.reload_nodes() tree.update_ext(force_track_shader_changes=False, force_update=True) from BlenderMalt import MaltMaterial MaltMaterial.track_shader_changes() __TIMESTAMP = start_time return 0.1 class NODE_PT_MaltNodeTree(bpy.types.Panel): bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' bl_category = "Malt Nodes" bl_label = "Malt Node Tree UI" @classmethod def poll(cls, context): return context.space_data.tree_type == 'MaltTree' def draw(self, context): layout = self.layout #layout.prop(context.space_data.node_tree, 'generated_source') def preload_menus(structs, functions, graph=None): if graph is None: return from nodeitems_utils import NodeCategory, NodeItem, register_node_categories, unregister_node_categories from collections import OrderedDict # Uses copied code from the module. Manual check for updates required. class MaltNodeItem(NodeItem): def __init__(self, nodetype, category, *, label=None, settings=None, poll=None, draw=None, item_params=None): if settings is None: settings = {} self.nodetype = nodetype self._label = f'{category} - {label}' self.button_label = label self.settings = settings self.poll = poll self.item_params = item_params def draw_default(self, layout, _context): props = layout.operator("node.add_node", text=self.button_label, text_ctxt=self.translation_context) props.type = self.nodetype props.use_transform = True for setting in self.settings.items(): ops = props.settings.add() ops.name = setting[0] ops.value = setting[1] self.draw = staticmethod(draw) if draw else staticmethod(draw_default) class MaltSearchMenuItem(MaltNodeItem): def __init__(self, nodetype, category, *, label=None, settings=None, poll=None): def draw_nothing(self, layout, _context): return super().__init__(nodetype, category, label=label, settings=settings, poll=poll, draw=draw_nothing) category_id = f'BLENDERMALT_{graph.name.upper()}' try: unregister_node_categories(category_id) # you could also check the hidden except: pass #First run categories = { 'Input' : [], 'Parameters' : [], 'Math' : [], 'Vector' : [], 'Color' : [], 'Texturing' : [], 'Shading' : [], 'Filter' : [], 'Other' : [], 'Node Tree' : [], 'Internal' : [], } for name in graph.graph_io: label = name.replace('_',' ') categories['Node Tree'].append(NodeItem('MaltIONode', label=f'{label} Input', settings=OrderedDict({ 'name' : repr(f'{label} Input'), 'is_output' : repr(False), 'io_type' : repr(name), }))) categories['Node Tree'].append(NodeItem('MaltIONode', label=f'{label} Output', settings=OrderedDict({ 'name' : repr(f'{label} Output'), 'is_output' : repr(True), 'io_type' : repr(name), }))) if graph.language == 'GLSL': categories['Other'].append(NodeItem('MaltInlineNode', label='Inline Code', settings={ 'name' : repr('Inline Code') })) categories['Other'].append(NodeItem('MaltArrayIndexNode', label='Array Element', settings={ 'name' : repr('Array Element') })) subcategories = set() def add_to_category(dic, node_type): for k,v in dic.items(): if (is_internal := v['meta'].get('internal')): category = 'Internal' else: category = v['meta'].get('category') if category is None: category = v['file'].replace('\\', '/').replace('/', ' - ').replace('.glsl', '').replace('_',' ') if category not in categories: categories[category] = [] _node_type = node_type label = v['meta'].get('label', v['name']) subcategory = v['meta'].get('subcategory') settings = OrderedDict({ 'name': repr(label), 'malt_label': repr(label) }) if subcategory and not is_internal: _node_type = 'MaltFunctionSubCategoryNode' label = subcategory settings.update({ 'name' : repr(label), 'malt_label': repr(label), 'subcategory': repr(subcategory), 'function_enum': repr(k), }) func_label = v['meta']['label'] def draw_subcategory_item(self: MaltNodeItem, layout, _context): props = layout.operator('node.add_subcategory_node', text=self.button_label) props.settings = repr(self.settings) #add subcategory functions to the search menu but not to the regular menus categories['Node Tree'].append(MaltSearchMenuItem(_node_type, category, label=f'{subcategory}: {func_label}', settings=settings)) if subcategory in subcategories: continue subcategories.add(subcategory) node_item = MaltNodeItem(_node_type, category, label=label, settings=settings, draw=draw_subcategory_item) else: if node_type == 'MaltFunctionNode': settings['function_type'] = repr(k) elif node_type == 'MaltStructNode': settings['struct_type'] = repr(k) node_item = MaltNodeItem(_node_type, category, label=label, settings=settings) categories[category].append(node_item) add_to_category(functions, 'MaltFunctionNode') add_to_category(structs, 'MaltStructNode') def poll(cls, context): tree = context.space_data.edit_tree return tree and tree.bl_idname == 'MaltTree' and tree.graph_type == graph.name def poll_internal(cls, context): preferences = bpy.context.preferences.addons['BlenderMalt'].preferences return poll(cls, context) and preferences.show_internal_nodes category_type = type(category_id, (NodeCategory,), { 'poll': classmethod(poll), }) category_internal_type = type(f'{category_id}_INTERNAL', (category_type,), { 'poll': classmethod(poll_internal), }) from BlenderMalt import _PLUGINS for plugin in _PLUGINS: try: for category, nodeitems in plugin.blendermalt_register_nodeitems(MaltNodeItem).items(): if category not in categories.keys(): categories[category] = [] categories[category].extend(nodeitems) except: import traceback traceback.print_exc() category_list = [] for category_name, node_items in categories.items(): if not len(node_items): continue bl_id = f'{category_id}_{category_name}' bl_id = ''.join(c for c in bl_id if c.isalnum()) if len(bl_id) > 64: bl_id = bl_id[:64] if category_name == 'Internal': category_list.append(category_internal_type(bl_id, category_name, items=node_items)) else: category_list.append(category_type(bl_id, category_name, items=node_items)) register_node_categories(category_id, category_list) def node_header_ui(self, context): node_tree = context.space_data.edit_tree if context.space_data.tree_type != 'MaltTree' or node_tree is None: return def duplicate(): context.space_data.node_tree = node_tree.get_copy() self.layout.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(duplicate, 'Duplicate') def recompile(): node_tree.update_ext(force_update=True) self.layout.operator("wm.malt_callback", text='', icon='FILE_REFRESH').callback.set(recompile, 'Recompile') self.layout.prop_search(node_tree, 'graph_type', context.scene.world.malt, 'graph_types',text='') def get_node_spaces(context): spaces = [] locked_spaces = [] for window in context.window_manager.windows: for area in window.screen.areas: if area.type == 'NODE_EDITOR': for space in area.spaces: if space.type == 'NODE_EDITOR' and space.tree_type == 'MaltTree': if space.pin == False or space.node_tree is None: spaces.append(space) else: locked_spaces.append(space) return spaces, locked_spaces def set_node_tree(context, node_tree, node = None): if context.space_data.type == 'NODE_EDITOR' and context.area.ui_type == 'MaltTree': context.space_data.path.append(node_tree, node = node) else: spaces, locked_spaces = get_node_spaces(context) if len(spaces) > 0: spaces[0].node_tree = node_tree elif len(locked_spaces) > 0: locked_spaces[0].node_tree = node_tree def active_material_update(dummy=None): try: material = bpy.context.object.active_material node_tree = material.malt.shader_nodes except: node_tree = None if node_tree: spaces, locked_spaces = get_node_spaces(bpy.context) for space in spaces: if space.node_tree is None or space.node_tree.graph_type == 'Mesh': space.node_tree = node_tree return @bpy.app.handlers.persistent def depsgraph_update(scene, depsgraph): # Show the active material node tree in the Node Editor from BlenderMalt import MaltPipeline if MaltPipeline.is_malt_active() == False: return scene_updated = False for deps_update in depsgraph.updates: if isinstance(deps_update.id, bpy.types.Scene): scene_updated = True if scene_updated == False: return active_material_update() @bpy.app.handlers.persistent def load_post(dummy=None): #msgbus subscriptions can't be persistent across file loads :( bpy.msgbus.subscribe_rna( key=(bpy.types.Object, "active_material_index"), owner=__msgbus_owner, args=(None,), notify=active_material_update ) classes = [ MaltTree, NODE_PT_MaltNodeTree, ] __msgbus_owner = object() def register(): for _class in classes: bpy.utils.register_class(_class) bpy.types.NODE_HT_header.append(node_header_ui) bpy.app.timers.register(track_library_changes, persistent=True) bpy.app.handlers.depsgraph_update_post.append(depsgraph_update) bpy.app.handlers.load_post.append(load_post) def unregister(): bpy.msgbus.clear_by_owner(__msgbus_owner) bpy.app.handlers.load_post.remove(load_post) bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update) bpy.app.timers.unregister(track_library_changes) bpy.types.NODE_HT_header.remove(node_header_ui) for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/MaltNodeUITools.py ================================================ import bpy import string from typing import Union from bpy.types import NodeTree from bpy.props import PointerProperty from . MaltNodeTree import MaltTree import blf, gpu from gpu_extras.batch import batch_for_shader from mathutils import Vector class NodeTreePreview(bpy.types.PropertyGroup): # Name of the node used as a preview node_name: bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) # Socket name and index are stored to provide fallbacks socket_name: bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) socket_index: bpy.props.IntProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) @property def node_tree(self) -> NodeTree: return self.id_data def is_socket_valid(self, socket: bpy.types.NodeSocket) -> bool: return ( socket.node in self.node_tree.nodes.values() and not socket.is_output ) def get_node(self) -> Union[bpy.types.Node, None]: return self.node_tree.nodes.get(self.node_name, None) def get_socket_ex(self) -> tuple[Union[bpy.types.NodeSocket, None], Union[str, int]]: '''Gets the stored socket. Because all references have to be strings and ints, the validity of the references have to be checked first.''' if (node := self.get_node()) == None: return None, '' if self.socket_name in node.inputs.keys(): return node.inputs[self.socket_name], self.socket_name elif len(node.inputs) > self.socket_index: return node.inputs[self.socket_index], self.socket_index else: return None, '' def get_socket(self) -> Union[bpy.types.NodeSocket, None]: return self.get_socket_ex()[0] @staticmethod def _get_visible_node_sockets(node: bpy.types.Node, get_outputs: bool = False) -> list[bpy.types.NodeSocket]: front = node.outputs if get_outputs else node.inputs return [s for s in front.values() if s.enabled and (not s.hide or len(s.links))] def set_socket(self, socket: bpy.types.NodeSocket) -> bool: '''Store the given socket as the preview.''' if not self.is_socket_valid(socket): return False self.node_name = socket.node.name self.socket_name = socket.name self.socket_index = next(i for i, s in enumerate(self._get_visible_node_sockets(socket.node, get_outputs=False)) if s == socket) return True def reset_socket_from_node(self, node: bpy.types.Node) -> bool: '''Store a socket as the preview of the given node. If a socket of that node is already the preview, cycle through the sockets, otherwise use the first socket.''' node_inputs = self._get_visible_node_sockets(node, get_outputs=False) if (input_count := len(node_inputs)) < 1: return False old_socket = self.get_socket() if not self.get_node() == node or old_socket == None: return self.set_socket(node_inputs[0]) else: old_index = next(i for i, s in enumerate(node_inputs) if s == old_socket) return self.set_socket(node_inputs[(old_index + 1) % input_count]) def connect_socket(self, socket: bpy.types.NodeSocket) -> bool: preview_socket = self.get_socket() if not preview_socket or preview_socket.node == socket.node or not socket.is_output: return False self.node_tree.links.new(socket, preview_socket) def reconnect_node(self, node: bpy.types.Node) -> bool: '''Connect a node to the preview socket by cycling through its sockets.''' preview_socket = self.get_socket() node_outputs = self._get_visible_node_sockets(node, get_outputs=True) if not preview_socket or preview_socket.node == node or len(node_outputs) < 1: return False try: # Node is already connected to preview socket. Connect next socket previous_socket = next( link.from_socket for link in preview_socket.links if link.from_socket.node == node and link.from_socket in node_outputs) previous_index = next(i for i, s in enumerate(node_outputs) if s == previous_socket) new_socket = node_outputs[(previous_index + 1) % len(node_outputs)] self.connect_socket(new_socket) except StopIteration: # When the node is not already connected, use the first socket self.connect_socket(node_outputs[0]) return True def is_malt_tree_context(context: bpy.types.Context) -> bool: return (context.area.ui_type == 'MaltTree' and context.space_data.type == 'NODE_EDITOR' and context.space_data.edit_tree is not None) def is_malt_node_context(context: bpy.types.Context) -> bool: return is_malt_tree_context(context) and context.active_node is not None class OT_MaltEditNodeTree(bpy.types.Operator): bl_idname = 'wm.malt_edit_node_tree' bl_label = 'Edit Node Tree' bl_description = 'Edit the graph of the active group node' @classmethod def poll( cls, context ): return is_malt_tree_context(context) def execute( self, context ): node = context.active_node space_path = context.space_data.path node_tree = None if node and hasattr(node, 'get_pass_node_tree'): node_tree = node.get_pass_node_tree() if node_tree: space_path.append(node_tree, node = node) else: space_path.pop() return {'FINISHED'} class OT_MaltSetTreePreview(bpy.types.Operator): bl_idname = 'wm.malt_set_tree_preview' bl_label = 'Set Tree Preview' bl_description = 'Set an input socket of the active node as the tree preview socket' bl_options = {'UNDO'} @classmethod def poll(cls, context): return is_malt_node_context(context) def execute(self, context): node_tree: NodeTree = context.space_data.edit_tree node = context.active_node if not node: return {'CANCELLED'} node_tree.tree_preview.reset_socket_from_node(node) context.area.tag_redraw() return {'FINISHED'} class OT_MaltConnectTreePreview(bpy.types.Operator): bl_idname = 'wm.malt_connect_tree_preview' bl_label = 'Connect To Tree Preview' bl_description = 'Connect an output of the active node to the node tree preview socket' bl_options = {'UNDO'} @classmethod def poll(cls, context): return is_malt_node_context(context) def execute(self, context): node_tree: NodeTree = context.space_data.edit_tree node = context.active_node if not node: return {'CANCELLED'} tp: NodeTreePreview = node_tree.tree_preview tp.reconnect_node(node) context.area.tag_redraw() return {'FINISHED'} class OT_MaltCycleSubCategories(bpy.types.Operator): bl_idname = 'wm.malt_cycle_sub_categories' bl_label = 'Cycle Subcategories' bl_description = 'Cycle the subcategories of the active subcategory node' bl_options = {'UNDO'} @classmethod def poll(cls, context): return is_malt_node_context(context) and context.active_node.bl_idname == 'MaltFunctionSubCategoryNode' def invoke(self, context: bpy.types.Context, event: bpy.types.Event): self.node_tree: MaltTree = context.space_data.edit_tree self.node_tree.disable_updates = True self.node = context.active_node self.function_enums:list[tuple[str,str,str]] = self.node.get_function_enums(context) self.init_function_enum = self.node.function_enum self.register_interface(True) wm = context.window_manager wm.modal_handler_add(self) return{'RUNNING_MODAL'} def register_interface(self, register: bool) -> None: space = bpy.types.SpaceNodeEditor if register: self.reset_ui_lists() self.draw_handler = space.draw_handler_add(self.draw_modal_interface, (self,), 'WINDOW', 'POST_PIXEL') elif self.draw_handler: space.draw_handler_remove(self.draw_handler, 'WINDOW') del self.draw_handler def reset_ui_lists(self): '''These lists are being read by the UI callback.''' self.prev_enums: list[tuple[str, str, str]] = [] self.next_enums: list[tuple[str, str, str]] = [] def cycle_function_enums(self, letter: str, cycle_forward: bool) -> None: letter = letter.lower() enum_subset = [enum for enum in self.function_enums if enum[1].lower().startswith(letter)] if not len(enum_subset): return #do nothing if there are no possible function_enums with the given letter self.reset_ui_lists() new_index = 0 if cycle_forward else len(enum_subset) - 1 new_function_enum = enum_subset[new_index][0] if self.node.function_enum in (enum[0] for enum in enum_subset): old_index = next(i for i, enum in enumerate(enum_subset) if enum[0] == self.node.function_enum) offset = 1 if cycle_forward else -1 new_index = (old_index + offset) % len(enum_subset) new_function_enum = enum_subset[new_index][0] self.prev_enums = enum_subset[:new_index] self.next_enums = enum_subset[new_index + 1:] self.node.function_enum = new_function_enum self.node.setup_width() def modal(self, context: bpy.types.Context, event: bpy.types.Event): context.area.tag_redraw() if event.type in ['LEFTMOUSE', 'RET', 'SPACE']: return self.execute(context) if event.type in ['ESC', 'RIGHTMOUSE'] and event.value == 'PRESS': return self.cancel(context) if event.type in string.ascii_uppercase and event.value == 'PRESS': self.cycle_function_enums(event.type, not event.shift) return{'RUNNING_MODAL'} return{'PASS_THROUGH'} def execute(self, context: bpy.types.Context): self.node_tree.disable_updates = False if self.node.function_enum != self.init_function_enum: self.node_tree.update_ext(force_update=True) self.register_interface(False) context.area.tag_redraw() return{'FINISHED'} def cancel(self, context): self.node.function_enum = self.init_function_enum self.node_tree.disable_updates = False self.register_interface(False) context.area.tag_redraw() return{'CANCELLED'} @staticmethod def draw_modal_interface(operator: 'OT_MaltCycleSubCategories') -> None: context = bpy.context node: bpy.types.Node = operator.node font_id = 0 if context.space_data.path[-1].node_tree != node.id_data: return prefs = context.preferences label_style = prefs.ui_styles[0].widget zoom = MaltNodeDrawCallbacks.get_view_zoom(context) dpifac = MaltNodeDrawCallbacks.get_dpifac(context) to_region_loc = MaltNodeDrawCallbacks.real_region_loc prev_enums = operator.prev_enums next_enums = operator.next_enums text_spacing = 15.0 * zoom has_display_items = any(len(x) for x in (prev_enums, next_enums)) rect_size = 70 if has_display_items else 40 rect_size *= zoom top_left = to_region_loc(Vector(node.location), context) bottom_right = to_region_loc(Vector(node.location) + Vector(node.dimensions) * Vector((1/dpifac, - 1/dpifac)), context) m_color = Vector((0.0, 0.0, 0.0, 0.8)) t_color = Vector((0.0, 0.0, 0.0, 0.0)) vertices = ( top_left + Vector((0, rect_size)), (bottom_right.x, top_left.y + rect_size), top_left, (bottom_right.x, top_left.y), (top_left.x, bottom_right.y), bottom_right, (top_left.x, bottom_right.y - rect_size), bottom_right + Vector((0, - rect_size)) ) vertex_colors = ( t_color, t_color, m_color, m_color, m_color, m_color, t_color, t_color ) indices = ( (0,1,2), (2,1,3), (4,5,6), (6,5,7), ) shader = gpu.shader.from_builtin('SMOOTH_COLOR') batch = batch_for_shader(shader, 'TRIS', {'pos': vertices, 'color': vertex_colors}, indices=indices) gpu.state.blend_set('ALPHA') batch.draw(shader) gpu.state.blend_set('NONE') blf.size(font_id, label_style.points * zoom) blf.color(font_id, 1,1,1,1) for i, e in enumerate(reversed(prev_enums)): loc = top_left + Vector((text_spacing * 0.5, i * text_spacing + text_spacing * 0.5)) blf.position(font_id, *loc, 0) blf.draw(font_id, e[1]) for i, e in enumerate(next_enums): loc = Vector((top_left.x, bottom_right.y)) + Vector((text_spacing * 0.5, -((i + 1) * text_spacing))) blf.position(font_id, *loc, 0) blf.draw(font_id, e[1]) class NODE_OT_add_malt_subcategory_node(bpy.types.Operator): bl_idname = 'node.add_subcategory_node' bl_label = 'Add Malt Subcategory Node' bl_description = 'Add a new Malt Subcategory node. Automatically invoke subcategory cycling' bl_options = {'UNDO'} nodetype: bpy.props.StringProperty(default='MaltFunctionSubCategoryNode') settings: bpy.props.StringProperty(default='dict()') @classmethod def poll(cls, context: bpy.types.Context): return is_malt_tree_context(context) def execute(self, context: bpy.types.Context): for n in context.space_data.edit_tree.nodes: n.select = False bpy.ops.node.add_node('INVOKE_DEFAULT', type=self.nodetype, use_transform=True) node: bpy.types.Node = context.active_node from collections import OrderedDict #maybe there is a better solution for this but without an exception will be thrown because the class is not imported for k, v in eval(self.settings).items(): try: setattr(node, k, eval(v)) except: print(f'Attribute {repr(k)} could not be set on {repr(node)}') if context.preferences.addons['BlenderMalt'].preferences.use_subfunction_cycling: bpy.ops.wm.malt_cycle_sub_categories('INVOKE_DEFAULT') return{'FINISHED'} classes = [ NodeTreePreview, OT_MaltEditNodeTree, OT_MaltSetTreePreview, OT_MaltConnectTreePreview, OT_MaltCycleSubCategories, NODE_OT_add_malt_subcategory_node, ] class MaltNodeDrawCallbacks: @staticmethod def get_dpifac(context: bpy.types.Context): p = context.preferences return p.system.dpi * p.system.pixel_size / 72 @staticmethod def real_region_loc(view_location: Vector|tuple|list, context: bpy.types.Context) -> Vector: return Vector( context.region.view2d.view_to_region( *[x * MaltNodeDrawCallbacks.get_dpifac(context) for x in view_location], clip=False ) ) @staticmethod def get_view_zoom(context: bpy.types.Context) -> float: get_loc = MaltNodeDrawCallbacks.real_region_loc return (get_loc((100, 0), context).x - get_loc((0, 0), context).x) / 100.0 @staticmethod def context_path_ui_callback(): import blf font_id = 0 context = bpy.context space = context.space_data if not is_malt_tree_context(context): return if not space.overlay.show_context_path: return path = space.path text = ' > '.join(x.node_tree.name for x in path) preferences = context.preferences ui_scale = preferences.view.ui_scale size = preferences.ui_styles[0].widget.points * ui_scale color = preferences.themes[0].node_editor.space.text dpi = preferences.system.dpi blf.size(font_id, size * (dpi / 72.0)) blf.position(font_id, 10, 10, 0) blf.color(font_id, *color, 1) blf.draw(font_id, text) @staticmethod def tree_preview_ui_callback(): font_id = 0 context = bpy.context space:bpy.types.SpaceNodeEditor = context.space_data if not is_malt_tree_context(context): return node_tree: MaltTree = space.edit_tree tp:NodeTreePreview = node_tree.tree_preview socket, identifier = tp.get_socket_ex() if socket == None: return preferences = context.preferences label_style = preferences.ui_styles[0].widget size = label_style.points #calculate the zoom of the view by taking the difference of transformed points in the x-axis zoom = MaltNodeDrawCallbacks.get_view_zoom(context) node = socket.node view_loc = Vector(node.location) + Vector((5,5)) region_loc = MaltNodeDrawCallbacks.real_region_loc(view_loc, context) text = f'Preview: {repr(identifier)}' #mix the socket color with white to get a result thats not too dark color = (Vector(socket.draw_color(context, node)) + Vector((1,1,1,1))) * 0.5 def draw_text(text: str, size: float, loc: tuple[float, float], color: tuple[float, float, float, float]): blf.size(font_id, size) blf.position(font_id, *loc, 0) blf.color(font_id, *color) blf.draw(font_id, text) draw_text(text, size * zoom, tuple(region_loc + Vector((0,-1))), (0,0,0,1)) draw_text(text, size * zoom, region_loc, color) keymaps = [] def register_node_tree_shortcuts(): wm = bpy.context.window_manager kc = wm.keyconfigs.addon def add_shortcut(keymaps: list, operator: bpy.types.Operator, *, type: str, value: str, **modifiers): km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR') kmi = km.keymap_items.new(operator.bl_idname, type=type, value=value, **modifiers) keymaps.append((km, kmi)) if kc: add_shortcut(keymaps, OT_MaltEditNodeTree, type='TAB', value='PRESS') add_shortcut(keymaps, OT_MaltSetTreePreview, type='LEFTMOUSE', value='PRESS', shift=True, alt=True) add_shortcut(keymaps, OT_MaltConnectTreePreview, type='LEFTMOUSE', value='PRESS', shift=True, ctrl=True) def register(): for _class in classes: bpy.utils.register_class(_class) global CONTEXT_PATH_DRAW_HANDLER, TREE_PREVIEW_DRAW_HANDLER CONTEXT_PATH_DRAW_HANDLER = bpy.types.SpaceNodeEditor.draw_handler_add(MaltNodeDrawCallbacks.context_path_ui_callback, (), 'WINDOW', 'POST_PIXEL') TREE_PREVIEW_DRAW_HANDLER = bpy.types.SpaceNodeEditor.draw_handler_add(MaltNodeDrawCallbacks.tree_preview_ui_callback, (), 'WINDOW', 'POST_PIXEL') register_node_tree_shortcuts() NodeTree.tree_preview = PointerProperty(type=NodeTreePreview, name='Node Tree Preview', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def unregister(): del NodeTree.tree_preview for km, kmi in keymaps: km.keymap_items.remove(kmi) keymaps.clear() global CONTEXT_PATH_DRAW_HANDLER, TREE_PREVIEW_DRAW_HANDLER bpy.types.SpaceNodeEditor.draw_handler_remove(CONTEXT_PATH_DRAW_HANDLER, 'WINDOW') bpy.types.SpaceNodeEditor.draw_handler_remove(TREE_PREVIEW_DRAW_HANDLER, 'WINDOW') for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/MaltSocket.py ================================================ import bpy __TYPE_COLORS = { 'bool': (0.8, 0.65, 0.84, 1.0), 'Bool': (0.8, 0.65, 0.84, 1.0), 'float': (0.63, 0.63, 0.63, 1.0), 'Float': (0.63, 0.63, 0.63, 1.0), 'int': (0.33, 0.55, 0.36, 1.0), 'Int': (0.33, 0.55, 0.36, 1.0), 'vec2': (0.39, 0.74, 0.78, 1.0), 'vec3': (0.39, 0.39, 0.78, 1.0), 'vec4': (0.60, 0.39, 0.78, 1.0), 'mat4': (0.78, 0.39, 0.58, 1.0), 'sampler1D': (0.78, 0.78, 0.46, 1.0), 'sampler2D': (0.78, 0.64, 0.16, 1.0), 'Texture': (0.78, 0.64, 0.16, 1.0), 'Scene': (1.0, 1.0, 1.0, 1.0), } def get_type_color(type): if type not in __TYPE_COLORS: import random, hashlib seed = hashlib.sha1(type.encode('ascii')).digest() rand = random.Random(seed) __TYPE_COLORS[type] = (rand.random(),rand.random(),rand.random(),1.0) return __TYPE_COLORS[type] class MaltSocket(bpy.types.NodeSocket): bl_label = "Malt Node Socket" def on_type_update(self, context): self.node.on_socket_update(self) data_type: bpy.props.StringProperty(update=on_type_update, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) array_size: bpy.props.IntProperty(default=0, update=on_type_update, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) default_initialization: bpy.props.StringProperty(default='', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def show_in_material_panel_update(self, context=None): key = self.node.get_input_parameter_name(self.name) show_in_children = self.node.id_data.malt_parameters.show_in_children if key in show_in_children.keys(): show_in_children[key].boolean = self.show_in_material_panel show_in_material_panel: bpy.props.BoolProperty(default=True, update=show_in_material_panel_update, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) active: bpy.props.BoolProperty(default=True, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) ui_label: bpy.props.StringProperty() def is_instantiable_type(self): return self.data_type.startswith('sampler') == False def get_source_reference(self, target_type=None): assert(self.active) if not self.is_instantiable_type() and not self.is_output and self.get_linked() is not None: self.get_linked().get_source_reference() else: reference = self.node.get_source_socket_reference(self) if target_type and target_type != self.data_type: cast_function = self.node.id_data.cast(self.data_type, target_type) return f'{cast_function}({reference})' return reference def get_source_global_reference(self): assert(self.active) transpiler = self.id_data.get_transpiler() result = transpiler.global_reference(self.node.get_source_name(), self.name) if len(result) > 63: #Blender dictionary keys are limited to 63 characters import xxhash result = result[:59] + xxhash.xxh32_hexdigest(result)[:4] return result def is_struct_member(self): return '.' in self.name def get_struct_socket(self): if self.is_struct_member(): struct_socket_name = self.name.split('.')[0] if self.is_output: return self.node.outputs[struct_socket_name] else: return self.node.inputs[struct_socket_name] return None def get_source_initialization(self): assert(self.active) if self.get_linked(): return self.get_linked().get_source_reference(self.data_type) elif self.default_initialization != '': return self.default_initialization elif self.is_struct_member() and (self.get_struct_socket().get_linked() or self.get_struct_socket().default_initialization != ''): return None else: return self.get_source_global_reference() def get_linked(self, ignore_muted=True): def get_linked_internal(socket): if socket.is_linked == False and ignore_muted: return None else: try: link = socket.links[0] except: return None #socket.links can be empty even if is_linked is true!?!?! if ignore_muted and link.is_muted: return None linked = link.to_socket if socket.is_output else link.from_socket if isinstance(linked.node, bpy.types.NodeReroute): sockets = linked.node.inputs if linked.is_output else linked.node.outputs if len(sockets) == 0: return None return get_linked_internal(sockets[0]) else: return linked if linked.active else None return get_linked_internal(self) def get_ui_label(self, print_type=True): name = self.ui_label if print_type == False: return name type = self.data_type if self.array_size > 0: type += f'[{self.array_size}]' if self.is_output: return f'({type}) : {name}' else: return f'{name} : ({type})' def draw(self, context, layout, node, text): if self.active == False: layout.active = False layout.label(text=text) elif context.region.type != 'UI' or self.get_source_global_reference() == self.get_source_initialization(): preferences = bpy.context.preferences.addons['BlenderMalt'].preferences text = self.get_ui_label(preferences.show_socket_types) node.draw_socket(context, layout, self, text) if context.region.type == 'UI': icon = 'HIDE_OFF' if self.show_in_material_panel else 'HIDE_ON' layout.prop(self, 'show_in_material_panel', text='', icon=icon) def setup_shape(self): from Malt.PipelineParameters import Parameter base_type = True try: Parameter.from_glsl_type(self.data_type) except: base_type = False array_type = self.array_size > 0 if base_type: if array_type: self.display_shape = 'CIRCLE_DOT' else: self.display_shape = 'CIRCLE' else: if array_type: self.display_shape = 'SQUARE_DOT' else: self.display_shape = 'SQUARE' def draw_color(self, context, node): color = get_type_color(self.data_type) if self.active == False: color = list(color) color[3] = 0.25 return color classes = [ MaltSocket ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/Nodes/MaltArrayIndexNode.py ================================================ from Malt.PipelineParameters import Parameter, Type import bpy from BlenderMalt.MaltNodes.MaltNode import MaltNode class MaltArrayIndexNode(bpy.types.Node, MaltNode): bl_label = "Array Index Node" def malt_init(self): self.setup() def malt_setup(self, copy=None): self.setup_sockets({ 'array' : {'type': '', 'size': 1}, 'index' : {'type': Parameter(0, Type.INT) }}, {'element' : {'type': ''} }, copy=copy) def malt_update(self): inputs = { 'array' : {'type': '', 'size': 1}, 'index' : {'type': 'int', 'meta':{'value':'0'} } } outputs = { 'element' : {'type': ''} } linked = self.inputs['array'].get_linked() if linked and linked.array_size > 0: inputs['array']['type'] = linked.data_type inputs['array']['size'] = linked.array_size outputs['element']['type'] = linked.data_type self.setup_sockets(inputs, outputs) def get_source_socket_reference(self, socket): return '{}_0_{}'.format(self.get_source_name(), socket.name) def get_source_code(self, transpiler): array = self.inputs['array'] index = self.inputs['index'] element = self.outputs['element'] element_reference = index.get_source_global_reference() if index.get_linked(): element_reference = index.get_linked().get_source_reference() initialization = '{}[{}]'.format(array.get_linked().get_source_reference(), element_reference) return transpiler.declaration(element.data_type, element.array_size, element.get_source_reference(), initialization) classes = [ MaltArrayIndexNode, ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py ================================================ from Malt.PipelineParameters import Type, Parameter, MaterialParameter, GraphParameter import bpy from BlenderMalt.MaltNodes.MaltNode import MaltNode class MaltFunctionNodeBase(MaltNode): def malt_setup(self, copy=None): pass_type = self.get_pass_type() if pass_type != '': self.pass_graph_type, self.pass_graph_io_type = pass_type.split('.') function = self.get_function(skip_overrides=False, find_replacement=True) inputs = {} outputs = {} if function['type'] != 'void': outputs['result'] = {'type': function['type']} #TODO: Array return type for parameter in function['parameters']: if parameter['io'] in ['out','inout']: outputs[parameter['name']] = parameter if parameter['io'] in ['','in','inout']: inputs[parameter['name']] = parameter show_in_material_panel = function['meta'].get('category', '') == 'Parameters' self.setup_sockets(inputs, outputs, show_in_material_panel=show_in_material_panel, copy=copy) if self.pass_graph_type != '': graph = self.id_data.get_pipeline_graph(self.pass_graph_type) if graph.graph_type == graph.GLOBAL_GRAPH: if graph.language == 'Python': self.malt_parameters.setup( {'PASS_GRAPH': GraphParameter(None, graph.name)}, replace_parameters=False, skip_private=False ) else: self.malt_parameters.setup( {'PASS_MATERIAL': MaterialParameter(None, graph.file_extension, self.pass_graph_type)}, replace_parameters=False, skip_private=False ) function_type : bpy.props.StringProperty(update=MaltNode.setup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) pass_graph_type: bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) pass_graph_io_type: bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_parameters(self, overrides, resources): parameters = super().get_parameters(overrides, resources) parameters['CUSTOM_IO'] = self.get_custom_io() if 'PASS_GRAPH' in self.malt_parameters.graphs.keys(): try: parameters['PASS_GRAPH'] = self.malt_parameters.get_parameter('PASS_GRAPH', overrides, resources) except: pass if 'PASS_MATERIAL' in self.malt_parameters.materials.keys(): try: parameters['PASS_MATERIAL'] = self.malt_parameters.get_parameter('PASS_MATERIAL', overrides, resources) except: pass return parameters def find_replacement_function(self): print(f'Try to find replacement function for "{self.name}"') print(f'Current function is "{self.function_type}"') library = self.id_data.get_full_library()['functions'] for key, function in library.items(): try: for replace in eval(function['meta']['replace']).split(','): if replace in self.function_type: self.function_type = key return function except: pass parameters = set() from itertools import chain for socket in chain(self.inputs.keys(), self.outputs.keys()): parameters.add(socket) key, function = None, None matching_parameters = 0 total_parameters = 0 for _key, _function in library.items(): if _function['name'] in self.function_type: matching_count = 0 for parameter in _function['parameters']: if parameter['name'] in parameters: matching_count += 1 if (key is None or matching_count > matching_parameters or (matching_count == matching_parameters and len(_function['parameters']) < total_parameters)): total_parameters = len(_function['parameters']) key = _key function = _function if key: print(f'Found replacement function: "{key}"') self.function_type = key return function else: self.select = True def get_function(self, skip_overrides=True, find_replacement=False): graph = self.id_data.get_pipeline_graph() function = None if self.function_type in graph.functions: function = graph.functions[self.function_type] elif self.function_type in self.id_data.get_library()['functions']: function = self.id_data.get_library()['functions'][self.function_type] elif find_replacement: function = self.find_replacement_function() if function: from copy import deepcopy function = deepcopy(function) function['parameters'] += self.get_custom_io() if skip_overrides: function['parameters'] = [p for p in function['parameters'] if '@' not in p['name']] return function def get_pass_type(self): graph = self.id_data.get_pipeline_graph() if graph.language == 'Python': pass_type = graph.functions[self.function_type]['pass_type'] if pass_type: return pass_type return '' def get_pass_node_tree(self): if self.pass_graph_type != '': graph = self.id_data.get_pipeline_graph(self.pass_graph_type) if graph.graph_type == graph.GLOBAL_GRAPH: if graph.language == 'Python': return self.malt_parameters.graphs['PASS_GRAPH'].graph else: material = self.malt_parameters.materials['PASS_MATERIAL'].material if material: return material.malt.shader_nodes def get_custom_io(self): if self.pass_graph_type != '': graph = self.id_data.get_pipeline_graph(self.pass_graph_type) if graph.graph_type == graph.GLOBAL_GRAPH: tree = None if graph.language == 'Python': if 'PASS_GRAPH' in self.malt_parameters.graphs.keys(): tree = self.malt_parameters.graphs['PASS_GRAPH'].graph else: if 'PASS_MATERIAL' in self.malt_parameters.materials.keys(): material = self.malt_parameters.materials['PASS_MATERIAL'].material if material: tree = material.malt.shader_nodes if tree: return tree.get_custom_io(self.pass_graph_io_type) else: world = bpy.context.scene.world if world: custom_io = world.malt_graph_types[self.pass_graph_type].custom_passes['Default'].io[self.pass_graph_io_type] result = [] for parameter in custom_io.inputs: result.append({ 'name': parameter.name, 'type': 'Texture', #TODO 'subtype': parameter.parameter, 'size': 0, 'io': 'in', }) for parameter in custom_io.outputs: result.append({ 'name': parameter.name, 'type': 'Texture', #TODO 'subtype': parameter.parameter, 'size': 0, 'io': 'out', }) return result return [] def get_source_socket_reference(self, socket): transpiler = self.id_data.get_transpiler() if transpiler.is_instantiable_type(socket.data_type): return transpiler.parameter_reference(self.get_source_name(), socket.name, 'out' if socket.is_output else 'in') else: source = self.get_source_code(transpiler) return source.splitlines()[-1].split('=')[-1].split(';')[0] def get_source_code(self, transpiler): function = self.get_function() source_name = self.get_source_name() parameters = [] post_parameter_initialization = '' for input in self.inputs: if input.active and input.is_struct_member(): initialization = input.get_source_initialization() if initialization: post_parameter_initialization += transpiler.asignment(input.get_source_reference(), initialization) for parameter in function['parameters']: initialization = None if parameter['io'] in ['','in','inout']: socket = self.inputs[parameter['name']] initialization = socket.get_source_initialization() parameters.append(initialization) if self.pass_graph_type != '': def add_implicit_parameter(name): parameter = transpiler.parameter_reference(self.get_source_name(), name, None) initialization = transpiler.global_reference(self.get_source_name(), name) nonlocal post_parameter_initialization post_parameter_initialization += transpiler.asignment(parameter, initialization) graph = self.id_data.get_pipeline_graph(self.pass_graph_type) if graph.graph_type == graph.GLOBAL_GRAPH: if graph.language == 'Python': add_implicit_parameter('PASS_GRAPH') else: add_implicit_parameter('PASS_MATERIAL') add_implicit_parameter('CUSTOM_IO') return transpiler.call(function, source_name, parameters, post_parameter_initialization) def draw_buttons(self, context, layout): if self.pass_graph_type != '': layout.operator('wm.malt_callback', text='Reload Sockets', icon='FILE_REFRESH').callback.set(self.setup, 'Reload Sockets') graph = self.id_data.get_pipeline_graph(self.pass_graph_type) if graph.graph_type == graph.GLOBAL_GRAPH: if graph.language == 'Python': self.malt_parameters.draw_parameter(layout, 'PASS_GRAPH', None, is_node_socket=True) else: self.malt_parameters.draw_parameter(layout, 'PASS_MATERIAL', None, is_node_socket=True) class MaltFunctionNode(bpy.types.Node, MaltFunctionNodeBase): bl_label = "Function Node" classes = [ MaltFunctionNode, ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/Nodes/MaltFunctionSubCategory.py ================================================ import bpy from BlenderMalt.MaltNodes.Nodes.MaltFunctionNode import MaltFunctionNodeBase class MaltFunctionSubCategoryNode(bpy.types.Node, MaltFunctionNodeBase): bl_label = "Function SubCategory Node" subcategory : bpy.props.StringProperty(options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_function_enums(self, context=None): library = self.id_data.get_full_library() items = [] for key in library['subcategories'][self.subcategory]: function = library['functions'].get(key) if function is None or function['meta'].get('internal'): continue label = function['meta'].get('label') if label is None: label = function['name'].replace('_',' ').title() items.append((key, label, label)) return items def update_function_enum(self, context=None): self.function_type = self.function_enum if self.disable_updates == False: self.id_data.update_ext(force_update=True) function_enum : bpy.props.EnumProperty(name='', items=get_function_enums, update=update_function_enum, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def malt_setup(self, copy=None): #Keep the correct function when the subcategory list changes if self.function_type != '' and self.function_type != self.function_enum: try: self.function_enum = self.function_type except: try: self.function_type = self.function_enum except: self.function_type = self.get_function_enums()[0][0] return super().malt_setup(copy=copy) def should_delete_outdated_links(self): return True def draw_buttons(self, context, layout): r = layout.row(align=True) r.context_pointer_set('active_node', self) r.prop(self, 'function_enum') if context.preferences.addons['BlenderMalt'].preferences.use_subfunction_cycling: r.operator('wm.malt_cycle_sub_categories', text='', icon='COLLAPSEMENU') return super().draw_buttons(context, layout) def calc_node_width(self, point_size) -> float: import blf blf.size(0, point_size) max_width = super().calc_node_width(point_size) layout_padding = 70 # account for the spaces on both sides of the enum dropdown label = next(enum[1] for enum in self.get_function_enums() if enum[0]==self.function_enum) return max(max_width, blf.dimensions(0, label)[0] + layout_padding) def draw_label(self): label = super().draw_label() if self.hide: label += ' - ' + self.get_function()['meta'].get('label', None) return label def register(): bpy.utils.register_class(MaltFunctionSubCategoryNode) def unregister(): bpy.utils.unregister_class(MaltFunctionSubCategoryNode) ================================================ FILE: BlenderMalt/MaltNodes/Nodes/MaltIONode.py ================================================ import bpy from BlenderMalt.MaltNodes.MaltNode import MaltNode from BlenderMalt.MaltProperties import MaltPropertyGroup from BlenderMalt.MaltNodes.MaltCustomPasses import * class MaltIONode(bpy.types.Node, MaltNode): bl_label = "IO Node" properties: bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) is_output: bpy.props.BoolProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_custom_pass_enums(self, context): custom_passes = ['Default'] if self.allow_custom_pass: custom_passes = context.scene.world.malt_graph_types[self.id_data.graph_type].custom_passes.keys() return [(p,p,p) for p in custom_passes] custom_pass: bpy.props.EnumProperty(items=get_custom_pass_enums, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) allow_custom_pass : bpy.props.BoolProperty(default=False, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) allow_custom_parameters : bpy.props.BoolProperty(default=False, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def malt_setup(self, copy=None): function = self.get_function() self.graph_type = self.id_data.graph_type self.pass_type = self.io_type graph = self.id_data.get_pipeline_graph() self.allow_custom_pass = graph.graph_type == graph.SCENE_GRAPH self.allow_custom_parameters = len(self.get_dynamic_parameter_types()) > 0 inputs = {} outputs = {} if function['type'] != 'void' and self.is_output: inputs['result'] = {'type': function['type']} for parameter in function['parameters']: if parameter['io'] in ['out','inout'] and self.is_output: if parameter['io'] == 'inout': if 'meta' not in parameter: parameter['meta'] = {} parameter['meta']['default_initialization'] = parameter['name'] inputs[parameter['name']] = parameter if parameter['io'] in ['','in','inout'] and self.is_output == False: outputs[parameter['name']] = parameter for parameter in self.get_custom_parameters(): list = inputs if self.is_output else outputs if parameter.name not in list.keys(): #Don't override properties list[parameter.name] = { 'type': parameter.parameter } self.setup_sockets(inputs, outputs, copy=copy) io_type : bpy.props.StringProperty(update=MaltNode.setup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) custom_parameters : bpy.props.CollectionProperty(type=MaltIOParameter, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) custom_parameters_index : bpy.props.IntProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_function(self): graph = self.id_data.get_pipeline_graph() return graph.graph_io[self.io_type].function def get_custom_pass_io(self): if self.allow_custom_pass and self.allow_custom_parameters: world = bpy.context.scene.world return world.malt_graph_types[self.id_data.graph_type].custom_passes[self.custom_pass].io[self.io_type] def get_custom_parameters(self): if self.allow_custom_parameters: if self.allow_custom_pass: io = self.get_custom_pass_io() return io.outputs if self.is_output else io.inputs else: return self.custom_parameters else: return {} def get_dynamic_parameter_types(self): graph = self.id_data.get_pipeline_graph() if self.is_output: return graph.graph_io[self.io_type].dynamic_output_types else: return graph.graph_io[self.io_type].dynamic_input_types def is_custom_socket(self, socket): parameters = [parameter['name'] for parameter in self.get_function()['parameters']] if (socket.name == 'result' and self.is_output) or socket.name in parameters: return False elif socket.name in self.get_custom_parameters().keys(): return True else: return False def get_source_socket_reference(self, socket): transpiler = self.id_data.get_transpiler() io = 'out' if self.is_output else 'in' if self.is_custom_socket(socket): return transpiler.custom_io_reference(io, self.io_type, socket.name) else: return transpiler.io_parameter_reference(socket.name, io) def get_source_code(self, transpiler): code = '' if self.is_output: function = self.get_function() custom_outputs = '' for socket in self.inputs: if socket.active == False: continue if self.is_custom_socket(socket): custom_outputs += transpiler.asignment(self.get_source_socket_reference(socket), socket.get_source_initialization()) else: if socket.name == 'result': code += transpiler.declaration(socket.data_type, socket.array_size, socket.name) initialization = socket.get_source_initialization() if initialization: code += transpiler.asignment(socket.get_source_reference(), initialization) if custom_outputs != '': graph_io = self.id_data.get_pipeline_graph().graph_io[self.io_type] try: io_wrap = graph_io.io_wrap except: io_wrap = '' code += transpiler.preprocessor_wrap(io_wrap, custom_outputs) if function['type'] != 'void': code += transpiler.result(self.inputs['result'].get_source_reference()) return code def get_source_global_parameters(self, transpiler): src = MaltNode.get_source_global_parameters(self, transpiler) custom_outputs = '' graph_io = self.id_data.get_pipeline_graph().graph_io[self.io_type] index = graph_io.custom_output_start_index for key, parameter in self.get_custom_parameters().items(): if parameter.is_output: socket = self.inputs[key] custom_outputs += transpiler.custom_output_declaration(socket.data_type, key, index, self.io_type) index += 1 else: socket = self.outputs[key] src += transpiler.global_declaration(parameter.parameter, 0, self.get_source_socket_reference(socket)) if custom_outputs != '': try: io_wrap = graph_io.io_wrap except: io_wrap = '' src += transpiler.preprocessor_wrap(io_wrap, custom_outputs) return src def draw_buttons(self, context, layout): return #TODO: only 1 custom pass signature for now if self.allow_custom_pass and (self.is_output or self.allow_custom_parameters): row = layout.row(align=True) row.prop(self, 'custom_pass', text='Custom Pass') row.operator('wm.malt_add_custom_pass', text='', icon='ADD').graph_type = self.id_data.graph_type if self.custom_pass != 'Default': def remove(): custom_passes = context.scene.world.malt_graph_types[self.id_data.graph_type].custom_passes custom_passes.remove(custom_passes.find(self.custom_pass)) #self.custom_pass = 'Default' row.operator('wm.malt_callback', text='', icon='REMOVE').callback.set(remove) def draw_buttons_ext(self, context, layout): if self.allow_custom_parameters: def refresh(): #TODO: Overkill for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree': tree.reload_nodes() tree.update_ext(force_update=True) layout.operator("wm.malt_callback", text='Reload', icon='FILE_REFRESH').callback.set(refresh, 'Reload') def draw_parameters_list(owner, parameters_key): row = layout.row() index_key = f'{parameters_key}_index' row.template_list('COMMON_UL_UI_List', '', owner, parameters_key, owner, index_key) col = row.column() parameters = getattr(owner, parameters_key) index = getattr(owner, index_key) def add_custom_socket(): new_param = parameters.add() new_param.graph_type = self.id_data.graph_type new_param.io_type = self.io_type new_param.is_output = self.is_output name = f"Custom {'Output' if new_param.is_output else 'Input'}" i = 1 #TODO: Check against default parameters while f'{name} {i}' in parameters.keys(): i += 1 new_param.name = f'{name} {i}' add_row = col.row() graph = self.id_data.get_pipeline_graph() if (self.is_output and graph.language == 'GLSL' and len(parameters) >= (8 - graph.graph_io[self.io_type].custom_output_start_index) and self.id_data.graph_type.endswith("(Group)") == False): add_row.enabled = False add_row.operator("wm.malt_callback", text='', icon='ADD').callback.set(add_custom_socket, 'Add') def remove_custom_socket(): parameters.remove(index) col.operator("wm.malt_callback", text='', icon='REMOVE').callback.set(remove_custom_socket, 'Remove') if self.allow_custom_pass: draw_parameters_list(self.get_custom_pass_io(), 'outputs' if self.is_output else 'inputs') else: draw_parameters_list(self, 'custom_parameters') classes = [ MaltIONode, ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/Nodes/MaltInlineNode.py ================================================ import bpy from BlenderMalt.MaltNodes.MaltNode import MaltNode class MaltInlineNode(bpy.types.Node, MaltNode): bl_label = "Inline Code Node" def code_update(self, context): #update the node tree self.id_data.update_ext(force_update=True) code : bpy.props.StringProperty(update=code_update, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def on_socket_update(self, socket): self.update() self.id_data.update_ext(force_update=True) def malt_init(self): self.setup() def malt_update(self): last = 0 for i, input in enumerate(self.inputs): if input.data_type != '' or input.get_linked(): last = i + 1 variables = 'abcdefgh'[:min(last+1,8)] inputs = {} for var in variables: inputs[var] = {'type': ''} if var in self.inputs: input = self.inputs[var] linked = self.inputs[var].get_linked() if linked and linked.data_type != '': inputs[var] = {'type': linked.data_type, 'size': linked.array_size} else: inputs[var] = {'type': input.data_type, 'size': input.array_size} outputs = { 'result' : {'type': ''} } if 'result' in self.outputs: out = self.outputs['result'].get_linked() if out: outputs['result'] = {'type': out.data_type, 'size': out.array_size} self.setup_sockets(inputs, outputs) def draw_buttons(self, context, layout): layout.prop(self, 'code', text='') def draw_socket(self, context, layout, socket, text): if socket.is_output == False: MaltNode.draw_socket(self, context, layout, socket, socket.name) layout.prop(socket, 'data_type', text='') else: MaltNode.draw_socket(self, context, layout, socket, socket.name) def get_source_socket_reference(self, socket): return '{}_0_{}'.format(self.get_source_name(), socket.name) def get_source_code(self, transpiler): code = '' result_socket = self.outputs['result'] code += transpiler.declaration(result_socket.data_type, result_socket.array_size, result_socket.get_source_reference()) scoped_code = '' for input in self.inputs: if input.data_type != '': initialization = input.get_source_initialization() scoped_code += transpiler.declaration(input.data_type, input.array_size, input.name, initialization) if self.code != '': scoped_code += transpiler.asignment(self.outputs['result'].get_source_reference(), self.code) return code + transpiler.scoped(scoped_code) classes = [ MaltInlineNode, ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/Nodes/MaltStructNode.py ================================================ import bpy from BlenderMalt.MaltNodes.MaltNode import MaltNode class MaltStructNode(bpy.types.Node, MaltNode): bl_label = "Struct Node" def malt_setup(self, copy=None): inputs = {} inputs[self.struct_type] = {'type' : self.struct_type} outputs = {} outputs[self.struct_type] = {'type' : self.struct_type} self.setup_sockets(inputs, outputs, copy=copy) struct_type : bpy.props.StringProperty(update=MaltNode.setup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_struct(self): graph = self.id_data.get_pipeline_graph() if self.struct_type in graph.structs: return graph.structs[self.struct_type] else: return self.id_data.get_library()['structs'][self.struct_type] def get_source_socket_reference(self, socket): if socket.name == self.struct_type: return self.get_source_name() else: return socket.name.replace(self.struct_type, self.get_source_name()) def struct_input_is_linked(self): return self.inputs[self.struct_type].get_linked() is not None def get_source_code(self, transpiler): code = '' for input in self.inputs: initialization = input.get_source_initialization() if input.is_struct_member(): if initialization: code += transpiler.asignment(input.get_source_reference(), initialization) else: code += transpiler.declaration(input.data_type, 0, self.get_source_name(), initialization) return code classes = [ MaltStructNode, ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/MaltNodes/_init_.py ================================================ def get_modules(): from . import MaltNode, MaltNodeUITools, MaltNodeTree, MaltSocket, MaltCustomPasses modules = [MaltNode, MaltNodeUITools,MaltNodeTree, MaltSocket, MaltCustomPasses] import importlib, os nodes_dir = os.path.join(os.path.dirname(__file__), 'Nodes') for name in os.listdir(nodes_dir): try: if name == '__pycache__': continue if name.endswith('.py'): name = name[:-3] module = importlib.import_module(f'BlenderMalt.MaltNodes.Nodes.{name}') modules.append(module) except ModuleNotFoundError: # Ignore it. The file or dir is not a python module pass except Exception: import traceback traceback.print_exc() return modules def register(): import importlib for module in get_modules(): importlib.reload(module) for module in get_modules(): module.register() def unregister(): for module in reversed(get_modules()): module.unregister() ================================================ FILE: BlenderMalt/MaltPipeline.py ================================================ import os, platform, time import bpy from BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform from . import MaltMaterial, MaltMeshes, MaltTextures _BRIDGE = None _PIPELINE_PARAMETERS = None _TIMESTAMP = time.time() def is_malt_active(): if bpy.context.scene.render.engine == 'MALT': return True for scene in bpy.data.scenes: if scene.render.engine == 'MALT': return True return False def get_bridge(world=None, force_creation=False): global _BRIDGE bridge = _BRIDGE if (bridge and bridge.lost_connection) or (bridge is None and force_creation): _BRIDGE = None if is_malt_active() == False: return None if world is None: world = bpy.context.scene.world world.malt.update_pipeline(bpy.context) return _BRIDGE def sync_pipeline_settings(default_world=None): for scene in bpy.data.scenes: if scene.render.engine == 'MALT' and scene.world is None: scene.world = bpy.data.worlds.new(f'{scene.name} World') setup_parameters([scene.world]) if default_world is None: default_world = bpy.data.worlds[0] for world in bpy.data.worlds: if world.malt.pipeline != default_world.malt.pipeline: world.malt.pipeline = default_world.malt.pipeline if world.malt.plugins_dir != default_world.malt.plugins_dir: world.malt.plugins_dir = default_world.malt.plugins_dir if world.malt.viewport_bit_depth != default_world.malt.viewport_bit_depth: world.malt.viewport_bit_depth = default_world.malt.viewport_bit_depth _ON_PIPELINE_SETTINGS_UPDATE = False class MaltPipeline(bpy.types.PropertyGroup): def update_pipeline(self, context): global _TIMESTAMP _TIMESTAMP = time.time() #TODO: Sync all scenes. Only one active pipeline per Blender instance is supported atm. pipeline = self.pipeline if pipeline == '': current_dir = os.path.dirname(os.path.abspath(__file__)) default_pipeline = os.path.join(current_dir,'.MaltPath','Malt','Pipelines','NPR_Pipeline','NPR_Pipeline.py') if platform.system() == 'Darwin': # The NPR Pipeline doesn't work on OpenGL implementations limited to 16 sampler uniforms default_pipeline = os.path.join(current_dir,'.MaltPath','Malt','Pipelines','MiniPipeline','MiniPipeline.py') pipeline = default_pipeline preferences = bpy.context.preferences.addons['BlenderMalt'].preferences debug_mode = bool(preferences.debug_mode) renderdoc_path = preferences.renderdoc_path plugin_dirs = [] if os.path.exists(preferences.plugins_dir): plugin_dirs.append(preferences.plugins_dir) plugin_dir = bpy.path.abspath(self.plugins_dir, library=self.id_data.library) if os.path.exists(plugin_dir): plugin_dirs.append(plugin_dir) docs_path = preferences.docs_path docs_path = docs_path if os.path.exists(docs_path) else None path = bpy.path.abspath(pipeline, library=self.id_data.library) import Bridge bridge = Bridge.Client_API.Bridge(path, int(self.viewport_bit_depth), debug_mode, renderdoc_path, plugin_dirs, docs_path) from Malt.Utils import LOG LOG.info('Blender {} {} {}'.format(bpy.app.version_string, bpy.app.build_branch, bpy.app.build_hash)) params = bridge.get_parameters() global _BRIDGE, _PIPELINE_PARAMETERS _BRIDGE = bridge _PIPELINE_PARAMETERS = params MaltMaterial.reset_materials() MaltMeshes.reset_meshes() MaltTextures.reset_textures() #TODO: This can fail depending on the current context, ID classes might not be writeable setup_all_ids() def update_pipeline_settings(self, context): global _ON_PIPELINE_SETTINGS_UPDATE if _ON_PIPELINE_SETTINGS_UPDATE: return _ON_PIPELINE_SETTINGS_UPDATE = True sync_pipeline_settings(self.id_data) _ON_PIPELINE_SETTINGS_UPDATE = False self.update_pipeline(context) pipeline : bpy.props.StringProperty(name="Malt Pipeline", subtype='FILE_PATH', update=update_pipeline_settings, set_transform=malt_path_set_transform, get_transform=malt_path_get_transform, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) plugins_dir : bpy.props.StringProperty(name="Local Plugins", subtype='DIR_PATH', update=update_pipeline_settings, set_transform=malt_path_set_transform, get_transform=malt_path_get_transform, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) viewport_bit_depth : bpy.props.EnumProperty(items=[('8', '8', ''),('16', '16', ''),('32', '32', '')], name="Bit Depth (Viewport)", update=update_pipeline_settings, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) graph_types : bpy.props.CollectionProperty(type=bpy.types.PropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) material_types : bpy.props.CollectionProperty(type=bpy.types.PropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) overrides : bpy.props.StringProperty(name='Pipeline Overrides', default='Preview,Final Render', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def draw_ui(self, layout): layout.use_property_split = True layout.use_property_decorate = False row = layout.row(align=True) row.prop(self, 'pipeline') row.operator('wm.malt_reload_pipeline', text='', icon='FILE_REFRESH') layout.prop(self, 'plugins_dir') layout.prop(self, 'viewport_bit_depth') class OT_MaltReloadPipeline(bpy.types.Operator): bl_idname = "wm.malt_reload_pipeline" bl_label = "Malt Reload Pipeline" @classmethod def poll(cls, context): return context.scene.render.engine == 'MALT' and context.scene.world is not None def execute(self, context): import Bridge Bridge.reload() context.scene.world.malt.update_pipeline(context) return {'FINISHED'} class MALT_PT_Pipeline(bpy.types.Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "world" bl_label = "Pipeline Settings" COMPAT_ENGINES = {'MALT'} @classmethod def poll(cls, context): return context.scene.render.engine == 'MALT' and context.scene.world is not None def draw(self, context): context.scene.world.malt.draw_ui(self.layout) classes = ( MaltPipeline, OT_MaltReloadPipeline, MALT_PT_Pipeline, ) def setup_all_ids(): setup_parameters(bpy.data.scenes) setup_parameters(bpy.data.worlds) setup_parameters(bpy.data.cameras) setup_parameters(bpy.data.objects) setup_parameters(bpy.data.materials) setup_parameters(bpy.data.meshes) setup_parameters(bpy.data.curves) setup_parameters(bpy.data.metaballs) setup_parameters(bpy.data.lights) from . MaltNodes.MaltNodeTree import setup_node_trees setup_node_trees() MaltMaterial.track_shader_changes(force_update=True) def setup_parameters(ids): global _PIPELINE_PARAMETERS pipeline_parameters = _PIPELINE_PARAMETERS class_parameters_map = { bpy.types.Scene : pipeline_parameters.scene, bpy.types.World : pipeline_parameters.world, bpy.types.Camera : pipeline_parameters.camera, bpy.types.Object : pipeline_parameters.object, bpy.types.Material : pipeline_parameters.material, bpy.types.Mesh : pipeline_parameters.mesh, bpy.types.Curve : pipeline_parameters.mesh, bpy.types.MetaBall : pipeline_parameters.mesh, bpy.types.Light : pipeline_parameters.light, } for bid in ids: if isinstance(bid, bpy.types.World): bid.malt.graph_types.clear() bid.malt.material_types.clear() for graph in get_bridge().graphs.values(): bid.malt.graph_types.add().name = graph.name if graph.language == 'GLSL': bid.malt.material_types.add().name = graph.name from BlenderMalt.MaltNodes import MaltCustomPasses MaltCustomPasses.setup_default_passes(get_bridge().graphs, bid) if isinstance(bid, bpy.types.Material): #Patch material types if bid.malt.material_type == '': if bid.malt.shader_nodes: bid.malt.material_type = bid.malt.shader_nodes.graph_type else: bid.malt.material_type = 'Mesh' for cls, parameters in class_parameters_map.items(): if isinstance(bid, cls): bid.malt_parameters.setup(parameters) _ON_DEPSGRAPH_UPDATE = False @bpy.app.handlers.persistent def depsgraph_update(scene, depsgraph): global _BRIDGE, _ON_DEPSGRAPH_UPDATE if _ON_DEPSGRAPH_UPDATE: return _ON_DEPSGRAPH_UPDATE = True try: if is_malt_active() == False: # Don't do anything if Malt is not the active renderer, # but make sure we setup all IDs the next time Malt is enabled _BRIDGE = None return sync_pipeline_settings() if _BRIDGE is None: scene.world.malt.update_pipeline(bpy.context) return ids = [] class_data_map = { bpy.types.Scene : bpy.data.scenes, bpy.types.World : bpy.data.worlds, bpy.types.Camera : bpy.data.cameras, bpy.types.Object : bpy.data.objects, bpy.types.Material : bpy.data.materials, bpy.types.Mesh : bpy.data.meshes, bpy.types.Curve : bpy.data.curves, bpy.types.MetaBall : bpy.data.metaballs, bpy.types.Light : bpy.data.lights, } for update in depsgraph.updates: # Try to avoid as much re-setups as possible. # Ideally we would do it only on ID creation. if update.is_updated_geometry == True or update.is_updated_transform == False: for cls, data in class_data_map.items(): if isinstance(update.id, cls): ids.append(data[update.id.name]) setup_parameters(ids) from . MaltNodes.MaltNodeTree import MaltTree redraw = False for update in depsgraph.updates: if update.is_updated_geometry: if isinstance(update.id, bpy.types.Object): MaltMeshes.unload_mesh(update.id) if isinstance(update.id, bpy.types.Image): MaltTextures.unload_texture(update.id) redraw = True elif isinstance(update.id, bpy.types.Texture): MaltTextures.unload_gradients(update.id) redraw = True elif update.id.__class__.__name__ == 'MaltTree': redraw = True if redraw: for screen in bpy.data.screens: for area in screen.areas: area.tag_redraw() except: import traceback traceback.print_exc() finally: _ON_DEPSGRAPH_UPDATE = False @bpy.app.handlers.persistent def load_scene(dummy1=None,dummy2=None): global _BRIDGE _BRIDGE = None @bpy.app.handlers.persistent def load_scene_post(dummy1=None,dummy2=None): from BlenderMalt.MaltNodes.MaltNodeTree import manual_skip_save manual_skip_save() if is_malt_active(): bpy.context.scene.world.malt.update_pipeline(bpy.context) __SAVE_PATH = None @bpy.app.handlers.persistent def save_pre(dummy1=None,dummy2=None): global __SAVE_PATH __SAVE_PATH = bpy.data.filepath @bpy.app.handlers.persistent def save_post(dummy1=None,dummy2=None): if __SAVE_PATH != bpy.data.filepath: load_scene_post() def track_pipeline_changes(): if is_malt_active() == False: return 1 try: scene = bpy.context.scene malt = scene.world.malt path = bpy.path.abspath(malt.pipeline, library=malt.id_data.library) if os.path.exists(path): stats = os.stat(path) if stats.st_mtime > _TIMESTAMP: malt.update_pipeline(bpy.context) except: import traceback print(traceback.format_exc()) return 1 def register(): for _class in classes: bpy.utils.register_class(_class) bpy.types.World.malt = bpy.props.PointerProperty(type=MaltPipeline, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.app.handlers.depsgraph_update_post.append(depsgraph_update) bpy.app.handlers.load_pre.append(load_scene) bpy.app.handlers.load_post.append(load_scene_post) bpy.app.handlers.save_pre.append(save_pre) bpy.app.handlers.save_post.append(save_post) bpy.app.timers.register(track_pipeline_changes, persistent=True) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) del bpy.types.World.malt bpy.app.handlers.depsgraph_update_post.remove(depsgraph_update) bpy.app.handlers.load_pre.remove(load_scene) bpy.app.handlers.load_post.remove(load_scene_post) bpy.app.handlers.save_pre.remove(save_pre) bpy.app.handlers.save_post.remove(save_post) bpy.app.timers.unregister(track_pipeline_changes) ================================================ FILE: BlenderMalt/MaltProperties.py ================================================ import os import bpy from Malt.PipelineParameters import Type, Parameter, MaterialParameter from Malt import Scene from . import MaltTextures class MaltBoolPropertyWrapper(bpy.types.PropertyGroup): boolean : bpy.props.BoolProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) class MaltEnumPropertyWrapper(bpy.types.PropertyGroup): enum_options : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def get_items(self, context=None): for option in self.enum_options.split(','): yield (option, option, option) enum : bpy.props.EnumProperty(items=get_items, name='', options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) # WORKAROUND: We can't declare color ramps from python, # so we use the ones stored inside textures class MaltGradientPropertyWrapper(bpy.types.PropertyGroup): def poll(self, texture): return texture.type == 'BLEND' and texture.use_color_ramp texture : bpy.props.PointerProperty(type=bpy.types.Texture, poll=poll, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def add_or_duplicate(self, name=None): if self.texture: self.texture = self.texture.copy() else: if name is None: name = f'{self.id_data.name} - {self.name}' texture = bpy.data.textures.new(name, 'BLEND') texture.use_color_ramp = True texture.color_ramp.elements[0].alpha = 1.0 self.texture = texture self.id_data.update_tag() self.texture.update_tag() from . MaltTextures import add_gradient_workaround add_gradient_workaround(self.texture) class MaltTexturePropertyWrapper(bpy.types.PropertyGroup): texture : bpy.props.PointerProperty(type=bpy.types.Image, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) class MaltMaterialPropertyWrapper(bpy.types.PropertyGroup): def poll(self, material): return material.malt.material_type == self.type or self.type == '' material : bpy.props.PointerProperty(type=bpy.types.Material, poll=poll, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) extension : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) type : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def add_or_duplicate(self, name=None): if name is None: name = f'{self.id_data.name} - {self.name}' if self.material: self.material = self.material.copy() else: self.material = bpy.data.materials.new(name) self.material.malt.material_type = self.type self.id_data.update_tag() self.material.update_tag() class MaltGraphPropertyWrapper(bpy.types.PropertyGroup): def poll(self, tree): return tree.bl_idname == 'MaltTree' and tree.graph_type == self.type graph : bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) type : bpy.props.StringProperty( options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def add_or_duplicate(self, name=None): if name is None: name = f'{self.id_data.name} - {self.name} - {self.type}' if self.graph: self.graph = self.graph.get_copy() else: self.graph = bpy.data.node_groups.new(name, 'MaltTree') self.graph.graph_type = self.type self.id_data.update_tag() self.graph.update_tag() class MaltPropertyGroup(bpy.types.PropertyGroup): bools : bpy.props.CollectionProperty(type=MaltBoolPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) enums : bpy.props.CollectionProperty(type=MaltEnumPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) gradients : bpy.props.CollectionProperty(type=MaltGradientPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) textures : bpy.props.CollectionProperty(type=MaltTexturePropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) materials : bpy.props.CollectionProperty(type=MaltMaterialPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) graphs : bpy.props.CollectionProperty(type=MaltGraphPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) parent : bpy.props.PointerProperty(type=bpy.types.ID, name="Override From", options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) override_from_parents : bpy.props.CollectionProperty(type=MaltBoolPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) show_in_children : bpy.props.CollectionProperty(type=MaltBoolPropertyWrapper, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}) def get_rna(self): try: if '_MALT_' not in self.keys(): if '_RNA_UI' in self.keys(): old_rna = self['_RNA_UI'] self['_MALT_'] = {k : old_rna[k] for k in old_rna.keys()} self.pop('_RNA_UI') else: self['_MALT_'] = {} return self['_MALT_'] except: return {} def setup(self, parameters, replace_parameters=True, reset_to_defaults=False, skip_private=True, copy_from=None, copy_map=None): rna = self.get_rna() copy_values = copy_from is not None if copy_from is None and self.parent: copy_from = self.parent.malt_parameters def setup_parameter(name, parameter): if name not in rna.keys(): rna[name] = {} type_changed = 'type' not in rna[name].keys() or rna[name]['type'] != parameter.type size_changed = 'size' in rna[name].keys() and rna[name]['size'] != parameter.size copy_name = name if copy_map and name in copy_map: copy_name = copy_map[name] if copy_from and copy_name in copy_from.get_rna(): rna_prop = copy_from.get_rna()[copy_name] parameter.default_value = rna_prop.get("default") parameter.type = rna_prop.get('type') parameter.subtype = rna_prop.get('malt_subtype') parameter.size = rna_prop.get('size') parameter.filter = rna_prop.get('filter') if self.parent: parameter.label = rna_prop.get('label') parameter.enum_options = rna_prop.get('enum_options') parameter.min = rna_prop.get('min') parameter.max = rna_prop.get('max') if hasattr(parameter, 'label') and parameter.label: rna[name]['label'] = parameter.label else: rna[name]['label'] = name.removeprefix('U_0_').replace('_0_','.').replace('_',' ').title() if reset_to_defaults: #TODO: Rename type_changed = True def to_basic_type(value): try: return tuple(value) except: return value def equals(a, b): return to_basic_type(a) == to_basic_type(b) def resize(): if parameter.size == 1: self[name] = self[name][0] else: if rna[name]['size'] > parameter.size: self[name] = self[name][:parameter.size] else: first = self[name] try: first = list(first) except: first = [first] second = list(parameter.default_value) self[name] = first + second[rna[name]['size']:] if parameter.type in (Type.INT, Type.FLOAT): if type_changed or equals(rna[name]['default'], self[name]): if copy_values and copy_name in copy_from.keys(): self[name] = copy_from[copy_name] else: self[name] = parameter.default_value elif size_changed: resize() if parameter.type == Type.STRING: if type_changed or equals(rna[name]['default'], self[name]): if copy_values and copy_name in copy_from.keys(): self[name] = copy_from[copy_name] else: self[name] = parameter.default_value if parameter.type == Type.BOOL: if name not in self.bools: self.bools.add().name = name if type_changed or equals(rna[name]['default'], self.bools[name].boolean): if copy_values and copy_name in copy_from.bools.keys(): self.bools[name].boolean = copy_from.bools[copy_name].boolean else: self.bools[name].boolean = parameter.default_value elif size_changed: resize() if parameter.type == Type.ENUM: if name not in self.enums: self.enums.add().name = name rna[name]['enum_options'] = parameter.enum_options self.enums[name].enum_options = ','.join(parameter.enum_options) if type_changed or equals(rna[name]['default'], self.enums[name].enum): if copy_values and copy_name in copy_from.enums.keys(): self.enums[name].enum = copy_from.enums[copy_name].enum else: self.enums[name].enum = parameter.default_value if parameter.type == Type.TEXTURE: if name not in self.textures: self.textures.add().name = name if type_changed or self.textures[name] == rna[name]['default']: if copy_values and copy_name in copy_from.textures.keys(): self.textures[name].texture = copy_from.textures[copy_name].texture elif isinstance(parameter.default_value, bpy.types.Image): self.textures.texture = parameter.default_value if parameter.type == Type.GRADIENT: if name not in self.gradients: self.gradients.add().name = name _name = f"{self.id_data.name} - {rna[name]['label'].replace('.',' - ')}" self.gradients[name].add_or_duplicate(name=_name) # Load gradient from material nodes (backward compatibility) if isinstance(self.id_data, bpy.types.Material): material = self.id_data if material.use_nodes and name in material.node_tree.nodes: old = material.node_tree.nodes[name].color_ramp new = self.gradients[name].texture.color_ramp MaltTextures.copy_color_ramp(old, new) if copy_values and copy_name in copy_from.gradients.keys(): parent = copy_from.gradients[copy_name].texture.color_ramp child = self.gradients[name].texture.color_ramp MaltTextures.copy_color_ramp(parent, child) elif isinstance(parameter.default_value, bpy.types.Texture): self.gradients.texture = parameter.default_value if parameter.type == Type.MATERIAL: if name not in self.materials: self.materials.add().name = name self.materials[name].extension = parameter.extension self.materials[name].type = parameter.graph_type shader_path = parameter.default_value if type_changed and copy_values and copy_name in copy_from.materials.keys(): self.materials[name].material = copy_from.materials[copy_name].material elif shader_path and shader_path != '': if isinstance(shader_path, str): material_name = name + ' : ' + os.path.basename(shader_path) if material_name not in bpy.data.materials: bpy.data.materials.new(material_name) material = bpy.data.materials[material_name] material.malt.shader_source = shader_path material = self.materials[name].material if type_changed or (material and rna[name]['default'] == material.malt.shader_source): self.materials[name].material = bpy.data.materials[material_name] if isinstance(shader_path, tuple): blend_path, material_name = parameter.default_value blend_path += '.blend' if material_name not in bpy.data.materials: internal_dir = 'Material' bpy.ops.wm.append( filepath=os.path.join(blend_path, internal_dir, material_name), directory=os.path.join(blend_path, internal_dir), filename=material_name ) if bpy.data.materials[material_name].malt.shader_nodes: bpy.data.materials[material_name].malt.shader_nodes.reload_nodes() if type_changed: self.materials[name].material = bpy.data.materials[material_name] if parameter.type == Type.GRAPH: if name not in self.graphs: self.graphs.add().name = name self.graphs[name].type = parameter.graph_type if type_changed and copy_values and copy_name in copy_from.graphs.keys(): self.graphs[name].graph = copy_from.graphs[copy_name].graph elif parameter.default_value and isinstance(parameter.default_value, tuple): blend_path, tree_name = parameter.default_value blend_path += '.blend' if tree_name not in bpy.data.node_groups: internal_dir = 'NodeTree' bpy.ops.wm.append( filepath=os.path.join(blend_path, internal_dir, tree_name), directory=os.path.join(blend_path, internal_dir), filename=tree_name ) bpy.data.node_groups[tree_name].reload_nodes() if type_changed: self.graphs[name].graph = bpy.data.node_groups[tree_name] if self.graphs[name].graph is not None: assert(parameter.graph_type == self.graphs[name].graph.graph_type) if name not in self.override_from_parents: self.override_from_parents.add().name = name if name not in self.show_in_children: prop = self.show_in_children.add() prop.name = name prop.boolean = True rna[name]['active'] = True rna[name]["default"] = parameter.default_value rna[name]['type'] = parameter.type rna[name]['malt_subtype'] = parameter.subtype rna[name]['size'] = parameter.size rna[name]['filter'] = parameter.filter rna[name]['min'] = getattr(parameter, 'min', None) rna[name]['max'] = getattr(parameter, 'max', None) #TODO: We should purge non active properties (specially textures) # at some point, likely on file save or load # so we don't lose them immediately when changing shaders/pipelines if replace_parameters: for key, value in rna.items(): if '@' not in key: rna[key]['active'] = False for name, parameter in parameters.items(): if skip_private and (name.isupper() or name.startswith('_')): # We treat underscored and all caps uniforms as "private" continue setup_parameter(name, parameter) for key, value in rna.items(): if '@' in key and key not in parameters.keys(): main_name = key.split(' @ ')[0] rna[key]['active'] = main_name in rna.keys() and rna[main_name]['active'] and rna[key]['active'] if rna[key]['active']: if rna[key]['type'] != rna[main_name]['type'] or rna[key]['size'] != rna[main_name]['size']: parameter = Parameter(rna[main_name]['default'], rna[main_name]['type'], rna[main_name]['size'], rna[main_name]['filter'], rna[main_name]['malt_subtype']) setup_parameter(key, parameter) for key, value in rna.items(): rna_prop = rna[key] if rna_prop['active'] == False: continue if rna_prop['type'] not in (Type.FLOAT, Type.INT) or isinstance(rna_prop['default'], str): continue #Default to color since it's the most common use case malt_subtype = rna_prop.get('malt_subtype') if rna_prop['type'] == Type.FLOAT and rna_prop['size'] >= 3 and (malt_subtype is None or malt_subtype == 'Color'): rna_prop['subtype'] = 'COLOR' rna_prop['soft_min'] = 0.0 rna_prop['soft_max'] = 1.0 else: rna_prop['subtype'] = 'NONE' ui_properties = {} for ui_key in ('default', 'subtype', 'min', 'max', 'soft_min', 'soft_max'): if ui_key in rna_prop and rna_prop[ui_key] is not None: ui_properties[ui_key] = rna_prop[ui_key] ui = self.id_properties_ui(key) ui.clear() ui.update(**ui_properties) # Force a depsgraph update. # Otherwise these won't be available inside scene_eval self.id_data.update_tag() for screen in bpy.data.screens: for area in screen.areas: area.tag_redraw() def rename_property(self, old_name, new_name): rna = self.get_rna() rna[new_name] = rna.pop(old_name) type = rna[new_name]['type'] self.override_from_parents[old_name].name = new_name if old_name in self.show_in_children.keys(): self.show_in_children[old_name].name = new_name if type in (Type.FLOAT, Type.INT, Type.STRING): self[new_name] = self.pop(old_name) elif type == Type.BOOL: self.bools[old_name].name = new_name elif type == Type.ENUM: self.enums[old_name].name = new_name elif type == Type.TEXTURE: self.textures[old_name].name = new_name elif type == Type.GRADIENT: self.gradients[old_name].name = new_name elif type == Type.MATERIAL: self.materials[old_name].name = new_name elif type == Type.GRAPH: self.graphs[old_name].name = new_name def remove_property(self, name): rna = self.get_rna() rna_prop = rna[name] type = rna_prop['type'] rna.pop(name) def remove(collection, key): collection.remove(collection.find(key)) remove(self.override_from_parents, name) remove(self.show_in_children, name) if type in (Type.FLOAT, Type.INT, Type.STRING): self.pop(name) elif type == Type.BOOL: remove(self.bools, name) elif type == Type.ENUM: remove(self.enums, name) elif type == Type.TEXTURE: remove(self.textures, name) elif type == Type.GRADIENT: remove(self.gradients, name) elif type == Type.MATERIAL: remove(self.materials, name) elif type == Type.GRAPH: remove(self.graphs, name) def add_override(self, property_name, override_name): main_prop = self.get_rna()[property_name] new_name = property_name + ' @ ' + override_name property = {} parameter = None if main_prop['type'] == Type.MATERIAL: parameter = MaterialParameter(main_prop['default'], self.materials[property_name].extension, self.materials[property_name].type) else: parameter = Parameter(main_prop['default'], main_prop['type'], main_prop['size'], main_prop['filter'], main_prop['malt_subtype']) parameter.default_value = main_prop.get("default") parameter.type = main_prop.get('type') parameter.subtype = main_prop.get('malt_subtype') parameter.size = main_prop.get('size') parameter.filter = main_prop.get('filter') parameter.label = main_prop.get('label') + ' @ ' + override_name parameter.enum_options = main_prop.get('enum_options') parameter.min = main_prop.get('min') parameter.max = main_prop.get('max') property[new_name] = parameter self.setup(property, replace_parameters= False) def remove_override(self, property): rna = self.get_rna() if property in rna: rna[property]['active'] = False self.id_data.update_tag() def handle_duplication(self): for gradient in self.gradients.values(): gradient.texture = gradient.texture.copy() def get_parameters(self, overrides, proxys): if '_MALT_' not in self.keys(): return {} rna = self.get_rna() parameters = {} for key in rna.keys(): if '@' in key: continue if rna[key]['active'] == False: continue parameters[key] = self.get_parameter(key, overrides, proxys) return parameters def get_parameter(self, key, overrides, proxys, retrieve_blender_type=False, rna_copy={}): if self.parent and self.override_from_parents[key].boolean == False: try: return self.parent.malt_parameters.get_parameter(key, overrides, proxys, retrieve_blender_type, rna_copy) except: pass rna = self.get_rna() rna_copy.update(rna[key]) for override in reversed(overrides): override_key = key + ' @ ' + override if override_key in rna.keys(): if rna[override_key]['active']: key = override_key if rna[key]['active'] == False: raise Exception() if rna[key]['type'] in (Type.INT, Type.FLOAT): try: return tuple(self[key]) except: return self[key] elif rna[key]['type'] == Type.STRING: return self[key] elif rna[key]['type'] == Type.BOOL: return bool(self.bools[key].boolean) elif rna[key]['type'] == Type.ENUM: return self.enums[key].enum_options.split(',').index(self.enums[key].enum) elif rna[key]['type'] == Type.TEXTURE: texture = self.textures[key].texture if retrieve_blender_type: return texture if texture: texture_key = ('texture', texture.name_full) if texture_key not in proxys.keys(): if proxy := MaltTextures.get_texture(texture): proxys[texture_key] = proxy else: return None return proxys[texture_key] else: return None elif rna[key]['type'] == Type.GRADIENT: texture = self.gradients[key].texture if retrieve_blender_type: return texture if texture: gradient_key = ('gradient', texture.name_full) if gradient_key not in proxys.keys(): proxys[gradient_key] = MaltTextures.get_gradient(texture) return proxys[gradient_key] else: return None elif rna[key]['type'] == Type.MATERIAL: material = self.materials[key].material extension = self.materials[key].extension if retrieve_blender_type: return material if material: material_key = ('material', material.name_full) if material_key not in proxys.keys(): path = material.malt.get_source_path() shader_parameters = material.malt.parameters.get_parameters(overrides, proxys) material_parameters = material.malt_parameters.get_parameters(overrides, proxys) from Bridge.Proxys import MaterialProxy proxys[material_key] = MaterialProxy(path, shader_parameters, material_parameters) return proxys[material_key] else: return None elif rna[key]['type'] == Type.GRAPH: graph = self.graphs[key].graph type = self.graphs[key].type if retrieve_blender_type: return graph if graph and graph.is_active(): result = {} result['source'] = graph.get_generated_source() result['parameters'] = {} for node in graph.nodes: if hasattr(node, 'get_source_name'): result['parameters'][node.get_source_name()] = node.get_parameters(overrides, proxys) return result else: return None def draw_ui(self, layout, filter=None): layout.use_property_decorate = False #layout.prop(self, "parent") if '_MALT_' not in self.keys(): return #Can't modify ID classes from here rna = self.get_rna() namespace_stack = [(None, layout)] def get_label(key): label = rna[key].get('label') if self.parent: parent_prop = self.parent.malt_parameters.get_rna().get(key) if parent_prop: label = parent_prop.get('label', label) if label is None: label = key.replace('_0_','.').replace('_',' ') return label # Most drivers sort the uniforms in alphabetical order anyway, # so there's no point in tracking the actual index since it doesn't follow # the declaration order import re def natural_sort_labels(k): label = get_label(k) n_split = re.split('([0-9]+)', label) ns_split = [] for n in n_split: ns_split.extend(n.split('.')) result = [] for e in ns_split: if e.isdigit(): result.append('z') result.append(int(e)) else: result.append(e) result.append(0) return result keys = sorted(rna.keys(), key=natural_sort_labels) # Put Settings first. Kind of hacky, but ¯\_(ツ)_/¯ def settings_first(k): return not rna[k].get('label', k).startswith('Settings.') keys.sort(key=settings_first) for key in keys: if rna[key]['active'] == False: continue if filter and rna[key]['filter'] and rna[key]['filter'] != filter: continue labels = get_label(key).split('.') label = labels[-1] #defer layout (box) creation until a property is actually drawn def get_layout(): nonlocal namespace_stack layout = None if len(labels) == 1: namespace_stack = namespace_stack[:1] layout = namespace_stack[0][1] else: for i in range(0, len(labels) - 1): label = labels[i] stack_i = i+1 if len(namespace_stack) > stack_i and namespace_stack[stack_i][0] != label: namespace_stack = namespace_stack[:stack_i] if len(namespace_stack) < stack_i+1: box = namespace_stack[stack_i - 1][1].box() box.label(text=label + " :") namespace_stack.append((label, box)) layout = namespace_stack[stack_i][1] return layout.column() def draw_callback(layout, property_group): is_self = self.as_pointer() == property_group.as_pointer() if self.parent and (is_self == False or self.override_from_parents[key].boolean == True): layout.prop(self.override_from_parents[key], 'boolean', text='') if is_self == False: layout.active = False self.draw_parameter(get_layout, key, label, draw_callback=draw_callback) def draw_parameter(self, layout, key, label, draw_callback=None, is_node_socket=False, drawn_from_child=False): if self.parent and self.override_from_parents[key].boolean == False: if self.parent.malt_parameters.draw_parameter(layout, key, label, draw_callback, is_node_socket, True): return True rna = self.get_rna() if key not in rna.keys(): return False if drawn_from_child and key in self.show_in_children.keys() and self.show_in_children[key].boolean == False: return True if callable(layout): layout = layout() def make_row(label_only = False): result = layout nonlocal label row = layout.row(align=True) result = row.split() is_override = '@' in key if is_override: if label is None: label = key label = '⇲ '+label.split(' @ ')[-1] if label is not None: if label_only == False and is_node_socket == False: result = result.split(factor=0.66) result.alignment = 'RIGHT' result.label(text=label) if is_override: row.operator('wm.malt_callback', text='', icon='X').callback.set( lambda : self.remove_override(key), 'Remove Override') else: row.operator('wm.malt_new_override', text='', icon='DECORATE_OVERRIDE').callback.set( lambda override_name: self.add_override(key, override_name)) if draw_callback: draw_callback(row, self) return result if rna[key]['type'] in (Type.INT, Type.FLOAT, Type.STRING): #TODO: add subtype toggle slider = rna[key]['malt_subtype'] == 'Slider' make_row().prop(self, '["{}"]'.format(key), text='', slider=slider) elif rna[key]['type'] == Type.BOOL: make_row().prop(self.bools[key], 'boolean', text='') elif rna[key]['type'] == Type.ENUM: make_row().prop(self.enums[key], 'enum', text='') elif rna[key]['type'] == Type.TEXTURE: make_row(True) row = layout.row(align=True) if self.textures[key].texture: row = row.split(factor=0.8, align=True) row.template_ID(self.textures[key], "texture", new="image.new", open="image.open") if self.textures[key].texture: row.prop(self.textures[key].texture.colorspace_settings, 'name', text='') elif rna[key]['type'] == Type.GRADIENT: make_row(True) column = layout.column() row = column.row(align=True) row.template_ID(self.gradients[key], "texture") if self.gradients[key].texture: row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set( self.gradients[key].add_or_duplicate, 'Duplicate') column.template_color_ramp(self.gradients[key].texture, 'color_ramp') else: row.operator('wm.malt_callback', text='New', icon='ADD').callback.set( self.gradients[key].add_or_duplicate, 'New') elif rna[key]['type'] == Type.MATERIAL: make_row(True) row = layout.row(align=True) row.template_ID(self.materials[key], "material") if self.materials[key].material: extension = self.materials[key].extension row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set( self.materials[key].add_or_duplicate, 'Duplicate') material = self.materials[key].material material.malt.draw_ui(layout.box(), extension, material.malt_parameters) else: row.operator('wm.malt_callback', text='New', icon='ADD').callback.set( self.materials[key].add_or_duplicate, 'New') elif rna[key]['type'] == Type.GRAPH: make_row(True) row = layout.row(align=True) row.template_ID(self.graphs[key], "graph") if self.graphs[key].graph: row.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set( self.graphs[key].add_or_duplicate, 'Duplicate') from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree node = self.id_data if isinstance(self.id_data, bpy.types.Node) else None row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set( lambda: set_node_tree(bpy.context, self.graphs[key].graph, node) ) else: row.operator('wm.malt_callback', text='New', icon='ADD').callback.set( self.graphs[key].add_or_duplicate, 'New') else: make_row(True) return True from . import MaltUtils class OT_MaltNewOverride(bpy.types.Operator): bl_idname = "wm.malt_new_override" bl_label = "Malt Add A Property Override" bl_options = {'INTERNAL'} def get_override_enums(self, context): overrides = context.scene.world.malt.overrides.split(',') result = [] for i, override in enumerate(overrides): result.append((override, override, '', i)) return result override : bpy.props.EnumProperty(items=get_override_enums, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) callback : bpy.props.PointerProperty(type=MaltUtils.MaltCallback, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def draw(self, context): layout = self.layout layout.prop(self, "override") def execute(self, context): self.callback.call(self.override) return {'FINISHED'} class MALT_PT_Base(bpy.types.Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "disabled" bl_label = "Malt Settings" COMPAT_ENGINES = {'MALT'} @classmethod def get_malt_property_owner(cls, context): return None @classmethod def get_parameter_type(cls): return None @classmethod def poll(cls, context): if context.scene.render.engine == 'MALT' and cls.get_malt_property_owner(context): from BlenderMalt.MaltPipeline import get_bridge bridge = get_bridge() parameter_type = cls.get_parameter_type() if bridge is None or parameter_type is None or len(getattr(bridge.parameters, parameter_type)) > 0: return True return False def draw(self, context): owner = self.__class__.get_malt_property_owner(context) if owner: self.layout.active = owner.library is None #Only local data can be edited owner.malt_parameters.draw_ui(self.layout) class MALT_PT_Scene(MALT_PT_Base): bl_context = "scene" @classmethod def get_parameter_type(cls): return 'scene' @classmethod def get_malt_property_owner(cls, context): return context.scene class MALT_PT_World(MALT_PT_Base): bl_context = "world" @classmethod def get_parameter_type(cls): return 'world' @classmethod def get_malt_property_owner(cls, context): return context.world class MALT_PT_Camera(MALT_PT_Base): bl_context = "data" @classmethod def get_parameter_type(cls): return 'camera' @classmethod def get_malt_property_owner(cls, context): return context.camera class MALT_PT_Object(MALT_PT_Base): bl_context = "object" @classmethod def get_parameter_type(cls): return 'object' @classmethod def get_malt_property_owner(cls, context): return context.object # In MaltMaterials ''' class MALT_PT_Material(MALT_PT_Base): bl_context = "material" @classmethod def get_malt_property_owner(cls, context): if context.material: return context.material ''' class MALT_PT_Mesh(MALT_PT_Base): bl_context = "data" @classmethod def get_parameter_type(cls): return 'mesh' @classmethod def get_malt_property_owner(cls, context): if context.mesh: return context.mesh if context.curve: return context.curve if context.meta_ball: return context.meta_ball if context.object and context.object.type in ('SURFACE', 'FONT'): return context.object.data class MALT_PT_Light(MALT_PT_Base): bl_context = "data" @classmethod def get_malt_property_owner(cls, context): return context.light def draw(self, context): layout = self.layout owner = self.__class__.get_malt_property_owner(context) if owner and owner.type != 'AREA': ''' layout.prop(owner, 'color') if owner.type == 'POINT': layout.prop(owner, 'shadow_soft_size', text='Radius') elif owner.type == 'SPOT': layout.prop(owner, 'cutoff_distance', text='Distance') layout.prop(owner, 'spot_size', text='Spot Angle') layout.prop(owner, 'spot_blend', text='Spot Blend') ''' owner.malt.draw_ui(layout) owner.malt_parameters.draw_ui(layout) classes = ( MaltBoolPropertyWrapper, MaltEnumPropertyWrapper, MaltGradientPropertyWrapper, MaltTexturePropertyWrapper, MaltMaterialPropertyWrapper, MaltGraphPropertyWrapper, MaltPropertyGroup, OT_MaltNewOverride, MALT_PT_Base, MALT_PT_Scene, MALT_PT_World, MALT_PT_Camera, MALT_PT_Object, MALT_PT_Mesh, MALT_PT_Light, ) def register(): for _class in classes: bpy.utils.register_class(_class) bpy.types.Scene.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.World.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Camera.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Object.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Material.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Mesh.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Curve.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.MetaBall.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) bpy.types.Light.malt_parameters = bpy.props.PointerProperty(type=MaltPropertyGroup, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) del bpy.types.Scene.malt_parameters del bpy.types.World.malt_parameters del bpy.types.Camera.malt_parameters del bpy.types.Object.malt_parameters del bpy.types.Material.malt_parameters del bpy.types.Mesh.malt_parameters del bpy.types.Curve.malt_parameters del bpy.types.Light.malt_parameters ================================================ FILE: BlenderMalt/MaltRenderEngine.py ================================================ import ctypes, time, platform import xxhash import bpy import gpu from mathutils import Vector, Matrix, Quaternion from Malt import Scene from Malt.Pipeline import SHADER_DIR from Malt.GL import GL from Malt.GL.Texture import Texture from Malt.GL.Shader import Shader, shader_preprocessor from Malt.GL.Mesh import Mesh from . import MaltPipeline, MaltMeshes, MaltMaterial, CBlenderMalt CAPTURE = False WINM = None if platform.system() == 'Windows': WINM = ctypes.WinDLL('winmm') def high_res_sleep(seconds): if WINM: WINM.timeBeginPeriod(1) time.sleep(seconds) WINM.timeEndPeriod(1) else: time.sleep(seconds) class MaltRenderEngine(bpy.types.RenderEngine): bl_idname = "MALT" bl_label = "Malt" bl_use_preview = False bl_use_postprocess = True bl_use_shading_nodes_custom = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.display_draw = None self.scene = Scene.Scene() self.view_matrix = None self.request_new_frame = True self.request_scene_update = True self.bridge = MaltPipeline.get_bridge() self.bridge_id = self.bridge.get_viewport_id() if self.bridge else None self.last_frame_time = 0 self.render_backend = gpu.platform.backend_type_get() def __del__(self): try: self.bridge.free_viewport_id(self.bridge_id) self.bridge = None except: # Sometimes Blender seems to call the destructor on un-initialized instances (???) pass try: super().__del__() except AttributeError: # Quiet __del__ not being defined on bpy.types.RenderEngine pass def get_scene(self, context, depsgraph, request_scene_update, overrides): if request_scene_update == True: scene = Scene.Scene() self.scene = scene scene = self.scene if hasattr(scene, 'proxys') == False: scene.proxys = {} scene.parameters = depsgraph.scene_eval.malt_parameters.get_parameters(overrides, scene.proxys) scene.world_parameters = depsgraph.scene_eval.world.malt_parameters.get_parameters(overrides, scene.proxys) override_material = scene.world_parameters['Material.Override'] default_material = scene.world_parameters['Material.Default'] scene.frame = depsgraph.scene_eval.frame_current r = depsgraph.scene_eval.render fps = r.fps / r.fps_base remap = r.frame_map_new / r.frame_map_old scene.time = (scene.frame / fps) * remap def flatten_matrix(matrix): return [e for v in matrix.transposed() for e in v] #Camera if depsgraph.mode == 'VIEWPORT': view_3d = context.region_data camera_matrix = flatten_matrix(view_3d.view_matrix) projection_matrix = flatten_matrix(view_3d.window_matrix) if view_3d.perspective_matrix != self.view_matrix: self.view_matrix = view_3d.perspective_matrix.copy() self.request_new_frame = True scene.camera = Scene.Camera(camera_matrix, projection_matrix) else: camera = depsgraph.scene_eval.camera camera_matrix = flatten_matrix(camera.matrix_world.inverted()) projection_matrix = flatten_matrix( camera.calc_matrix_camera( depsgraph, x=depsgraph.scene_eval.render.resolution_x, y=depsgraph.scene_eval.render.resolution_y )) scene.camera = Scene.Camera(camera_matrix, projection_matrix) if request_scene_update == False: return scene meshes = {} #Objects def add_object(obj, matrix, id): if obj.type in ('MESH','CURVE','SURFACE','META', 'FONT'): name = MaltMeshes.get_mesh_name(obj) if depsgraph.mode == 'RENDER': name = '___F12___' + name if name not in meshes: # (Uses obj.original) Malt Parameters are not present in the evaluated mesh parameters = obj.original.data.malt_parameters.get_parameters(overrides, scene.proxys) malt_mesh = None if depsgraph.mode == 'VIEWPORT': malt_mesh = MaltMeshes.get_mesh(obj) else: #always load the mesh for final renders malt_mesh = MaltMeshes.load_mesh(obj, name) if malt_mesh: meshes[name] = [Scene.Mesh(submesh, parameters) for submesh in malt_mesh] for i, mesh in enumerate(meshes[name]): scene.proxys[('mesh',name,i)] = mesh.mesh else: meshes[name] = None mesh = meshes[name] if mesh is None: return scale = matrix.to_scale() mirror_scale = scale[0]*scale[1]*scale[2] < 0.0 matrix = flatten_matrix(matrix) obj_parameters = obj.malt_parameters.get_parameters(overrides, scene.proxys) obj_parameters['ID'] = id tags = set(collection.name for collection in obj.original.users_collection) if len(obj.material_slots) > 0: for i, slot in enumerate(obj.material_slots): material = default_material if slot.material and slot.material.malt.get_source_path() != '': material_name = slot.material.name_full material_key = ('material',material_name) if material_key not in scene.proxys.keys(): path = slot.material.malt.get_source_path() shader_parameters = slot.material.malt.parameters.get_parameters(overrides, scene.proxys) material_parameters = slot.material.malt_parameters.get_parameters(overrides, scene.proxys) from Bridge.Proxys import MaterialProxy scene.proxys[material_key] = MaterialProxy(path, shader_parameters, material_parameters) material = scene.proxys[material_key] if override_material: material = override_material result = Scene.Object(matrix, mesh[i], material, obj_parameters, mirror_scale, tags) scene.objects.append(result) else: material = default_material if override_material: material = override_material result = Scene.Object(matrix, mesh[0], material, obj_parameters, mirror_scale, tags) scene.objects.append(result) elif obj.type == 'LIGHT': if obj.data.type == 'AREA': return #Not supported malt_light = obj.data.malt light = Scene.Light() light.color = tuple(obj.data.color * malt_light.strength) light.position = tuple(matrix.translation) light.direction = tuple(matrix.to_quaternion() @ Vector((0.0,0.0,-1.0))) if malt_light.override_global_settings: light.sun_max_distance = malt_light.max_distance light.radius = malt_light.radius light.spot_angle = malt_light.spot_angle light.spot_blend = malt_light.spot_blend_angle light.parameters = obj.data.malt_parameters.get_parameters(overrides, scene.proxys) types = { 'SUN' : 1, 'POINT' : 2, 'SPOT' : 3, } light.type = types[obj.data.type] if light.type == types['SUN']: light.matrix = flatten_matrix(matrix.to_quaternion().to_matrix().to_4x4().inverted()) else: #Scaling too ???? light.matrix = flatten_matrix(matrix.inverted()) scene.lights.append(light) is_f12 = depsgraph.mode == 'RENDER' def visible_display(obj): return obj.display_type in ('TEXTURED','SOLID') or obj.type == 'LIGHT' for obj in depsgraph.objects: if is_f12 or (visible_display(obj) and obj.visible_in_viewport_get(context.space_data)): id = xxhash.xxh3_64_intdigest(obj.name_full.encode()) % (2**16) add_object(obj, obj.matrix_world, id) for instance in depsgraph.object_instances: if instance.instance_object: obj = instance.instance_object parent = instance.parent if is_f12 or (visible_display(obj) and visible_display(parent) and parent.visible_in_viewport_get(context.space_data)): id = abs(instance.random_id) % (2**16) add_object(instance.instance_object, instance.matrix_world, id) return scene def get_AOVs(self, scene): #TODO: Hardcoded for now result = {} try: render_tree = scene.world.malt_parameters.graphs['Render'].graph for io in render_tree.get_custom_io('Render'): if io['io'] in ['out', 'inout'] and io['type'] == 'Texture': result[io['name']] = GL.GL_RGBA32F except: import traceback traceback.print_exc() return result def update_render_passes(self, scene=None, renderlayer=None): bridge = MaltPipeline.get_bridge(scene.world, True) render_outputs = bridge.render_outputs if 'COLOR' in render_outputs.keys(): self.register_pass(scene, renderlayer, "Combined", 4, "RGBA", 'COLOR') if 'DEPTH' in render_outputs.keys(): self.register_pass(scene, renderlayer, "Depth", 1, "R", 'VALUE') from itertools import chain for output, format in chain(render_outputs.items(), self.get_AOVs(scene).items()): if output not in ('COLOR', 'DEPTH'): #TODO: 'COLOR' vs 'VECTOR' ??? self.register_pass(scene, renderlayer, output, 4, "RGBA", 'COLOR') def render(self, depsgraph): scene = depsgraph.scene_eval scale = scene.render.resolution_percentage / 100.0 self.size_x = int(scene.render.resolution_x * scale) self.size_y = int(scene.render.resolution_y * scale) resolution = (self.size_x, self.size_y) overrides = ['Final Render'] bridge = MaltPipeline.get_bridge(depsgraph.scene.world, True) if self.bridge is not bridge: self.bridge = bridge self.bridge_id = self.bridge.get_viewport_id() MaltMaterial.track_shader_changes(force_update=True, async_compilation=False) AOVs = self.get_AOVs(scene) scene = self.get_scene(None, depsgraph, True, overrides) self.bridge.render(0, resolution, scene, True, AOVs=AOVs) buffers = None finished = False import time while not finished: buffers, finished, read_resolution = self.bridge.render_result(0) time.sleep(0.1) if finished: break size = self.size_x * self.size_y from itertools import chain for output in chain(self.bridge.render_outputs.keys(), AOVs.keys()): if output not in ('COLOR', 'DEPTH'): self.add_pass(output, 4, 'RGBA') result = self.begin_result(0, 0, self.size_x, self.size_y, layer=depsgraph.view_layer.name) passes = result.layers[0].passes for key, value in passes.items(): buffer_name = key if key == 'Combined': buffer_name = 'COLOR' if key == 'Depth': buffer_name = 'DEPTH' if buffer_name in buffers and hasattr(buffers[buffer_name], 'buffer'): value.rect = buffers[buffer_name].as_np_array((size , value.channels)) self.end_result(result) # Delete the scene. Otherwise we get memory leaks. # Blender never deletes RenderEngine instances ??? del self.scene def view_update(self, context, depsgraph): self.request_new_frame = True self.request_scene_update = True for update in depsgraph.updates: if update.is_updated_geometry: if isinstance(update.id, bpy.types.Object): MaltMeshes.unload_mesh(update.id) def view_draw(self, context, depsgraph): if self.bridge is not MaltPipeline.get_bridge(): #The Bridge has been reset self.bridge = MaltPipeline.get_bridge() self.bridge_id = self.bridge.get_viewport_id() self.request_new_frame = True self.request_scene_update = True global CAPTURE if CAPTURE: self.request_new_frame = True overrides = [] if context.space_data.shading.type == 'MATERIAL': overrides.append('Preview') scene = self.get_scene(context, depsgraph, self.request_scene_update, overrides) viewport_resolution = context.region.width, context.region.height resolution = viewport_resolution resolution_scale = scene.world_parameters['Viewport.Resolution Scale'] mag_filter = GL.GL_LINEAR if resolution_scale != 1.0: w,h = resolution resolution = round(w*resolution_scale), round(h*resolution_scale) smooth_interpolation = scene.world_parameters['Viewport.Smooth Interpolation'] mag_filter = GL.GL_LINEAR if smooth_interpolation else GL.GL_NEAREST if self.request_new_frame: self.bridge.render(self.bridge_id, resolution, scene, self.request_scene_update, CAPTURE) CAPTURE = False self.request_new_frame = False self.request_scene_update = False target_fps = context.preferences.addons['BlenderMalt'].preferences.render_fps_cap if target_fps > 0: delta_time = time.perf_counter() - self.last_frame_time target_delta = 1.0 / target_fps if delta_time < target_delta: high_res_sleep(target_delta - delta_time) self.last_frame_time = time.perf_counter() buffers, finished, read_resolution = self.bridge.render_result(self.bridge_id) pixels = buffers['COLOR'] if not finished: self.tag_redraw() if pixels is None or resolution != read_resolution: # Only render if resolution is the same as read_resolution. # This avoids visual glitches when the viewport is resizing. # The alternative would be locking when writing/reading the pixel buffer. return for region in context.area.regions: if region.type == 'UI': region.tag_redraw() global DISPLAY_DRAW if self.render_backend == 'OPENGL': fbo = GL.gl_buffer(GL.GL_INT, 1) GL.glGetIntegerv(GL.GL_FRAMEBUFFER_BINDING, fbo) data_format = GL.GL_FLOAT texture_format = GL.GL_RGBA32F if self.bridge.viewport_bit_depth == 8: data_format = GL.GL_UNSIGNED_BYTE texture_format = GL.GL_RGBA8 if GL.glGetInternalformativ(GL.GL_TEXTURE_2D, texture_format, GL.GL_READ_PIXELS, 1) != GL.GL_ZERO: data_format = GL.glGetInternalformativ(GL.GL_TEXTURE_2D, texture_format, GL.GL_TEXTURE_IMAGE_TYPE, 1) elif self.bridge.viewport_bit_depth == 16: data_format = GL.GL_HALF_FLOAT texture_format = GL.GL_RGBA16F try: render_texture = Texture(resolution, texture_format, data_format, pixels.buffer(), mag_filter=mag_filter, pixel_format=GL.GL_RGBA) except: # Fallback to unsigned byte, just in case (matches Server behavior) render_texture = Texture(resolution, GL.GL_RGBA8, GL.GL_UNSIGNED_BYTE, pixels.buffer(), mag_filter=mag_filter) if DISPLAY_DRAW is None: DISPLAY_DRAW = DisplayDrawGL() DISPLAY_DRAW.draw(fbo, render_texture) else: import gpu data_size = len(pixels) w,h = resolution if self.bridge.viewport_bit_depth == 8: data_size = data_size // 4 h = h // 4 elif self.bridge.viewport_bit_depth == 16: data_size = data_size // 2 h = h // 2 data_format = 'FLOAT' #Pretend we are uploading float data, since it's the only supported format. texture_format = 'RGBA32F' data_as_float = (ctypes.c_float * data_size).from_address(pixels._buffer.data) buffer = gpu.types.Buffer(data_format, data_size, data_as_float) render_texture = gpu.types.GPUTexture((w, h), format=texture_format, data=buffer) if DISPLAY_DRAW is None: DISPLAY_DRAW = DisplayDrawGPU() DISPLAY_DRAW.draw(self.bridge.viewport_bit_depth, resolution, render_texture) DISPLAY_DRAW = None class DisplayDrawGL(): def __init__(self): positions=[ 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, ] indices=[ 0, 1, 3, 1, 2, 3, ] self.quad = Mesh(positions, indices) source='#include "Passes/sRGBConversion.glsl"' vertex_src = shader_preprocessor(source, [SHADER_DIR], ['VERTEX_SHADER']) pixel_src = shader_preprocessor(source, [SHADER_DIR], ['PIXEL_SHADER']) self.shader = Shader(vertex_src, pixel_src) def draw(self, fbo, texture): GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fbo[0]) self.shader.uniforms["to_srgb"].set_value(False) self.shader.uniforms["convert"].set_value(texture.internal_format == GL.GL_RGBA8) self.shader.textures["input_texture"] = texture self.shader.bind() self.quad.draw() class DisplayDrawGPU(): def __init__(self): import gpu from gpu_extras.batch import batch_for_shader vertex_src = """ void main() { IO_POSITION = IN_POSITION * vec3(1000, 1000, 0.5); gl_Position = vec4(IO_POSITION, 1); } """ pixel_src = """ vec3 srgb_to_linear(vec3 srgb) { vec3 low = srgb / 12.92; vec3 high = pow((srgb + 0.055)/1.055, vec3(2.4)); return mix(low, high, greaterThan(srgb, vec3(0.04045))); } void main() { vec2 uv = IO_POSITION.xy * 0.5 + 0.5; int divisor = 32 / bit_depth; ivec2 output_texel = ivec2(vec2(output_res) * uv); int output_texel_linear = output_texel.y * output_res.x + output_texel.x; int texel_linear_read = output_texel_linear / divisor; ivec2 texel_read = ivec2(texel_linear_read % output_res.x, texel_linear_read / output_res.x); int sub_texel_index = output_texel_linear % divisor; vec4 texel_value = texelFetch(input_texture, texel_read, 0); if(bit_depth == 32) { OUT_COLOR = texel_value; } else if(bit_depth == 16) { vec2 sub_texel_value = sub_texel_index == 0 ? texel_value.xy : texel_value.zw; uint packed_xy = floatBitsToUint(sub_texel_value.x); uint packed_yz = floatBitsToUint(sub_texel_value.y); OUT_COLOR.rg = unpackHalf2x16(packed_xy); OUT_COLOR.ba = unpackHalf2x16(packed_yz); } else if(bit_depth == 8) { float sub_texel_value = texel_value[sub_texel_index]; uint packed_value = floatBitsToUint(sub_texel_value); OUT_COLOR = unpackUnorm4x8(packed_value); OUT_COLOR.rgb = srgb_to_linear(OUT_COLOR.rgb); } else{ OUT_COLOR = vec4(1,1,0,1); } } """ self.iface = gpu.types.GPUStageInterfaceInfo("IFace") self.iface.smooth('VEC3', "IO_POSITION") self.sh_info = gpu.types.GPUShaderCreateInfo() self.sh_info.push_constant('INT', "bit_depth") self.sh_info.push_constant('IVEC2', "output_res") self.sh_info.sampler(0, 'FLOAT_2D', "input_texture") self.sh_info.vertex_source(vertex_src) self.sh_info.vertex_in(0, 'VEC3', "IN_POSITION") self.sh_info.vertex_out(self.iface) self.sh_info.fragment_source(pixel_src) self.sh_info.fragment_out(0, 'VEC4', "OUT_COLOR") self.shader = gpu.shader.create_from_info(self.sh_info) positions=[ ( 1.0, 1.0, 1.0), ( 1.0, -1.0, 1.0), (-1.0, -1.0, 1.0), (-1.0, 1.0, 1.0), ] indices=[ (0, 1, 3), (1, 2, 3), ] self.quad = batch_for_shader( self.shader, 'TRIS', {"IN_POSITION": positions}, indices=indices ) def draw(self, bit_depth, resolution, texture): self.shader.bind() self.shader.uniform_int("bit_depth", bit_depth) self.shader.uniform_int("output_res", resolution) self.shader.uniform_sampler("input_texture", texture) self.quad.draw(self.shader) class OT_MaltRenderDocCapture(bpy.types.Operator): bl_idname = "wm.malt_renderdoc_capture" bl_label = "RenderDoc Capture" def execute(self, context): global CAPTURE CAPTURE = True context.area.tag_redraw() return {'FINISHED'} class VIEW3D_PT_Malt_Stats(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "View" bl_label = "Malt Stats" @classmethod def poll(cls, context): return context.scene.render.engine == 'MALT' def draw(self, context): from . import MaltPipeline self.layout.operator("wm.malt_renderdoc_capture") stats = MaltPipeline.get_bridge().get_stats() for line in stats.splitlines(): self.layout.label(text=line) classes = [ MaltRenderEngine, OT_MaltRenderDocCapture, VIEW3D_PT_Malt_Stats, ] def get_panels(): exclude_panels = { 'VIEWLAYER_PT_filter', 'VIEWLAYER_PT_layer_passes', 'DATA_PT_area', } panels = [] for panel in bpy.types.Panel.__subclasses__(): if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES: if panel.__name__ not in exclude_panels: panels.append(panel) return panels def register(): for cls in classes: bpy.utils.register_class(cls) for panel in get_panels(): panel.COMPAT_ENGINES.add('MALT') def unregister(): for cls in classes: bpy.utils.unregister_class(cls) for panel in get_panels(): if 'MALT' in panel.COMPAT_ENGINES: panel.COMPAT_ENGINES.remove('MALT') ================================================ FILE: BlenderMalt/MaltTextures.py ================================================ import ctypes from . import MaltPipeline __TEXTURES = {} def get_texture(texture): name = texture.name_full if name not in __TEXTURES or __TEXTURES[name] is None: __TEXTURES[name] = __load_texture(texture) return __TEXTURES[name] def __load_texture(texture): w,h = texture.size channels = int(texture.channels) size = w*h*channels sRGB = texture.colorspace_settings.name == 'sRGB' and texture.is_float == False if size == 0: return False buffer = MaltPipeline.get_bridge().get_shared_buffer(ctypes.c_float, size) texture.pixels.foreach_get(buffer.as_np_array()) MaltPipeline.get_bridge().load_texture(texture.name_full, buffer, (w,h), channels, sRGB) from Bridge.Proxys import TextureProxy return TextureProxy(texture.name_full) __GRADIENTS = {} __GRADIENT_RESOLUTION = 256 # Blender doesn't trigger depsgraph updates for newly created textures, # so we always reload gradients until it's been succesfully unloaded once (TODO) __GRADIENTS_WORKAROUND = [] def add_gradient_workaround(texture): __GRADIENTS_WORKAROUND.append(texture.name_full) def get_gradient(texture): name = texture.name_full if name not in __GRADIENTS or __GRADIENTS[name] is None or name in __GRADIENTS_WORKAROUND: __GRADIENTS[name] = __load_gradient(texture) return __GRADIENTS[name] def __load_gradient(texture): pixels = [] for i in range(0, __GRADIENT_RESOLUTION): pixel = texture.color_ramp.evaluate( i*(1.0 / __GRADIENT_RESOLUTION)) pixels.extend(pixel) nearest = texture.color_ramp.interpolation == 'CONSTANT' MaltPipeline.get_bridge().load_gradient(texture.name_full, pixels, nearest) from Bridge.Proxys import GradientProxy return GradientProxy(texture.name_full) def copy_color_ramp(old, new): new.color_mode = old.color_mode new.hue_interpolation = old.hue_interpolation new.interpolation = old.interpolation while len(new.elements) > len(old.elements): new.elements.remove(new.elements[len(new.elements)-1]) for i, o in enumerate(old.elements): n = new.elements[i] if i < len(new.elements) else new.elements.new(o.position) n.position = o.position n.color = o.color[:] n.alpha = o.alpha def reset_textures(): global __TEXTURES __TEXTURES = {} global __GRADIENTS __GRADIENTS = {} def unload_texture(texture): __TEXTURES[texture.name_full] = None def unload_gradients(texture): __GRADIENTS[texture.name_full] = None if texture.name_full in __GRADIENTS_WORKAROUND: __GRADIENTS_WORKAROUND.remove(texture.name_full) def register(): pass def unregister(): pass ================================================ FILE: BlenderMalt/MaltUtils.py ================================================ import bpy class OT_MaltPrintError(bpy.types.Operator): bl_idname = "wm.malt_print_error" bl_label = "Print Malt Error" bl_description = "MALT ERROR" bl_options = {'INTERNAL'} message : bpy.props.StringProperty(default="Malt Error", description='Error Message') @classmethod def description(cls, context, properties): return properties.message def execute(self, context): self.report({'ERROR'}, self.message) return {'FINISHED'} def modal(self, context, event): self.report({'ERROR'}, self.message) return {'FINISHED'} def invoke(self, context, event): context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} # Always store paths in UNIX format so saved files work across OSs def malt_path_set_transform(self, new_value, curr_value, is_set): return new_value.replace('\\','/') def malt_path_get_transform(self, curr_value, is_set): return curr_value.replace('\\','/') # Operator buttons are generated every time the UI is redrawn. # The UI is redrawn for every frame the cursor hovers over it # The operator button called is not the last one created (???) # So _MAX_CALLBACKS should be at least (max drawn operator buttons) * (max ui redraws after the button was created) _MAX_CALLBACKS = 1000 _CALLBACKS = [None] * _MAX_CALLBACKS _LAST_HANDLE = 0 class MaltCallback(bpy.types.PropertyGroup): def set(self, callback, message=''): global _CALLBACKS, _LAST_HANDLE, _MAX_CALLBACKS _LAST_HANDLE += 1 _LAST_HANDLE = _LAST_HANDLE % _MAX_CALLBACKS _CALLBACKS[_LAST_HANDLE] = callback self['_MALT_CALLBACK_'] = _LAST_HANDLE self['_MESSAGE_'] = message def call(self, *args, **kwargs): global _CALLBACKS _CALLBACKS[self['_MALT_CALLBACK_']](*args, **kwargs) class OT_MaltCallback(bpy.types.Operator): bl_idname = "wm.malt_callback" bl_label = "Malt Callback Operator" bl_options = {'INTERNAL'} callback : bpy.props.PointerProperty(type=MaltCallback) @classmethod def description(self, context, properties): return properties.callback['_MESSAGE_'] def execute(self, context): self.callback.call() return {'FINISHED'} class COMMON_UL_UI_List(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): item.draw(context, layout, data) def malt_template_list(layout, owner, list_property, index_property, add_callback = None, remove_callback = None): row = layout.row() row.template_list('COMMON_UL_UI_List', '', owner, list_property, owner, index_property) col = row.column() list = getattr(owner, list_property) index = getattr(owner, index_property) col.operator('wm.malt_callback', text='', icon='ADD').callback.set( add_callback if add_callback else list.add, 'Add') col.operator('wm.malt_callback', text='', icon='REMOVE').callback.set( remove_callback if remove_callback else lambda: list.remove(index), 'Remove') import json # https://developer.blender.org/T51096 def to_json_rna_path_node_workaround(malt_property_group, path_from_group): tree = malt_property_group.id_data assert(isinstance(tree, bpy.types.NodeTree)) for node in tree.nodes: if node.malt_parameters.as_pointer() == malt_property_group.as_pointer(): path = 'nodes["{}"].{}'.format(node.name, path_from_group) return json.dumps(('NodeTree', tree.name_full, path)) def to_json_rna_path(prop): blend_id = prop.id_data id_type = str(blend_id.__class__).split('.')[-1] if isinstance(prop.id_data, bpy.types.NodeTree): id_type = 'NodeTree' id_name = blend_id.name_full path = prop.path_from_id() return json.dumps((id_type, id_name, path)) def from_json_rna_path(prop): id_type, id_name, path = json.loads(prop) data_map = { 'Object' : bpy.data.objects, 'Mesh' : bpy.data.meshes, 'Light' : bpy.data.lights, 'Camera' : bpy.data.cameras, 'Material' : bpy.data.materials, 'World': bpy.data.worlds, 'Scene': bpy.data.scenes, 'NodeTree' : bpy.data.node_groups } for class_name, data in data_map.items(): if class_name in id_type: return data[id_name].path_resolve(path) return None classes=[ OT_MaltPrintError, MaltCallback, OT_MaltCallback, COMMON_UL_UI_List, ] def register(): for _class in classes: bpy.utils.register_class(_class) def unregister(): global _CALLBACKS _CALLBACKS = [None] * _MAX_CALLBACKS for _class in reversed(classes): bpy.utils.unregister_class(_class) ================================================ FILE: BlenderMalt/__init__.py ================================================ bl_info = { "name": "BlenderMalt", "description" : "Extensible Python Render Engine", "author" : "Miguel Pozo", "version": (1,0,0,'Release'), "blender" : (5, 1, 0), "doc_url": "https://malt3d.com", "tracker_url": "https://github.com/bnpr/Malt/issues/new/choose", "category": "Render" } import sys, os from os import path import bpy #Add Malt and dependencies to the import path __CURRENT_DIR = path.dirname(path.realpath(__file__)) __MALT_PATH = path.join(__CURRENT_DIR, '.MaltPath') if __MALT_PATH not in sys.path: sys.path.append(__MALT_PATH) _PY_VERSION = str(sys.version_info[0])+str(sys.version_info[1]) __MALT_DEPENDENCIES_PATH = path.join(__MALT_PATH,'Malt','.Dependencies-{}'.format(_PY_VERSION)) if __MALT_DEPENDENCIES_PATH not in sys.path: sys.path.append(__MALT_DEPENDENCIES_PATH) from BlenderMalt.MaltUtils import malt_path_set_transform, malt_path_get_transform class Preferences(bpy.types.AddonPreferences): # this must match the addon name bl_idname = __package__ setup_vs_code : bpy.props.BoolProperty(name="Auto setup VSCode", default=True, description="Setups a VSCode project on your .blend file folder") renderdoc_path : bpy.props.StringProperty(name="RenderDoc Path", subtype='FILE_PATH', set_transform=malt_path_set_transform, get_transform=malt_path_get_transform) plugins_dir : bpy.props.StringProperty(name="Global Plugins", subtype='DIR_PATH', set_transform=malt_path_set_transform, get_transform=malt_path_get_transform) docs_path : bpy.props.StringProperty(name="Docs Path", subtype='DIR_PATH', set_transform=malt_path_set_transform, get_transform=malt_path_get_transform) render_fps_cap : bpy.props.IntProperty(name="Max Viewport Render Framerate", default=30) def update_debug_mode(self, context): if context.scene.render.engine == 'MALT': context.scene.world.malt.update_pipeline(context) debug_mode : bpy.props.BoolProperty(name="Debug Mode", default=False, update=update_debug_mode, description="Developers only. Do not touch !!!") #Drawn in NODE_PT_overlay show_socket_types : bpy.props.BoolProperty(name='Show Socket Types', default=True) show_internal_nodes : bpy.props.BoolProperty(name='Show Internal Nodes', default=False) use_subfunction_cycling: bpy.props.BoolProperty(name='Use Subfunction Cycling', default=True) def draw(self, context): layout = self.layout if context.scene.render.engine == 'MALT': layout.operator('wm.path_open', text="Open Session Log").filepath=sys.stdout.log_path else: row = layout.row() row.enabled = False row.operator('wm.path_open', text="Open Session Log") layout.prop(self, "plugins_dir") layout.prop(self, "render_fps_cap") layout.prop(self, "setup_vs_code") layout.prop(self, "renderdoc_path") layout.label(text='Developer Settings :') layout.prop(self, "debug_mode") layout.prop(self, "docs_path") def draw_node_tree_overlays(self:bpy.types.Menu, context: bpy.types.Context): preferences = bpy.context.preferences.addons['BlenderMalt'].preferences self.layout.label(text='Malt') self.layout.prop(preferences, 'show_socket_types') self.layout.prop(preferences, 'show_internal_nodes') self.layout.prop(preferences, 'use_subfunction_cycling') _VS_CODE_SETTINGS = ''' {{ "files.associations": {{ "*.glsl": "cpp" }}, "C_Cpp.default.includePath": ["{}"], "C_Cpp.default.forcedInclude": ["{}"], "C_Cpp.autoAddFileAssociations": true, "C_Cpp.default.cppStandard": "c++03", "C_Cpp.default.compilerPath": "", "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true, "C_Cpp.errorSquiggles": "Disabled", "python.analysis.extraPaths": ["{}","{}"], }} ''' @bpy.app.handlers.persistent def setup_vs_code(dummy): if bpy.context.scene.render.engine == 'MALT': if bpy.context.preferences.addons['BlenderMalt'].preferences.setup_vs_code: shaders_path = path.join(__MALT_PATH, 'Malt', 'Shaders') intellisense_path = path.join(shaders_path, 'Intellisense', 'intellisense.glsl') vscode_settings = _VS_CODE_SETTINGS.format(shaders_path, intellisense_path, __MALT_PATH, __MALT_DEPENDENCIES_PATH) vscode_settings = vscode_settings.replace('\\','\\\\') settings_dir = bpy.path.abspath('//.vscode') if path.exists(settings_dir) == False: os.makedirs(settings_dir) with open(path.join(settings_dir, 'settings.json'), 'w') as f: f.write(vscode_settings) _PLUGINS = [] _PLUGIN_DIRS = [] def register_plugins(): global _PLUGINS, _PLUGIN_DIRS preferences = bpy.context.preferences.addons['BlenderMalt'].preferences plugins_dir = preferences.plugins_dir if not os.path.exists(plugins_dir): return import importlib if plugins_dir not in sys.path: sys.path.append(plugins_dir) _PLUGIN_DIRS.append(plugins_dir) for e in os.scandir(plugins_dir): if (e.path.startswith('.') or e.path.startswith('_') or e.is_file() and e.path.endswith('.py') == False): continue try: module = importlib.import_module(e.name) importlib.reload(module) module.PLUGIN.blendermalt_register() _PLUGINS.append(module.PLUGIN) except: import traceback traceback.print_exc() def unregister_plugins(): global _PLUGINS, _PLUGIN_DIRS for plugin in _PLUGINS: try: plugin.blendermalt_unregister() except: import traceback traceback.print_exc() _PLUGINS = [] for dir in _PLUGIN_DIRS: sys.path.remove(dir) _PLUGIN_DIRS = [] class OT_MaltReloadPlugins(bpy.types.Operator): bl_idname = "wm.malt_reload_plugins" bl_label = "Malt Reload Plugins" def execute(self, context): unregister_plugins() bpy.ops.wm.malt_reload_pipeline() register_plugins() return{"FINISHED"} def get_modules(): from . import MaltUtils, MaltTextures, MaltMeshes, MaltLights, MaltProperties, MaltPipeline, MaltMaterial, MaltRenderEngine from . MaltNodes import _init_ as MaltNodes return [ MaltUtils, MaltTextures, MaltMeshes, MaltLights, MaltProperties, MaltPipeline, MaltNodes, MaltMaterial, MaltRenderEngine ] classes=[ Preferences, OT_MaltReloadPlugins, ] def register(): for _class in classes: bpy.utils.register_class(_class) import importlib for module in get_modules(): importlib.reload(module) import Bridge Bridge.reload() for module in get_modules(): module.register() register_plugins() bpy.app.handlers.save_post.append(setup_vs_code) bpy.types.NODE_PT_overlay.append(draw_node_tree_overlays) def unregister(): for _class in reversed(classes): bpy.utils.unregister_class(_class) unregister_plugins() for module in reversed(get_modules()): module.unregister() bpy.app.handlers.save_post.remove(setup_vs_code) bpy.types.NODE_PT_overlay.remove(draw_node_tree_overlays) ================================================ FILE: BlenderMalt/readme.md ================================================ # BlenderMalt ## Introduction *BlenderMalt* is a [*Malt Host*](../Malt#malt-pipelines) for *Blender*. It handles all the *Scene* data loading and syncronization and exposes a minimal UI suitable for a code-centric workflow. *BlenderMalt* communicates with *Malt* through a [*Bridge*](../Bridge) instance. ## RenderEngine [*MaltRenderEngine.py*](MaltRenderEngine.py) implements the *Blender* [*RenderEngine*](https://docs.blender.org/api/current/bpy.types.RenderEngine.html) interface. 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*. ## Pipeline [*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. ## Pipeline Properties [*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. 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. ## Materials [*MaltMaterial.py*](MaltMaterial.py) handles the compilation (including automatic hot-reloading), UI rendering and storage as native *Blender* materials of *Malt* *Pipeline* materials. ## Meshes [*MaltMeshes.py*](MaltMeshes.py) retrieves any *Blender* geometry as vertex and index buffers and sends it to *Bridge*. 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. ## Textures [*MaltTextures.py*](MaltTextures.py) sends 1D and 2D textures pixel data to *Bridge*. ================================================ FILE: Bridge/Client_API.py ================================================ import ctypes, logging as LOG, io, sys from Bridge.ipc import SharedBuffer def bridge_method(function): def result(*args, **kwargs): self = args[0] try: if self.lost_connection == False: return function(*args, **kwargs) except: import traceback LOG.error(traceback.format_exc()) self.lost_connection = True return None return result class IOCapture(io.StringIO): def __init__(self, parent, log_path, log_level): super().__init__() self.parent = parent self.log_path = log_path self.log_level = log_level def write(self, s): self.parent.write(s) LOG.log(self.log_level, s) return super().write(s) class Bridge(): def __init__(self, pipeline_path, viewport_bit_depth=8, debug_mode=False, renderdoc_path=None, plugins_paths=[], docs_path=None): super().__init__() import sys if not isinstance(sys.stdout, IOCapture): import os, tempfile, time date = time.strftime("%Y-%m-%d(%H-%M)") log_path = os.path.join(tempfile.gettempdir(),'malt ' + date + '.log') LOG.basicConfig(filename=log_path, level=LOG.DEBUG, format='Blender > %(message)s') sys.stdout = IOCapture(sys.stdout, log_path, LOG.INFO) sys.stderr = IOCapture(sys.stderr, log_path, LOG.ERROR) LOG.info('SETUP IOCapture') import multiprocessing, random, string mp = multiprocessing.get_context('spawn') self.viewport_bit_depth = viewport_bit_depth self.manager = mp.Manager() self.shared_dict = self.manager.dict() self.lock = None #SharedBuffer.setup_class(self.manager) self.connections = {} self.process = None self.lost_connection = True self.parameters = {} self.graphs = {} self.render_outputs = {} self.render_buffers = {} self.shared_buffers = [] self.id = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) self.viewport_ids = [] listeners = {} bridge_to_malt = {} malt_to_bridge = {} import multiprocessing.connection as connection def add_connection(name): address = ('localhost', 0) listener = connection.Listener(address) address = listener.address listeners[name] = listener malt_to_bridge[name] = address for name in ['MAIN','REFLECTION']: add_connection(name) from . import start_server self.process = mp.Process(target=start_server, kwargs={ 'pipeline_path': pipeline_path, 'viewport_bit_depth': viewport_bit_depth, 'connection_addresses': malt_to_bridge, 'shared_dic': self.shared_dict, 'lock': self.lock, 'log_path': sys.stdout.log_path, 'debug_mode': debug_mode, 'renderdoc_path': renderdoc_path, 'plugins_paths': plugins_paths, 'docs_path': docs_path, }) self.process.daemon = True self.process.start() for name, listener in listeners.items(): bridge_to_malt[name] = listener.accept() self.connections = bridge_to_malt params = self.connections['MAIN'].recv() assert(params['msg_type'] == 'PARAMS') self.parameters = params['params'] self.graphs = params['graphs'] self.render_outputs = params['outputs'] self.lost_connection = False def __del__(self): if self.process: self.process.terminate() def get_parameters(self): return self.parameters @bridge_method def get_stats(self): if 'STATS' in self.shared_dict and self.shared_dict['STATS']: return self.shared_dict['STATS'] else: return '' @bridge_method def compile_material(self, path, search_paths=[], custom_passes=[]): self.connections['MAIN'].send({ 'msg_type': 'MATERIAL', 'path': path, 'search_paths': search_paths, 'custom_passes': custom_passes, }) return self.connections['MAIN'].recv() @bridge_method def compile_materials(self, paths, search_paths=[], async_compilation=False): for path in paths: self.connections['MAIN'].send({ 'msg_type': 'MATERIAL', 'path': path, 'search_paths': search_paths, 'custom_passes': [], }) results = {} received = [] if async_compilation == False: while True: completed = True for path in paths: if path not in received: completed = False break if completed: break msg = self.connections['MAIN'].recv() assert(msg['msg_type'] == 'MATERIAL') material = msg['material'] results[material.path] = material received.append(material.path) return results @bridge_method def receive_async_compilation_materials(self): results = {} while self.connections['MAIN'].poll(): msg = self.connections['MAIN'].recv() assert(msg['msg_type'] == 'MATERIAL') material = msg['material'] results[material.path] = material return results @bridge_method def reflect_source_libraries(self, paths): self.connections['REFLECTION'].send({ 'msg_type': 'SHADER REFLECTION', 'paths': paths }) return self.connections['REFLECTION'].recv() @bridge_method def reload_graphs(self, graph_types): self.connections['REFLECTION'].send({ 'msg_type': 'GRAPH RELOAD', 'graph_types': graph_types }) self.graphs.update(self.connections['REFLECTION'].recv()) @bridge_method def get_shared_buffer(self, ctype, size): from . import ipc #return ipc.SharedBuffer(ctype, size) requested_size = ctypes.sizeof(ctype) * size reuse_buffer = None for buffer in self.shared_buffers: release_flag = ctypes.c_bool.from_address(buffer._release_flag.data) min_ref_count = 3 # shared_buffers + local buffer var + getrefcount ref if release_flag.value == True and sys.getrefcount(buffer) == min_ref_count: if buffer._buffer.size >= requested_size: if reuse_buffer is None or buffer._buffer.size < reuse_buffer._buffer.size: reuse_buffer = buffer if reuse_buffer is None: min_size = 1024*1024 new_size = max(requested_size * 2, min_size) reuse_buffer = ipc.SharedBuffer(ctypes.c_byte, new_size) self.shared_buffers.append(reuse_buffer) ctypes.c_bool.from_address(reuse_buffer._release_flag.data).value = True reuse_buffer._ctype = ctype reuse_buffer._size = size return reuse_buffer @bridge_method def load_mesh(self, name, mesh_data): self.connections['MAIN'].send({ 'msg_type': 'MESH', 'name': name, 'data': mesh_data }) @bridge_method def load_texture(self, name, buffer, resolution, channels, sRGB): self.connections['MAIN'].send({ 'msg_type': 'TEXTURE', 'buffer': buffer, 'name': name, 'resolution': resolution, 'channels': channels, 'sRGB' : sRGB, }) @bridge_method def load_gradient(self, name, pixels, nearest): self.connections['MAIN'].send({ 'msg_type': 'GRADIENT', 'name': name, 'pixels': pixels, 'nearest' : nearest, }) @bridge_method def get_viewport_id(self): i = 1 #0 is reserved for F12 while True: if i not in self.viewport_ids: self.viewport_ids.append(i) return i i+=1 @bridge_method def free_viewport_id(self, viewport_id): self.viewport_ids.remove(viewport_id) @bridge_method def render(self, viewport_id, resolution, scene, scene_update, renderdoc_capture=False, AOVs={}): assert(viewport_id in self.viewport_ids or viewport_id == 0) new_buffers = None buffers = self.render_buffers.get(viewport_id) if buffers is None or buffers['__resolution'] != resolution or buffers['__AOVs'] != AOVs: self.render_buffers[viewport_id] = { '__resolution' : resolution, '__AOVs' : AOVs } from itertools import chain for key, texture_format in chain(self.render_outputs.items(), AOVs.items()): buffer_type = ctypes.c_float if viewport_id != 0: if self.viewport_bit_depth == 8: buffer_type = ctypes.c_byte elif self.viewport_bit_depth == 16: buffer_type = ctypes.c_ushort w,h = resolution self.render_buffers[viewport_id][key] = self.get_shared_buffer(buffer_type, w*h*4) if viewport_id != 0: #viewport render #we only need the color buffer break new_buffers = self.render_buffers[viewport_id] if (viewport_id, 'SETUP') in self.shared_dict: import time start = time.perf_counter() while self.shared_dict[(viewport_id, 'SETUP')] == False: # Don't stack multiple render workloads for the same viewport if time.perf_counter() - start > 1: #But don't stall Blender forever if new_buffers is None and scene_update == False: #Never skip new_buffers setup or scene update return else: break self.shared_dict[(viewport_id, 'FINISHED')] = None self.connections['MAIN'].send({ 'msg_type': 'RENDER', 'viewport_id': viewport_id, 'resolution': resolution, 'scene': scene, 'scene_update': scene_update, 'new_buffers': new_buffers, 'renderdoc_capture' : renderdoc_capture, }) self.shared_dict[(viewport_id, 'SETUP')] = False @bridge_method def render_result(self, viewport_id): finished = False if (viewport_id, 'FINISHED') in self.shared_dict: finished = self.shared_dict[(viewport_id, 'FINISHED')] == True read_resolution = None if (viewport_id, 'READ_RESOLUTION') in self.shared_dict: read_resolution = self.shared_dict[viewport_id, 'READ_RESOLUTION'] if viewport_id in self.render_buffers.keys(): return self.render_buffers[viewport_id], finished, read_resolution else: return None, finished, read_resolution ================================================ FILE: Bridge/Docs.py ================================================ def build_docs(pipeline, docs_path): import os output_path = os.path.join(docs_path, 'reference') parameters = pipeline.get_parameters() graphs = pipeline.get_graphs() def clean_str(str): return ''.join(line.lstrip() for line in str.strip().splitlines(True)) parameters_result = "# Malt Settings\n" def parameters_string(name, parameters): if len(parameters) == 0: return nonlocal parameters_result parameters_result += f"## {name}\n" stack = [] for key, parameter in parameters.items(): if '@' in key: continue _stack = key.split('.')[:-1] for i, e in enumerate(_stack): if i+1 > len(stack) or e != stack[i]: parameters_result += '\t'*i + f"- **{e}**\n" stack = _stack key = key.split('.')[-1] tabs = '\t'*len(stack) if stack else '' parameters_result += f"{tabs}- **{key}** *: ( {parameter.type_string()} )*" default = parameter.default_value if default is None or default == '': default = 'None' elif isinstance(default, tuple): default = default[1] parameters_result += f'* = {default}* \n' if parameter.doc: parameters_result += f'{tabs}>' + clean_str(parameter.doc) + "\n" parameters_string('Scene', parameters.scene) parameters_string('World', parameters.world) parameters_string('Camera', parameters.camera) parameters_string('Object', parameters.object) parameters_string('Material', parameters.material) parameters_string('Mesh', parameters.mesh) parameters_string('Light', parameters.light) open(os.path.join(output_path, 'settings.md'), 'w').write(parameters_result) from textwrap import indent for graph in graphs.values(): result = f"# {graph.name} Graph Reference\n" if len(graph.functions) > 0: categories = { 'Input' : {}, 'Parameters' : {}, 'Math' : {}, 'Vector' : {}, 'Color' : {}, 'Texturing' : {}, 'Shading' : {}, 'Filter' : {}, 'Other' : {}, 'Node Tree' : {}, } for key, function in graph.functions.items(): if function['meta'].get('internal'): continue category = function['meta'].get('category') if category is None: category = function['file'].replace('\\', '/').replace('/', ' - ').replace('.glsl', '').replace('_',' ') if category not in categories: categories[category] = {} subcategory = function['meta'].get('subcategory') if subcategory: if subcategory not in categories[category]: categories[category][subcategory] = [] categories[category][subcategory].append(function) else: categories[category][key] = function def draw_function(function, depth=3): nonlocal result result += '---\n' result += f"{'#'*depth} **{function['meta'].get('label', function['name'])}**\n" if pass_type := function.get('pass_type'): result += f">Graph Type / Pass : *{pass_type.replace('.', ' / ')}*\n\n" if doc := function['meta'].get('doc'): result += clean_str(doc) + "\n\n" inputs = {} outputs = {} if function['type'] != 'void': outputs['result'] = {'type': function['type'], 'meta':{}} for parameter in function['parameters']: label = parameter['meta'].get('label', parameter['name']) if parameter['io'] in ('in', 'inout'): inputs[label] = parameter if parameter['io'] in ('out', 'inout'): outputs[label] = parameter def draw_params(type, dict): if len(dict) == 0: return nonlocal result result += f"- **{type}** \n" params = "" for key, parameter in dict.items(): if '@' in key: continue try: type = parameter['type'].type_string() except: type = parameter['type'] if subtype := parameter['meta'].get('subtype'): type += f' | {subtype}' type = f'( {type} )' if default := parameter['meta'].get('default'): type += f' - default = {default}' params += f"- **{key}** *: {type}* \n" if doc := parameter['meta'].get('doc'): params += '>' + clean_str(doc) + "\n" result += indent(params, '\t') draw_params('Inputs', inputs) draw_params('Outputs', outputs) for category, items in categories.items(): if len(items) == 0: continue result += '---\n' result += f"## {category}\n" for k, v in items.items(): if isinstance(v, dict): draw_function(v) else: result += '---\n' result += f"### **{k}**\n" for subcategory_function in v: draw_function(subcategory_function, 4) open(os.path.join(output_path, f'{graph.name}-graph.md'), 'w').write(result) ================================================ FILE: Bridge/Material.py ================================================ from Malt.PipelineParameters import Parameter MATERIAL_SHADERS = {} class Material(): def __init__(self, path, pipeline, search_paths=[], custom_passes={}): self.path = path self.parameters = {} self.compiler_error = '' compiled_material = pipeline.compile_material(path, search_paths)#, custom_passes) if isinstance(compiled_material, str): self.compiler_error = compiled_material else: for pass_name, shader in compiled_material.items(): for uniform_name, uniform in shader.uniforms.items(): self.parameters[uniform_name] = Parameter.from_uniform(uniform) if shader.error: self.compiler_error += pass_name + " : " + shader.error if shader.validator: self.compiler_error += pass_name + " : " + shader.validator if self.compiler_error == '': global MATERIAL_SHADERS MATERIAL_SHADERS[self.path] = compiled_material else: MATERIAL_SHADERS[self.path] = {} def get_shader(path, parameters): if path not in MATERIAL_SHADERS.keys(): return {} shaders = MATERIAL_SHADERS[path] new_shader = {} for pass_name, pass_shader in shaders.items(): if pass_shader.error: new_shader = {} break pass_shader_copy = pass_shader.copy() new_shader[pass_name] = pass_shader_copy for name, parameter in parameters.items(): if name in pass_shader_copy.textures.keys(): pass_shader_copy.textures[name] = parameter elif name in pass_shader_copy.uniforms.keys(): pass_shader_copy.uniforms[name].set_value(parameter) return new_shader ================================================ FILE: Bridge/Mesh.py ================================================ MESHES = {} def load_mesh(pipeline, msg): name = msg['name'] data = msg['data'] MESHES[name] = pipeline.load_mesh( position = data['positions'], indices = data['indices'], normal = data['normals'], tangent = data['tangents'], uvs = data['uvs'], colors = data['colors'] ) ================================================ FILE: Bridge/Proxys.py ================================================ from Malt.GL.Mesh import Mesh from Malt.GL.Texture import Texture from Malt.GL.Texture import Gradient from Malt.Scene import Material class MeshProxy(Mesh): def __init__(self, name, submesh_index): self.name = name self.mesh = None self.submesh_index = submesh_index def resolve(self): import Bridge.Mesh self.mesh = Bridge.Mesh.MESHES[self.name][self.submesh_index] self.__dict__.update(self.mesh.__dict__) def __del__(self): pass class TextureProxy(Texture): def __init__(self, name): self.name = name self.texture = None def resolve(self): import Bridge.Texture self.texture = Bridge.Texture.TEXTURES[self.name] self.__dict__.update(self.texture.__dict__) def __del__(self): pass class GradientProxy(Gradient): def __init__(self, name): self.name = name self.gradient = None def resolve(self): import Bridge.Texture self.gradient = Bridge.Texture.GRADIENTS[self.name] self.__dict__.update(self.gradient.__dict__) def __del__(self): pass class MaterialProxy(Material): def __init__(self, path, shader_parameters, parameters): self.path = path self.shader_parameters = shader_parameters super().__init__(None, parameters) def resolve(self): import Bridge.Material self.shader = Bridge.Material.get_shader(self.path, self.shader_parameters) ================================================ FILE: Bridge/Server.py ================================================ import importlib import os, sys, time, ctypes, os, copy import cProfile, pstats, io import multiprocessing.connection as connection import glfw from Malt.GL import GL from Malt.GL.GL import * from Malt.GL.RenderTarget import RenderTarget from Malt.GL.Texture import Texture from Malt.PipelinePlugin import load_plugins_from_dir import Bridge.Mesh, Bridge.Material, Bridge.Texture from . import ipc as ipc from Malt.Utils import LOG def log_system_info(): import sys, platform LOG.info('SYSTEM INFO') LOG.info('-'*80) LOG.info('PYTHON: {}'.format(sys.version)) LOG.info('OS: {}'.format(platform.platform())) LOG.info('CPU: {}'.format(platform.processor())) LOG.info('OPENGL CONTEXT:') LOG.info(glGetString(GL_VENDOR).decode()) LOG.info(glGetString(GL_RENDERER).decode()) LOG.info(glGetString(GL_VERSION).decode()) LOG.info(glGetString(GL_SHADING_LANGUAGE_VERSION).decode()) LOG.info(f"GL_ARB_bindless_texture support : {hasGLExtension('GL_ARB_bindless_texture')}") for key, value in GL_NAMES.items(): if key.startswith('GL_MAX'): try: LOG.info('{}: {}'.format(key, glGetInteger(value))) except: pass def log_format_prop(format, prop): read = glGetInternalformativ(GL_TEXTURE_2D, format, prop, 1) try: LOG.info('{} {}: {}'.format(GL_ENUMS[format], GL_ENUMS[prop], GL_ENUMS[read])) except: #Some returned formats are not present in GL_ENUMS? See #393 import traceback traceback.print_exc() def log_format_props(format): log_format_prop(format, GL_READ_PIXELS) log_format_prop(format, GL_READ_PIXELS_FORMAT) log_format_prop(format, GL_READ_PIXELS_TYPE) log_format_prop(format, GL_TEXTURE_IMAGE_FORMAT) log_format_prop(format, GL_TEXTURE_IMAGE_TYPE) log_format_props(GL_RGB8) log_format_props(GL_RGBA8) log_format_props(GL_RGBA) log_format_props(GL_SRGB) log_format_props(GL_SRGB_ALPHA) log_format_props(GL_RGB16F) log_format_props(GL_RGBA16F) log_format_props(GL_RGB32F) log_format_props(GL_RGBA32F) LOG.info('-'*80) class PBO(): def __init__(self): self.handle = gl_buffer(GL_INT, 1) self.size = None self.sync = None self.buffer = None def __del__(self): glDeleteBuffers(1, self.handle) def setup(self, texture, buffer): self.buffer = buffer render_target = RenderTarget([texture]) w,h = texture.resolution size = w * h * texture.channel_count * texture.channel_size assert(buffer.size_in_bytes() >= size) if self.size != size: self.size = size glDeleteBuffers(1, self.handle) glGenBuffers(1, self.handle) glBindBuffer(GL_PIXEL_PACK_BUFFER, self.handle[0]) glBufferData(GL_PIXEL_PACK_BUFFER, size, None, GL_STREAM_READ) glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) render_target.bind() GL.glReadBuffer(GL.GL_COLOR_ATTACHMENT0) glBindBuffer(GL_PIXEL_PACK_BUFFER, self.handle[0]) GL.glReadPixels(0, 0, w, h, texture.format, texture.data_format, 0) glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) if self.sync: glDeleteSync(self.sync) self.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0) def poll(self): wait = glClientWaitSync(self.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0) return wait == GL_ALREADY_SIGNALED def load(self): wait = glClientWaitSync(self.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0) if wait == GL_ALREADY_SIGNALED: glBindBuffer(GL_PIXEL_PACK_BUFFER, self.handle[0]) result = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY) if result: ctypes.memmove(self.buffer.buffer(), result, self.size) glUnmapBuffer(GL_PIXEL_PACK_BUFFER) glBindBuffer(GL_PIXEL_PACK_BUFFER, 0) return True return False class Viewport(): def __init__(self, pipeline, is_final_render, bit_depth): self.pipeline = pipeline self.buffers = None self.resolution = None self.read_resolution = None self.scene = None self.bit_depth = bit_depth self.target_format = None self.final_texture = None self.final_target = None self.pbos_active = [] self.pbos_inactive = [] self.is_new_frame = True self.needs_more_samples = True self.is_final_render = is_final_render self.renderdoc_capture = False self.stat_max_frame_latency = 0 self.stat_cpu_frame_time = 0 self.stat_time_start = 0 self.stat_render_time = 0 def get_print_stats(self): return '\n'.join(( 'Resolution : {}'.format(self.resolution), 'Sample : {} / {}'.format(self.pipeline.sample_count, len(self.pipeline.get_samples())), 'Sample Time : {:.3f} ms'.format((self.stat_render_time * 1000) / self.pipeline.sample_count), 'Total Time : {:.3f} s'.format(self.stat_render_time), 'Latency : {} frames'.format(len(self.pbos_active)), 'Max Latency : {} frames'.format(self.stat_max_frame_latency), )) def setup(self, new_buffers, resolution, scene, scene_update, renderdoc_capture): if self.resolution != resolution: self.resolution = resolution self.pbos_inactive.extend(self.pbos_active) self.pbos_active = [] assert(new_buffers is not None) if self.bit_depth == 8: self.target_format = GL_UNSIGNED_BYTE if glGetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_READ_PIXELS, 1) != GL_ZERO: self.target_format = glGetInternalformativ(GL_TEXTURE_2D, GL_RGBA8, GL_TEXTURE_IMAGE_TYPE, 1) try: self.final_texture = Texture(resolution, GL_RGBA8, self.target_format, pixel_format=GL_RGBA) except: # Fallback to unsigned byte, just in case self.target_format = GL_UNSIGNED_BYTE self.final_texture = Texture(resolution, GL_RGBA8, self.target_format) self.final_texture.channel_size = 1 self.final_target = RenderTarget([self.final_texture]) else: if self.bit_depth == 16: self.target_format = GL_RGBA16F elif self.bit_depth == 32: self.target_format = GL_RGBA32F self.final_texture = Texture(resolution, self.target_format) self.final_target = RenderTarget([self.final_texture]) if new_buffers: self.buffers = new_buffers self.sample_index = 0 self.is_new_frame = True self.needs_more_samples = True self.renderdoc_capture = renderdoc_capture self.stat_time_start = time.perf_counter() if scene_update or self.scene is None: for key, proxy in scene.proxys.items(): proxy.resolve() for obj in scene.objects: obj.matrix = (ctypes.c_float * 16)(*obj.matrix) scene.batches = self.pipeline.build_scene_batches(scene.objects) self.scene = scene else: self.scene.camera = scene.camera self.scene.time = scene.time self.scene.frame = scene.frame TO_SRGB_SHADER = None def to_srgb(self, texture, target): if Viewport.TO_SRGB_SHADER is None: source='#include "Passes/sRGBConversion.glsl"' Viewport.TO_SRGB_SHADER = self.pipeline.compile_shader_from_source(source) Viewport.TO_SRGB_SHADER.uniforms["to_srgb"].set_value(True) Viewport.TO_SRGB_SHADER.textures["input_texture"] = texture self.pipeline.draw_screen_pass(Viewport.TO_SRGB_SHADER, target) def ensure_correct_format(self, key, texture): format = GL_R32F if key == 'DEPTH' else self.target_format if texture.format == format and texture.resolution == self.resolution: return texture target = self.final_target if key != 'COLOR': #Create on the fly, since final render targets can be large target = RenderTarget([Texture(self.resolution, format)]) if self.bit_depth == 8 and key != 'DEPTH': self.to_srgb(texture, target) else: self.pipeline.copy_textures(target, [texture]) return target.targets[0] def render(self): from . import renderdoc if self.renderdoc_capture: renderdoc.capture_start() if self.needs_more_samples: result = self.pipeline.render(self.resolution, self.scene, self.is_final_render, self.is_new_frame) self.is_new_frame = False self.needs_more_samples = self.pipeline.needs_more_samples() if self.is_final_render == False or self.needs_more_samples == False: pbos = None if len(self.pbos_inactive) > 0: pbos = self.pbos_inactive.pop() else: pbos = {} for key, texture in result.items(): if texture and key in self.buffers.keys(): if key not in pbos.keys(): pbos[key] = PBO() texture = self.ensure_correct_format(key, texture) pbos[key].setup(texture, self.buffers[key]) self.pbos_active.append(pbos) if len(self.pbos_active) > 0: for i, pbos in reversed(list(enumerate(self.pbos_active))): is_ready = True for pbo in pbos.values(): if pbo.poll() == False: is_ready = False if is_ready: for pbo in pbos.values(): pbo.load() self.pbos_inactive.extend(self.pbos_active[:i+1]) self.pbos_active = self.pbos_active[i+1:] self.read_resolution = self.resolution break self.stat_render_time = time.perf_counter() - self.stat_time_start self.stat_max_frame_latency = max(len(self.pbos_active), self.stat_max_frame_latency) if self.renderdoc_capture: renderdoc.capture_end() self.renderdoc_capture = False return self.needs_more_samples == False and len(self.pbos_active) == 0 PROFILE = False @GLDEBUGPROC def gl_debug_callback(source, type, id, severity, length, message, user_param): def fmt(enum): try: return GL_ENUMS[enum].removeprefix("GL_DEBUG_").removesuffix("_KHR").removesuffix("_ARB").replace("_"," ") except: return "(format error)" msg = f"OPENGL DEBUG CALLBACK ({id}):\n{fmt(type)} > {fmt(severity)} > {fmt(source)}\n{message.decode('utf-8')}" if type == GL_DEBUG_TYPE_ERROR: LOG.error(msg) elif severity == GL_DEBUG_SEVERITY_NOTIFICATION: LOG.info(msg) else: LOG.warning(msg) def main(pipeline_path, viewport_bit_depth, connection_addresses, shared_dic, lock, log_path, debug_mode, plugins_paths, docs_path): LOG.info('DEBUG MODE: {}'.format(debug_mode)) LOG.info('CONNECTIONS:') connections = {} for name, address in connection_addresses.items(): LOG.info('Name: {} Adress: {}'.format(name, address)) connections[name] = connection.Client(address) glfw.ERROR_REPORTING = True glfw.init() glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4) glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 5) glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) if debug_mode: glfw.window_hint(glfw.OPENGL_DEBUG_CONTEXT, True) window = glfw.create_window(256, 256, 'Malt', None, None) glfw.make_context_current(window) if debug_mode: glEnable(GL_DEBUG_OUTPUT) glDebugMessageCallback(gl_debug_callback, None) # Don't hide for better OS/Drivers schedule priority #glfw.hide_window(window) # Minimize instead: glfw.iconify_window(window) glfw.swap_interval(0) log_system_info() LOG.info('INIT PIPELINE: ' + pipeline_path) try: pipeline_dir, pipeline_name = os.path.split(pipeline_path) if pipeline_dir not in sys.path: sys.path.append(pipeline_dir) module_name = pipeline_name.split('.')[0] module = __import__(module_name) pipeline_class = module.PIPELINE pipeline_class.SHADER_INCLUDE_PATHS.append(pipeline_dir) if docs_path: #build docs before loading plugins from . import Docs try: Docs.build_docs(pipeline_class(), docs_path) except: import traceback traceback.print_exc() plugins = [] for dir in plugins_paths: plugins += load_plugins_from_dir(dir) pipeline = pipeline_class(plugins) params = pipeline.get_parameters() graphs = pipeline.get_graphs() outputs = pipeline.get_render_outputs() connections['MAIN'].send({ 'msg_type': 'PARAMS', 'params': params, 'graphs': graphs, 'outputs': outputs }) except: import traceback exception = traceback.format_exc() LOG.error(exception) from Malt import PipelineParameters connections['MAIN'].send({ 'msg_type': 'PARAMS', 'params': PipelineParameters.PipelineParameters(), 'graphs': {}, 'outputs': {} }) viewports = {} while glfw.window_should_close(window) == False: try: profiler = cProfile.Profile() profiling_data = io.StringIO() global PROFILE if PROFILE: profiler.enable() start_time = time.perf_counter() glfw.poll_events() while connections['REFLECTION'].poll(): msg = connections['REFLECTION'].recv() if msg['msg_type'] == 'SHADER REFLECTION': LOG.debug('REFLECT SHADER : {}'.format(msg)) paths = msg['paths'] results = {} from Malt.GL.Shader import glsl_reflection, shader_preprocessor for path in paths: try: root_path = os.path.dirname(path) src = '#include "{}"\n'.format(path) src = shader_preprocessor(src, [root_path]) reflection = glsl_reflection(src, root_path) reflection['paths'] = set([path]) for struct in reflection['structs'].values(): reflection['paths'].add(struct['file']) for function in reflection['functions'].values(): reflection['paths'].add(function['file']) results[path] = reflection except: results[path] = { 'structs':{}, 'functions':{}, 'paths':[], } import traceback LOG.error(traceback.format_exc()) connections['REFLECTION'].send(results) if msg['msg_type'] == 'GRAPH RELOAD': graph_types = msg['graph_types'] for type in graph_types: pipeline.graphs[type].setup_reflection() for viewport in viewports.values(): viewport.pipeline.graphs = pipeline.graphs graphs = pipeline.get_graphs() connections['REFLECTION'].send(graphs) while connections['MAIN'].poll(): msg = connections['MAIN'].recv() if msg['msg_type'] == 'MATERIAL': LOG.debug('COMPILE MATERIAL : {}'.format(msg)) path = msg['path'] search_paths = msg['search_paths'] custom_passes = msg['custom_passes'] material = Bridge.Material.Material(path, pipeline, search_paths, custom_passes) connections['MAIN'].send({ 'msg_type': 'MATERIAL', 'material' : material }) if msg['msg_type'] == 'MESH': msg_log = copy.copy(msg) msg_log['data'] = None LOG.debug('LOAD MESH : {}'.format(msg_log)) Bridge.Mesh.load_mesh(pipeline, msg) if msg['msg_type'] == 'TEXTURE': LOG.debug('LOAD TEXTURE : {}'.format(msg)) Bridge.Texture.load_texture(msg) if msg['msg_type'] == 'GRADIENT': msg_log = copy.copy(msg) msg_log['pixels'] = None LOG.debug('LOAD GRADIENT : {}'.format(msg_log)) name = msg['name'] pixels = msg['pixels'] nearest = msg['nearest'] Bridge.Texture.load_gradient(name, pixels, nearest) if msg['msg_type'] == 'RENDER': LOG.debug('SETUP RENDER : {}'.format(msg)) viewport_id = msg['viewport_id'] resolution = msg['resolution'] scene = msg['scene'] scene_update = msg['scene_update'] new_buffers = msg['new_buffers'] renderdoc_capture = msg['renderdoc_capture'] if viewport_id not in viewports: bit_depth = viewport_bit_depth if viewport_id != 0 else 32 viewports[viewport_id] = Viewport(pipeline_class(plugins), viewport_id == 0, bit_depth) viewports[viewport_id].setup(new_buffers, resolution, scene, scene_update, renderdoc_capture) shared_dic[(viewport_id, 'FINISHED')] = False shared_dic[(viewport_id, 'SETUP')] = True if viewport_id == 0: # Final Render # Render all samples at once to ensure render is done with the correct state while viewports[0].render() == False: continue active_viewports = {} render_finished = True for v_id, v in viewports.items(): if v.needs_more_samples: active_viewports[v_id] = v has_finished = v.render() if has_finished == False: render_finished = False shared_dic[(v_id, 'READ_RESOLUTION')] = v.read_resolution if has_finished and shared_dic[(v_id, 'FINISHED')] == False: shared_dic[(v_id, 'FINISHED')] = True if render_finished: glfw.swap_interval(1) else: glfw.swap_interval(0) glfw.swap_buffers(window) if len(active_viewports) > 0: stats = '' for v_id, v in active_viewports.items(): stats += "Viewport ({}):\n{}\n\n".format(v_id, v.get_print_stats()) shared_dic['STATS'] = stats LOG.debug('STATS: {} '.format(stats)) if PROFILE: profiler.disable() stats = pstats.Stats(profiler, stream=profiling_data) stats.strip_dirs() stats.sort_stats(pstats.SortKey.CUMULATIVE) stats.print_stats() if active_viewports: LOG.debug(profiling_data.getvalue()) except (ConnectionResetError, EOFError): #Connection Lost break except: import traceback LOG.error(traceback.format_exc()) glfw.terminate() ================================================ FILE: Bridge/Texture.py ================================================ from Malt.GL import Texture from Malt.GL.GL import * TEXTURES = {} def load_texture(msg): name = msg['name'] data = msg['buffer'].buffer() resolution = msg['resolution'] channels = msg['channels'] sRGB = msg['sRGB'] internal_formats = [ GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F, ] pixel_formats = [ GL_RED, GL_RG, GL_RGB, GL_RGBA ] internal_format = internal_formats[channels-1] pixel_format = pixel_formats[channels-1] if sRGB: if channels == 4: internal_format = GL_SRGB_ALPHA else: internal_format = GL_SRGB #Nearest + Anisotropy seems to yield the best results with temporal super sampling TEXTURES[name] = Texture.Texture(resolution, internal_format, GL_FLOAT, data, pixel_format=pixel_format, wrap=GL_REPEAT, min_filter=GL_NEAREST_MIPMAP_NEAREST, build_mipmaps=True, anisotropy=True) GRADIENTS = {} def load_gradient(name, pixels, nearest): GRADIENTS[name] = Texture.Gradient(pixels, len(pixels)/4, nearest_interpolation=nearest) ================================================ FILE: Bridge/__init__.py ================================================ def reload(): import importlib from . import Client_API, Server, Material, Mesh, Texture for module in [ Client_API, Server, Material, Mesh, Texture ]: importlib.reload(module) def start_server(pipeline_path, viewport_bit_depth, connection_addresses, shared_dic, lock, log_path, debug_mode, renderdoc_path, plugins_paths, docs_path): import logging log_level = logging.DEBUG if debug_mode else logging.INFO logging.basicConfig(filename=log_path, level=log_level, format='Malt > %(message)s') console_logger = logging.StreamHandler() console_logger.setLevel(logging.WARNING) logging.getLogger().addHandler(console_logger) import os, sys, ctypes if sys.platform == 'win32': win = ctypes.windll.kernel32 HIGH_PRIORITY_CLASS = 0x00000080 PROCESS_SET_INFORMATION = 0x0200 process = win.OpenProcess(PROCESS_SET_INFORMATION, 0, win.GetCurrentProcessId()) win.SetPriorityClass(process, HIGH_PRIORITY_CLASS) win.CloseHandle(process) if renderdoc_path and os.path.exists(renderdoc_path): import subprocess subprocess.call([renderdoc_path, 'inject', '--PID={}'.format(os.getpid())]) from . import Server try: Server.main(pipeline_path, viewport_bit_depth, connection_addresses, shared_dic, lock, log_path, debug_mode, plugins_paths, docs_path) except: import traceback logging.error(traceback.format_exc()) ================================================ FILE: Bridge/ipc/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) # set(CMAKE_GENERATOR_PLATFORM x64) project(Ipc) SET(CMAKE_BUILD_TYPE Release) SET(BUILD_SHARED_LIBS ON) add_library(Ipc ipc.c) if(NOT WIN32) target_link_libraries(Ipc pthread) if(NOT APPLE) target_link_libraries(Ipc rt) endif() endif() install(TARGETS Ipc CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR}) ================================================ FILE: Bridge/ipc/__init__.py ================================================ import os, ctypes, platform src_dir = os.path.abspath(os.path.dirname(__file__)) library = 'libIpc.so' if platform.system() == 'Windows': library = 'Ipc.dll' if platform.system() == 'Darwin': library = 'libIpc.dylib' Ipc = ctypes.CDLL(os.path.join(src_dir, library)) class C_SharedMemory(ctypes.Structure): _fields_ = [ ('name', ctypes.c_char_p), ('data', ctypes.c_void_p), ('size', ctypes.c_size_t), ('handle', ctypes.c_void_p), ('int', ctypes.c_int), ] def errcheck(ret, func, args): if ret != 0: import os raise OSError(ret, os.strerror(ret)) return ret create_shared_memory = Ipc['create_shared_memory'] create_shared_memory.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.POINTER(C_SharedMemory)] create_shared_memory.restype = ctypes.c_int create_shared_memory.errcheck = errcheck open_shared_memory = Ipc['open_shared_memory'] open_shared_memory.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.POINTER(C_SharedMemory)] open_shared_memory.restype = ctypes.c_int open_shared_memory.errcheck = errcheck close_shared_memory = Ipc['close_shared_memory'] close_shared_memory.argtypes = [C_SharedMemory, ctypes.c_bool] close_shared_memory.restype = None from Malt.Utils import IBuffer class SharedBuffer(IBuffer): _GARBAGE = [] @classmethod def GC(cls): from copy import copy for buffer, release_flag in copy(cls._GARBAGE): if ctypes.c_bool.from_address(release_flag.data).value == True: close_shared_memory(buffer, True) close_shared_memory(release_flag, True) cls._GARBAGE.remove((buffer, release_flag)) def __init__(self, ctype, size): import random, string self._ctype = ctype self._size = size self.id = ''.join(random.choices(string.ascii_letters + string.digits, k=16)) self._buffer = C_SharedMemory() create_shared_memory(('MALT_SHARED_'+self.id).encode('ascii'), self.size_in_bytes(), ctypes.byref(self._buffer)) self._release_flag = C_SharedMemory() create_shared_memory(('MALT_FLAG_'+self.id).encode('ascii'), ctypes.sizeof(ctypes.c_bool), ctypes.byref(self._release_flag)) ctypes.c_bool.from_address(self._release_flag.data).value = True self._is_owner = True def ctype(self): return self._ctype def __len__(self): return self._size def buffer(self): return (self._ctype*self._size).from_address(self._buffer.data) def __getstate__(self): assert(self._is_owner) ctypes.c_bool.from_address(self._release_flag.data).value = False state = self.__dict__.copy() state['_buffer'] = None state['_release_flag'] = None return state def __setstate__(self, state): self.__dict__.update(state) self._is_owner = False self._buffer = C_SharedMemory() open_shared_memory(('MALT_SHARED_'+self.id).encode('ascii'), self.size_in_bytes(), ctypes.byref(self._buffer)) self._release_flag = C_SharedMemory() open_shared_memory(('MALT_FLAG_'+self.id).encode('ascii'), ctypes.sizeof(ctypes.c_bool), ctypes.byref(self._release_flag)) def __del__(self): if self._is_owner == False or ctypes.c_bool.from_address(self._release_flag.data).value == True: ctypes.c_bool.from_address(self._release_flag.data).value = True close_shared_memory(self._buffer, self._is_owner) close_shared_memory(self._release_flag, self._is_owner) else: buffer_copy = C_SharedMemory() ctypes.memmove(ctypes.addressof(buffer_copy), ctypes.addressof(self._buffer), ctypes.sizeof(C_SharedMemory)) flag_copy = C_SharedMemory() ctypes.memmove(ctypes.addressof(flag_copy), ctypes.addressof(self._release_flag), ctypes.sizeof(C_SharedMemory)) self._GARBAGE.append((buffer_copy, flag_copy)) self.GC() ================================================ FILE: Bridge/ipc/build.py ================================================ import subprocess import os import platform src_dir = os.path.abspath(os.path.dirname(__file__)) build_dir = os.path.join(src_dir, '.build') try: os.mkdir(build_dir) except: pass if platform.system() == 'Windows': #Multi-config generators, like Visual Studio subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir) else: #Single-config generators subprocess.check_call(['cmake', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir) subprocess.check_call(['cmake', '--install', '.'], cwd=build_dir) ================================================ FILE: Bridge/ipc/ipc.c ================================================ #ifdef _WIN32 #define EXPORT __declspec( dllexport ) #else #define EXPORT __attribute__ ((visibility ("default"))) #endif #define IPC_IMPLEMENTATION #include "ipc.h" EXPORT int create_shared_memory(char* name, size_t size, ipc_sharedmemory* mem) { ipc_mem_init(mem, name, size); if (ipc_mem_create(mem) != 0) { return errno; } return 0; } EXPORT int open_shared_memory(char* name, size_t size, ipc_sharedmemory* mem) { ipc_mem_init(mem, name, size); if (ipc_mem_open_existing(mem) != 0) { return errno; } return 0; } EXPORT void close_shared_memory(ipc_sharedmemory mem, bool release) { ipc_mem_close(&mem, release); } ================================================ FILE: Bridge/ipc/ipc.h ================================================ /* ipc.h - v0.2 - public domain cross platform inter process communication no warranty implied; use at your own risk by Jari Komppa, http://iki.fi/sol/ INCLUDE Do this: #define IPC_IMPLEMENTATION before you include this file in *one* C or C++ file to create the implementation. // i.e. it should look like this: #include ... #include ... #include ... #define IPC_IMPLEMENTATION #include "ipc.h" You can #define IPC_MALLOC, and IPC_FREE to avoid using malloc,free LICENSE This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to USAGE Shared memory API: void ipc_mem_init(ipc_sharedmemory *mem, unsigned char *name, size_t size); - Initialize ipc_sharedmemory structure for use. Call this first. int ipc_mem_open_existing(ipc_sharedmemory *mem); - Try to open existing shared memory. Returns 0 for success. int ipc_mem_create(ipc_sharedmemory *mem); - Try to create shared memory. Returns 0 for success. void ipc_mem_close(ipc_sharedmemory *mem); - Close shared memory and free allocated stuff. Shared memory will only get destroyed when nobody is accessing it. Call this last. unsigned char *ipc_mem_access(ipc_sharedmemory *mem); - Access the shared memory. Same as poking mem->data directly, but some people like accessors. Shared semaphore API: void ipc_sem_init(ipc_sharedsemaphore *sem, unsigned char *name); - Initialize ipc_sharedsemaphore for use. Call this first. int ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue); - Try to create semaphore. Returns 0 for success. void ipc_sem_close(ipc_sharedsemaphore *sem); - Close the semaphore and deallocate. Shared semaphore will only go away after nobody is using it. Call this last. void ipc_sem_increment(ipc_sharedsemaphore *sem); - Increment the semaphore. void ipc_sem_decrement(ipc_sharedsemaphore *sem); - Decrement the semaphore, waiting forever if need be. int ipc_sem_try_decrement(ipc_sharedsemaphore *sem); - Try to decrement the semaphore, returns 1 for success, 0 for failure. TROUBLESHOOTING - Thread programming issues apply; don't mess with the memory someone else might be reading, make sure you release semaphore after use not to hang someone else, etc. - If process crashes while holding a semaphore, the others may end up waiting forever for them to release. - On Linux, named items will remain after your application closes unless you close them. This is particularly fun if your application crashes. Having a commandline mode that just tries to create and then close all your shared resources may be convenient. Saves on rebooting, at least.. On windows, if nobody is around to use the resource, they disappear. */ #ifndef IPC_H_INCLUDE_GUARD #define IPC_H_INCLUDE_GUARD #include typedef void* HANDLE; #ifndef _WIN32 #include #endif #ifdef __cplusplus extern "C" { #endif typedef struct ipc_sharedmemory_ { char *name; unsigned char *data; size_t size; HANDLE handle; int fd; } ipc_sharedmemory; extern void ipc_mem_init(ipc_sharedmemory *mem, char *name, size_t size); extern int ipc_mem_open_existing(ipc_sharedmemory *mem); extern int ipc_mem_create(ipc_sharedmemory *mem); extern void ipc_mem_close(ipc_sharedmemory *mem, bool unlink); extern unsigned char *ipc_mem_access(ipc_sharedmemory *mem); typedef struct ipc_sharedsemaphore_ { char *name; #if defined(_WIN32) HANDLE handle; #else sem_t *semaphore; #endif } ipc_sharedsemaphore; extern void ipc_sem_init(ipc_sharedsemaphore *sem, char *name); extern int ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue); extern void ipc_sem_close(ipc_sharedsemaphore *sem); extern void ipc_sem_increment(ipc_sharedsemaphore *sem); extern void ipc_sem_decrement(ipc_sharedsemaphore *sem); extern int ipc_sem_try_decrement(ipc_sharedsemaphore *sem); #ifdef __cplusplus } #endif //////// end of header //////// #ifdef IPC_IMPLEMENTATION #if defined(_WIN32) #include #else // !_WIN32 #include #include #include #include #include #include #endif // !_WIN32 #ifndef IPC_ASSERT #define IPC_ASSERT(x) assert(x) #endif #ifndef IPC_MALLOC #define IPC_MALLOC(x) malloc(x) #endif #ifndef IPC_FREE #define IPC_FREE(x) free(x) #endif static char * ipc_strdup(char *src) { int i, len; char *dst = NULL; len = 0; while (src[len]) len++; #if !defined(_WIN32) len++; #endif dst = (char *)IPC_MALLOC(len+1); if (!dst) return NULL; dst[len] = 0; #if defined(_WIN32) for (i = 0; i < len; i++) dst[i] = src[i]; #else dst[0] = '/'; for (i = 0; i < len-1; i++) dst[i+1] = src[i]; #endif return dst; } void ipc_mem_init(ipc_sharedmemory *mem, char *name, size_t size) { mem->name = ipc_strdup(name); mem->size = size; mem->data = NULL; #if defined(_WIN32) mem->handle = 0; #else mem->fd = -1; #endif } unsigned char *ipc_mem_access(ipc_sharedmemory *mem) { return mem->data; } void ipc_sem_init(ipc_sharedsemaphore *sem, char *name) { sem->name = ipc_strdup(name); #if defined(_WIN32) sem->handle = 0; #else sem->semaphore = NULL; #endif } #if defined(_WIN32) int ipc_mem_open_existing(ipc_sharedmemory *mem) { mem->handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, mem->name); if (!mem->handle) return -1; mem->data = (unsigned char*)MapViewOfFile(mem->handle, FILE_MAP_ALL_ACCESS, 0, 0, mem->size); if (!mem->data) return -1; return 0; } int ipc_mem_create(ipc_sharedmemory *mem) { mem->handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, mem->size, mem->name); if (!mem->handle) return -1; mem->data = (unsigned char*)MapViewOfFile(mem->handle, FILE_MAP_ALL_ACCESS, 0, 0, mem->size); if (!mem->data) return -1; return 0; } void ipc_mem_close(ipc_sharedmemory *mem, bool unlink) { if (mem->data != NULL) { UnmapViewOfFile(mem->data); mem->data = NULL; } IPC_FREE(mem->name); mem->name = NULL; mem->size = 0; } int ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue) { sem->handle = CreateSemaphoreA(NULL, initialvalue, 0x7fffffff, sem->name); if (!sem->handle) return -1; return 0; } void ipc_sem_close(ipc_sharedsemaphore *sem) { CloseHandle(sem->handle); IPC_FREE(sem->name); sem->handle = 0; } void ipc_sem_increment(ipc_sharedsemaphore *sem) { ReleaseSemaphore(sem->handle, 1, NULL); } void ipc_sem_decrement(ipc_sharedsemaphore *sem) { WaitForSingleObject(sem->handle, INFINITE); } int ipc_sem_try_decrement(ipc_sharedsemaphore *sem) { DWORD ret = WaitForSingleObject(sem->handle, 0); if (ret == WAIT_OBJECT_0) return 1; return 0; } #else // !defined(_WIN32) int ipc_mem_open_existing(ipc_sharedmemory *mem) { mem->fd = shm_open(mem->name, O_RDWR, 0755); if (mem->fd < 0) return -1; mem->data = (unsigned char *)mmap(NULL, mem->size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->fd, 0); if (!mem->data) return -1; // file descriptor can close after mmap close(mem->fd); return 0; } int ipc_mem_create(ipc_sharedmemory *mem) { int ret; ret = shm_unlink(mem->name); if (ret < 0 && errno != ENOENT) return -1; mem->fd = shm_open(mem->name, O_CREAT | O_RDWR, 0755); if (mem->fd < 0) return -1; ftruncate(mem->fd, mem->size); mem->data = (unsigned char *)mmap(NULL, mem->size, PROT_READ | PROT_WRITE, MAP_SHARED, mem->fd, 0); if (!mem->data) return -1; // file descriptor can close after mmap close(mem->fd); return 0; } void ipc_mem_close(ipc_sharedmemory *mem, bool unlink) { if (mem->data != NULL) { munmap(mem->data, mem->size); // close(mem->fd); if (unlink) shm_unlink(mem->name); } IPC_FREE(mem->name); mem->name = NULL; mem->size = 0; } int ipc_sem_create(ipc_sharedsemaphore *sem, int initialvalue) { sem->semaphore = sem_open(sem->name, O_CREAT, 0700, initialvalue); if (sem->semaphore == SEM_FAILED) return -1; return 0; } void ipc_sem_close(ipc_sharedsemaphore *sem) { sem_close(sem->semaphore); sem_unlink(sem->name); IPC_FREE(sem->name); } void ipc_sem_increment(ipc_sharedsemaphore *sem) { sem_post(sem->semaphore); } void ipc_sem_decrement(ipc_sharedsemaphore *sem) { sem_wait(sem->semaphore); } int ipc_sem_try_decrement(ipc_sharedsemaphore *sem) { int res = sem_trywait(sem->semaphore); if (res == 0) return 1; return 0; } #endif // !_WIN32 #endif // IPC_IMPLEMENTATION #endif // IPC_H_INCLUDE_GUARD ================================================ FILE: Bridge/renderdoc/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) # set(CMAKE_GENERATOR_PLATFORM x64) project(RenderDocWrapper) SET(CMAKE_BUILD_TYPE Release) SET(BUILD_SHARED_LIBS ON) add_library(renderdoc_wrapper renderdoc_wrapper.c) if(NOT WIN32) target_link_libraries(renderdoc_wrapper dl) endif() install(TARGETS renderdoc_wrapper CONFIGURATIONS Release DESTINATION ${PROJECT_SOURCE_DIR}) ================================================ FILE: Bridge/renderdoc/__init__.py ================================================ import subprocess import os import ctypes import platform src_dir = os.path.abspath(os.path.dirname(__file__)) library = 'librenderdoc_wrapper.so' if platform.system() == 'Windows': library = 'renderdoc_wrapper.dll' if platform.system() == 'Darwin': library = 'librenderdoc_wrapper.dylib' renderdoc = ctypes.CDLL(os.path.join(src_dir, library)) capture_start = renderdoc['capture_start'] capture_start.restype = None capture_end = renderdoc['capture_end'] capture_end.restype = None ================================================ FILE: Bridge/renderdoc/build.py ================================================ import subprocess import os import platform src_dir = os.path.abspath(os.path.dirname(__file__)) build_dir = os.path.join(src_dir, '.build') try: os.mkdir(build_dir) except: pass if platform.system() == 'Windows': #Multi-config generators, like Visual Studio subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir) else: #Single-config generators subprocess.check_call(['cmake', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir) subprocess.check_call(['cmake', '--install', '.'], cwd=build_dir) ================================================ FILE: Bridge/renderdoc/renderdoc_app.h ================================================ /****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2019-2021 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #pragma once ////////////////////////////////////////////////////////////////////////////////////////////////// // // Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html // #if !defined(RENDERDOC_NO_STDINT) #include #endif #if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define RENDERDOC_CC __cdecl #elif defined(__linux__) #define RENDERDOC_CC #elif defined(__APPLE__) #define RENDERDOC_CC #else #error "Unknown platform" #endif #ifdef __cplusplus extern "C" { #endif ////////////////////////////////////////////////////////////////////////////////////////////////// // Constants not used directly in below API // This is a GUID/magic value used for when applications pass a path where shader debug // information can be found to match up with a stripped shader. // the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue = // RENDERDOC_ShaderDebugMagicValue_value #define RENDERDOC_ShaderDebugMagicValue_struct \ { \ 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ } // as an alternative when you want a byte array (assuming x86 endianness): #define RENDERDOC_ShaderDebugMagicValue_bytearray \ { \ 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \ } // truncated version when only a uint64_t is available (e.g. Vulkan tags): #define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL ////////////////////////////////////////////////////////////////////////////////////////////////// // RenderDoc capture options // typedef enum RENDERDOC_CaptureOption { // Allow the application to enable vsync // // Default - enabled // // 1 - The application can enable or disable vsync at will // 0 - vsync is force disabled eRENDERDOC_Option_AllowVSync = 0, // Allow the application to enable fullscreen // // Default - enabled // // 1 - The application can enable or disable fullscreen at will // 0 - fullscreen is force disabled eRENDERDOC_Option_AllowFullscreen = 1, // Record API debugging events and messages // // Default - disabled // // 1 - Enable built-in API debugging features and records the results into // the capture, which is matched up with events on replay // 0 - no API debugging is forcibly enabled eRENDERDOC_Option_APIValidation = 2, eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum // Capture CPU callstacks for API events // // Default - disabled // // 1 - Enables capturing of callstacks // 0 - no callstacks are captured eRENDERDOC_Option_CaptureCallstacks = 3, // When capturing CPU callstacks, only capture them from drawcalls. // This option does nothing without the above option being enabled // // Default - disabled // // 1 - Only captures callstacks for drawcall type API events. // Ignored if CaptureCallstacks is disabled // 0 - Callstacks, if enabled, are captured for every event. eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4, // Specify a delay in seconds to wait for a debugger to attach, after // creating or injecting into a process, before continuing to allow it to run. // // 0 indicates no delay, and the process will run immediately after injection // // Default - 0 seconds // eRENDERDOC_Option_DelayForDebugger = 5, // Verify buffer access. This includes checking the memory returned by a Map() call to // detect any out-of-bounds modification, as well as initialising buffers with undefined contents // to a marker value to catch use of uninitialised memory. // // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do // not do the same kind of interception & checking and undefined contents are really undefined. // // Default - disabled // // 1 - Verify buffer access // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in // RenderDoc. eRENDERDOC_Option_VerifyBufferAccess = 6, // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites. // This option now controls the filling of uninitialised buffers with 0xdddddddd which was // previously always enabled eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess, // Hooks any system API calls that create child processes, and injects // RenderDoc into them recursively with the same options. // // Default - disabled // // 1 - Hooks into spawned child processes // 0 - Child processes are not hooked by RenderDoc eRENDERDOC_Option_HookIntoChildren = 7, // By default RenderDoc only includes resources in the final capture necessary // for that frame, this allows you to override that behaviour. // // Default - disabled // // 1 - all live resources at the time of capture are included in the capture // and available for inspection // 0 - only the resources referenced by the captured frame are included eRENDERDOC_Option_RefAllResources = 8, // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or // getting it will be ignored, to allow compatibility with older versions. // In v1.1 the option acts as if it's always enabled. // // By default RenderDoc skips saving initial states for resources where the // previous contents don't appear to be used, assuming that writes before // reads indicate previous contents aren't used. // // Default - disabled // // 1 - initial contents at the start of each captured frame are saved, even if // they are later overwritten or cleared before being used. // 0 - unless a read is detected, initial contents will not be saved and will // appear as black or empty data. eRENDERDOC_Option_SaveAllInitials = 9, // In APIs that allow for the recording of command lists to be replayed later, // RenderDoc may choose to not capture command lists before a frame capture is // triggered, to reduce overheads. This means any command lists recorded once // and replayed many times will not be available and may cause a failure to // capture. // // NOTE: This is only true for APIs where multithreading is difficult or // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option // and always capture all command lists since the API is heavily oriented // around it and the overheads have been reduced by API design. // // 1 - All command lists are captured from the start of the application // 0 - Command lists are only captured if their recording begins during // the period when a frame capture is in progress. eRENDERDOC_Option_CaptureAllCmdLists = 10, // Mute API debugging output when the API validation mode option is enabled // // Default - enabled // // 1 - Mute any API debug messages from being displayed or passed through // 0 - API debugging is displayed as normal eRENDERDOC_Option_DebugOutputMute = 11, // Option to allow vendor extensions to be used even when they may be // incompatible with RenderDoc and cause corrupted replays or crashes. // // Default - inactive // // No values are documented, this option should only be used when absolutely // necessary as directed by a RenderDoc developer. eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12, } RENDERDOC_CaptureOption; // Sets an option that controls how RenderDoc behaves on capture. // // Returns 1 if the option and value are valid // Returns 0 if either is invalid and the option is unchanged typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val); typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val); // Gets the current value of an option as a uint32_t // // If the option is invalid, 0xffffffff is returned typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt); // Gets the current value of an option as a float // // If the option is invalid, -FLT_MAX is returned typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt); typedef enum RENDERDOC_InputButton { // '0' - '9' matches ASCII values eRENDERDOC_Key_0 = 0x30, eRENDERDOC_Key_1 = 0x31, eRENDERDOC_Key_2 = 0x32, eRENDERDOC_Key_3 = 0x33, eRENDERDOC_Key_4 = 0x34, eRENDERDOC_Key_5 = 0x35, eRENDERDOC_Key_6 = 0x36, eRENDERDOC_Key_7 = 0x37, eRENDERDOC_Key_8 = 0x38, eRENDERDOC_Key_9 = 0x39, // 'A' - 'Z' matches ASCII values eRENDERDOC_Key_A = 0x41, eRENDERDOC_Key_B = 0x42, eRENDERDOC_Key_C = 0x43, eRENDERDOC_Key_D = 0x44, eRENDERDOC_Key_E = 0x45, eRENDERDOC_Key_F = 0x46, eRENDERDOC_Key_G = 0x47, eRENDERDOC_Key_H = 0x48, eRENDERDOC_Key_I = 0x49, eRENDERDOC_Key_J = 0x4A, eRENDERDOC_Key_K = 0x4B, eRENDERDOC_Key_L = 0x4C, eRENDERDOC_Key_M = 0x4D, eRENDERDOC_Key_N = 0x4E, eRENDERDOC_Key_O = 0x4F, eRENDERDOC_Key_P = 0x50, eRENDERDOC_Key_Q = 0x51, eRENDERDOC_Key_R = 0x52, eRENDERDOC_Key_S = 0x53, eRENDERDOC_Key_T = 0x54, eRENDERDOC_Key_U = 0x55, eRENDERDOC_Key_V = 0x56, eRENDERDOC_Key_W = 0x57, eRENDERDOC_Key_X = 0x58, eRENDERDOC_Key_Y = 0x59, eRENDERDOC_Key_Z = 0x5A, // leave the rest of the ASCII range free // in case we want to use it later eRENDERDOC_Key_NonPrintable = 0x100, eRENDERDOC_Key_Divide, eRENDERDOC_Key_Multiply, eRENDERDOC_Key_Subtract, eRENDERDOC_Key_Plus, eRENDERDOC_Key_F1, eRENDERDOC_Key_F2, eRENDERDOC_Key_F3, eRENDERDOC_Key_F4, eRENDERDOC_Key_F5, eRENDERDOC_Key_F6, eRENDERDOC_Key_F7, eRENDERDOC_Key_F8, eRENDERDOC_Key_F9, eRENDERDOC_Key_F10, eRENDERDOC_Key_F11, eRENDERDOC_Key_F12, eRENDERDOC_Key_Home, eRENDERDOC_Key_End, eRENDERDOC_Key_Insert, eRENDERDOC_Key_Delete, eRENDERDOC_Key_PageUp, eRENDERDOC_Key_PageDn, eRENDERDOC_Key_Backspace, eRENDERDOC_Key_Tab, eRENDERDOC_Key_PrtScrn, eRENDERDOC_Key_Pause, eRENDERDOC_Key_Max, } RENDERDOC_InputButton; // Sets which key or keys can be used to toggle focus between multiple windows // // If keys is NULL or num is 0, toggle keys will be disabled typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num); // Sets which key or keys can be used to capture the next frame // // If keys is NULL or num is 0, captures keys will be disabled typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num); typedef enum RENDERDOC_OverlayBits { // This single bit controls whether the overlay is enabled or disabled globally eRENDERDOC_Overlay_Enabled = 0x1, // Show the average framerate over several seconds as well as min/max eRENDERDOC_Overlay_FrameRate = 0x2, // Show the current frame number eRENDERDOC_Overlay_FrameNumber = 0x4, // Show a list of recent captures, and how many captures have been made eRENDERDOC_Overlay_CaptureList = 0x8, // Default values for the overlay mask eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate | eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList), // Enable all bits eRENDERDOC_Overlay_All = ~0U, // Disable all bits eRENDERDOC_Overlay_None = 0, } RENDERDOC_OverlayBits; // returns the overlay bits that have been set typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)(); // sets the overlay bits with an and & or mask typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or); // this function will attempt to remove RenderDoc's hooks in the application. // // Note: that this can only work correctly if done immediately after // the module is loaded, before any API work happens. RenderDoc will remove its // injected hooks and shut down. Behaviour is undefined if this is called // after any API functions have been called, and there is still no guarantee of // success. typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)(); // DEPRECATED: compatibility for code compiled against pre-1.4.1 headers. typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown; // This function will unload RenderDoc's crash handler. // // If you use your own crash handler and don't want RenderDoc's handler to // intercede, you can call this function to unload it and any unhandled // exceptions will pass to the next handler. typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)(); // Sets the capture file path template // // pathtemplate is a UTF-8 string that gives a template for how captures will be named // and where they will be saved. // // Any extension is stripped off the path, and captures are saved in the directory // specified, and named with the filename and the frame number appended. If the // directory does not exist it will be created, including any parent directories. // // If pathtemplate is NULL, the template will remain unchanged // // Example: // // SetCaptureFilePathTemplate("my_captures/example"); // // Capture #1 -> my_captures/example_frame123.rdc // Capture #2 -> my_captures/example_frame456.rdc typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate); // returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)(); // DEPRECATED: compatibility for code compiled against pre-1.1.2 headers. typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate; typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate; // returns the number of captures that have been made typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)(); // This function returns the details of a capture, by index. New captures are added // to the end of the list. // // filename will be filled with the absolute path to the capture file, as a UTF-8 string // pathlength will be written with the length in bytes of the filename string // timestamp will be written with the time of the capture, in seconds since the Unix epoch // // Any of the parameters can be NULL and they'll be skipped. // // The function will return 1 if the capture index is valid, or 0 if the index is invalid // If the index is invalid, the values will be unchanged // // Note: when captures are deleted in the UI they will remain in this list, so the // capture path may not exist anymore. typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename, uint32_t *pathlength, uint64_t *timestamp); // Sets the comments associated with a capture file. These comments are displayed in the // UI program when opening. // // filePath should be a path to the capture file to add comments to. If set to NULL or "" // the most recent capture file created made will be used instead. // comments should be a NULL-terminated UTF-8 string to add as comments. // // Any existing comments will be overwritten. typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath, const char *comments); // returns 1 if the RenderDoc UI is connected to this application, 0 otherwise typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)(); // DEPRECATED: compatibility for code compiled against pre-1.1.1 headers. // This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for // backwards compatibility with old code, it is castable either way since it's ABI compatible // as the same function pointer type. typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected; // This function will launch the Replay UI associated with the RenderDoc library injected // into the running application. // // if connectTargetControl is 1, the Replay UI will be launched with a command line parameter // to connect to this application // cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open // if cmdline is NULL, the command line will be empty. // // returns the PID of the replay UI if successful, 0 if not successful. typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl, const char *cmdline); // RenderDoc can return a higher version than requested if it's backwards compatible, // this function returns the actual version returned. If a parameter is NULL, it will be // ignored and the others will be filled out. typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch); ////////////////////////////////////////////////////////////////////////// // Capturing functions // // A device pointer is a pointer to the API's root handle. // // This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc typedef void *RENDERDOC_DevicePointer; // A window handle is the OS's native window handle // // This would be an HWND, GLXDrawable, etc typedef void *RENDERDOC_WindowHandle; // A helper macro for Vulkan, where the device handle cannot be used directly. // // Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use. // // Specifically, the value needed is the dispatch table pointer, which sits as the first // pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and // indirect once. #define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst))) // This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will // respond to keypresses. Neither parameter can be NULL typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); // capture the next frame on whichever window and API is currently considered active typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)(); // capture the next N frames on whichever window and API is currently considered active typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames); // When choosing either a device pointer or a window handle to capture, you can pass NULL. // Passing NULL specifies a 'wildcard' match against anything. This allows you to specify // any API rendering to a specific window, or a specific API instance rendering to any window, // or in the simplest case of one window and one API, you can just pass NULL for both. // // In either case, if there are two or more possible matching (device,window) pairs it // is undefined which one will be captured. // // Note: for headless rendering you can pass NULL for the window handle and either specify // a device pointer or leave it NULL as above. // Immediately starts capturing API calls on the specified device pointer and window handle. // // If there is no matching thing to capture (e.g. no supported API has been initialised), // this will do nothing. // // The results are undefined (including crashes) if two captures are started overlapping, // even on separate devices and/oror windows. typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); // Returns whether or not a frame capture is currently ongoing anywhere. // // This will return 1 if a capture is ongoing, and 0 if there is no capture running typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)(); // Ends capturing immediately. // // This will return 1 if the capture succeeded, and 0 if there was an error capturing. typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); // Ends capturing immediately and discard any data stored without saving to disk. // // This will return 1 if the capture was discarded, and 0 if there was an error or no capture // was in progress typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device, RENDERDOC_WindowHandle wndHandle); ////////////////////////////////////////////////////////////////////////////////////////////////// // RenderDoc API versions // // RenderDoc uses semantic versioning (http://semver.org/). // // MAJOR version is incremented when incompatible API changes happen. // MINOR version is incremented when functionality is added in a backwards-compatible manner. // PATCH version is incremented when backwards-compatible bug fixes happen. // // Note that this means the API returned can be higher than the one you might have requested. // e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned // instead of 1.0.0. You can check this with the GetAPIVersion entry point typedef enum RENDERDOC_Version { eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00 eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01 eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02 eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00 eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01 eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02 eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00 eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00 eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00 eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01 } RENDERDOC_Version; // API version changelog: // // 1.0.0 - initial release // 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered // by keypress or TriggerCapture, instead of Start/EndFrameCapture. // 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation // 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new // function pointer is added to the end of the struct, the original layout is identical // 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote // replay/remote server concept in replay UI) // 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these // are captures and not debug logging files. This is the first API version in the v1.0 // branch. // 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be // displayed in the UI program on load. // 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions // which allows users to opt-in to allowing unsupported vendor extensions to function. // Should be used at the user's own risk. // Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to // eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to // 0xdddddddd of uninitialised buffer contents. // 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop // capturing without saving anything to disk. // 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening typedef struct RENDERDOC_API_1_4_1 { pRENDERDOC_GetAPIVersion GetAPIVersion; pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32; pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32; pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32; pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32; pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys; pRENDERDOC_SetCaptureKeys SetCaptureKeys; pRENDERDOC_GetOverlayBits GetOverlayBits; pRENDERDOC_MaskOverlayBits MaskOverlayBits; // Shutdown was renamed to RemoveHooks in 1.4.1. // These unions allow old code to continue compiling without changes union { pRENDERDOC_Shutdown Shutdown; pRENDERDOC_RemoveHooks RemoveHooks; }; pRENDERDOC_UnloadCrashHandler UnloadCrashHandler; // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2. // These unions allow old code to continue compiling without changes union { // deprecated name pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate; // current name pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate; }; union { // deprecated name pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate; // current name pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate; }; pRENDERDOC_GetNumCaptures GetNumCaptures; pRENDERDOC_GetCapture GetCapture; pRENDERDOC_TriggerCapture TriggerCapture; // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1. // This union allows old code to continue compiling without changes union { // deprecated name pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected; // current name pRENDERDOC_IsTargetControlConnected IsTargetControlConnected; }; pRENDERDOC_LaunchReplayUI LaunchReplayUI; pRENDERDOC_SetActiveWindow SetActiveWindow; pRENDERDOC_StartFrameCapture StartFrameCapture; pRENDERDOC_IsFrameCapturing IsFrameCapturing; pRENDERDOC_EndFrameCapture EndFrameCapture; // new function in 1.1.0 pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture; // new function in 1.2.0 pRENDERDOC_SetCaptureFileComments SetCaptureFileComments; // new function in 1.4.0 pRENDERDOC_DiscardFrameCapture DiscardFrameCapture; } RENDERDOC_API_1_4_1; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_0; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_1; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_0_2; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_0; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_1; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_1_2; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_2_0; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_3_0; typedef RENDERDOC_API_1_4_1 RENDERDOC_API_1_4_0; ////////////////////////////////////////////////////////////////////////////////////////////////// // RenderDoc API entry point // // This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available. // // The name is the same as the typedef - "RENDERDOC_GetAPI" // // This function is not thread safe, and should not be called on multiple threads at once. // Ideally, call this once as early as possible in your application's startup, before doing // any API work, since some configuration functionality etc has to be done also before // initialising any APIs. // // Parameters: // version is a single value from the RENDERDOC_Version above. // // outAPIPointers will be filled out with a pointer to the corresponding struct of function // pointers. // // Returns: // 1 - if the outAPIPointers has been filled with a pointer to the API struct requested // 0 - if the requested version is not supported or the arguments are invalid. // typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers); #ifdef __cplusplus } // extern "C" #endif ================================================ FILE: Bridge/renderdoc/renderdoc_wrapper.c ================================================ #include "renderdoc_app.h" #include #ifdef _WIN32 #define EXPORT __declspec( dllexport ) #else #define EXPORT __attribute__ ((visibility ("default"))) #endif RENDERDOC_API_1_4_1* API = NULL; #ifdef _WIN32 #include void init() { if (API) return; HMODULE module = GetModuleHandleA("renderdoc.dll"); if (module) { pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(module, "RENDERDOC_GetAPI"); int result = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void **)&API); if (result != 1) API = NULL; } } #else #include void init() { if (API) return; void* module = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD); if (module) { pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)dlsym(module, "RENDERDOC_GetAPI"); int result = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_4_1, (void **)&API); if (result != 1) API = NULL; } } #endif EXPORT void capture_start() { init(); if (API) API->StartFrameCapture(NULL, NULL); } EXPORT void capture_end() { init(); if (API) API->EndFrameCapture(NULL, NULL); } ================================================ FILE: LICENSE ================================================ MIT License Malt - Copyright (c) 2020-2022 BNPR, Miguel Pozo and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: LICENSE - DEPENDENCIES ================================================ Malt: PyOpenGL BSD License http://pyopengl.sourceforge.net https://github.com/mcfletch/pyopengl/blob/master/license.txt pyGLFW MIT License https://github.com/FlorianRhiem/pyGLFW https://github.com/FlorianRhiem/pyGLFW/blob/master/LICENSE.txt GLFW zlib License https://www.glfw.org/ https://github.com/glfw/glfw/blob/master/LICENSE.md mcpp MIT License https://github.com/pragma37/mcpp https://github.com/pragma37/mcpp/blob/master/LICENSE multipledispatch BSD License http://github.com/mrocklin/multipledispatch/ https://github.com/mrocklin/multipledispatch/blob/master/LICENSE.txt numpy BSD License https://www.numpy.org https://github.com/numpy/numpy/blob/main/LICENSE.txt https://github.com/numpy/numpy/blob/main/LICENSES_bundled.txt pyrr BSD License https://github.com/adamlwgriffiths/Pyrr https://github.com/adamlwgriffiths/Pyrr/blob/master/LICENSE six MIT License https://github.com/benjaminp/six https://github.com/benjaminp/six/blob/master/LICENSE GLSLParser: PEGTL Boost Software License https://github.com/taocpp/PEGTL https://github.com/taocpp/PEGTL/blob/main/LICENSE_1_0.txt rapidjson MIT License https://github.com/Tencent/rapidjson https://github.com/Tencent/rapidjson/blob/master/license.txt Bridge: ipc The Unlicense https://github.com/jarikomppa/ipc https://github.com/jarikomppa/ipc/blob/master/LICENSE renderdoc (renderdoc_app.h) MIT License https://renderdoc.org/ https://github.com/baldurk/renderdoc/blob/v1.x/LICENSE.md BlenderMalt: blender GNU GPL https://blender.org https://github.com/blender/blender/blob/master/doc/license/GPL3-license.txt MikkTSpace zlib License http://mikktspace.com/ https://github.com/mmikk/MikkTSpace/blob/master/mikktspace.h ================================================ FILE: LICENSE - DEPENDENCIES (FULL TEXT) ================================================ Malt: ******************************************************************************** PyOpenGL BSD License http://pyopengl.sourceforge.net NOTE: THIS SOFTWARE IS NOT FAULT TOLERANT AND SHOULD NOT BE USED IN ANY SITUATION ENDANGERING HUMAN LIFE OR PROPERTY. OpenGL-ctypes License Copyright (c) 2005-2014, Michael C. Fletcher and Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. The name of Michael C. Fletcher, or the name of any Contributor, may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OpenGL-ctypes includes code from the PyOpenGL 2.x series licensed under version 3 of the PyOpenGL License (BSD-style): PyOpenGL License (v3) PyOpenGL is based on PyOpenGL 1.5.5, Copyright © 1997-1998 by James Hugunin, Cambridge MA, USA, Thomas Schwaller, Munich, Germany and David Ascher, San Francisco CA, USA. Contributors to the PyOpenGL project in addition to those listed above include: * David Konerding * Soren Renner * Rene Liebscher * Randall Hopper * Michael Fletcher * Thomas Malik * Thomas Hamelryck * Jack Jansen * Michel Sanner * Tarn Weisner Burton * Andrew Cox * Rene Dudfield PyOpenGL is Copyright (c) 1997-1998, 2000-2006 by the contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OpenGL-ctypes includes code from the Pyglet project, licensed under the Pyglet License (BSD Style): Copyright (c) 2006-2008 Alex Holkner All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of pyglet nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OpenGL-ctypes may include source code from the GLE (GL Tubing and Extrusion) library, which is licensed under the license declared in OpenGL/DLLS/gle_COPYING.src if GLE is included in this distribution. Copyright notice follows: This software is owned by International Business Machines Corporation ("IBM"), or its subsidiaries or IBM's suppliers, and is copyrighted and licensed, not sold. IBM retains title to the software, and grants you a nonexclusive license for the software. OpenGL-ctypes may include source code from FreeGLUT (GL Utility Toolkit) library, which is licensed under the MIT/X-Consortium License, available in OpenGL/DLLS/freeglut_COPYING.txt if FreeGLUT is included in this distribution: Freeglut code without an explicit copyright is covered by the following copyright: Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name of Pawel W. Olszta shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Pawel W. Olszta. OpenGL-ctypes may include binary distributions of the Tk Togl widget, which is licensed under the following license/copyright notice (available in OpenGL/Tk/*/LICENSE if Togl is included). This software is copyrighted by Brian Paul (brian@mesa3d.org), Benjamin Bederson (bederson@cs.umd.edu), and Greg Couch (gregcouch@users.sourceforge.net). The following terms apply to all files associated with the software unless explicitly disclaimed in individual files. The authors hereby grant permission to use, copy, modify, distribute, and license this software and its documentation for any purpose, provided that existing copyright notices are retained in all copies and that this notice is included verbatim in any distributions. No written agreement, license, or royalty fee is required for any of the authorized uses. Modifications to this software may be copyrighted by their authors and need not follow the licensing terms described here, provided that the new terms are clearly indicated on the first page of each file where they apply. IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. OpenGL-ctypes includes an OS-Mesa platform driver which is (MIT Licensed), Copyright (c) 2012 Xu, Yuan : https://github.com/xuyuan/PyOSMesa http://opensource.org/licenses/MIT OpenGL-ctypes uses a table from the Chromium Regal project to provide constant:array-size mappings. Regal is: Copyright (c) 2011-2012 NVIDIA Corporation Copyright (c) 2011-2012 Cass Everitt Copyright (c) 2012 Scott Nations Copyright (c) 2012 Mathias Schott Copyright (c) 2012 Nigel Stewart All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************** pyGLFW MIT License https://github.com/FlorianRhiem/pyGLFW The MIT License (MIT) Copyright (c) 2013-2019 Florian Rhiem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************** GLFW zlib License https://www.glfw.org/ Copyright (c) 2002-2006 Marcus Geelnard Copyright (c) 2006-2019 Camilla Lwy This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ******************************************************************************** mcpp MIT License https://github.com/pragma37/mcpp /*- * Copyright (c) 1998, 2002-2008 Kiyoshi Matsui * All rights reserved. * * This software including the files in this directory is provided under * the following license. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ ******************************************************************************** multipledispatch BSD License http://github.com/mrocklin/multipledispatch/ Copyright (c) 2014 Matthew Rocklin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: a. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. b. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. c. Neither the name of multipledispatch nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ******************************************************************************** numpy BSD License https://www.numpy.org Copyright (c) 2005-2022, NumPy Developers. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the NumPy Developers nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The NumPy repository and source distributions bundle several libraries that are compatibly licensed. We list these here. Name: lapack-lite Files: numpy/linalg/lapack_lite/* License: BSD-3-Clause For details, see numpy/linalg/lapack_lite/LICENSE.txt Name: tempita Files: tools/npy_tempita/* License: MIT For details, see tools/npy_tempita/license.txt Name: dragon4 Files: numpy/core/src/multiarray/dragon4.c License: MIT For license text, see numpy/core/src/multiarray/dragon4.c Name: libdivide Files: numpy/core/include/numpy/libdivide/* License: Zlib For license text, see numpy/core/include/numpy/libdivide/LICENSE.txt ******************************************************************************** pyrr BSD License https://github.com/adamlwgriffiths/Pyrr In the original BSD license, both occurrences of the phrase "COPYRIGHT HOLDERS AND CONTRIBUTORS" in the disclaimer read "REGENTS AND CONTRIBUTORS". Here is the license template: Copyright (c) 2015, Adam Griffiths All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. ******************************************************************************** six MIT License https://github.com/benjaminp/six Copyright (c) 2010-2020 Benjamin Peterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************** GLSLParser: ******************************************************************************** PEGTL Boost Software License https://github.com/taocpp/PEGTL Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************** rapidjson MIT License https://github.com/Tencent/rapidjson Tencent is pleased to support the open source community by making RapidJSON available. Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. A copy of the MIT License is included in this file. Other dependencies and licenses: Open Source Software Licensed Under the BSD License: -------------------------------------------------------------------- The msinttypes r29 Copyright (c) 2006-2013 Alexander Chemeris All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Open Source Software Licensed Under the JSON License: -------------------------------------------------------------------- json.org Copyright (c) 2002 JSON.org All Rights Reserved. JSON_checker Copyright (c) 2002 JSON.org All Rights Reserved. Terms of the JSON License: --------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Terms of the MIT License: -------------------------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************** Bridge: ******************************************************************************** ipc The Unlicense https://github.com/jarikomppa/ipc This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ******************************************************************************** renderdoc (renderdoc_app.h) MIT License https://renderdoc.org/ # The MIT License (MIT) Copyright (c) 2015-2021 Baldur Karlsson Copyright (c) 2014 Crytek Copyright (c) 1998-2018 [Third party code and tools](docs/credits_acknowledgements.rst) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ******************************************************************************** BlenderMalt: ******************************************************************************** blender GNU GPL https://blender.org GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ******************************************************************************** MikkTSpace zlib License http://mikktspace.com/ Copyright (C) 2011 by Morten S. Mikkelsen This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ******************************************************************************** ******************************************************************************** ================================================ FILE: Malt/GL/GL.py ================================================ import collections import OpenGL #OpenGL.ERROR_LOGGING = False #OpenGL.FULL_LOGGING = False #OpenGL.ERROR_ON_COPY = False from OpenGL.GL import * from OpenGL.extensions import hasGLExtension if True: #For some reason PyOpenGL doesnt support the most common depth/stencil buffer by default ??? #https://sourceforge.net/p/pyopengl/bugs/223/ from OpenGL import images images.TYPE_TO_ARRAYTYPE[GL_UNSIGNED_INT_24_8] = GL_UNSIGNED_INT images.TIGHT_PACK_FORMATS[GL_UNSIGNED_INT_24_8] = 4 images.TYPE_TO_ARRAYTYPE[GL_HALF_FLOAT] = GL_HALF_FLOAT from OpenGL import arrays if arrays.ADT: arrays.GL_CONSTANT_TO_ARRAY_TYPE[GL_HALF_FLOAT] = arrays.ADT(GL_HALF_FLOAT, GLhalfARB) else: class GLhalfFloatArray(ArrayDatatype, ctypes.POINTER(GLhalfARB)): baseType = GLhalfARB typeConstant = GL_HALF_FLOAT arrays.GL_CONSTANT_TO_ARRAY_TYPE[GL_HALF_FLOAT] = GLhalfFloatArray NULL = None GL_ENUMS = {} GL_NAMES = {} if True: #create new scope to import OpenGL from OpenGL import GL for e in dir(GL): if e.startswith('GL_'): GL_ENUMS[getattr(GL, e)] = e GL_NAMES[e] = getattr(GL, e) class DrawQuery(): def __init__(self, query_type=GL_ANY_SAMPLES_PASSED): self.query = None self.query_type = query_type def begin_query(self): if self.query: glDeleteQueries(1, self.query) self.query = gl_buffer(GL_UNSIGNED_INT, 1) glGenQueries(1, self.query) glBeginQuery(self.query_type, self.query[0]) def end_query(self): glEndQuery(self.query_type) def begin_conditional_draw(self, wait_mode=GL_QUERY_WAIT): glBeginConditionalRender(self.query[0], wait_mode) def end_conditional_draw(self): glEndConditionalRender() def gl_buffer(type, size, data=None): types = { GL_BYTE : GLbyte, GL_UNSIGNED_BYTE : GLubyte, GL_SHORT : GLshort, GL_UNSIGNED_SHORT : GLushort, GL_INT : GLint, GL_UNSIGNED_INT : GLuint, #GL_HALF_FLOAT : GLhalfARB, GL_HALF_FLOAT : GLfloat, GL_FLOAT : GLfloat, GL_DOUBLE : GLdouble, GL_BOOL : GLboolean, } gl_type = (types[type] * size) if data: try: return gl_type(*data) except: return gl_type(data) else: return gl_type() def buffer_to_string(buffer): chars = [] for char in list(buffer): if chr(char) == '\0': break chars.append(chr(char)) return ''.join(chars) ================================================ FILE: Malt/GL/GLSLEval.py ================================================ def glsl_vector(convert, length, *args): unpacked_args = [] for arg in args: try: unpacked_args.extend([*arg]) except: unpacked_args.append(arg) unpacked_args = [convert(arg) for arg in unpacked_args] if len(unpacked_args) == 0: return (0.0)*length elif len(unpacked_args) == 1: return (unpacked_args[0],) * length else: assert(len(unpacked_args) == length) return tuple(unpacked_args) def _vec2(convert, *args): return glsl_vector(convert, 2, *args) def _vec3(convert, *args): return glsl_vector(convert, 3, *args) def _vec4(convert, *args): return glsl_vector(convert, 4, *args) def vec2(*args): return _vec2(float, *args) def vec3(*args): return _vec3(float, *args) def vec4(*args): return _vec4(float, *args) def ivec2(*args): return _vec2(int, *args) def ivec3(*args): return _vec3(int, *args) def ivec4(*args): return _vec4(int, *args) def uint(n): return max(int(n), 0) def uvec2(*args): return _vec2(uint, *args) def uvec3(*args): return _vec3(uint, *args) def uvec4(*args): return _vec4(uint, *args) def glsl_eval(str): true = True false = False return eval(str) ================================================ FILE: Malt/GL/GLSLParser/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(GLSLParser) set(CMAKE_CXX_STANDARD 17) add_subdirectory(external) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.bin") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) file(GLOB_RECURSE src_glob "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") add_executable(${PROJECT_NAME} ${src_glob}) target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") #target_precompile_headers(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/pch.h) target_link_libraries(${PROJECT_NAME} pegtl) target_include_directories(${PROJECT_NAME} PUBLIC ${RAPIDJSON_INCLUDE_DIR}) ================================================ FILE: Malt/GL/GLSLParser/build.py ================================================ import subprocess import os import platform current_dir = os.path.abspath(os.path.dirname(__file__)) build_dir = os.path.join(current_dir, '.build') try: os.mkdir(build_dir) except: pass if platform.system() == 'Windows': #Multi-config generators, like Visual Studio subprocess.check_call(['cmake', '-A', 'x64', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.', '--config', 'Release'], cwd=build_dir) else: #Single-config generators subprocess.check_call(['cmake', '..'], cwd=build_dir) subprocess.check_call(['cmake', '--build', '.'], cwd=build_dir) #subprocess.check_call(['cmake', '--install', '.', '--prefix', '.'], cwd=build_dir) ================================================ FILE: Malt/GL/GLSLParser/external/CMakeLists.txt ================================================ include(FetchContent) include(ExternalProject) FetchContent_Declare( pegtl GIT_REPOSITORY https://github.com/taocpp/PEGTL GIT_TAG 3.2.8 ) FetchContent_MakeAvailable(pegtl) FetchContent_Declare( rapidjson GIT_REPOSITORY https://github.com/Tencent/rapidjson GIT_TAG 24b5e7a8b27f42fa16b96fc70aade9106cf7102f ) set(RAPIDJSON_BUILD_DOC OFF CACHE INTERNAL "") set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE INTERNAL "") set(RAPIDJSON_BUILD_TESTS OFF CACHE INTERNAL "") FetchContent_MakeAvailable(rapidjson) FetchContent_GetProperties(rapidjson) set(RAPIDJSON_INCLUDE_DIR ${rapidjson_SOURCE_DIR}/include PARENT_SCOPE) ================================================ FILE: Malt/GL/GLSLParser/external/PEGTL-LICENSE ================================================ The MIT License (MIT) Copyright (c) 2007-2021 Dr. Colin Hirsch and Daniel Frey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Malt/GL/GLSLParser/external/rapidjson-LICENSE ================================================ Tencent is pleased to support the open source community by making RapidJSON available. Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. A copy of the MIT License is included in this file. Other dependencies and licenses: Open Source Software Licensed Under the BSD License: -------------------------------------------------------------------- The msinttypes r29 Copyright (c) 2006-2013 Alexander Chemeris All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Open Source Software Licensed Under the JSON License: -------------------------------------------------------------------- json.org Copyright (c) 2002 JSON.org All Rights Reserved. JSON_checker Copyright (c) 2002 JSON.org All Rights Reserved. Terms of the JSON License: --------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Terms of the MIT License: -------------------------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Malt/GL/GLSLParser/src/main.cpp ================================================ #include #include #include #include #include #include #include #include #include #include "rapidjson/prettywriter.h" using namespace tao::pegtl; #define STRING(string) TAO_PEGTL_STRING(string) #define ISTRING(string) TAO_PEGTL_ISTRING(string) #define KEYWORD(string) TAO_PEGTL_KEYWORD(string) struct line_comment : seq> {}; struct multiline_comment : seq> {}; struct preprocessor_directive : seq> {}; struct META; struct META_GLOBAL; struct _s_ : star>, sor> {}; struct LPAREN : one<'('> {}; struct RPAREN : one<')'> {}; struct LBRACE : one<'{'> {}; struct RBRACE : one<'}'> {}; struct LBRACKET : one<'['> {}; struct RBRACKET : one<']'> {}; struct COMMA : one<','> {}; struct END : one<';'> {}; struct STRUCT : KEYWORD("struct") {}; struct IDENTIFIER : identifier {}; struct TYPE : identifier {}; struct DIGITS : plus {}; struct PRECISION : sor {}; struct IO : sor {}; struct ARRAY_SIZE : seq {}; struct FILE_PATH : plus> {}; struct QUOTED_FILE_PATH : seq, FILE_PATH, one<'"'>> {}; struct LINE_DIRECTIVE : seq {}; struct _ms_ : star> {}; struct META_PROP_VALUE : star>, any> {}; struct META_PROP : seq>, IDENTIFIER, _ms_, one<'='>, _ms_, META_PROP_VALUE, _ms_, one<';'>> {}; struct META_PROPS : plus> {}; struct META_MEMBER : seq, _ms_, IDENTIFIER, _ms_, one<':'>, META_PROPS> {}; struct META_MEMBERS : plus> {}; struct META : seq {}; struct META_GLOBAL : seq {}; struct MEMBER : seq, _s_, TYPE, _s_, IDENTIFIER, _s_, opt, _s_, END> {}; struct MEMBERS : plus> {}; struct STRUCT_DEF : seq, STRUCT, _s_, IDENTIFIER, _s_, LBRACE, _s_, opt, _s_, RBRACE> {}; struct PARAMETER : seq, _s_, opt, _s_, TYPE, _s_, IDENTIFIER, _s_, opt> {}; struct PARAMETERS : list, seq<_s_, COMMA, _s_>> {}; struct FUNCTION_SIG : seq, _s_, RPAREN> {}; struct FUNCTION_DEC : seq, FUNCTION_SIG, _s_, LBRACE> {}; struct GLSL_GRAMMAR : star> {}; template using selector = parse_tree::selector < Rule, parse_tree::store_content::on < IDENTIFIER, TYPE, DIGITS, PRECISION, IO, ARRAY_SIZE, FILE_PATH, META_PROP_VALUE, META_PROP, META_MEMBER, META, META_GLOBAL, MEMBER, MEMBERS, STRUCT_DEF, PARAMETER, PARAMETERS, FUNCTION_SIG, FUNCTION_DEC > >; void print_nodes(parse_tree::node& node) { if(node.has_content()) { std::cout << node.type << " : " << node.string_view() << std::endl; } for(auto& child : node.children) { if(child) { print_nodes(*child); } } } template parse_tree::node* get(parse_tree::node* parent) { for(auto& child : parent->children) { if(child->is_type()) { return child.get(); } } return nullptr; } std::map get_meta_dict(parse_tree::node* meta, rapidjson::MemoryPoolAllocator<>& allocator) { using namespace rapidjson; std::map meta_dict = {}; if(meta) { for(auto& meta_member : meta->children) { std::string member_name = std::string(get(meta_member.get())->string_view()); Value member_meta = Value(kObjectType); for(auto& meta_prop : meta_member->children) { if(!meta_prop->is_type()) { continue; //Other type of child } std::string prop_name = std::string(get(meta_prop.get())->string_view()); std::string prop_value = std::string(get(meta_prop.get())->string_view()); member_meta.AddMember( Value(prop_name.c_str(), allocator), Value(prop_value.c_str(), allocator), allocator ); } meta_dict[member_name] = member_meta; } } return meta_dict; } std::string remove_extra_whitespace(const std::string& str) { int len = str.length(); std::string result; result.reserve(len); bool was_space = false; for(int i=0; i < len; i++) { bool is_space = isspace(str[i]); if(is_space) { if(!was_space) result += " "; } else { result += str[i]; } was_space = is_space; } return result; } int main(int argc, char* argv[]) { if(argc < 2) return 1; const char* path = argv[1]; file_input input(path); #ifdef _DEBUG { const std::size_t issues = analyze(); if(issues) return issues; } #endif //standard_trace(input); //input.restart(); auto root = parse_tree::parse(input); input.restart(); if(!root) return 1; //print_nodes(*root); //parse_tree::print_dot(std::cout, *root); using namespace rapidjson; Document json; json.Parse("{}"); json.AddMember("meta globals", Value(kObjectType), json.GetAllocator()); Value& metal_globals = json["meta globals"]; json.AddMember("structs", Value(kObjectType), json.GetAllocator()); Value& structs = json["structs"]; json.AddMember("functions", Value(kObjectType), json.GetAllocator()); Value& functions = json["functions"]; std::string current_file = ""; for(auto& child : root->children) { if(child->is_type()) { current_file = std::string(child->string_view()); } else if(child->is_type()) { auto meta_dict = get_meta_dict(child.get(), json.GetAllocator()); if(meta_dict.count("meta")) { metal_globals.AddMember(Value(current_file.c_str(), json.GetAllocator()), meta_dict["meta"], json.GetAllocator()); } } else if(child->is_type()) { std::string name = std::string(get(child.get())->string_view()); structs.AddMember(Value(name.c_str(), json.GetAllocator()), Value(kObjectType), json.GetAllocator()); Value& struct_def = structs[name.c_str()]; struct_def.AddMember("name", Value(name.c_str(), json.GetAllocator()), json.GetAllocator()); struct_def.AddMember("file", Value(current_file.c_str(), json.GetAllocator()), json.GetAllocator()); struct_def.AddMember("members", Value(kArrayType), json.GetAllocator()); parse_tree::node* meta = get(child.get()); auto meta_dict = get_meta_dict(meta, json.GetAllocator()); if(meta_dict.count("meta")) { struct_def.AddMember("meta", meta_dict["meta"], json.GetAllocator()); } else { struct_def.AddMember("meta", Value(kObjectType), json.GetAllocator()); } Value& members_array = struct_def["members"]; parse_tree::node* members = get(child.get()); if(!members) continue; for(auto& member : members->children) { std::string name = std::string(get(member.get())->string_view()); std::string type = std::string(get(member.get())->string_view()); int array_size = 0; parse_tree::node* array_size_node = get(member.get()); if(array_size_node) { std::string size = std::string(get(array_size_node)->string_view()); array_size = std::stoi(size); } Value member_def = Value(kObjectType); member_def.AddMember("name", Value(name.c_str(), json.GetAllocator()), json.GetAllocator()); member_def.AddMember("type", Value(type.c_str(), json.GetAllocator()), json.GetAllocator()); member_def.AddMember("size", Value(array_size), json.GetAllocator()); if(meta_dict.count(name)) { member_def.AddMember("meta", meta_dict[name], json.GetAllocator()); } else { member_def.AddMember("meta", Value(kObjectType), json.GetAllocator()); } members_array.PushBack(member_def, json.GetAllocator()); } } else if(child->is_type()) { parse_tree::node* signature = get(child.get()); std::string name = std::string(get(signature)->string_view()); std::string signature_str = std::string(signature->string_view()); signature_str = remove_extra_whitespace(signature_str); std::string key_name = name; if(functions.HasMember(key_name.c_str())) { key_name += " - " + signature_str; } std::string type = std::string(get(signature)->string_view()); functions.AddMember(Value(key_name.c_str(), json.GetAllocator()), Value(kObjectType), json.GetAllocator()); Value& function_dec = functions[key_name.c_str()]; function_dec.AddMember("name", Value(name.c_str(), json.GetAllocator()), json.GetAllocator()); function_dec.AddMember("type", Value(type.c_str(), json.GetAllocator()), json.GetAllocator()); function_dec.AddMember("file", Value(current_file.c_str(), json.GetAllocator()), json.GetAllocator()); function_dec.AddMember("signature", Value(signature_str.c_str(), json.GetAllocator()), json.GetAllocator()); function_dec.AddMember("parameters", Value(kArrayType), json.GetAllocator()); parse_tree::node* meta = get(child.get()); auto meta_dict = get_meta_dict(meta, json.GetAllocator()); if(meta_dict.count("meta")) { function_dec.AddMember("meta", meta_dict["meta"], json.GetAllocator()); } else { function_dec.AddMember("meta", Value(kObjectType), json.GetAllocator()); } Value& parameters_array = function_dec["parameters"]; parse_tree::node* parameters = get(signature); if(!parameters) continue; for(auto& parameter : parameters->children) { std::string name = std::string(get(parameter.get())->string_view()); std::string type = std::string(get(parameter.get())->string_view()); std::string io = "in"; int array_size = 0; parse_tree::node* array_size_node = get(parameter.get()); if(array_size_node) { std::string size = std::string(get(array_size_node)->string_view()); array_size = std::stoi(size); } parse_tree::node* io_node = get(parameter.get()); if(io_node) { io = std::string(io_node->string_view()); } Value parameter_dec = Value(kObjectType); parameter_dec.AddMember("name", Value(name.c_str(), json.GetAllocator()), json.GetAllocator()); parameter_dec.AddMember("type", Value(type.c_str(), json.GetAllocator()), json.GetAllocator()); parameter_dec.AddMember("size", Value(array_size), json.GetAllocator()); parameter_dec.AddMember("io", Value(io.c_str(), json.GetAllocator()), json.GetAllocator()); if(meta_dict.count(name)) { parameter_dec.AddMember("meta", meta_dict[name], json.GetAllocator()); } else { parameter_dec.AddMember("meta", Value(kObjectType), json.GetAllocator()); } parameters_array.PushBack(parameter_dec, json.GetAllocator()); } } } StringBuffer result; PrettyWriter writer(result); json.Accept(writer); std::cout << result.GetString(); return 0; } ================================================ FILE: Malt/GL/Mesh.py ================================================ import ctypes from Malt.GL.GL import * class Mesh(): def __init__(self, position, index, normal=None, tangent=None, uvs=[], colors=[]): self.position = None self.normal = None self.tangent = None self.uvs = [] self.colors = [] self.color_is_srgb = [False]*4 self.index_count = len(index) self.VAO = None self.EBO = gl_buffer(GL_INT, 1) index_buffer = index #make sure it's an uint32 c array if isinstance(index_buffer, ctypes.Array) == False or index_buffer._type_ != ctypes.c_uint32: index_buffer = gl_buffer(GL_UNSIGNED_INT, len(index), index) glGenBuffers(1, self.EBO) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO[0]) glBufferData(GL_ELEMENT_ARRAY_BUFFER, len(index_buffer) * 4, index_buffer, GL_STATIC_DRAW) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) def load_VBO(data): #make sure it's a float c array if isinstance(data, ctypes.Array) == False or data._type_ != ctypes.c_float: data = gl_buffer(GL_FLOAT, len(data), data) VBO = gl_buffer(GL_INT, 1) glGenBuffers(1, VBO) glBindBuffer(GL_ARRAY_BUFFER, VBO[0]) glBufferData(GL_ARRAY_BUFFER, len(data) * 4, data, GL_STATIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) return VBO self.position = load_VBO(position) if normal: self.normal = load_VBO(normal) if tangent: self.tangent = load_VBO(tangent) for uv in uvs: self.uvs.append(load_VBO(uv)) for color in colors: self.colors.append(load_VBO(color)) #Blender uses different OGL contexts, this function should only be called from the draw callback #https://developer.blender.org/T65208 def __load_VAO(self): self.VAO = gl_buffer(GL_INT, 1) glGenVertexArrays(1, self.VAO) glBindVertexArray(self.VAO[0]) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.EBO[0]) def bind_VBO(VBO, index, element_size): glBindBuffer(GL_ARRAY_BUFFER, VBO[0]) glEnableVertexAttribArray(index) glVertexAttribPointer(index, element_size, GL_FLOAT, GL_FALSE, 0, None) bind_VBO(self.position, 0, 3) if(self.normal): bind_VBO(self.normal, 1, 3) if(self.tangent): bind_VBO(self.tangent, 2, 4) max_uv = 4 uv0_index = 3 color0_index = uv0_index + max_uv for i, uv in enumerate(self.uvs): assert(i < max_uv) bind_VBO(uv, uv0_index + i, 2) for i, color in enumerate(self.colors): bind_VBO(color, color0_index + i, 4) glBindVertexArray(0) def bind(self): if self.VAO is None: self.__load_VAO() glBindVertexArray(self.VAO[0]) def draw(self, bind=True): if bind: self.bind() glDrawElements(GL_TRIANGLES, self.index_count, GL_UNSIGNED_INT, NULL) if bind: glBindVertexArray(0) def __del__(self): if self.VAO: glDeleteVertexArrays(1, self.VAO) def delete_buffer(buffer): if buffer: glDeleteBuffers(1, buffer) delete_buffer(self.EBO) delete_buffer(self.position) delete_buffer(self.normal) delete_buffer(self.tangent) for uv in self.uvs: delete_buffer(uv) for color in self.colors: delete_buffer(color) #Class for custom mesh loading #See Bridge/Mesh.py class MeshCustomLoad(Mesh): def __init__(self): self.position = None self.normal = None self.tangent = None self.uvs = [] self.colors = [] self.color_is_srgb = [False]*4 self.index_count = 0 self.VAO = None self.EBO = None ================================================ FILE: Malt/GL/RenderTarget.py ================================================ import ctypes from Malt.GL.GL import * class RenderTarget(): def __init__(self, targets=[], depth_stencil=None): self.FBO = gl_buffer(GL_INT, 1) glGenFramebuffers(1, self.FBO) self.targets = targets self.depth_stencil = depth_stencil self.resolution = None glBindFramebuffer(GL_FRAMEBUFFER, self.FBO[0]) attachments = gl_buffer(GL_INT, len(targets)) for i, target in enumerate(targets): if target: if self.resolution is None : self.resolution = target.resolution assert(target.resolution == self.resolution) if hasattr(target, 'attach'): target.attach(GL_COLOR_ATTACHMENT0+i) else: glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+i, GL_TEXTURE_2D, target.texture[0], 0) attachments[i] = GL_COLOR_ATTACHMENT0+i else: attachments[i] = GL_NONE glDrawBuffers(len(attachments), attachments) if depth_stencil: if self.resolution is None : self.resolution = depth_stencil.resolution assert(depth_stencil.resolution == self.resolution) attachment = { GL_DEPTH_STENCIL : GL_DEPTH_STENCIL_ATTACHMENT, GL_DEPTH_COMPONENT : GL_DEPTH_ATTACHMENT, GL_STENCIL : GL_STENCIL_ATTACHMENT, } if hasattr(depth_stencil, 'attach'): depth_stencil.attach(attachment[depth_stencil.format]) else: glFramebufferTexture2D(GL_FRAMEBUFFER, attachment[depth_stencil.format], GL_TEXTURE_2D, depth_stencil.texture[0], 0) glBindFramebuffer(GL_FRAMEBUFFER, 0) def bind(self): glBindFramebuffer(GL_FRAMEBUFFER, self.FBO[0]) glViewport(0, 0, self.resolution[0], self.resolution[1]) glDisable(GL_SCISSOR_TEST) def clear(self, colors=[], depth=None, stencil=None): self.bind() flags = 0 for i, color in enumerate(colors): function_map = { GL_INT : glClearBufferiv, GL_UNSIGNED_INT : glClearBufferuiv, GL_FLOAT : glClearBufferfv, GL_HALF_FLOAT : glClearBufferfv, GL_UNSIGNED_BYTE : glClearBufferfv, } target = self.targets[i] if target is None: continue if isinstance(color, ctypes.Array) == False: size = 1 try: size = len(color) except: pass color = gl_buffer(target.data_format, size, color) function_map[target.data_format](GL_COLOR, i, color) if depth: glClearDepth(depth) flags |= GL_DEPTH_BUFFER_BIT if stencil: glClearStencil(stencil) flags |= GL_STENCIL_BUFFER_BIT glClear(flags) def __del__(self): glDeleteFramebuffers(1, self.FBO) class TargetBase(): def attach(self, attachment): pass class ArrayLayerTarget(TargetBase): def __init__(self, texture_array, layer): self.texture_array = texture_array.texture[0] self.layer = layer self.resolution = texture_array.resolution self.internal_format = texture_array.internal_format self.format = texture_array.format self.data_format = texture_array.data_format def attach(self, attachment): glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, self.texture_array, 0, self.layer) ================================================ FILE: Malt/GL/Shader.py ================================================ import ctypes, os from Malt.GL.GL import * from Malt.Utils import LOG class Shader(): def __init__(self, vertex_source, pixel_source): if vertex_source and pixel_source: self.vertex_source = vertex_source self.pixel_source = pixel_source self.program, self.error = compile_gl_program(vertex_source, pixel_source) self.validator = glslang_validator(vertex_source,'vert') self.validator += glslang_validator(pixel_source,'frag') if self.validator == '': self.validator = None else: self.vertex_source = vertex_source self.pixel_source = pixel_source self.program = None self.error = 'NO SOURCE' self.validator = None self.uniforms = {} self.textures = {} self.uniform_blocks = {} if self.error == '': self.error = None self.uniforms = reflect_program_uniforms(self.program) texture_index = 0 for name, uniform in self.uniforms.items(): if uniform.is_sampler(): uniform.set_value(texture_index) texture_index += 1 self.textures[name] = None self.uniform_blocks = reflect_program_uniform_blocks(self.program) elif self.error != 'NO SOURCE': LOG.error(self.error) def bind(self): glUseProgram(self.program) for uniform in self.uniforms.values(): uniform.bind() for name, texture in self.textures.items(): if name not in self.uniforms: LOG.debug("Texture Uniform {} not found".format(name)) continue uniform = self.uniforms[name] glActiveTexture(GL_TEXTURE0 + uniform.value[0]) if texture: if hasattr(texture, 'bind'): texture.bind() else: #Then it's just a externally generated bind code glBindTexture(uniform.texture_type(), texture) else: glBindTexture(uniform.texture_type(), 0) def copy(self): new = Shader(None, None) new.vertex_source = self.vertex_source new.pixel_source = self.pixel_source new.program = self.program new.error = self.error for name, uniform in self.uniforms.items(): new.uniforms[name] = uniform.copy() for name, texture in self.textures.items(): new.textures[name] = texture for name, block in self.uniform_blocks.items(): new.uniform_blocks[name] = block return new def __del__(self): #TODO: Programs are shared between Shaders. Should refcount them pass class GLUniform(): def __init__(self, index, type, value, array_length=1): self.index = index self.type = type self.base_type, self.base_size = uniform_type_to_base_type_and_size(self.type) self.array_length = array_length self.set_function = uniform_type_set_function(self.type) self.value = None self.set_value(value) def is_sampler(self): return 'SAMPLER' in GL_ENUMS[self.type] def texture_type(self): if self.is_sampler() == False: return None table = { '1D_ARRAY': GL_TEXTURE_1D_ARRAY, '_1D': GL_TEXTURE_1D, '2D_ARRAY': GL_TEXTURE_2D_ARRAY, '_2D': GL_TEXTURE_2D, '_3D': GL_TEXTURE_3D, 'CUBE_MAP_ARRAY': GL_TEXTURE_CUBE_MAP_ARRAY, '_CUBE_MAP': GL_TEXTURE_CUBE_MAP, } name = GL_ENUMS[self.type] for key, value in table.items(): if key in name: return value def set_value(self, value): if self.base_type == GL_UNSIGNED_INT: try: value = max(0, value) except: value = [max(0, v) for v in value] self.value = gl_buffer(self.base_type, self.base_size * self.array_length, value) def set_buffer(self, buffer): self.value = buffer def bind(self, buffer=None): if buffer is None: buffer = self.value self.set_function(self.index, self.array_length, buffer) def copy(self): return GLUniform( self.index, self.type, self.value, self.array_length) class UBO(): BINDS = {} def __init__(self): self.size = 0 self.buffer = gl_buffer(GL_INT, 1) self.location = None glGenBuffers(1, self.buffer) def load_data(self, structure): self.size = ctypes.sizeof(structure) glBindBuffer(GL_UNIFORM_BUFFER, self.buffer[0]) glBufferData(GL_UNIFORM_BUFFER, self.size, ctypes.pointer(structure), GL_STREAM_DRAW) glBindBuffer(GL_UNIFORM_BUFFER, 0) def bind(self, uniform_block): location = uniform_block['bind'] if self.location != location or self.BINDS[location] != self: glBindBufferRange(GL_UNIFORM_BUFFER, location, self.buffer[0], 0, min(self.size, uniform_block['size'])) self.location = location self.BINDS[location] = self def __del__(self): glDeleteBuffers(1, self.buffer[0]) def shader_preprocessor(shader_source, include_directories=[], definitions=[]): import tempfile, subprocess, sys, platform if hasGLExtension('GL_ARB_bindless_texture'): definitions.append('GL_ARB_bindless_texture') shader_source = shader_source + '\n' tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(shader_source.encode('utf-8')) tmp.close() py_version = str(sys.version_info[0])+str(sys.version_info[1]) dependencies_path = os.path.join(os.path.dirname(__file__), '..', f'.Dependencies-{py_version}') mcpp = os.path.join(dependencies_path, f'mcpp-{platform.system()}') if platform.system() == "Linux": # NixOS and Guix System will cannot run the included mcpp binary result = subprocess.run(f'"{mcpp}"', shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if result.returncode != 0: # Search for system mcpp for directory in os.get_exec_path(): path = os.path.join(directory, "mcpp") if os.path.isfile(path) and os.access(path, os.X_OK): mcpp = path command = f'"{mcpp}"' command += ' -C' #keep comments for directory in include_directories: command += f' -I"{directory}"' for definition in definitions: command += f' -D"{definition}"' command += f' "{tmp.name}"' if platform.system() == 'Windows': #run with utf8 code page to support non-ascii paths command = f'CHCP 65001 > nul && {command}' try: result = subprocess.run(command, shell=True, stdout = subprocess.PIPE, stderr=subprocess.PIPE) if result.returncode != 0: raise Exception(result.stderr.decode('utf-8')) except: if platform.system() == 'Linux': #Linux seemingly removes exec permissions when unzipping (?) import stat os.chmod(mcpp, os.stat(mcpp).st_mode | stat.S_IEXEC) result = subprocess.run(command, shell=True, stdout = subprocess.PIPE, stderr=subprocess.PIPE) finally: os.remove(tmp.name) if result.returncode != 0: raise Exception(result.stderr.decode('utf-8')) else: return result.stdout.decode('utf-8') __LINE_DIRECTIVE_SUPPORT = None def directive_line_support(): global __LINE_DIRECTIVE_SUPPORT if __LINE_DIRECTIVE_SUPPORT is not None: return __LINE_DIRECTIVE_SUPPORT def try_compilation (line_directive): src = f''' #version 410 core #extension GL_ARB_shading_language_include : enable {line_directive} layout (location = 0) out vec4 OUT_COLOR; void main() {{ OUT_COLOR = vec4(1); }} ''' status = gl_buffer(GL_INT,1) shader = glCreateShader(GL_FRAGMENT_SHADER) glShaderSource(shader, src) glCompileShader(shader) glGetShaderiv(shader, GL_COMPILE_STATUS, status) glDeleteShader(shader) return status[0] != GL_FALSE if try_compilation('#line 1 "/ .A1a1!·$%&/()=?¿|@#~€/test.glsl"'): __LINE_DIRECTIVE_SUPPORT = 'FULL' elif try_compilation('#line 1 "/Basic test/test_1.glsl"'): __LINE_DIRECTIVE_SUPPORT = 'BASIC_STRING' elif try_compilation('#line 1 1'): __LINE_DIRECTIVE_SUPPORT = 'FILE_NUMBER' elif try_compilation('#line 1'): __LINE_DIRECTIVE_SUPPORT = 'LINE_NUMBER' else: __LINE_DIRECTIVE_SUPPORT = 'NONE' return __LINE_DIRECTIVE_SUPPORT def fix_line_directive_paths(source): support = directive_line_support() if support == 'FULL': return source include_paths = [] result = '' for line in source.splitlines(keepends=True): if line.startswith("#line") and '"' in line: start = line.index('"') end = line.index('"', start + 1) include_path = line[start:end+1] if support == 'BASIC_STRING': basic_string = ''.join([c for c in include_path if c.isalnum() or c in '/._ ']) line = line.replace(include_path, f'"{basic_string}"') elif support == 'FILE_NUMBER': if include_path not in include_paths: include_paths.append(include_path) line = line.replace(include_path, str(include_paths.index(include_path))) elif support == 'LINE_NUMBER': if '"' in line: line = line.split('"',1)[0] else: line = "\n" result += line return result def compile_gl_program(vertex, fragment): def finalize_source(source): bindless_setup = ''' #define OPTIONALLY_BINDLESS ''' if hasGLExtension('GL_ARB_bindless_texture'): bindless_setup = ''' #extension GL_ARB_bindless_texture : enable #define OPTIONALLY_BINDLESS layout(bindless_sampler) ''' import textwrap source = textwrap.dedent(f''' #version 450 core #extension GL_ARB_shading_language_include : enable {bindless_setup} #line 1 "src" ''') + source return fix_line_directive_paths(source) vertex = finalize_source(vertex) fragment = finalize_source(fragment) status = gl_buffer(GL_INT,1) info_log = gl_buffer(GL_BYTE, 1024) hash_src = vertex + fragment ''.splitlines() hash_src = ''.join([line for line in hash_src.splitlines(True) if line.startswith('#line') == False]) import hashlib, tempfile shader_hash = hashlib.sha1(hash_src.encode()).hexdigest() cache_folder = os.path.join(tempfile.gettempdir(), 'MALT_SHADERS_CACHE') os.makedirs(cache_folder, exist_ok=True) cache_path = os.path.join(cache_folder, shader_hash+'.bin') format_path = os.path.join(cache_folder, shader_hash+'.fmt') cache, format = None, None if os.path.exists(cache_path) and os.path.exists(format_path): from pathlib import Path Path(cache_path).touch() with open(cache_path, 'rb') as f: bin = f.read() cache = (GLubyte*len(bin)).from_buffer_copy(bin) Path(format_path).touch() with open(format_path, 'rb') as f: format = GLuint.from_buffer_copy(f.read()) program = glCreateProgram() error = "" if cache: try: glProgramBinary(program, format, cache, len(cache)) glGetProgramiv(program, GL_LINK_STATUS, status) if status[0] != GL_FALSE: return (program, error) except: #Program binary format can change on driver updates LOG.error(f"Failed to load cached program binary: {shader_hash} ({format})") def compile_shader (source, shader_type): shader = glCreateShader(shader_type) glShaderSource(shader, source) glCompileShader(shader) glGetShaderiv(shader, GL_COMPILE_STATUS, status) if status[0] == GL_FALSE: info_log = glGetShaderInfoLog(shader) nonlocal error error += 'SHADER COMPILER ERROR :\n' + buffer_to_string(info_log) return shader vertex_shader = compile_shader(vertex, GL_VERTEX_SHADER) fragment_shader = compile_shader(fragment, GL_FRAGMENT_SHADER) glAttachShader(program, vertex_shader) glAttachShader(program, fragment_shader) glLinkProgram(program) glDeleteShader(vertex_shader) glDeleteShader(fragment_shader) glGetProgramiv(program, GL_LINK_STATUS, status) if status[0] == GL_FALSE: info_log = glGetProgramInfoLog(program) error += 'SHADER LINKER ERROR :\n' + buffer_to_string(info_log) else: length = gl_buffer(GL_INT, 1) glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, length) format = gl_buffer(GL_UNSIGNED_INT, 1) buffer = gl_buffer(GL_UNSIGNED_BYTE, length[0]) glGetProgramBinary(program, length[0], NULL, format, buffer) with open(cache_path, 'wb') as f: f.write(buffer) with open(format_path, 'wb') as f: f.write(format) return (program, error) def reflect_program_uniforms(program): max_string_length = 128 string_length = gl_buffer(GL_INT, 1) uniform_type = gl_buffer(GL_INT, 1) uniform_name = gl_buffer(GL_BYTE, max_string_length) array_length = gl_buffer(GL_INT, 1) uniform_count = gl_buffer(GL_INT,1) glGetProgramiv(program, GL_ACTIVE_UNIFORMS, uniform_count) uniforms = {} query_indices = gl_buffer(GL_INT, uniform_count[0]) for i in range(len(query_indices)): query_indices[i] = i block_indices = gl_buffer(GL_INT, uniform_count[0]) glGetActiveUniformsiv(program, uniform_count[0], query_indices, GL_UNIFORM_BLOCK_INDEX, block_indices) for i in range(0, uniform_count[0]): if block_indices[i] != -1: continue #A built-in uniform or an Uniform Block glGetActiveUniform(program, i, max_string_length, string_length, array_length, uniform_type, uniform_name) name = buffer_to_string(uniform_name) #Uniform location can be different from index location = glGetUniformLocation(program, name) if location == -1: continue #A built-in uniform or an Uniform Block base_type, size = uniform_type_to_base_type_and_size(uniform_type[0]) #TODO: Should use glGetnUniform to support arrays gl_get = { GL_FLOAT : glGetUniformfv, GL_DOUBLE : glGetUniformdv, GL_INT : glGetUniformiv, GL_UNSIGNED_INT : glGetUniformuiv, GL_BOOL : glGetUniformiv, #TODO: check, } value = gl_buffer(base_type, size * array_length[0]) gl_get[base_type](program, location, value) if array_length[0] > 1: for i in range(array_length[0]): _name = '[{}]'.format(i).join(name.rsplit('[0]', 1)) _value = value[i*size:i*size+size] _location = glGetUniformLocation(program, _name) uniforms[_name] = GLUniform(_location, uniform_type[0], _value) else: uniforms[name] = GLUniform(location, uniform_type[0], value) return uniforms def uniform_type_to_base_type_and_size(type): base_types = { 'GL_FLOAT' : GL_FLOAT, 'GL_DOUBLE' : GL_DOUBLE, 'GL_INT' : GL_INT, 'GL_UNSIGNED_INT' : GL_UNSIGNED_INT, 'GL_BOOL' : GL_BOOL, #TODO: check docs, } gl_sizes = { 'VEC2' : 2, 'VEC3' : 3, 'VEC4' : 4, 'MAT2' : 4, 'MAT3' : 9, 'MAT4' : 16, } type_name = GL_ENUMS[type] if 'SAMPLER' in type_name or 'IMAGE' in type_name: return (GL_INT, 1) for base_name, base_type in base_types.items(): if type_name.startswith(base_name): for size_name, size in gl_sizes.items(): if size_name in type_name: return (base_type, size) return (base_type, 1) raise Exception(type_name, ' Uniform type not supported') def uniform_type_set_function(uniform_type): base_type, size = uniform_type_to_base_type_and_size(uniform_type) gl_types = { GL_FLOAT : 'fv', GL_DOUBLE : 'dv', GL_INT : 'iv', GL_UNSIGNED_INT : 'uiv', GL_BOOL : 'uiv', } gl_size = { 1 : '1', 2 : '2', 3 : '3', 4 : '4', #TODO: Matrix2 9 : 'Matrix3', 16: 'Matrix4' } function_name = 'glUniform' + gl_size[size] + gl_types[base_type] function = globals()[function_name] if size > 4: #is matrix def set_matrix_wrapper(location, count, value): function(location, count, GL_FALSE, value) return set_matrix_wrapper elif base_type == GL_BOOL and size > 1: function = globals()[f'glUniform{gl_size[size]}ui'] #uiv doesn't work on AMD (See #439) def bool_wrapper(location, count, value): function(location, *value) return bool_wrapper else: return function def reflect_program_uniform_blocks(program): block_count = gl_buffer(GL_INT,1) max_string_length = 128 block_name = gl_buffer(GL_BYTE, max_string_length) block_bind = gl_buffer(GL_INT, 1) block_size = gl_buffer(GL_INT, 1) glGetProgramiv(program, GL_ACTIVE_UNIFORM_BLOCKS, block_count) blocks = {} for i in range(0, block_count[0]): glGetActiveUniformBlockName(program, i, max_string_length, NULL, block_name) name = buffer_to_string(block_name) glUniformBlockBinding(program, i, i) # All Uniform Blocks are binded at 0 by default. :/ glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_BINDING, block_bind) glGetActiveUniformBlockiv(program, i, GL_UNIFORM_BLOCK_DATA_SIZE, block_size) blocks[name] = { 'bind' : block_bind[0], 'size' : block_size[0], 'name' : name, } return blocks USE_GLSLANG_VALIDATOR = False def glsl_reflection(code, root_paths=[]): import tempfile, subprocess, json, platform GLSLParser = os.path.join(os.path.dirname(__file__), 'GLSLParser', '.bin', 'GLSLParser') tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(code.encode('utf-8')) tmp.close() command = f'"{GLSLParser}" "{tmp.name}"' if platform.system() == 'Windows': #run with utf8 code page to support non-ascii paths command = f'CHCP 65001 > nul && {command}' try: json_string = subprocess.check_output(command, shell=True) except: import stat os.chmod(GLSLParser, os.stat(GLSLParser).st_mode | stat.S_IEXEC) json_string = subprocess.check_output(command, shell=True) os.remove(tmp.name) reflection = json.loads(json_string) def patch_meta(dic): for e in dic.values(): path = e['file'] if path in reflection['meta globals']: for k, v in reflection['meta globals'][path].items(): if k not in e['meta'].keys(): e['meta'][k] = v patch_meta(reflection['functions']) patch_meta(reflection['structs']) from Malt.GL.GLSLEval import glsl_eval def eval_meta(dict): meta_dict = dict['meta'] for k, v in meta_dict.items(): try: meta_dict[k] = glsl_eval(v) except: pass def meta_label(dict): label = dict['meta'].get('label') if label is None: label = dict['name'].replace('_',' ').title() dict['meta']['label'] = label for function in reflection['functions'].values(): eval_meta(function) meta_label(function) for parameter in function['parameters']: eval_meta(parameter) meta_label(parameter) for struct in reflection['structs'].values(): eval_meta(struct) meta_label(struct) for member in struct['members']: eval_meta(member) meta_label(member) def handle_paths(dic): for e in dic.values(): path = e['file'] if '.internal.' in path or '__internal__' in path: if 'internal' not in e['meta'].keys(): e['meta']['internal'] = True path = os.path.normpath(path) for root_path in root_paths: try: _path = os.path.relpath(e['file'], root_path) if len(_path) < len(path): path = _path except: pass e['file'] = path.replace('\\','/') handle_paths(reflection['structs']) handle_paths(reflection['functions']) functions = {} for key, function in reflection['functions'].items(): new_key = function['file'].replace('/',' - ').replace('.glsl',' - ') + function['name'] if function['name'].isupper() or function['name'].startswith('_'): new_key = function['name'] if new_key not in functions.keys(): functions[new_key] = [] functions[new_key].append(function) reflection['functions'] = {} for key, functions in functions.items(): if len(functions) == 1: reflection['functions'][key] = functions[0] else: for function in functions: new_key = key + ' - ' + function['signature'] reflection['functions'][new_key] = function reflection['subcategories'] = {} for key, function in reflection['functions'].items(): subcategory = function['meta'].get('subcategory') if subcategory: if subcategory not in reflection['subcategories'].keys(): reflection['subcategories'][subcategory] = [] reflection['subcategories'][subcategory].append(key) return reflection def glslang_validator(source, stage): if not USE_GLSLANG_VALIDATOR: return '' import subprocess import tempfile import os tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(source.replace('GL_ARB_shading_language_include', 'GL_GOOGLE_include_directive').encode('utf-8')) tmp.close() out = None try: out = subprocess.check_output(['glslangValidator','-S',stage,tmp.name]) if out != b'': out = out except subprocess.CalledProcessError as error: out = error.output except: pass os.unlink(tmp.name) if out: out = out.decode('utf-8') #Remove the first line since it's the path to a temp file out = out.split('\n')[1] stages = { 'vert' : 'VERTEX', 'frag' : 'PIXEL', } return '{} SHADER VALIDATION :\n{}'.format(stages[stage], out) else: return '' ================================================ FILE: Malt/GL/Texture.py ================================================ from Malt.GL.GL import * from Malt.GL import Mesh class Texture(): def __init__(self, resolution, internal_format=GL_RGB32F, data_format = None, data = NULL, wrap=GL_CLAMP_TO_EDGE, min_filter=GL_LINEAR, mag_filter=GL_LINEAR, pixel_format=None, build_mipmaps = False, anisotropy = False): self.resolution = resolution self.internal_format = internal_format self.format = pixel_format or internal_format_to_format(internal_format) self.data_format = data_format or internal_format_to_data_format(internal_format) self.channel_count = format_channels(self.format) self.channel_size = data_format_size(self.data_format) self.texture = gl_buffer(GL_INT, 1) glGenTextures(1, self.texture) glBindTexture(GL_TEXTURE_2D, self.texture[0]) glTexImage2D(GL_TEXTURE_2D, 0, self.internal_format, resolution[0], resolution[1], 0, self.format, self.data_format, data) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter) if build_mipmaps: glGenerateMipmap(GL_TEXTURE_2D) if anisotropy: level = glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, level) glBindTexture(GL_TEXTURE_2D, 0) def bind(self): glBindTexture(GL_TEXTURE_2D, self.texture[0]) def __del__(self): glDeleteTextures(1, self.texture) class TextureArray(): def __init__(self, resolution, length, internal_format=GL_RGB32F, data_format = GL_FLOAT, data = NULL, wrap=GL_CLAMP_TO_EDGE, min_filter=GL_LINEAR, mag_filter=GL_LINEAR): self.resolution = resolution self.internal_format = internal_format self.format = internal_format_to_format(internal_format) self.data_format = data_format self.length = length self.texture = gl_buffer(GL_INT, 1) glGenTextures(1, self.texture) glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture[0]) glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, self.internal_format, resolution[0], resolution[1], length); ''' glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, self.internal_format, resolution[0], resolution[1], length, 0, self.format, self.data_format, data) ''' glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, wrap) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, wrap) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, min_filter) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, mag_filter) glBindTexture(GL_TEXTURE_2D_ARRAY, 0) def bind(self): glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture[0]) def __del__(self): glDeleteTextures(1, self.texture) class CubeMap(): def __init__(self, resolution, internal_format=GL_RGB32F, data_format = GL_FLOAT, data = [NULL]*6, min_filter=GL_LINEAR, mag_filter=GL_LINEAR): self.resolution = resolution self.internal_format = internal_format self.format = internal_format_to_format(internal_format) self.data_format = data_format self.texture = gl_buffer(GL_INT, 1) glGenTextures(1, self.texture) glBindTexture(GL_TEXTURE_CUBE_MAP, self.texture[0]) for i in range(6): glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, self.internal_format, resolution[0], resolution[1], 0, self.format, self.data_format, NULL) #glGenerateMipmap(GL_TEXTURE_2D) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, min_filter) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, mag_filter) glBindTexture(GL_TEXTURE_CUBE_MAP, 0) def bind(self): glBindTexture(GL_TEXTURE_CUBE_MAP, self.texture[0]) def __del__(self): glDeleteTextures(1, self.texture) class CubeMapArray(): def __init__(self, resolution, length, internal_format=GL_RGB32F, data_format = GL_FLOAT, data = NULL, min_filter=GL_LINEAR, mag_filter=GL_LINEAR): self.resolution = resolution self.internal_format = internal_format self.format = internal_format_to_format(internal_format) self.data_format = data_format self.length = length self.texture = gl_buffer(GL_INT, 1) glGenTextures(1, self.texture) glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, self.texture[0]) glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, self.internal_format, resolution[0], resolution[1], length*6); ''' glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, self.internal_format, resolution[0], resolution[1], length, 0, self.format, self.data_format, data) ''' glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, min_filter) glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, mag_filter) glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0) def bind(self): glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, self.texture[0]) def __del__(self): glDeleteTextures(1, self.texture) class Gradient(): def __init__(self, data, resolution, internal_format=GL_RGBA32F, data_format = GL_FLOAT, nearest_interpolation = False): self.resolution = resolution self.internal_format = internal_format self.format = internal_format_to_format(internal_format) self.data_format = data_format self.texture = gl_buffer(GL_INT, 1) glGenTextures(1, self.texture) glBindTexture(GL_TEXTURE_1D, self.texture[0]) glTexImage1D(GL_TEXTURE_1D, 0, self.internal_format, resolution, 0, self.format, self.data_format, data) #glGenerateMipmap(GL_TEXTURE_2D) glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) interpolation = GL_NEAREST if nearest_interpolation else GL_LINEAR glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, interpolation) glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, interpolation) glBindTexture(GL_TEXTURE_1D, 0) def bind(self): glBindTexture(GL_TEXTURE_1D, self.texture[0]) def __del__(self): glDeleteTextures(1, self.texture) def internal_format_to_data_format(internal_format): name = GL_ENUMS[internal_format] table = { '32F' : GL_FLOAT, '16F' : GL_HALF_FLOAT, 'UI' : GL_UNSIGNED_INT, 'I' : GL_INT, } for key, value in table.items(): if name.endswith(key): return value return GL_UNSIGNED_BYTE def data_format_size(data_format): name = GL_ENUMS[data_format] table = { 'BYTE' : 1, 'SHORT' : 2, 'HALF' : 2, } for key, value in table.items(): if key in name: return value return 4 def internal_format_to_sampler_type(internal_format): table = { GL_UNSIGNED_BYTE : 'sampler2D', GL_FLOAT : 'sampler2D', GL_HALF_FLOAT : 'sampler2D', GL_INT : 'isampler2D', GL_UNSIGNED_INT : 'usampler2D' } return table[internal_format_to_data_format(internal_format)] def internal_format_to_vector_type(internal_format): table = { 'sampler2D' : 'vec4', 'isampler2D' : 'ivec4', 'usampler2D' : 'uvec4', } return table[internal_format_to_sampler_type(internal_format)] def internal_format_to_format(internal_format): name = GL_ENUMS[internal_format] table = { 'GL_RGBA' : GL_RGBA, 'GL_RGB' : GL_RGB, 'GL_RG' : GL_RG, 'GL_R' : GL_RED, 'GL_DEPTH_COMPONENT' : GL_DEPTH_COMPONENT, 'GL_DEPTH24_STENCIL8' : GL_DEPTH_STENCIL, 'GL_DEPTH32F_STENCIL8' : GL_DEPTH_STENCIL, 'GL_STENCIL' : GL_STENCIL, } for key, value in table.items(): if key in name: if name.endswith('I'): return GL_NAMES[GL_ENUMS[value] + '_INTEGER'] else: return value raise Exception(name, ' Texture format not supported') def format_channels(format): table = { GL_RGBA : 4, GL_RGB : 3, GL_RG : 2, GL_RED : 1, } if format in table.keys(): return table[format] return 1 ================================================ FILE: Malt/GL/readme.md ================================================ # GL The GL folder contains a series of modules that abstracts commonly needed OpenGL functionality. It doesn't try to be a complete abstraction, so it's meant to be used alongside raw OpenGL calls. ## [GL.py](GL.py) 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*. ## [Mesh.py](Mesh.py) Builds VAOs with the same layout used by [Common.glsl](Malt/Shaders/Common.glsl). Each parameter expects a flat 1D iterable with all the per-vertex data. 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. 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). * [OpenGL Wiki - Vertex Array Object](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Array_Object) * [LearnOpenGL - Hello Triangle](https://learnopengl.com/Getting-started/Hello-Triangle) ## [Shader.py](Shader.py) Builds OpenGL programs from GLSL vertex and fragment shader source code and provides an interface for reflection and configuration of shader parameters. The *shader_preprocessor* function parses source code with a C preprocessor to provide support for *#include directives* in glsl shaders. * [OpenGL Wiki - GLSL Objects](https://www.khronos.org/opengl/wiki/GLSL_Object) * [Learn OpenGL - Shaders](https://learnopengl.com/Getting-started/Shaders) ## [Texture.py](Texture.py) 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. 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. * [OpenGL Wiki - Texture](https://www.khronos.org/opengl/wiki/Texture) * [Learn OpenGL - Textures](https://learnopengl.com/Getting-started/Textures) ## [RenderTarget.py](RenderTarget.py) Builds FBOs from Textures. It accepts an arbitrary number of color targets and a single depth/stencil target. Color target Textures must be passed inside a list, even if you pass only one. 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). * [OpenGL Wiki - Framebuffer Objects](https://www.khronos.org/opengl/wiki/Framebuffer_Object) * [Learn OpenGL - Framebuffers](https://learnopengl.com/Advanced-OpenGL/Framebuffers) ## Basic Example (draw a full screen quad) ```python positions=[ 1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0, 1.0, 0.0, ] indices=[ 0, 1, 3, 1, 2, 3, ] mesh = Mesh(positions, indices) vertex_source=''' layout (location = 0) in vec3 POSITION; void main() { gl_Position = vec4(POSITION, 1); } ''' pixel_source=''' layout (location = 0) out vec4 RESULT; uniform vec4 color = vec4(1,0,0,1); void main() { RESULT = color; } ''' shader = Shader(vertex_source, pixel_source) shader.uniforms['color'].set_value((0,1,0,1)) result_texture = Texture((1024, 1024), GL_RGBA32F) result_target = RenderTarget([result_texture]) result_target.bind() shader.bind() mesh.draw() ``` ================================================ FILE: Malt/Nodes/LineRender.py ================================================ from Malt.GL.GL import * from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type _SHADER = None class LineRender(PipelineNode): """ Expands the line up to the width especified in the *Line Width* texture and composites it on top of the *Color* texture. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None @classmethod def reflect_inputs(cls): inputs = {} inputs['Color'] = Parameter('', Type.TEXTURE) inputs['Line Color'] = Parameter('', Type.TEXTURE) inputs['Line Width'] = Parameter('', Type.TEXTURE) inputs['Max Width'] = Parameter(10, Type.INT, doc=""" The maximum width the shader can render. Increasing the value lowers the render performance.""") inputs['Line Scale'] = Parameter(1.0, Type.FLOAT, doc=""" Scale all Line Width values with this one. *(Useful for rendering at different resolutions)*""") inputs['Normal Depth'] = Parameter('', Type.TEXTURE) inputs['ID'] = Parameter('', Type.TEXTURE) return inputs @classmethod def reflect_outputs(cls): outputs = {} outputs['Color'] = Parameter('', Type.TEXTURE) return outputs def setup_render_targets(self, resolution): self.t_color = Texture(resolution, GL_RGBA16F) self.fbo_color = RenderTarget([self.t_color]) def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] if self.pipeline.resolution != self.resolution: self.setup_render_targets(self.pipeline.resolution) self.resolution = self.pipeline.resolution global _SHADER if _SHADER is None: _SHADER = self.pipeline.compile_shader_from_source('#include "Passes/LineComposite.glsl"') _SHADER.textures['color_texture'] = inputs['Color'] _SHADER.textures['depth_texture'] = inputs['Normal Depth'] _SHADER.uniforms['depth_channel'].set_value(3) _SHADER.textures['id_texture'] = inputs['ID'] _SHADER.textures['line_color_texture'] = inputs['Line Color'] _SHADER.textures['line_width_texture'] = inputs['Line Width'] _SHADER.uniforms['line_width_scale'].set_value(inputs['Line Scale']) _SHADER.uniforms['brute_force_range'].set_value(inputs['Max Width']) self.pipeline.common_buffer.shader_callback(_SHADER) self.pipeline.draw_screen_pass(_SHADER, self.fbo_color) outputs['Color'] = self.t_color NODE = LineRender ================================================ FILE: Malt/Nodes/SceneFilter.py ================================================ from Malt.GL.GL import * from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type from copy import copy, deepcopy class SceneFilter(PipelineNode): """ Filters a Scene based on object tags. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.last_scene = None self.matches = None self.non_matches = None @classmethod def reflect_inputs(cls): return { 'Scene' : Parameter('Scene', Type.OTHER), 'Filter' : Parameter('', Type.STRING) } @classmethod def reflect_outputs(cls): return { 'Matches' : Parameter('Scene', Type.OTHER), 'Non Matches' : Parameter('Scene', Type.OTHER) } def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] scene = inputs['Scene'] tag = inputs['Filter'] if scene != self.last_scene: self.last_scene = scene self.matches = copy(scene) self.matches.objects = [] self.matches.shader_resources = copy(self.matches.shader_resources) self.non_matches = copy(scene) self.non_matches.objects = [] self.non_matches.shader_resources = copy(self.non_matches.shader_resources) for obj in scene.objects: if tag in obj.tags: self.matches.objects.append(obj) else: self.non_matches.objects.append(obj) self.matches.batches = self.pipeline.build_scene_batches(self.matches.objects) self.non_matches.batches = self.pipeline.build_scene_batches(self.non_matches.objects) outputs['Matches'] = self.matches outputs['Non Matches'] = self.non_matches NODE = SceneFilter ================================================ FILE: Malt/Nodes/SuperSamplingAA.py ================================================ from Malt.GL.GL import * from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type class SuperSamplingAA(PipelineNode): """ Performs anti-aliasing by accumulating multiple render samples into a single texture. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None @classmethod def reflect_inputs(cls): inputs = {} inputs['Color'] = Parameter('', Type.TEXTURE) return inputs @classmethod def reflect_outputs(cls): outputs = {} outputs['Color'] = Parameter('', Type.TEXTURE) return outputs def setup_render_targets(self, resolution): self.t_color = Texture(resolution, GL_RGBA16F) self.fbo = RenderTarget([self.t_color]) def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] if self.pipeline.resolution != self.resolution: self.setup_render_targets(self.pipeline.resolution) self.resolution = self.pipeline.resolution if self.pipeline.is_new_frame: self.fbo.clear([(0,0,0,0)]) if inputs['Color']: self.pipeline.blend_texture(inputs['Color'], self.fbo, 1.0 / (self.pipeline.sample_count + 1)) outputs['Color'] = self.t_color NODE = SuperSamplingAA ================================================ FILE: Malt/Pipeline.py ================================================ import math, os, ctypes from os import path from Malt.Utils import LOG from Malt.GL.GL import * from Malt.GL.Mesh import Mesh, MeshCustomLoad from Malt.GL.Shader import Shader, UBO, shader_preprocessor from Malt.Render import Common from Malt.PipelineParameters import * SHADER_DIR = path.join(path.dirname(__file__), 'Shaders') class Pipeline(): SHADER_INCLUDE_PATHS = [] BLEND_SHADER = None COPY_SHADER = None def __init__(self, plugins=[]): from multiprocessing.dummy import Pool self.pool = Pool(16) if SHADER_DIR not in Pipeline.SHADER_INCLUDE_PATHS: Pipeline.SHADER_INCLUDE_PATHS.append(SHADER_DIR) self.resolution = None self.sample_count = 0 self.result = None self.is_final_render = None plugins = [plugin for plugin in plugins if plugin.poll_pipeline(self)] self.setup_parameters() for plugin in plugins: plugin.register_pipeline_parameters(self.parameters) self.setup_graphs() for plugin in plugins: for graph in plugin.register_pipeline_graphs(): self.add_graph(graph) for plugin in plugins: plugin.register_graph_libraries(self.graphs) for graph in self.graphs.values(): graph.setup_reflection() self.setup_resources() def setup_parameters(self): self.parameters = PipelineParameters() self.parameters.mesh['double_sided'] = Parameter(False, Type.BOOL, doc= "Disables backface culling, so geometry is rendered from both sides.") self.parameters.mesh['precomputed_tangents'] = Parameter(False, Type.BOOL, doc=""" Load precomputed mesh tangents *(needed for improving normal mapping quality on low poly meshes)*. It's disabled by default since it slows down mesh loading in Blender. When disabled, the *tangents* are calculated on the fly from the *pixel shader*.""") self.parameters.world['Material.Default'] = MaterialParameter('', '.mesh.glsl', 'Mesh', doc= "The default material, used for objects with no material assigned.") self.parameters.world['Material.Override'] = MaterialParameter('', '.mesh.glsl', 'Mesh', doc= "When set, overrides all scene materials with this one.") self.parameters.world['Viewport.Resolution Scale'] = Parameter(1.0 , Type.FLOAT, doc=""" A multiplier for the viewport resolution. It can be lowered to improve viewport performance or for specific styles, like *pixel art*.""" ) self.parameters.world['Viewport.Smooth Interpolation'] = Parameter(True , Type.BOOL, doc=""" The interpolation mode used when *Resolution Scale* is not 1. Toggles between *Nearest/Bilinear* interpolation.""") def get_parameters(self): return self.parameters def setup_graphs(self): self.graphs = {} def add_graph(self, graph): if graph.file_extension.endswith('glsl'): graph.include_paths += self.SHADER_INCLUDE_PATHS self.graphs[graph.name] = graph def get_graphs(self): result = {} for name, graph in self.graphs.items(): result[name] = graph.get_serializable_copy() return result def setup_resources(self): self.common_buffer = Common.CommonBuffer() positions=[ 1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0, -1.0, 1.0, 0.0, ] indices=[ 0, 1, 3, 1, 2, 3, ] self.quad = Mesh(positions, indices) if Pipeline.BLEND_SHADER is None: source='''#include "Passes/BlendTexture.glsl"''' Pipeline.BLEND_SHADER = self.compile_shader_from_source(source) self.blend_shader = Pipeline.BLEND_SHADER if Pipeline.COPY_SHADER is None: source = '''#include "Passes/CopyTextures.glsl"''' Pipeline.COPY_SHADER = self.compile_shader_from_source(source) self.copy_shader = Pipeline.COPY_SHADER def get_render_outputs(self): return { 'COLOR' : GL_RGBA32F, 'DEPTH' : GL_R32F, } def get_samples(self): return [(0,0)] def needs_more_samples(self): return self.sample_count < len(self.get_samples()) def setup_render_targets(self, resolution): pass def find_shader_path(self, path, search_paths=[]): if os.path.exists(path): return path else: for shader_path in self.SHADER_INCLUDE_PATHS + search_paths: full_path = os.path.join(shader_path, path) if os.path.exists(full_path): return full_path return None def compile_shader_from_source(self, source, include_paths=[], defines=[]): vertex_src = shader_preprocessor(source, include_paths + self.SHADER_INCLUDE_PATHS, defines + ['VERTEX_SHADER']) pixel_src = shader_preprocessor(source, include_paths + self.SHADER_INCLUDE_PATHS, defines + ['PIXEL_SHADER']) return Shader(vertex_src, pixel_src) def compile_material_from_source(self, material_type, source, include_paths=[]): return self.graphs[material_type].compile_material(source, include_paths) def compile_material(self, shader_path, search_paths=[]): try: file_dir = path.dirname(shader_path) source = '#include "{}"'.format(path.basename(shader_path)) material_type = shader_path.split('.')[-2] for graph in self.graphs.values(): if shader_path.endswith(graph.file_extension): material_type = graph.name return self.compile_material_from_source(material_type, source, [file_dir] + search_paths) except Exception as e: import traceback traceback.print_exc() return str(e) def load_mesh(self, position, indices, normal, tangent=None, uvs=[], colors=[]): # Each parameter implements the Malt.Utils.IBuffer interface # Indices is an array of index buffers corresponding to each of the materials a mesh has # VBOs are shared for all the materials def load_VBO(data): VBO = gl_buffer(GL_INT, 1) glGenBuffers(1, VBO) glBindBuffer(GL_ARRAY_BUFFER, VBO[0]) glBufferData(GL_ARRAY_BUFFER, data.size_in_bytes(), data.buffer(), GL_STATIC_DRAW) glBindBuffer(GL_ARRAY_BUFFER, 0) return VBO position_vbo = load_VBO(position) normal_vbo = load_VBO(normal) tangent_vbo = load_VBO(tangent) if tangent else None uv_vbos = [load_VBO(e) for e in uvs] color_vbos = [load_VBO(e) if e else None for e in colors] results = [] for i, index in enumerate(indices): result = MeshCustomLoad() result.VAO = gl_buffer(GL_INT, 1) glGenVertexArrays(1, result.VAO) glBindVertexArray(result.VAO[0]) result.EBO = gl_buffer(GL_INT, 1) glGenBuffers(1, result.EBO) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, result.EBO[0]) glBufferData(GL_ELEMENT_ARRAY_BUFFER, index.size_in_bytes(), index.buffer(), GL_STATIC_DRAW) result.index_count = len(index) result.position = position_vbo result.normal = normal_vbo result.tangent = tangent_vbo result.uvs = uv_vbos result.colors = color_vbos def bind_VBO(VBO, index, element_size, gl_type=GL_FLOAT, gl_normalize=GL_FALSE): glBindBuffer(GL_ARRAY_BUFFER, VBO[0]) glEnableVertexAttribArray(index) glVertexAttribPointer(index, element_size, gl_type, gl_normalize, 0, None) bind_VBO(result.position, 0, 3) if position.size_in_bytes() == normal.size_in_bytes(): bind_VBO(result.normal, 1, 3) else: bind_VBO(result.normal, 1, 3, GL_SHORT, GL_TRUE) if tangent: bind_VBO(result.tangent, 2, 4) max_uv = 4 max_vertex_colors = 4 uv0_index = 3 color0_index = uv0_index + max_uv for i, uv in enumerate(result.uvs): if i >= max_uv: LOG.warning('{} : UV count exceeds max supported UVs ({})'.format(name, max_uv)) break bind_VBO(uv, uv0_index + i, 2) for i, color in enumerate(result.colors): if i >= max_vertex_colors: LOG.warning('{} : Vertex Color Layer count exceeds max supported layers ({})'.format(name, max_uv)) break if color: if colors[i]._ctype == ctypes.c_uint8: bind_VBO(color, color0_index + i, 4, GL_UNSIGNED_BYTE, GL_TRUE) result.color_is_srgb[i] = True if colors[i]._ctype == ctypes.c_float: bind_VBO(color, color0_index + i, 4, GL_FLOAT) glBindVertexArray(0) results.append(result) return results def draw_screen_pass(self, shader, target, blend = False): #Allow screen passes draw to gl_FragDepth glEnable(GL_DEPTH_TEST) glDepthFunc(GL_ALWAYS) glDisable(GL_CULL_FACE) if blend: glEnable(GL_BLEND) else: glDisable(GL_BLEND) target.bind() shader.bind() self.quad.draw() def blend_texture(self, blend_texture, target, opacity): self.blend_shader.textures['blend_texture'] = blend_texture glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA) glBlendEquation(GL_FUNC_ADD) glBlendColor(0, 0, 0, opacity) self.draw_screen_pass(self.blend_shader, target, True) def copy_textures(self, target, color_sources=[], depth_source=None): for i, texture in enumerate(color_sources): self.copy_shader.textures[f'IN[{str(i)}]'] = texture self.copy_shader.textures['IN_DEPTH'] = depth_source self.draw_screen_pass(self.copy_shader, target) def build_scene_batches(self, objects): result = {} for obj in objects: if obj.material not in result: result[obj.material] = {} if obj.mesh not in result[obj.material]: result[obj.material][obj.mesh] = {} mesh_dict = result[obj.material][obj.mesh] if obj.mirror_scale: if 'mirror_scale' not in mesh_dict: mesh_dict['mirror_scale'] = [] mesh_dict['mirror_scale'].append(obj) else: if 'normal_scale' not in mesh_dict: mesh_dict['normal_scale'] = [] mesh_dict['normal_scale'].append(obj) # Assume at least 64kb of UBO storage (d3d11 requirement) and max element size of mat4 max_instances = 1000 models = (max_instances * (ctypes.c_float * 16))() ids = (max_instances * ctypes.c_uint)() for material, meshes in result.items(): for mesh, scale_groups in meshes.items(): for scale_group, objs in scale_groups.items(): batches = [] scale_groups[scale_group] = batches i = 0 batch_length = len(objs) while i < batch_length: instance_i = i % max_instances models[instance_i] = objs[i].matrix ids[instance_i] = objs[i].parameters['ID'] i+=1 instances_count = instance_i + 1 if i == batch_length or instances_count == max_instances: local_models = ((ctypes.c_float * 16) * instances_count).from_address(ctypes.addressof(models)) # IDs are stored as uvec4, so we make sure the buffer count is a multiple of 4, # since some drivers will only bind a full uvec4 (see issue #319) id_buffer_count = math.ceil(instances_count/4)*4 local_ids = (ctypes.c_uint * id_buffer_count).from_address(ctypes.addressof(ids)) models_UBO = UBO() ids_UBO = UBO() models_UBO.load_data(local_models) ids_UBO.load_data(local_ids) batches.append({ 'instances_count': instances_count, 'BATCH_MODELS':models_UBO, 'BATCH_IDS':ids_UBO, }) return result def draw_scene_pass(self, render_target, scene_batches, pass_name=None, default_shader=None, shader_resources={}, depth_test_function=GL_LEQUAL): glDisable(GL_BLEND) glEnable(GL_DEPTH_TEST) glDepthFunc(depth_test_function) glDepthMask(GL_TRUE) glDepthRange(0,1) render_target.bind() _double_sided = None for material in scene_batches.keys(): shader = default_shader if material and pass_name in material.shader and material.shader[pass_name]: shader = material.shader[pass_name] for resource in shader_resources.values(): resource.shader_callback(shader) shader.bind() precomputed_tangents_uniform = shader.uniforms.get('PRECOMPUTED_TANGENTS') _precomputed_tangents = None _scale_group = None _color_is_srgb = None meshes = scene_batches[material] for mesh in meshes.keys(): mesh.mesh.bind() double_sided = mesh.parameters['double_sided'] if double_sided != _double_sided: _double_sided = double_sided if _double_sided: glDisable(GL_CULL_FACE) else: glEnable(GL_CULL_FACE) glCullFace(GL_BACK) color_is_srgb = tuple(mesh.mesh.color_is_srgb) if color_is_srgb != _color_is_srgb : if 'COLOR_IS_SRGB' in shader.uniforms: shader.uniforms['COLOR_IS_SRGB'].bind(color_is_srgb) _color_is_srgb = color_is_srgb if precomputed_tangents_uniform: precomputed_tangents = mesh.parameters['precomputed_tangents'] if _precomputed_tangents != precomputed_tangents: _precomputed_tangents = precomputed_tangents precomputed_tangents_uniform.bind(precomputed_tangents) for scale_group, batches in meshes[mesh].items(): if scale_group != _scale_group: _scale_group = scale_group if scale_group == 'normal_scale': glFrontFace(GL_CCW) if 'MIRROR_SCALE' in shader.uniforms: shader.uniforms['MIRROR_SCALE'].bind(False) else: glFrontFace(GL_CW) if 'MIRROR_SCALE' in shader.uniforms: shader.uniforms['MIRROR_SCALE'].bind(True) for batch in batches: batch['BATCH_MODELS'].bind(shader.uniform_blocks['BATCH_MODELS']) batch['BATCH_IDS'].bind(shader.uniform_blocks['BATCH_IDS']) glDrawElementsInstanced(GL_TRIANGLES, mesh.mesh.index_count, GL_UNSIGNED_INT, NULL, batch['instances_count']) def render(self, resolution, scene, is_final_render, is_new_frame): self.is_final_render = is_final_render if self.resolution != resolution: self.resolution = resolution self.setup_render_targets(resolution) self.sample_count = 0 if is_new_frame: self.sample_count = 0 if self.needs_more_samples() == False: return self.result self.common_buffer.load(scene, resolution) self.result = self.do_render(resolution, scene, is_final_render, is_new_frame) self.sample_count += 1 return self.result def do_render(self, resolution, scene, is_final_render, is_new_frame): return {} ================================================ FILE: Malt/PipelineGraph.py ================================================ from Malt.Utils import scan_dirs, LOG class PipelineGraphIO(): def __init__(self, name, dynamic_input_types = [], dynamic_output_types = [], default_dynamic_inputs = {}, default_dynamic_outputs = {}, function=None): self.name = name self.dynamic_input_types = dynamic_input_types self.dynamic_output_types = dynamic_output_types self.default_dynamic_inputs = default_dynamic_inputs self.default_dynamic_outputs = default_dynamic_outputs self.function = function self.custom_output_start_index = 0 class PipelineGraph(): INTERNAL_GRAPH = 0 GLOBAL_GRAPH = 1 SCENE_GRAPH = 2 def __init__(self, name, language, file_extension, graph_type, graph_io, default_graph_path): self.name = name self.language = language self.file_extension = file_extension self.graph_type = graph_type self.libs = [] self.lib_files = [] self.include_paths = [] self.functions = {} self.structs = {} self.subcategories = {} self.graph_io = { io.name : io for io in graph_io } self.default_graph_path = default_graph_path self.timestamp = 0 def add_library(self, path): import os from Malt.Utils import scan_dirs extension = self.file_extension.split('.')[-1] if os.path.isdir(path): self.include_paths.append(path) def file_callback(file): if file.path.endswith(extension): self.lib_files.append(file.path) scan_dirs(path, file_callback) else: self.include_paths.append(os.path.dirname(path)) self.lib_files.append(path) self.libs.append(path) def needs_reload(self): extension = self.file_extension.split('.')[-1] needs_reload = False def file_callback(file): if file.path.endswith(extension) and file.stat().st_mtime > self.timestamp: nonlocal needs_reload needs_reload = True for path in self.include_paths: scan_dirs(path, file_callback) return needs_reload def setup_reflection(self): import time self.timestamp = time.time() def generate_source(self, parameters): return '' def get_serializable_copy(self): from copy import copy return copy(self) class GLSLGraphIO(PipelineGraphIO): COMMON_INPUT_TYPES = ['sampler2D'] COMMON_OUTPUT_TYPES = ['float','vec2','vec3','vec4'] def __init__(self, name, define = None, io_wrap=None, dynamic_input_types = [], dynamic_output_types = [], default_dynamic_inputs = {}, default_dynamic_outputs = {}, shader_type=None, custom_output_start_index=0): super().__init__(name, dynamic_input_types, dynamic_output_types, default_dynamic_inputs, default_dynamic_outputs) self.define = define self.io_wrap = io_wrap self.shader_type = shader_type self.signature = None self.custom_output_start_index = custom_output_start_index class GLSLPipelineGraph(PipelineGraph): def __init__(self, name, graph_type, default_global_scope, default_shader_src, shaders=['SHADER'], graph_io=[], default_graph_path=None): file_extension = f'.{name.lower()}.glsl' super().__init__(name, 'GLSL', file_extension, graph_type, graph_io, default_graph_path) self.default_global_scope = default_global_scope self.default_shader_src = default_shader_src self.shaders = shaders from multiprocessing.dummy import Pool self.pool = Pool(16) def get_serializable_copy(self): result = super().get_serializable_copy() result.pool = None return result def name_as_macro(self, name): return ''.join(c for c in name.replace(' ','_').upper() if c.isalnum() or c == '_') def get_material_define(self): return f'IS_{self.name_as_macro(self.name)}_SHADER' def preprocess_shader_from_source(self, source, include_paths=[], defines=[]): from Malt.GL.Shader import shader_preprocessor return shader_preprocessor(source, self.include_paths + include_paths, [self.get_material_define()] + defines) def setup_reflection(self): super().setup_reflection() src = self.default_global_scope + self.default_shader_src for file in self.lib_files: src += f'\n#include "{file}"\n' src = self.preprocess_shader_from_source(src, [], ['VERTEX_SHADER','PIXEL_SHADER','REFLECTION']) from Malt.GL.Shader import glsl_reflection reflection = glsl_reflection(src, self.include_paths) functions = reflection["functions"] structs = reflection["structs"] subcategories = reflection["subcategories"] for io in self.graph_io.values(): io.function = functions[io.name] io.signature = io.function['signature'] for key in [*functions.keys()]: name = functions[key]['name'] if name.startswith('_') or name.isupper() or name == 'main': functions.pop(key) for name in [*structs.keys()]: if name.startswith('_'): #TODO: Upper??? structs.pop(name) for key, subcategory in subcategories.items(): subcategories[key] = [k for k in subcategory if k in functions.keys()] self.functions = functions self.structs = structs self.subcategories = subcategories def generate_source(self, parameters): import textwrap from Malt.SourceTranspiler import GLSLTranspiler code = '' for graph_io in self.graph_io.values(): if graph_io.name in parameters.keys() and graph_io.define: code += '#define {}\n'.format(graph_io.define) code += '\n\n' + self.default_global_scope + '\n\n' for file in self.lib_files: code += f'#include "{file}"\n' code += '\n\n' + parameters['GLOBAL'] + '\n\n' for graph_io in self.graph_io.values(): if graph_io.name in parameters.keys(): code += GLSLTranspiler.preprocessor_wrap(graph_io.shader_type, '{}\n{{\n{}\n}}'.format(graph_io.signature, textwrap.indent(parameters[graph_io.name],'\t'))) code += '\n\n' return code def compile_material(self, source, include_paths=[]): def preprocess(params): return self.preprocess_shader_from_source(*params) params = [] for shader in self.shaders: params.append((source, include_paths, [shader, 'VERTEX_SHADER'])) params.append((source, include_paths, [shader, 'PIXEL_SHADER'])) preprocessed = self.pool.map(preprocess, params) from Malt.GL.Shader import Shader shaders = {} for shader in self.shaders: shaders[shader] = Shader(preprocessed.pop(0), preprocessed.pop(0)) return shaders class PythonGraphIO(PipelineGraphIO): COMMON_IO_TYPES = ['Texture'] def __init__(self, name, dynamic_input_types = [], dynamic_output_types = [], default_dynamic_inputs = {}, default_dynamic_outputs = {}, function=None): super().__init__(name, dynamic_input_types, dynamic_output_types, default_dynamic_inputs, default_dynamic_outputs, function) class PythonPipelineGraph(PipelineGraph): def __init__(self, name, graph_io, default_graph_path=None): extension = f'-{name}.py' super().__init__(name, 'Python', extension, self.GLOBAL_GRAPH, graph_io, default_graph_path) self.node_instances = {} self.nodes = {} def get_serializable_copy(self): result = super().get_serializable_copy() result.nodes = None result.node_instances = None return result def setup_reflection(self): super().setup_reflection() import importlib.util nodes = [] self.node_instances = {} for file in self.lib_files: try: spec = importlib.util.spec_from_file_location("_dynamic_node_module_", file) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) nodes.append(module.NODE) except: import traceback LOG.error('FILEPATH : ', file, '\n', traceback.format_exc()) self.functions = {} self.structs = {} for node_class in nodes: reflection = node_class.reflect() self.functions[reflection['name']] = reflection self.nodes[reflection['name']] = node_class def generate_source(self, parameters): src = '' for io in self.graph_io.keys(): if io in parameters.keys(): src += parameters[io] return src def run_source(self, pipeline, source, PARAMETERS, IN, OUT): try: def run_node(node_name, node_type, parameters): if node_name not in self.node_instances.keys(): node_class = self.nodes[node_type] self.node_instances[node_name] = node_class(pipeline) parameters['__GLOBALS__'] = PARAMETERS self.node_instances[node_name].execute(parameters) exec(source) except: raise MaltGraphExecutionException(source, PARAMETERS, IN, OUT) class MaltGraphExecutionException(Exception): def __init__(self, source, parameters, inputs, outputs): import pprint self.message = "\n".join(("", "IN:", pprint.pformat(inputs), "OUT:", pprint.pformat(outputs), "SOURCE:", source, "PARAMETERS:", pprint.pformat(parameters))) super().__init__(self.message) ================================================ FILE: Malt/PipelineNode.py ================================================ class PipelineNode(): def __init__(self, pipeline): self.pipeline = pipeline @staticmethod def get_pass_type(): return None @classmethod def static_reflect(cls, name, inputs, outputs): meta = {} if cls.__doc__: meta['doc'] = cls.__doc__ dictionary = { 'name' : name, 'type' : 'void', 'file' : 'Render', 'parameters' : [], 'pass_type' : cls.get_pass_type(), 'meta' : meta, } for name, input in inputs.items(): meta = {} if input.doc: meta['doc'] = input.doc if input.subtype: meta['subtype'] = input.subtype if input.default_value is not None and input.default_value != input.type_string(): meta['default'] = input.default_value dictionary['parameters'].append({ 'name' : name, 'type' : input, 'size' : input.size, 'io' : 'in', 'meta' : meta, }) for name, output in outputs.items(): meta = {} if output.doc: meta['doc'] = output.doc if output.subtype: meta['subtype'] = output.subtype if output.default_value is not None and output.default_value != output.type_string(): meta['default'] = output.default_value dictionary['parameters'].append({ 'name' : name, 'type' : output, 'size' : output.size, 'io' : 'out', 'meta' : {}, }) return dictionary @classmethod def reflect(cls): return cls.static_reflect(cls.__name__, cls.reflect_inputs(), cls.reflect_outputs()) @classmethod def reflect_inputs(cls): return {} @classmethod def reflect_outputs(cls): return {} def execute(self, parameters): pass ================================================ FILE: Malt/PipelineParameters.py ================================================ class PipelineParameters(): def __init__(self, scene={}, world={}, camera={}, object={}, material={}, mesh={}, light={}): self.scene = scene self.world = world self.camera = camera self.object = object self.material = material self.mesh = mesh self.light = light class Type(): BOOL=0 INT=1 FLOAT=2 STRING=3 ENUM=4 OTHER=5 TEXTURE=6 GRADIENT=7 MATERIAL=8 #RENDER_TARGET=9 #TODO GRAPH=10 @classmethod def string_list(cls): return ['Bool', 'Int', 'Float', 'String', 'Other', 'Enum', 'Texture', 'Gradient', 'Material', 'RenderTarget', 'Graph'] @classmethod def to_string(cls, type): return cls.string_list()[type] @classmethod def from_string(cls, type): return cls.string_list().index(type) class Parameter(): def __init__(self, default_value, type, size=1, filter=None, subtype=None, doc=None): self.default_value = default_value self.type = type self.subtype = subtype self.size = size self.filter = filter self.doc = doc def type_string(self): if self.type == Type.OTHER: return self.default_value else: return Type.to_string(self.type) @classmethod def from_uniform(cls, uniform): type, size = gl_type_to_malt_type(uniform.type) value = uniform.value if size > 1: value = tuple(value) else: value = value[0] #TODO: uniform length ??? (Arrays) return Parameter(value, type, size) @classmethod def from_glsl_type(cls, glsl_type, subtype=None, default_value=None): type, size = glsl_type_to_malt_type(glsl_type) if subtype and subtype.startswith('ENUM'): try: enum_options = subtype.split('ENUM(')[1][:-1] enum_options = enum_options.split(',') if isinstance(default_value, int): default_value = enum_options[default_value] if default_value is None: default_value = enum_options[0] return EnumParameter(enum_options, default_value) except: pass if default_value is None: if type is Type.INT: default_value = tuple([0] * size) if type is Type.FLOAT: default_value = tuple([0.0] * size) if type is Type.BOOL: default_value = tuple([False] * size) if default_value and len(default_value) == 1: default_value = default_value[0] overrides = { ('vec3',None):(0.5,0.5,0.5), ('vec4',None):(0.5,0.5,0.5,1.0), ('vec3','Color'):(0.5,0.5,0.5), ('vec4','Color'):(0.5,0.5,0.5,1.0), ('vec3','Normal'):(1.0,0.0,0.0), ('vec4','Quaternion'):(0.0,0.0,0.0,1.0), ('mat3',None):( 1.0,0.0,0.0, 0.0,1.0,0.0, 0.0,0.0,1.0, ), ('mat4',None):( 1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,0.0,0.0,1.0, ), } override = overrides.get((glsl_type, subtype)) if override is not None: default_value = override return Parameter(default_value, type, size, subtype=subtype) class MaterialParameter(Parameter): def __init__(self, default_path, extension, graph_type=None, filter=None, doc=None): super().__init__(default_path, Type.MATERIAL, 1, filter, extension, doc) self.extension = extension self.graph_type = graph_type class GraphParameter(Parameter): def __init__(self, default_path, graph_type, filter=None, doc=None): super().__init__(default_path, Type.GRAPH, 1, filter, graph_type, doc) self.graph_type = graph_type class EnumParameter(Parameter): def __init__(self, options, default_option, filter=None, doc=None): self.enum_options = options super().__init__(default_option, Type.ENUM, 1, filter, None, doc) def from_index(self, index): return self.enum_options[index] class FloatParameter(Parameter): def __init__(self, default_value, min=None, max=None, size=1, filter=None, doc=None): self.min = min self.max = max super().__init__(default_value, Type.FLOAT, size, filter, None, doc) class IntParameter(Parameter): def __init__(self, default_value, min=None, max=None, size=1, filter=None, doc=None): self.min = min self.max = max super().__init__(default_value, Type.INT, size, filter, None, doc) def gl_type_to_malt_type(gl_type): from Malt.GL import GL types = { 'FLOAT' : Type.FLOAT, 'DOUBLE' : Type.FLOAT, 'INT' : Type.INT, 'BOOL' : Type.BOOL, 'SAMPLER_1D' : Type.GRADIENT, 'SAMPLER' : Type.TEXTURE, } sizes = { 'VEC2' : 2, 'VEC3' : 3, 'VEC4' : 4, 'MAT2' : 4, 'MAT3' : 9, 'MAT4' : 16, } gl_name = GL.GL_ENUMS[gl_type] for type_name, type in types.items(): if type_name in gl_name: for size_name, size in sizes.items(): if size_name in gl_name: return (type, size) return (type, 1) raise Exception(gl_name, ' Uniform type not supported') def glsl_type_to_malt_type(glsl_type): types = { 'float' : Type.FLOAT, 'vec' : Type.FLOAT, 'mat' : Type.FLOAT, 'double' : Type.FLOAT, 'd' : Type.FLOAT, 'int' : Type.INT, 'i' : Type.INT, 'uint' : Type.INT, 'u' : Type.INT, 'bool' : Type.BOOL, 'b' : Type.BOOL, 'sampler1D' : Type.GRADIENT, 'sampler2D' : Type.TEXTURE, } sizes = { 'vec2' : 2, 'vec3' : 3, 'vec4' : 4, 'mat2' : 4, 'mat3' : 9, 'mat4' : 16, } for type_name, type in types.items(): if glsl_type.startswith(type_name): for size_name, size in sizes.items(): if size_name in glsl_type: return (type, size) return (type, 1) return None ================================================ FILE: Malt/PipelinePlugin.py ================================================ from Malt.Utils import isinstance_str class PipelinePlugin(): @classmethod def poll_pipeline(self, pipeline): return True @classmethod def register_pipeline_parameters(self, parameters): pass @classmethod def register_pipeline_graphs(self): return [] @classmethod def register_graph_libraries(self, graphs): pass @classmethod def blendermalt_register(self): pass @classmethod def blendermalt_unregister(self): pass @classmethod def blendermalt_register_nodeitems(self, MaltNodeItemClass): # Should return a dictionary where keys are category names and values are arrays of MaltNodeItems return {} def load_plugins_from_dir(dir): import sys, os, importlib if dir not in sys.path: sys.path.append(dir) plugins=[] for e in os.scandir(dir): if (e.path.startswith('.') or e.path.startswith('_') or e.is_file() and e.path.endswith('.py') == False): continue try: ''' spec = importlib.util.spec_from_file_location("_dynamic_plugin_module_", e.path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) ''' module = importlib.import_module(e.name) importlib.reload(module) plugins.append(module.PLUGIN) except: import traceback traceback.print_exc() print('FILEPATH : ', e.path) return plugins ================================================ FILE: Malt/Pipelines/MiniPipeline/MiniPipeline.py ================================================ from os import path from Malt.GL.GL import * from Malt.GL.Mesh import Mesh from Malt.GL.RenderTarget import RenderTarget from Malt.GL.Shader import Shader, UBO from Malt.GL.Texture import Texture from Malt.Pipeline import * class MiniPipeline(Pipeline): DEFAULT_SHADER = None def __init__(self, plugins=[]): super().__init__(plugins) self.parameters.world['Background Color'] = Parameter((0.5,0.5,0.5,1), Type.FLOAT, 4) if MiniPipeline.DEFAULT_SHADER is None: source = ''' #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER layout (location = 0) out vec4 RESULT; void main() { PIXEL_SETUP_INPUT(); RESULT = vec4(1); } #endif ''' MiniPipeline.DEFAULT_SHADER = self.compile_material_from_source('mesh', source) self.default_shader = MiniPipeline.DEFAULT_SHADER def compile_material_from_source(self, material_type, source, include_paths=[]): return { 'MAIN_PASS' : self.compile_shader_from_source( source, include_paths, ['MAIN_PASS'] ) } def setup_render_targets(self, resolution): self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F) self.t_main_color = Texture(resolution, GL_RGBA32F) self.fbo_main = RenderTarget([self.t_main_color], self.t_depth) def do_render(self, resolution, scene, is_final_render, is_new_frame): shader_resources = { 'COMMON_UNIFORMS' : self.common_buffer } self.fbo_main.clear([scene.world_parameters['Background Color']], 1) self.draw_scene_pass(self.fbo_main, scene.batches, 'MAIN_PASS', self.default_shader['MAIN_PASS'], shader_resources) return { 'COLOR' : self.t_main_color } PIPELINE = MiniPipeline ================================================ FILE: Malt/Pipelines/NPR_Pipeline/NPR_LightShaders.py ================================================ from Malt.GL.GL import * from Malt.GL.Shader import UBO from Malt.GL.Texture import TextureArray from Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget from Malt.Render import Lighting from Malt.PipelineGraph import * import ctypes class NPR_LightShaders(): def __init__(self): class C_NPR_LightShadersBuffer(ctypes.Structure): _fields_ = [ ('custom_shading_index', ctypes.c_int*Lighting.MAX_LIGHTS), ] self.data = C_NPR_LightShadersBuffer() self.custom_shading_count = 0 self.UBO = UBO() self.texture = None self.fbos = None def load(self, pipeline, depth_texture, scene): self.custom_shading_count = 0 for i, light in enumerate(scene.lights): custom_shading_index = -1 if light.parameters['Shader'] is not None: custom_shading_index = self.custom_shading_count self.custom_shading_count += 1 self.data.custom_shading_index[i] = custom_shading_index self.UBO.load_data(self.data) if self.custom_shading_count == 0: return lights = [l for l in scene.lights if l.parameters['Shader'] is not None] tex = self.texture if tex is None or tex.resolution != depth_texture.resolution or tex.length < len(lights): self.texture = TextureArray(depth_texture.resolution, len(lights), GL_RGB32F) self.fbos = [] for i in range(len(lights)): self.fbos.append(RenderTarget([ArrayLayerTarget(self.texture, i)])) for i, light in enumerate(lights): material = light.parameters['Shader'] if material.shader and 'SHADER' in material.shader.keys(): shader = material.shader['SHADER'] for resource in scene.shader_resources.values(): resource.shader_callback(shader) shader.textures['IN_DEPTH'] = depth_texture if 'LIGHT_INDEX' in shader.uniforms: light_index = scene.lights.index(light) shader.uniforms['LIGHT_INDEX'].set_value(light_index) pipeline.draw_screen_pass(shader, self.fbos[i]) def shader_callback(self, shader): if 'LIGHTS_CUSTOM_SHADING' in shader.uniform_blocks: self.UBO.bind(shader.uniform_blocks['LIGHTS_CUSTOM_SHADING']) shader.textures['IN_LIGHT_CUSTOM_SHADING'] = self.texture ================================================ FILE: Malt/Pipelines/NPR_Pipeline/NPR_Lighting.py ================================================ from Malt.GL.GL import * from Malt.GL.Shader import UBO from Malt.GL.Texture import TextureArray, CubeMapArray from Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget from Malt.Render import Lighting class NPR_LightsGroupsBuffer(): def __init__(self): class C_NPR_LightGroupsBuffer(ctypes.Structure): _fields_ = [ ('light_group_index', ctypes.c_int*Lighting.MAX_LIGHTS), ] self.data = C_NPR_LightGroupsBuffer() self.UBO = UBO() def load(self, scene): for i, light in enumerate(scene.lights): self.data.light_group_index[i] = light.parameters['Light Group'] self.UBO.load_data(self.data) for material in scene.batches.keys(): for shader in material.shader.values(): if 'MATERIAL_LIGHT_GROUPS' in shader.uniforms.keys(): shader.uniforms['MATERIAL_LIGHT_GROUPS'].set_value(material.parameters['Light Groups.Light']) def shader_callback(self, shader): if 'LIGHT_GROUPS' in shader.uniform_blocks: self.UBO.bind(shader.uniform_blocks['LIGHT_GROUPS']) class NPR_ShadowMaps(Lighting.ShadowMaps): def __init__(self): super().__init__() self.spot_id_t = None self.sun_id_t = None self.point_id_t = None def setup(self, create_fbos=True): super().setup(False) self.spot_id_t = TextureArray((self.spot_resolution, self.spot_resolution), self.max_spots, GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST) self.sun_id_t = TextureArray((self.sun_resolution, self.sun_resolution), self.max_suns, GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST) self.point_id_t = CubeMapArray((self.point_resolution, self.point_resolution), self.max_points, GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST) if create_fbos: self.spot_fbos = [] for i in range(self.spot_depth_t.length): self.spot_fbos.append(RenderTarget([ArrayLayerTarget(self.spot_id_t, i)], ArrayLayerTarget(self.spot_depth_t, i))) self.sun_fbos = [] for i in range(self.sun_depth_t.length): self.sun_fbos.append(RenderTarget([ArrayLayerTarget(self.sun_id_t, i)], ArrayLayerTarget(self.sun_depth_t, i))) self.point_fbos = [] for i in range(self.point_depth_t.length*6): self.point_fbos.append(RenderTarget([ArrayLayerTarget(self.point_id_t, i)], ArrayLayerTarget(self.point_depth_t, i))) def clear(self, spot_count, sun_count, point_count): for i in range(spot_count): self.spot_fbos[i].clear([0], depth=1) for i in range(sun_count): self.sun_fbos[i].clear([0], depth=1) for i in range(point_count*6): self.point_fbos[i].clear([0], depth=1) def shader_callback(self, shader): super().shader_callback(shader) shader.textures['SHADOWMAPS_ID_SPOT'] = self.spot_id_t shader.textures['SHADOWMAPS_ID_SUN'] = self.sun_id_t shader.textures['SHADOWMAPS_ID_POINT'] = self.point_id_t class NPR_TransparentShadowMaps(NPR_ShadowMaps): def __init__(self): super().__init__() self.spot_color_t = None self.sun_color_t = None self.point_color_t = None def setup(self, create_fbos=True): super().setup(False) self.spot_color_t = TextureArray((self.spot_resolution, self.spot_resolution), self.max_spots, GL_RGB8) self.sun_color_t = TextureArray((self.sun_resolution, self.sun_resolution), self.max_suns, GL_RGB8) self.point_color_t = CubeMapArray((self.point_resolution, self.point_resolution), self.max_points, GL_RGB8) if create_fbos: self.spot_fbos = [] for i in range(self.spot_depth_t.length): targets = [ArrayLayerTarget(self.spot_id_t, i), ArrayLayerTarget(self.spot_color_t, i)] self.spot_fbos.append(RenderTarget(targets, ArrayLayerTarget(self.spot_depth_t, i))) self.sun_fbos = [] for i in range(self.sun_depth_t.length): targets = [ArrayLayerTarget(self.sun_id_t, i), ArrayLayerTarget(self.sun_color_t, i)] self.sun_fbos.append(RenderTarget(targets, ArrayLayerTarget(self.sun_depth_t, i))) self.point_fbos = [] for i in range(self.point_depth_t.length*6): targets = [ArrayLayerTarget(self.point_id_t, i), ArrayLayerTarget(self.point_color_t, i)] self.point_fbos.append(RenderTarget(targets, ArrayLayerTarget(self.point_depth_t, i))) def clear(self, spot_count, sun_count, point_count): for i in range(spot_count): self.spot_fbos[i].clear([0, (0,0,0,0)], depth=1) for i in range(sun_count): self.sun_fbos[i].clear([0, (0,0,0,0)], depth=1) for i in range(point_count*6): self.point_fbos[i].clear([0, (0,0,0,0)], depth=1) def shader_callback(self, shader): shader.textures['TRANSPARENT_SHADOWMAPS_DEPTH_SPOT'] = self.spot_depth_t shader.textures['TRANSPARENT_SHADOWMAPS_DEPTH_SUN'] = self.sun_depth_t shader.textures['TRANSPARENT_SHADOWMAPS_DEPTH_POINT'] = self.point_depth_t shader.textures['TRANSPARENT_SHADOWMAPS_ID_SPOT'] = self.spot_id_t shader.textures['TRANSPARENT_SHADOWMAPS_ID_SUN'] = self.sun_id_t shader.textures['TRANSPARENT_SHADOWMAPS_ID_POINT'] = self.point_id_t shader.textures['TRANSPARENT_SHADOWMAPS_COLOR_SPOT'] = self.spot_color_t shader.textures['TRANSPARENT_SHADOWMAPS_COLOR_SUN'] = self.sun_color_t shader.textures['TRANSPARENT_SHADOWMAPS_COLOR_POINT'] = self.point_color_t ================================================ FILE: Malt/Pipelines/NPR_Pipeline/NPR_Pipeline.py ================================================ from os import path from Malt.GL.RenderTarget import RenderTarget from Malt.GL.Texture import Texture from Malt.Pipeline import * from Malt.PipelineGraph import * from Malt.PipelineNode import PipelineNode from Malt.GL.GL import * from Malt.Render import DepthToCompositeDepth from Malt.Render import Sampling _SCREEN_SHADER_HEADER=''' #include "NPR_ScreenShader.glsl" #include "Node Utils 2/node_utils_2.glsl" #include "Node Utils/node_utils.glsl" ''' _MESH_SHADER_HEADER=''' #include "NPR_MeshShader.glsl" #include "Node Utils 2/node_utils_2.glsl" #include "Node Utils/node_utils.glsl" ''' _LIGHT_SHADER_HEADER=''' #include "NPR_LightShader.glsl" #include "Node Utils 2/node_utils_2.glsl" #include "Node Utils/node_utils.glsl" ''' _DEFAULT_SHADER = None _DEFAULT_SHADER_SRC=''' void PRE_PASS_PIXEL_SHADER(inout PrePassOutput PPO){ } void DEPTH_OFFSET(inout float depth_offset, inout bool offset_position){ } #ifdef MAIN_PASS layout (location = 0) out vec4 OUT_0; layout (location = 1) out vec4 OUT_1; layout (location = 2) out vec4 OUT_2; layout (location = 3) out vec4 OUT_3; layout (location = 4) out vec4 OUT_4; layout (location = 5) out vec4 OUT_5; layout (location = 6) out vec4 OUT_6; layout (location = 7) out vec4 OUT_7; #endif //MAIN_PASS void MAIN_PASS_PIXEL_SHADER() { #ifdef MAIN_PASS { OUT_0 = vec4(1,1,0,1); OUT_1 = vec4(1,1,0,1); OUT_2 = vec4(1,1,0,1); OUT_3 = vec4(1,1,0,1); OUT_4 = vec4(1,1,0,1); OUT_5 = vec4(1,1,0,1); OUT_6 = vec4(1,1,0,1); OUT_7 = vec4(1,1,0,1); } #endif //MAIN_PASS } ''' DEFAULTS_PATH = os.path.join(os.path.dirname(__file__), 'Defaults', 'defaults') class NPR_Pipeline(Pipeline): def __init__(self, plugins=[]): shader_dir = path.join(path.dirname(__file__), 'Shaders') if shader_dir not in self.SHADER_INCLUDE_PATHS: self.SHADER_INCLUDE_PATHS.append(shader_dir) self.sampling_grid_size = 1 self.samples = None super().__init__(plugins) def setup_parameters(self): super().setup_parameters() self.parameters.world['Samples.Grid Size'] = Parameter(8, Type.INT, doc=""" The number of render samples per side in the sampling grid. The total number of samples is the square of this value minus the samples that fall outside the sampling radius. Higher values will provide cleaner renders at the cost of increased render times.""") self.parameters.world['Samples.Grid Size @ Preview'] = Parameter(4, Type.INT) self.parameters.world['Samples.Width'] = Parameter(1.0, Type.FLOAT, doc=""" The width (and height) of the sampling grid. Larger values will result in smoother/blurrier images while lower values will result in sharper/more aliased ones. Keep it withing the 1-2 range for best results.""") self.parameters.world['Material.Default'] = MaterialParameter((DEFAULTS_PATH, 'Malt - Default Mesh Material'), '.mesh.glsl', 'Mesh', doc = self.parameters.world['Material.Default'].doc) self.parameters.world['Render'] = GraphParameter((DEFAULTS_PATH, 'Default Render'), 'Render', doc=""" The *Render Node Tree* used to render the scene. See [Render & Render Layers](#Render & Render Layers) for more info.""") self.parameters.light['Light Group'] = Parameter(1, Type.INT, doc= "Lights only affect materials with a matching *Light Group* value.") self.parameters.light['Shader'] = MaterialParameter('', '.light.glsl', 'Light', doc= "When set, the *Material* with a custom *Light Shader* or *Light Node Tree* that will be used to render this light.") self.parameters.material['Light Groups.Light'] = Parameter([1,0,0,0], Type.INT, 4, '.mesh.glsl', doc= "The *Light Groups* (up to 4) that lit this material.") self.parameters.material['Light Groups.Shadow'] = Parameter([1,0,0,0], Type.INT, 4, '.mesh.glsl', doc= "The *Light Groups* (up to 4) that this material casts shadows on.") def setup_graphs(self): super().setup_graphs() mesh = GLSLPipelineGraph( name='Mesh', graph_type=GLSLPipelineGraph.SCENE_GRAPH, default_global_scope=_MESH_SHADER_HEADER, default_shader_src=_DEFAULT_SHADER_SRC, shaders=['PRE_PASS', 'MAIN_PASS', 'SHADOW_PASS'], default_graph_path=(DEFAULTS_PATH, 'Mesh Node Tree Base'), graph_io=[ GLSLGraphIO( name='PRE_PASS_PIXEL_SHADER', define='CUSTOM_PRE_PASS', io_wrap='PRE_PASS', shader_type='PIXEL_SHADER', dynamic_output_types=GLSLGraphIO.COMMON_OUTPUT_TYPES, custom_output_start_index=2, ), GLSLGraphIO( name='DEPTH_OFFSET', define='CUSTOM_DEPTH_OFFSET', shader_type='PIXEL_SHADER', ), GLSLGraphIO( name='MAIN_PASS_PIXEL_SHADER', io_wrap='MAIN_PASS', shader_type='PIXEL_SHADER', dynamic_input_types=GLSLGraphIO.COMMON_INPUT_TYPES, dynamic_output_types=GLSLGraphIO.COMMON_OUTPUT_TYPES, default_dynamic_outputs={ 'Color': 'vec4', 'Line Color': 'vec4', 'Line Width': 'float', } ), GLSLGraphIO( name='VERTEX_DISPLACEMENT_SHADER', define='CUSTOM_VERTEX_DISPLACEMENT', shader_type='VERTEX_SHADER' ), GLSLGraphIO( name='COMMON_VERTEX_SHADER', define='CUSTOM_VERTEX_SHADER', shader_type='VERTEX_SHADER', ), ] ) self.add_graph(mesh) screen = GLSLPipelineGraph( name='Screen', graph_type=GLSLPipelineGraph.GLOBAL_GRAPH, default_global_scope=_SCREEN_SHADER_HEADER, default_shader_src="void SCREEN_SHADER(){ }", default_graph_path=(DEFAULTS_PATH, 'Screen Node Tree Base'), graph_io=[ GLSLGraphIO( name='SCREEN_SHADER', shader_type='PIXEL_SHADER', dynamic_input_types= GLSLGraphIO.COMMON_INPUT_TYPES, dynamic_output_types= GLSLGraphIO.COMMON_OUTPUT_TYPES, default_dynamic_outputs={ 'Color': 'vec4', } ) ] ) self.add_graph(screen) light = GLSLPipelineGraph( name='Light', graph_type=GLSLPipelineGraph.INTERNAL_GRAPH, default_global_scope=_LIGHT_SHADER_HEADER, default_shader_src="void LIGHT_SHADER(vec3 relative_coordinates, vec3 uvw, inout vec3 color, inout float attenuation) { }", default_graph_path=(DEFAULTS_PATH, 'Light Node Tree Base'), graph_io=[ GLSLGraphIO( name='LIGHT_SHADER', shader_type='PIXEL_SHADER', ) ] ) self.add_graph(light) render_layer = PythonPipelineGraph( name='Render Layer', default_graph_path=(DEFAULTS_PATH, 'Default Render Layer'), graph_io = [ PythonGraphIO( name = 'Render Layer', dynamic_input_types= PythonGraphIO.COMMON_IO_TYPES, dynamic_output_types= PythonGraphIO.COMMON_IO_TYPES, function = PipelineNode.static_reflect( name = 'Render Layer', inputs = { 'Scene' : Parameter('Scene', Type.OTHER), }, outputs = { 'Color' : Parameter('', Type.TEXTURE), }, ) ) ] ) render_layer.add_library(os.path.join(os.path.dirname(__file__),'..','..','Nodes')) render_layer.add_library(os.path.join(os.path.dirname(__file__), 'Nodes', 'RenderLayer')) self.add_graph(render_layer) render = PythonPipelineGraph( name='Render', default_graph_path=(DEFAULTS_PATH, 'Default Render'), graph_io = [ PythonGraphIO( name = 'Render', dynamic_output_types= PythonGraphIO.COMMON_IO_TYPES, function = PipelineNode.static_reflect( name = 'Render', inputs = { 'Scene' : Parameter('Scene', Type.OTHER), }, outputs = { 'Color' : Parameter('', Type.TEXTURE), 'Depth' : Parameter('', Type.TEXTURE), }, ) ) ] ) render.add_library(os.path.join(os.path.dirname(__file__),'..','..','Nodes')) render.add_library(os.path.join(os.path.dirname(__file__), 'Nodes', 'Render')) self.add_graph(render) def setup_resources(self): super().setup_resources() self.composite_depth = DepthToCompositeDepth.CompositeDepth() global _DEFAULT_SHADER if _DEFAULT_SHADER is None: _DEFAULT_SHADER = self.compile_material_from_source('Mesh', _MESH_SHADER_HEADER + _DEFAULT_SHADER_SRC) self.default_shader = _DEFAULT_SHADER def get_samples(self): if self.samples is None: self.samples = Sampling.get_RGSS_samples(self.sampling_grid_size, 1.0) return self.samples def get_sample(self, width): w, h = self.get_samples()[self.sample_count] w*=width h*=width return w, h def get_scene_batches(self, scene): opaque_batches = {} transparent_batches = {} for material, meshes in scene.batches.items(): if material and material.shader: if material.shader['PRE_PASS'].uniforms['Settings.Transparency'].value[0] == True: transparent_batches[material] = meshes continue opaque_batches[material] = meshes return opaque_batches, transparent_batches def do_render(self, resolution, scene, is_final_render, is_new_frame): #SETUP SAMPLING if self.sampling_grid_size != scene.world_parameters['Samples.Grid Size']: self.sampling_grid_size = scene.world_parameters['Samples.Grid Size'] self.samples = None self.is_new_frame = is_new_frame sample_offset = self.get_sample(scene.world_parameters['Samples.Width']) opaque_batches, transparent_batches = self.get_scene_batches(scene) self.common_buffer.load(scene, resolution, sample_offset, self.sample_count) scene.shader_resources = { 'COMMON_UNIFORMS' : self.common_buffer } result = { 'COLOR': None, 'DEPTH': None, } graph = scene.world_parameters['Render'] if graph: IN = {'Scene' : scene} OUT = {'Color' : None} self.graphs['Render'].run_source(self, graph['source'], graph['parameters'], IN, OUT) result = OUT result['COLOR'] = result['Color'] result['DEPTH'] = result['Depth'] #COMPOSITE DEPTH if is_final_render and result['DEPTH'] is None: if self.sample_count == len(self.samples) - 1: normal_depth = Texture(resolution, GL_RGBA32F) target = RenderTarget([normal_depth], Texture(resolution, GL_DEPTH_COMPONENT32F)) target.clear([(0,0,1,1)], 1) self.common_buffer.load(scene, resolution) self.draw_scene_pass(target, opaque_batches, 'PRE_PASS', self.default_shader, scene.shader_resources) result['DEPTH'] = self.composite_depth.render(self, self.common_buffer, normal_depth, depth_channel=3) return result PIPELINE = NPR_Pipeline ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Nodes/Render/RenderLayers.py ================================================ from Malt.GL import GL from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type _BLEND_TRANSPARENCY_SHADER = None class RenderLayers(PipelineNode): """ Renders the scene geometry, using multiple *depth peeling* layers for transparent objects. The node sockets are dynamic, based on the graph selected. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None self.texture_targets = {} self.render_target = None self.custom_io = [] @staticmethod def get_pass_type(): return 'Render Layer.Render Layer' @classmethod def reflect_inputs(cls): inputs = {} inputs['Scene'] = Parameter('Scene', Type.OTHER) inputs['Transparent Layers'] = Parameter(4, Type.INT, doc=""" The maximum number of overlapping transparency layers. Incresing this values lowers performance. """) inputs['Transparent Layers @ Preview'] = Parameter(2, Type.INT) return inputs @classmethod def reflect_outputs(cls): outputs = {} return outputs def setup_render_targets(self, resolution, custom_io): self.opaque_targets = {} self.transparent_targets = {} self.color_targets = {} for io in custom_io: if io['io'] == 'out' and io['type'] == 'Texture':#TODO self.opaque_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F) self.transparent_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F) self.color_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F) self.fbo_opaque = RenderTarget([*self.opaque_targets.values()]) self.fbo_transparent = RenderTarget([*self.transparent_targets.values()]) self.fbo_color = RenderTarget([*self.color_targets.values()]) def blend_transparency(self, back_textures, front_textures, fbo): global _BLEND_TRANSPARENCY_SHADER if _BLEND_TRANSPARENCY_SHADER is None: _BLEND_TRANSPARENCY_SHADER = self.pipeline.compile_shader_from_source('#include "Passes/BlendTransparency.glsl"') for i in range(len(fbo.targets)): _BLEND_TRANSPARENCY_SHADER.textures[f'IN_BACK[{str(i)}]'] = back_textures[i] _BLEND_TRANSPARENCY_SHADER.textures[f'IN_FRONT[{str(i)}]'] = front_textures[i] self.pipeline.draw_screen_pass(_BLEND_TRANSPARENCY_SHADER, fbo) def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] custom_io = parameters['CUSTOM_IO'] graph = parameters['PASS_GRAPH'] scene = inputs['Scene'] if scene and graph: if self.pipeline.resolution != self.resolution or self.custom_io != custom_io: self.setup_render_targets(self.pipeline.resolution, custom_io) self.resolution = self.pipeline.resolution self.custom_io = custom_io self.fbo_color.clear([(0,0,0,0)]*len(self.fbo_color.targets)) self.fbo_transparent.clear([(0,0,0,0)]*len(self.fbo_transparent.targets)) self.layer_index = 0 self.layer_count = inputs['Transparent Layers'] + 1 graph['parameters']['__RENDER_LAYERS__'] = self for i in range(self.layer_count): graph['parameters']['__LAYER_INDEX__'] = self.layer_index graph['parameters']['__LAYER_COUNT__'] = self.layer_count self.pipeline.graphs['Render Layer'].run_source(self.pipeline, graph['source'], graph['parameters'], inputs, outputs) results = [] for io in self.custom_io: if io['io'] == 'out' and io['type'] == 'Texture': results.append(outputs[io['name']]) if i == 0: self.pipeline.copy_textures(self.fbo_opaque, results) else: self.blend_transparency(results, self.fbo_transparent.targets, self.fbo_color) self.pipeline.copy_textures(self.fbo_transparent, self.fbo_color.targets) self.layer_index += 1 self.blend_transparency(self.fbo_opaque.targets, self.fbo_transparent.targets, self.fbo_color) outputs.update(self.color_targets) NODE = RenderLayers ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Nodes/Render/SceneLighting.py ================================================ from Malt.GL.GL import * from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type from Malt.Render import Common from Malt.Render import Lighting from Malt.Pipelines.NPR_Pipeline import NPR_Lighting class SceneLighting(PipelineNode): """ Renders the shadow maps and attaches them along the scene lights data to the *Scene* shader resources. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.lights_buffer = Lighting.get_lights_buffer() self.light_groups_buffer = NPR_Lighting.NPR_LightsGroupsBuffer() self.shadowmaps_opaque = NPR_Lighting.NPR_ShadowMaps() self.shadowmaps_transparent = NPR_Lighting.NPR_TransparentShadowMaps() self.common_buffer = Common.CommonBuffer() @classmethod def reflect_inputs(cls): inputs = {} inputs['Scene'] = Parameter('Scene', Type.OTHER) inputs['Point Resolution'] = Parameter(2048, Type.INT, doc= "Shadowmap resolution for point lights *(for each cubemap side)*.") inputs['Point Resolution @ Preview'] = Parameter(512, Type.INT) inputs['Spot Resolution'] = Parameter(2048, Type.INT, doc= "Shadowmap resolution for spot lights.") inputs['Spot Resolution @ Preview'] = Parameter(512, Type.INT) inputs['Sun Resolution'] = Parameter(2048, Type.INT, doc= "Shadowmap resolution for sun light lights *(for each cascade side)*.") inputs['Sun Max Distance'] = Parameter(100, Type.FLOAT,doc=""" The maximum distance from the view origin at which objects will still cast shadows. The lower the value, the higher the perceived resolution.""") inputs['Sun Max Distance @ Preview'] = Parameter(25, Type.FLOAT) inputs['Sun CSM Count'] = Parameter(4, Type.INT, doc= "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.") inputs['Sun CSM Count @ Preview'] = Parameter(2, Type.INT) inputs['Sun CSM Distribution'] = Parameter(0.9, Type.FLOAT, doc=""" Interpolates the cascades distribution along the view distance between linear distribution *(at 0)* and logarithmic distribution *(at 1)*. The appropriate value depends on camera FOV and scene characteristics.""") return inputs @classmethod def reflect_outputs(cls): outputs = {} outputs['Scene'] = Parameter('Scene', Type.OTHER, doc= "The scene with the light data already loaded in the shader resources.") return outputs def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] scene = inputs['Scene'] if scene is None: return sample_offset = self.pipeline.get_sample(scene.world_parameters['Samples.Width']) opaque_batches, transparent_batches = self.pipeline.get_scene_batches(scene) inputs['Spot Resolution'] = max(8, inputs['Spot Resolution']) inputs['Sun Resolution'] = max(8, inputs['Sun Resolution']) inputs['Point Resolution'] = max(8, inputs['Point Resolution']) inputs['Sun CSM Count'] = max(1, inputs['Sun CSM Count']) self.lights_buffer.load(scene, inputs['Spot Resolution'], inputs['Sun Resolution'], inputs['Point Resolution'], inputs['Sun CSM Count'], inputs['Sun CSM Distribution'], inputs['Sun Max Distance'], sample_offset) self.light_groups_buffer.load(scene) self.shadowmaps_opaque.load(scene, inputs['Spot Resolution'], inputs['Sun Resolution'], inputs['Point Resolution'], inputs['Sun CSM Count']) self.shadowmaps_transparent.load(scene, inputs['Spot Resolution'], inputs['Sun Resolution'], inputs['Point Resolution'], inputs['Sun CSM Count']) shader_resources = scene.shader_resources.copy() shader_resources['COMMON_UNIFORMS'] = self.common_buffer shader_resources['SCENE_LIGHTS'] = self.lights_buffer def render_shadowmaps(lights, fbos_opaque, fbos_transparent): for light_index, light_matrices_pair in enumerate(lights.items()): light, matrices = light_matrices_pair for matrix_index, camera_projection_pair in enumerate(matrices): camera, projection = camera_projection_pair i = light_index * len(matrices) + matrix_index self.common_buffer.load(scene, fbos_opaque[i].resolution, (0,0), self.pipeline.sample_count, camera, projection) def get_light_group_batches(batches): result = {} for material, meshes in batches.items(): if material and light.parameters['Light Group'] in material.parameters['Light Groups.Shadow']: result[material] = meshes return result #TODO: Callback self.pipeline.draw_scene_pass(fbos_opaque[i], get_light_group_batches(opaque_batches), 'SHADOW_PASS', self.pipeline.default_shader['SHADOW_PASS'], shader_resources) self.pipeline.draw_scene_pass(fbos_transparent[i], get_light_group_batches(transparent_batches), 'SHADOW_PASS', self.pipeline.default_shader['SHADOW_PASS'], shader_resources) render_shadowmaps(self.lights_buffer.spots, self.shadowmaps_opaque.spot_fbos, self.shadowmaps_transparent.spot_fbos) glEnable(GL_DEPTH_CLAMP) render_shadowmaps(self.lights_buffer.suns, self.shadowmaps_opaque.sun_fbos, self.shadowmaps_transparent.sun_fbos) glDisable(GL_DEPTH_CLAMP) render_shadowmaps(self.lights_buffer.points, self.shadowmaps_opaque.point_fbos, self.shadowmaps_transparent.point_fbos) import copy scene = copy.copy(scene) scene.shader_resources = copy.copy(scene.shader_resources) scene.shader_resources['SCENE_LIGHTS'] = self.lights_buffer scene.shader_resources['LIGHT_GROUPS'] = self.light_groups_buffer scene.shader_resources['SHADOWMAPS'] = self.shadowmaps_opaque scene.shader_resources['TRANSPARENT_SHADOWMAPS'] = self.shadowmaps_transparent outputs['Scene'] = scene NODE = SceneLighting ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Nodes/Render/ScreenPass.py ================================================ from Malt.GL import GL from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type from Malt.Scene import TextureShaderResource class ScreenPass(PipelineNode): """ Renders a full screen shader pass. The node sockets are dynamic, based on the shader selected. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None self.texture_targets = {} self.render_target = None self.custom_io = [] @staticmethod def get_pass_type(): return 'Screen.SCREEN_SHADER' def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] material = parameters['PASS_MATERIAL'] custom_io = parameters['CUSTOM_IO'] if self.pipeline.resolution != self.resolution or self.custom_io != custom_io: self.texture_targets = {} for io in custom_io: if io['io'] == 'out': if io['type'] == 'Texture':#TODO self.texture_targets[io['name']] = Texture(self.pipeline.resolution, GL.GL_RGBA16F) self.render_target = RenderTarget([*self.texture_targets.values()]) self.resolution = self.pipeline.resolution self.custom_io = custom_io self.render_target.clear([(0,0,0,0)]*len(self.texture_targets)) if material and material.shader and 'SHADER' in material.shader: shader = material.shader['SHADER'] for io in custom_io: if io['io'] == 'in': if io['type'] == 'Texture':#TODO from Malt.SourceTranspiler import GLSLTranspiler glsl_name = GLSLTranspiler.custom_io_reference('IN', 'SCREEN_SHADER', io['name']) shader.textures[glsl_name] = inputs[io['name']] self.pipeline.common_buffer.shader_callback(shader) shader.uniforms['RENDER_LAYER_MODE'].set_value(False) self.pipeline.draw_screen_pass(shader, self.render_target) for io in custom_io: if io['io'] == 'out': if io['type'] == 'Texture':#TODO outputs[io['name']] = self.texture_targets[io['name']] NODE = ScreenPass ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/MainPass.py ================================================ from Malt.GL.GL import * from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type from Malt.Scene import TextureShaderResource class MainPass(PipelineNode): """ Renders the scene geometry using the *Mesh Main Pass*. The node sockets are dynamic, based on the *Main Pass Custom IO*. If *Normal Depth/ID* is empty, the *Pre Pass* *Normal Depth/ID* will be used. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None self.t_depth = None @staticmethod def get_pass_type(): return 'Mesh.MAIN_PASS_PIXEL_SHADER' @classmethod def reflect_inputs(cls): inputs = {} inputs['Scene'] = Parameter('Scene', Type.OTHER) inputs['Normal Depth'] = Parameter('', Type.TEXTURE) inputs['ID'] = Parameter('', Type.TEXTURE) return inputs @classmethod def reflect_outputs(cls): outputs = {} return outputs def setup_render_targets(self, resolution, t_depth, custom_io): self.custom_targets = {} for io in custom_io: if io['io'] == 'out' and io['type'] == 'Texture':#TODO formats = { 'float' : GL.GL_R16F, 'vec2' : GL.GL_RG16F, 'vec3' : GL.GL_RGB16F, 'vec4' : GL.GL_RGBA16F, } self.custom_targets[io['name']] = Texture(resolution, formats[io['subtype']]) self.t_depth = t_depth self.fbo = RenderTarget([*self.custom_targets.values()], self.t_depth) def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] custom_io = parameters['CUSTOM_IO'] scene = inputs['Scene'] if scene is None: return t_normal_depth = inputs['Normal Depth'] t_id = inputs['ID'] shader_resources = scene.shader_resources.copy() if t_normal_depth: shader_resources['IN_NORMAL_DEPTH'] = TextureShaderResource('IN_NORMAL_DEPTH', t_normal_depth) if t_id: shader_resources['IN_ID'] = TextureShaderResource('IN_ID', t_id) t_depth = shader_resources['T_DEPTH'].texture if self.pipeline.resolution != self.resolution or self.custom_io != custom_io or t_depth != self.t_depth: self.setup_render_targets(self.pipeline.resolution, t_depth, custom_io) self.resolution = self.pipeline.resolution self.custom_io = custom_io for io in custom_io: if io['io'] == 'in': if io['type'] == 'Texture':#TODO from Malt.SourceTranspiler import GLSLTranspiler glsl_name = GLSLTranspiler.custom_io_reference('IN', 'MAIN_PASS_PIXEL_SHADER', io['name']) shader_resources['CUSTOM_IO'+glsl_name] = TextureShaderResource(glsl_name, inputs[io['name']]) self.fbo.clear([(0,0,0,0)] * len(self.fbo.targets)) self.pipeline.draw_scene_pass(self.fbo, scene.batches, 'MAIN_PASS', self.pipeline.default_shader['MAIN_PASS'], shader_resources, GL_EQUAL) outputs.update(self.custom_targets) NODE = MainPass ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/PrePass.py ================================================ from Malt.GL.GL import * from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type from Malt.Scene import TextureShaderResource from Malt.Pipelines.NPR_Pipeline.NPR_LightShaders import NPR_LightShaders class PrePass(PipelineNode): """ Renders the scene geometry using the *Mesh Pre Pass*. The node sockets are dynamic, based on the *Pre Pass Custom IO*. If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None self.custom_io = [] self.npr_light_shaders = NPR_LightShaders() @staticmethod def get_pass_type(): return 'Mesh.PRE_PASS_PIXEL_SHADER' @classmethod def reflect_inputs(cls): inputs = {} inputs['Scene'] = Parameter('Scene', Type.OTHER) return inputs @classmethod def reflect_outputs(cls): outputs = {} outputs['Scene'] = Parameter('Scene', Type.OTHER) outputs['Normal Depth'] = Parameter('', Type.TEXTURE) outputs['ID'] = Parameter('', Type.TEXTURE) return outputs def setup_render_targets(self, resolution, custom_io): self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F) self.t_normal_depth = Texture(resolution, GL_RGBA32F) self.t_id = Texture(resolution, GL_RGBA16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST) self.custom_targets = {} for io in custom_io: if io['io'] == 'out' and io['type'] == 'Texture':#TODO self.custom_targets[io['name']] = Texture(resolution, GL.GL_RGBA16F) self.fbo = RenderTarget([self.t_normal_depth, self.t_id, *self.custom_targets.values()], self.t_depth) self.t_last_layer_id = Texture(resolution, GL_R16UI, min_filter=GL_NEAREST, mag_filter=GL_NEAREST) self.fbo_last_layer_id = RenderTarget([self.t_last_layer_id]) self.t_opaque_depth = Texture(resolution, GL_DEPTH_COMPONENT32F) self.fbo_opaque_depth = RenderTarget([], self.t_opaque_depth) self.t_transparent_depth = Texture(resolution, GL_DEPTH_COMPONENT32F) self.fbo_transparent_depth = RenderTarget([], self.t_transparent_depth) def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] custom_io = parameters['CUSTOM_IO'] scene = inputs['Scene'] is_opaque_pass = parameters['__GLOBALS__']['__LAYER_INDEX__'] == 0 if self.pipeline.resolution != self.resolution or self.custom_io != custom_io: self.setup_render_targets(self.pipeline.resolution, custom_io) self.resolution = self.pipeline.resolution self.custom_io = custom_io import copy scene = copy.copy(scene) opaque_batches, transparent_batches = self.pipeline.get_scene_batches(scene) scene.batches = opaque_batches if is_opaque_pass else transparent_batches shader_resources = scene.shader_resources.copy() shader_resources.update({ 'IN_OPAQUE_DEPTH': TextureShaderResource('IN_OPAQUE_DEPTH', self.t_opaque_depth), 'IN_TRANSPARENT_DEPTH': TextureShaderResource('IN_TRANSPARENT_DEPTH', self.t_transparent_depth), 'IN_LAST_ID': TextureShaderResource('IN_LAST_ID', self.t_last_layer_id), }) self.fbo.clear([(0,0,0,1), (0,0,0,0)] + [(0,0,0,0)]*len(self.custom_targets), 1) self.pipeline.draw_scene_pass(self.fbo, scene.batches, 'PRE_PASS', self.pipeline.default_shader['PRE_PASS'], shader_resources) if is_opaque_pass: self.fbo_last_layer_id.clear([(0,0,0,0)]) self.fbo_transparent_depth.clear([], -1) self.pipeline.copy_textures(self.fbo_opaque_depth, [], self.t_depth) else: self.pipeline.copy_textures(self.fbo_last_layer_id, [self.t_id]) self.pipeline.copy_textures(self.fbo_transparent_depth, [], self.t_depth) #CUSTOM LIGHT SHADERS self.npr_light_shaders.load(self.pipeline, self.t_depth, scene) scene.shader_resources = scene.shader_resources.copy() scene.shader_resources.update({ 'LIGHTS_CUSTOM_SHADING': self.npr_light_shaders, 'IN_NORMAL_DEPTH': TextureShaderResource('IN_NORMAL_DEPTH', self.t_normal_depth), 'IN_ID': TextureShaderResource('IN_ID', self.t_id), 'T_DEPTH': TextureShaderResource('', self.t_depth), #just pass the reference }) outputs['Scene'] = scene outputs['Normal Depth'] = self.t_normal_depth outputs['ID'] = self.t_id outputs.update(self.custom_targets) NODE = PrePass ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Nodes/RenderLayer/ScreenPass.py ================================================ from Malt.GL import GL from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type from Malt.Scene import TextureShaderResource class ScreenPass(PipelineNode): """ Renders a full screen shader pass. The node sockets are dynamic, based on the shader selected. If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used. """ def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) self.resolution = None self.texture_targets = {} self.render_target = None self.custom_io = [] @staticmethod def get_pass_type(): return 'Screen.SCREEN_SHADER' @classmethod def reflect_inputs(cls): inputs = {} inputs['Layer Only'] = Parameter(True, Type.BOOL, doc=""" Draw only on top of the current layer geometry, to avoid accidentally covering previous layers. """) inputs['Scene'] = Parameter('Scene', Type.OTHER) inputs['Normal Depth'] = Parameter('', Type.TEXTURE) inputs['ID'] = Parameter('', Type.TEXTURE) return inputs def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] material = parameters['PASS_MATERIAL'] custom_io = parameters['CUSTOM_IO'] deferred_mode = inputs['Layer Only'] scene = inputs['Scene'] t_normal_depth = inputs['Normal Depth'] t_id = inputs['ID'] shader_resources = {} if scene: shader_resources = scene.shader_resources.copy() if t_normal_depth: shader_resources['IN_NORMAL_DEPTH'] = TextureShaderResource('IN_NORMAL_DEPTH', t_normal_depth) if t_id: shader_resources['IN_ID'] = TextureShaderResource('IN_ID', t_id) if self.pipeline.resolution != self.resolution or self.custom_io != custom_io: self.texture_targets = {} for io in custom_io: if io['io'] == 'out': if io['type'] == 'Texture':#TODO self.texture_targets[io['name']] = Texture(self.pipeline.resolution, GL.GL_RGBA16F) self.render_target = RenderTarget([*self.texture_targets.values()]) self.resolution = self.pipeline.resolution self.custom_io = custom_io self.render_target.clear([(0,0,0,0)]*len(self.texture_targets)) if material and material.shader and 'SHADER' in material.shader: shader = material.shader['SHADER'] for io in custom_io: if io['io'] == 'in': if io['type'] == 'Texture':#TODO from Malt.SourceTranspiler import GLSLTranspiler glsl_name = GLSLTranspiler.custom_io_reference('IN', 'SCREEN_SHADER', io['name']) shader.textures[glsl_name] = inputs[io['name']] self.pipeline.common_buffer.bind(shader.uniform_blocks['COMMON_UNIFORMS']) for resource in shader_resources.values(): resource.shader_callback(shader) shader.uniforms['RENDER_LAYER_MODE'].set_value(True) shader.uniforms['DEFERRED_MODE'].set_value(deferred_mode) self.pipeline.draw_screen_pass(shader, self.render_target) for io in custom_io: if io['io'] == 'out': if io['type'] == 'Texture':#TODO outputs[io['name']] = self.texture_targets[io['name']] NODE = ScreenPass ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Intellisense.glsl ================================================ //This is just for getting text editor autocompletion on user shaders. //It doesn't have any effect at runtime #ifdef __INTELLISENSE__ #define VERTEX_SHADER #define PIXEL_SHADER #define IS_MESH_SHADER #define CUSTOM_VERTEX_DISPLACEMENT #define SHADOW_PASS #define PRE_PASS #define MAIN_PASS #define IS_LIGHT_SHADER #define IS_SCREEN_SHADER #endif //__INTELLISENSE__ ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_LightShader.glsl ================================================ #define NO_NORMAL_INPUT #define NO_UV_INPUT #define NO_VERTEX_COLOR_INPUT #define NO_MODEL_INPUT #define NO_ID_INPUT #ifdef PIXEL_SHADER float DEFERRED_PIXEL_DEPTH; #define CUSTOM_PIXEL_DEPTH DEFERRED_PIXEL_DEPTH #endif #include "NPR_Intellisense.glsl" #include "Common.glsl" #include "Lighting/Lighting.glsl" uniform int LIGHT_INDEX; #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D IN_DEPTH; layout (location = 0) out vec3 RESULT; void LIGHT_SHADER(vec3 relative_coordinates, vec3 uvw, inout vec3 color, inout float attenuation); void main() { PIXEL_SETUP_INPUT(); float depth = texelFetch(IN_DEPTH, screen_pixel(), 0).x; DEFERRED_PIXEL_DEPTH = depth; POSITION = screen_to_camera(screen_uv(), depth); POSITION = transform_point(inverse(CAMERA), POSITION); Light L = LIGHTS.lights[LIGHT_INDEX]; LitSurface LS = lit_surface(POSITION, vec3(0), L, false); vec3 light_space = vec3(0); vec3 uvw = vec3(0); if(L.type == LIGHT_SPOT) { light_space = project_point(LIGHTS.spot_matrices[L.type_index], POSITION); uvw.xy = light_space.xy * 0.5 + 0.5; } if(L.type == LIGHT_SUN) { vec3 z = L.direction; vec3 c = vec3(0,0,1); if(abs(dot(z, c)) < 1.0) { vec3 x = normalize(cross(c, z)); vec3 y = normalize(cross(x, z)); mat3 rotation = mat3(x,y,z); mat4 m = mat4_translation(L.position) * mat4(rotation); m = inverse(m); light_space = transform_point(m, POSITION); } else { light_space = POSITION; light_space -= L.position; } uvw.xy = light_space.xy; } if(L.type == LIGHT_POINT) { light_space = POSITION - L.position; light_space /= L.radius; uvw = normalize(light_space); } vec3 color = L.color; float attenuation = LS.P; LIGHT_SHADER(light_space, uvw, color, attenuation); RESULT = color * attenuation; } #endif //PIXEL_SHADER ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_MeshShader.glsl ================================================ #include "NPR_Intellisense.glsl" #include "Common.glsl" /* META GLOBAL @meta: internal=true; */ struct NPR_Settings { // Global material settings. Can be modified in the material panel UI bool Receive_Shadow; bool Self_Shadow; bool Transparency; bool Transparency_Single_Layer; float Vertex_Displacement_Offset; }; uniform NPR_Settings Settings = NPR_Settings(true, true, false, false, 0.001); uniform ivec4 MATERIAL_LIGHT_GROUPS; struct Vertex { vec3 position; vec3 normal; uvec4 id; vec3 tangent; vec3 bitangent; vec2 uv[4]; vec4 color[4]; }; /* META @id: label=ID; @opacity: label=Opacity Mask; @transparent_shadowmap_color: label=Transparent Shadow Color; */ struct PrePassOutput { vec3 normal; uvec4 id; float opacity; vec3 transparent_shadowmap_color; }; #ifdef VERTEX_SHADER void COMMON_VERTEX_SHADER(inout Vertex V); #ifndef CUSTOM_VERTEX_SHADER void COMMON_VERTEX_SHADER(inout Vertex V){} #endif vec3 VERTEX_DISPLACEMENT_SHADER(); vec3 VERTEX_DISPLACEMENT_WRAPPER(Vertex V) { Vertex real; real.position = POSITION; real.tangent = TANGENT; real.bitangent = BITANGENT; POSITION = V.position; TANGENT = V.tangent; BITANGENT = V.bitangent; vec3 result = VERTEX_DISPLACEMENT_SHADER(); POSITION = real.position; TANGENT = real.tangent; BITANGENT = real.bitangent; return result; } #ifndef CUSTOM_VERTEX_DISPLACEMENT vec3 VERTEX_DISPLACEMENT_SHADER(){ return vec3(0); } #endif #ifndef VERTEX_DISPLACEMENT_OFFSET #define VERTEX_DISPLACEMENT_OFFSET 0.1 #endif #ifndef CUSTOM_MAIN void main() { DEFAULT_VERTEX_SHADER(); Vertex V; V.position = POSITION; V.normal = NORMAL; V.tangent = TANGENT; V.bitangent = BITANGENT; V.uv = UV; V.color = COLOR; V.id = ID; COMMON_VERTEX_SHADER(V); POSITION = V.position; NORMAL = V.normal; TANGENT = V.tangent; BITANGENT = V.bitangent; UV = V.uv; COLOR = V.color; ID = V.id; #ifdef CUSTOM_VERTEX_DISPLACEMENT { vec3 displaced_position = POSITION + VERTEX_DISPLACEMENT_WRAPPER(V); if(!PRECOMPUTED_TANGENTS) { vec3 axis = vec3(0,0,1); axis = abs(dot(axis, NORMAL)) < 0.99 ? axis : vec3(1,0,0); vec3 tangent = normalize(cross(axis, NORMAL)); TANGENT = tangent; BITANGENT = normalize(cross(NORMAL, tangent)); } Vertex v = V; v.position = POSITION + TANGENT * Settings.Vertex_Displacement_Offset; vec3 displaced_tangent = v.position + VERTEX_DISPLACEMENT_WRAPPER(v); TANGENT = normalize(displaced_tangent - displaced_position); v.position = POSITION + BITANGENT * Settings.Vertex_Displacement_Offset; vec3 displaced_bitangent = v.position + VERTEX_DISPLACEMENT_WRAPPER(v); BITANGENT = normalize(displaced_bitangent - displaced_position); POSITION = displaced_position; NORMAL = normalize(cross(TANGENT, BITANGENT)); if(!PRECOMPUTED_TANGENTS) { TANGENT = vec3(0); BITANGENT = vec3(0); } } #endif VERTEX_SETUP_OUTPUT(); } #endif //NDEF CUSTOM_MAIN #endif //VERTEX_SHADER #ifdef PIXEL_SHADER #ifdef SHADOW_PASS layout (location = 0) out uint OUT_ID; layout (location = 1) out vec3 OUT_SHADOW_MULTIPLY_COLOR; #endif //PRE_PASS #ifdef PRE_PASS uniform sampler2D IN_OPAQUE_DEPTH; uniform sampler2D IN_TRANSPARENT_DEPTH; uniform usampler2D IN_LAST_ID; layout (location = 0) out vec4 OUT_NORMAL_DEPTH; layout (location = 1) out uvec4 OUT_ID; #endif //PRE_PASS #ifdef MAIN_PASS uniform sampler2D IN_NORMAL_DEPTH; uniform usampler2D IN_ID; #endif //MAIN_PASS #ifndef CUSTOM_MAIN #ifdef CUSTOM_PRE_PASS void PRE_PASS_PIXEL_SHADER(inout PrePassOutput PPO); #endif #ifdef CUSTOM_DEPTH_OFFSET void DEPTH_OFFSET(inout float depth_offset, inout bool offset_position); #endif #ifdef MAIN_PASS void MAIN_PASS_PIXEL_SHADER(); #endif void main() { PIXEL_SETUP_INPUT(); PrePassOutput PPO; PPO.normal = NORMAL; PPO.id = ID; PPO.opacity = 1; PPO.transparent_shadowmap_color = vec3(0); float depth = gl_FragCoord.z; vec3 offset_position = POSITION; // Discard pixel at the end of the shader, to avoid derivative glitches. bool discard_pixel = false; #ifdef CUSTOM_PRE_PASS { PRE_PASS_PIXEL_SHADER(PPO); if(PPO.opacity <= 0) { discard_pixel = true; } else if(!Settings.Transparency) { PPO.opacity = 1.0; } } #endif #ifdef CUSTOM_DEPTH_OFFSET { float depth_offset = 0; bool offset_position = false; DEPTH_OFFSET(depth_offset, offset_position); #ifdef SHADOW_PASS { if(!offset_position) depth_offset = 0; } #endif vec3 position = POSITION + view_direction() * depth_offset; depth = project_point_to_screen_coordinates(PROJECTION * CAMERA, position).z; gl_FragDepth = depth; if(offset_position) POSITION = position; } #endif #ifdef SHADOW_PASS { OUT_ID = PPO.id.r; if(Settings.Transparency) { float pass_through = hash(vec2(ID.x, SAMPLE_COUNT)).x; if(pass_through > PPO.opacity) { discard_pixel = true; } OUT_SHADOW_MULTIPLY_COLOR = PPO.transparent_shadowmap_color; } } #endif #ifdef PRE_PASS { if(Settings.Transparency) { float opaque_depth = texelFetch(IN_OPAQUE_DEPTH, ivec2(gl_FragCoord.xy), 0).x; float transparent_depth = texelFetch(IN_TRANSPARENT_DEPTH, ivec2(gl_FragCoord.xy), 0).x; if(depth >= opaque_depth || depth <= transparent_depth) { discard_pixel = true; } if(Settings.Transparency_Single_Layer) { if(PPO.id.r == texelFetch(IN_LAST_ID, ivec2(gl_FragCoord.xy), 0).x) { discard_pixel = true; } } } OUT_NORMAL_DEPTH.xyz = PPO.normal; OUT_NORMAL_DEPTH.w = depth; OUT_ID = PPO.id; } #endif #ifdef MAIN_PASS { NORMAL = texelFetch(IN_NORMAL_DEPTH, ivec2(gl_FragCoord.xy), 0).xyz; ID = texelFetch(IN_ID, ivec2(gl_FragCoord.xy), 0); MAIN_PASS_PIXEL_SHADER(); } #endif if(discard_pixel) { discard; } } #endif //NDEF CUSTOM_MAIN #endif //PIXEL_SHADER #include "NPR_Pipeline/NPR_Mesh.glsl" #include "NPR_Pipeline/NPR_Shading2.glsl" ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Filters.glsl ================================================ #ifndef NPR_FILTERS_GLSL #define NPR_FILTERS_GLSL #include "Filters/AO.glsl" #include "Filters/Bevel.glsl" #include "Filters/Curvature.glsl" #include "Filters/Line.glsl" #if defined(PIXEL_SHADER) && (defined(MAIN_PASS) || defined(IS_SCREEN_SHADER)) #define NPR_FILTERS_ACTIVE #endif /* META GLOBAL @meta: category=Shading; */ /* META @meta: label=Ambient Occlusion; @samples: default=32; min=1; @radius: default=1.0; min=0.0; @distribution_exponent: default=5.0; @contrast: subtype=Slider; default=0.1; min=0.0; max=1.0; @bias: subtype=Slider; default=0.01; min=0.01; max=0.1; */ float ao(int samples, float radius, float distribution_exponent, float contrast, float bias) { #ifdef NPR_FILTERS_ACTIVE { vec3 normal = NORMAL; #ifdef IS_MESH_SHADER { normal = is_front_facing() ? IO_NORMAL : -IO_NORMAL; } #endif float ao = ao(IN_NORMAL_DEPTH, 3, POSITION, normal, samples, radius, distribution_exponent, bias); return pow(ao, map_range(contrast, 0.0, 1.0, 1.0, 10.0)); } #else { return 1.0; } #endif } /* META @meta: subcategory=Curvature; */ float curvature() { #ifdef NPR_FILTERS_ACTIVE { vec3 x = transform_normal(inverse(CAMERA), vec3(1,0,0)); vec3 y = transform_normal(inverse(CAMERA), vec3(0,1,0)); return curvature(IN_NORMAL_DEPTH, screen_uv(), 1.0, x, y); } #else { return 0.5; } #endif } /* META @meta: subcategory=Curvature; @depth_range: default=0.1; */ float surface_curvature(float depth_range) { #ifdef NPR_FILTERS_ACTIVE { vec3 x = transform_normal(inverse(CAMERA), vec3(1,0,0)); vec3 y = transform_normal(inverse(CAMERA), vec3(0,1,0)); return surface_curvature(IN_NORMAL_DEPTH, IN_NORMAL_DEPTH, 3, screen_uv(), 1.0, x, y, depth_range); } #else { return 0.5; } #endif } /* META @meta: category=Vector; subcategory=Bevel; @samples: default=32; min=1; @radius: default=0.02; min=0.0; @distribution_exponent: default=2.0; */ vec3 soft_bevel(int samples, float radius, float distribution_exponent, bool only_self) { #ifdef NPR_FILTERS_ACTIVE { uint id = texture(IN_ID, screen_uv())[0]; return bevel( IN_NORMAL_DEPTH, IN_NORMAL_DEPTH, 3, id, only_self, IN_ID, 0, samples, radius, distribution_exponent, false, 1); } #endif return NORMAL; } /* META @meta: category=Vector; subcategory=Bevel; @samples: default=32; min=1; @radius: default=0.01; min=0.0; @max_dot: default=0.75; */ vec3 hard_bevel(int samples, float radius, float max_dot, bool only_self) { #ifdef NPR_FILTERS_ACTIVE { uint id = texture(IN_ID, screen_uv())[0]; return bevel( IN_NORMAL_DEPTH, IN_NORMAL_DEPTH, 3, id, only_self, IN_ID, 0, samples, radius, 1.0, true, max_dot); } #endif return NORMAL; } void _fix_range(inout float value, inout float range) { if(range < 0) { range = abs(range); value -= range; } } /* META @meta: label=Line Detection; @is_id_boundary: label=Is ID Boundary; */ void line_detection_2( out float delta_distance, out float delta_angle, out vec4 is_id_boundary ) { #ifdef NPR_FILTERS_ACTIVE { LineDetectionOutput result; result = line_detection_2( IN_NORMAL_DEPTH, 3, IN_NORMAL_DEPTH, IN_ID ); delta_distance = result.delta_distance; delta_angle = result.delta_angle; is_id_boundary = vec4(result.id_boundary); } #endif } /*META @meta: internal=true;*/ LineDetectionOutput line_detection() { LineDetectionOutput result; #ifdef NPR_FILTERS_ACTIVE { result = line_detection( POSITION, NORMAL, true_normal(), 1, LINE_DEPTH_MODE_NEAR, screen_uv(), IN_NORMAL_DEPTH, 3, IN_NORMAL_DEPTH, IN_ID ); } #endif return result; } /* META @meta: label=Line Width; @width_scale: min=0.0; default=4.0; @width_units: subtype=ENUM(Pixel,Screen,World); @id_boundary_width: subtype=Slider; min=0.0; max=1.0; default=vec4(1.0); @depth_width: subtype=Slider; min=0.0; max=1.0; default=1.0; @depth_threshold: subtype=Slider; min=0.0; max=1.0; default=0.1; @depth_threshold_range: subtype=Slider; min=0.0; max=1.0; default=0.0; @normal_width: subtype=Slider; min=0.0; max=1.0; default=1.0; @normal_threshold: subtype=Slider; min=0.0; max=1.0; default=0.5; @normal_threshold_range: subtype=Slider; min=0.0; max=1.0; default=0.0; */ float line_width_2( float width_scale, int width_units, float depth_width, float depth_threshold, float depth_threshold_range, float normal_width, float normal_threshold, float normal_threshold_range, vec4 id_boundary_width ) { #ifdef NPR_FILTERS_ACTIVE { depth_threshold = pow(depth_threshold, 10) * 999 + 1; if (depth_threshold_range > 0) depth_threshold_range = pow(depth_threshold_range, 10) * 1000; LineDetectionOutput lo = line_detection_2( IN_NORMAL_DEPTH, 3, IN_NORMAL_DEPTH, IN_ID ); float line = 0; vec4 id = vec4(lo.id_boundary) * id_boundary_width; for(int i = 0; i < 4; i++) { line = max(line, id[i]); } _fix_range(depth_threshold, depth_threshold_range); if(lo.delta_distance > depth_threshold) { float depth = depth_width; if(depth_threshold_range != 0) { depth = map_range_clamped( lo.delta_distance, depth_threshold, depth_threshold + depth_threshold_range, 0, depth_width ); } line = max(line, depth); } _fix_range(normal_threshold, normal_threshold_range); normal_threshold = max(0.01, normal_threshold); if(lo.delta_angle > normal_threshold) { float angle = normal_width; if(normal_threshold_range != 0) { angle = map_range_clamped( lo.delta_angle, normal_threshold, normal_threshold + normal_threshold_range, 0, normal_width ); } line = max(line, angle); } if(width_units == 1)//Screen % { width_scale *= length(vec2(RESOLUTION)) / 1000.0; } if(width_units == 2)//World { width_scale /= pixel_world_size() * 100.0; } return line * width_scale; } #else { return 0.0; } #endif } /* META @meta: internal=true; @line_width_scale: default=2.0; @id_boundary_width: subtype=Data; default=vec4(1); @depth_width: default=1.0; @depth_threshold: default=0.5; @normal_width: default=1.0; @normal_threshold: default=0.5; */ float line_width( float line_width_scale, vec4 id_boundary_width, float depth_width, float depth_width_range, float depth_threshold, float depth_threshold_range, float normal_width, float normal_width_range, float normal_threshold, float normal_threshold_range ) { #ifdef NPR_FILTERS_ACTIVE { LineDetectionOutput lo = line_detection(); float line = 0; vec4 id = vec4(lo.id_boundary) * id_boundary_width; for(int i = 0; i < 4; i++) { line = max(line, id[i]); } _fix_range(depth_width, depth_width_range); _fix_range(depth_threshold, depth_threshold_range); if(lo.delta_distance > depth_threshold) { float depth = map_range_clamped( lo.delta_distance, depth_threshold, depth_threshold + depth_threshold_range, depth_width, depth_width + depth_width_range ); line = max(line, depth); } _fix_range(normal_width, normal_width_range); _fix_range(normal_threshold, normal_threshold_range); if(lo.delta_angle > normal_threshold) { float angle = map_range_clamped( lo.delta_angle, normal_threshold, normal_threshold + normal_threshold_range, normal_width, normal_width + normal_width_range ); line = max(line, angle); } return line * line_width_scale; } #else { return 0.0; } #endif } #endif //NPR_FILTERS_GLSL ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Lighting.glsl ================================================ #ifndef NPR_LIGHTING_GLSL #define NPR_LIGHTING_GLSL #include "Lighting/Lighting.glsl" uniform usampler2DArray SHADOWMAPS_ID_SPOT; uniform usampler2DArray SHADOWMAPS_ID_SUN; uniform usamplerCubeArray SHADOWMAPS_ID_POINT; uniform sampler2DArray TRANSPARENT_SHADOWMAPS_DEPTH_SPOT; uniform sampler2DArray TRANSPARENT_SHADOWMAPS_DEPTH_SUN; uniform samplerCubeArray TRANSPARENT_SHADOWMAPS_DEPTH_POINT; uniform usampler2DArray TRANSPARENT_SHADOWMAPS_ID_SPOT; uniform usampler2DArray TRANSPARENT_SHADOWMAPS_ID_SUN; uniform usamplerCubeArray TRANSPARENT_SHADOWMAPS_ID_POINT; uniform sampler2DArray TRANSPARENT_SHADOWMAPS_COLOR_SPOT; uniform sampler2DArray TRANSPARENT_SHADOWMAPS_COLOR_SUN; uniform samplerCubeArray TRANSPARENT_SHADOWMAPS_COLOR_POINT; layout(std140) uniform LIGHT_GROUPS { //Use ivec4 so the ints are tightly packed ivec4 LIGHT_GROUP_INDEX[MAX_LIGHTS/4+1]; }; #define LIGHT_GROUP_INDEX(light_index) LIGHT_GROUP_INDEX[(light_index)/4][(light_index)%4] layout(std140) uniform LIGHTS_CUSTOM_SHADING { //Use ivec4 so the ints are tightly packed ivec4 CUSTOM_SHADING_INDEX[MAX_LIGHTS/4+1]; }; #define CUSTOM_SHADING_INDEX(light_index) CUSTOM_SHADING_INDEX[(light_index)/4][(light_index)%4]; uniform sampler2DArray IN_LIGHT_CUSTOM_SHADING; /* META @meta: internal=true; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @id: default=ID[0]; */ LitSurface npr_lit_surface(vec3 position, vec3 normal, uint id, Light light, int light_index, bool shadows, bool self_shadows) { LitSurface S = lit_surface(position, normal, light, false); S.light_color = light.color * S.P; int custom_shading_index = CUSTOM_SHADING_INDEX(light_index); if(custom_shading_index >= 0) { S.light_color = texelFetch(IN_LIGHT_CUSTOM_SHADING, ivec3(screen_pixel(), custom_shading_index), 0).rgb; } if(!shadows) { return S; } if(light.type_index >= 0) { float bias = 1e-5; //Opaque shadow ShadowData shadow; uint shadow_id; //Transparent shadow ShadowData t_shadow; vec3 t_shadow_multiply; uint t_shadow_id; if(light.type == LIGHT_SPOT) { shadow = spot_shadow(position, light, SHADOWMAPS_DEPTH_SPOT, bias); vec2 uv = shadow.light_uv.xy; int index = light.type_index; shadow_id = texture(SHADOWMAPS_ID_SPOT, vec3(uv, index)).x; t_shadow = spot_shadow(position, light, TRANSPARENT_SHADOWMAPS_DEPTH_SPOT, bias); t_shadow_multiply = texture(TRANSPARENT_SHADOWMAPS_COLOR_SPOT, vec3(uv, index)).rgb; t_shadow_id = texture(TRANSPARENT_SHADOWMAPS_ID_SPOT, vec3(uv, index)).x; } if(light.type == LIGHT_SUN) { bias = 1e-3; shadow = sun_shadow(position, light, SHADOWMAPS_DEPTH_SUN, bias, S.cascade); vec2 uv = shadow.light_uv.xy; int index = light.type_index * LIGHTS.cascades_count + S.cascade; shadow_id = texture(SHADOWMAPS_ID_SUN, vec3(uv, index)).x; t_shadow = sun_shadow(position, light, TRANSPARENT_SHADOWMAPS_DEPTH_SUN, bias, S.cascade); t_shadow_multiply = texture(TRANSPARENT_SHADOWMAPS_COLOR_SUN, vec3(uv, index)).rgb; t_shadow_id = texture(TRANSPARENT_SHADOWMAPS_ID_SUN, vec3(uv, index)).x; } if(light.type == LIGHT_POINT) { shadow = point_shadow(position, light, SHADOWMAPS_DEPTH_POINT, bias); vec3 uv = shadow.light_uv; int index = light.type_index; shadow_id = texture(SHADOWMAPS_ID_POINT, vec4(uv, index)).x; t_shadow = point_shadow(position, light, TRANSPARENT_SHADOWMAPS_DEPTH_POINT, bias); t_shadow_multiply = texture(TRANSPARENT_SHADOWMAPS_COLOR_POINT, vec4(uv, index)).rgb; t_shadow_id = texture(TRANSPARENT_SHADOWMAPS_ID_POINT, vec4(uv, index)).x; } S.shadow = shadow.shadow; if(self_shadows == false && id == shadow_id) { S.shadow = false; } S.shadow_multiply = S.shadow ? vec3(0) : vec3(1); if(!S.shadow && t_shadow.shadow) { if(self_shadows == false && id == t_shadow_id) { t_shadow.shadow = false; } if(t_shadow.shadow) { S.shadow = true; S.shadow_multiply = t_shadow_multiply; } } } return S; } #endif //NPR_LIGHTING_GLSL ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Mesh.glsl ================================================ #ifndef NPR_MESH_GLSL #define NPR_MESH_GLSL #include "NPR_Pipeline/NPR_Filters.glsl" #include "NPR_Pipeline/NPR_Shading.glsl" /* META GLOBAL @meta: internal=true; */ /* META @meta: subcategory=NPR Diffuse;*/ vec3 diffuse_shading() { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += diffuse_shading(POSITION, NORMAL, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: subcategory=NPR Diffuse;*/ vec3 diffuse_half_shading() { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += diffuse_half_shading(POSITION, NORMAL, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: subcategory=NPR Diffuse;*/ vec3 diffuse_gradient_shading(sampler1D gradient_texture) { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += diffuse_gradient_shading(POSITION, NORMAL, gradient_texture, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: subcategory=NPR Specular; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @anisotropy: default=0.5; @roughness: default=0.5; */ vec3 specular_shading(float roughness) { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += specular_shading(POSITION, NORMAL, roughness, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: subcategory=NPR Specular; @roughness: default=0.5; */ vec3 specular_gradient_shading(sampler1D gradient_texture, float roughness) { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += specular_gradient_shading(POSITION, NORMAL, roughness, gradient_texture, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: subcategory=NPR Specular; @roughness: default=0.5; @anisotropy: default=0.5; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); */ vec3 specular_anisotropic_shading(float roughness, float anisotropy, vec3 tangent) { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += specular_anisotropic_shading(POSITION, NORMAL, tangent, anisotropy, roughness, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: subcategory=NPR Specular; @roughness: default=0.5; @anisotropy: default=0.5; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); */ vec3 specular_anisotropic_gradient_shading(sampler1D gradient_texture, float roughness, float anisotropy, vec3 tangent) { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += specular_anisotropic_gradient_shading(POSITION, NORMAL, tangent, anisotropy, roughness, gradient_texture, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } /* META @meta: label=NPR Toon Shading; */ vec3 toon_shading(float size, float gradient_size, float specularity, float offset) { vec3 result = vec3(0); for(int i = 0; i < 4; i++) { result += toon_shading(POSITION, NORMAL, size, gradient_size, specularity, offset, MATERIAL_LIGHT_GROUPS[i], Settings.Receive_Shadow, Settings.Self_Shadow); } return result; } bool is_shadow_pass() { #ifdef SHADOW_PASS { return true; } #else { return false; } #endif } bool is_pre_pass() { #ifdef PRE_PASS { return true; } #else { return false; } #endif } bool is_main_pass() { #ifdef MAIN_PASS { return true; } #else { return false; } #endif } /*META @meta: category=Input; internal=false;*/ void pass_info( out bool Is_Main_Pass, out bool Is_Pre_Pass, out bool Is_Shadow_Pass ) { Is_Main_Pass = is_main_pass(); Is_Pre_Pass = is_pre_pass(); Is_Shadow_Pass = is_shadow_pass(); } uint _pack_id_channel(vec4 color) { uint max_uint16 = 65535; uint p = packUnorm4x8(color); if(p <= max_uint16) { //Since it's already in the 16 bit range, assume the custom ID has not been overriden //For actual colors, they will only fit if alpha < 0.002 return p; } //Otherwise, generate a pcg4d hash and scale it to the 16 bit range uvec4 h = _pcg4d(floatBitsToUint(color)); return h.x / max_uint16; } /* META @meta: label=Pack ID; category=Node Tree; internal=false; @object_id: label=Object ID; default=unpackUnorm4x8(ID.x); @custom_id_a: label=Custom ID A; default=unpackUnorm4x8(ID.y); @custom_id_b: label=Custom ID B; default=unpackUnorm4x8(ID.z); @custom_id_c: label=Custom ID C; default=unpackUnorm4x8(ID.w); @id: label=ID; */ void pack_id( vec4 object_id, vec4 custom_id_a, vec4 custom_id_b, vec4 custom_id_c, out uvec4 id ) { id.x = _pack_id_channel(object_id); id.y = _pack_id_channel(custom_id_a); id.z = _pack_id_channel(custom_id_b); id.w = _pack_id_channel(custom_id_c); } #endif //NPR_MESH_GLSL ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Shading.glsl ================================================ #ifndef NPR_SHADING_GLSL #define NPR_SHADING_GLSL #include "NPR_Lighting.glsl" #include "Shading/ShadingModels.glsl" /* META GLOBAL @meta: internal=true; */ #define _LIT_SCENE_MACRO(callback, light_group, shadows, self_shadows)\ vec3 result = vec3(0,0,0);\ for (int i = 0; i < LIGHTS.lights_count; i++)\ {\ if(LIGHT_GROUP_INDEX(i) != light_group) continue;\ Light L = LIGHTS.lights[i];\ LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadows, self_shadows);\ result += (callback);\ }\ return result;\ /* META @meta: subcategory=Diffuse; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 diffuse_shading(vec3 position, vec3 normal, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(diffuse_lit_surface(LS), light_group, shadows, self_shadows); } /* META @meta: subcategory=Diffuse; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 diffuse_half_shading(vec3 position, vec3 normal, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(diffuse_half_lit_surface(LS), light_group, shadows, self_shadows); } /* META @meta: subcategory=Diffuse; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 diffuse_gradient_shading(vec3 position, vec3 normal, sampler1D gradient, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(diffuse_gradient_lit_surface(LS, gradient), light_group, shadows, self_shadows); } /* META @meta: subcategory=Specular; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @roughness: default=0.5; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 specular_shading(vec3 position, vec3 normal, float roughness, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(specular_lit_surface(LS, roughness), light_group, shadows, self_shadows); } /* META @meta: subcategory=Specular; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @roughness: default=0.5; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 specular_gradient_shading(vec3 position, vec3 normal, float roughness, sampler1D gradient, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(specular_gradient_lit_surface(LS, roughness, gradient), light_group, shadows, self_shadows); } /* META @meta: subcategory=Specular; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @anisotropy: default=0.5; @roughness: default=0.5; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 specular_anisotropic_shading(vec3 position, vec3 normal, vec3 tangent, float anisotropy, float roughness, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(specular_anisotropic_lit_surface(LS, tangent, anisotropy, roughness), light_group, shadows, self_shadows); } /* META @meta: subcategory=Specular; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @anisotropy: default=0.5; @roughness: default=0.5; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 specular_anisotropic_gradient_shading(vec3 position, vec3 normal, vec3 tangent, float anisotropy, float roughness, sampler1D gradient, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(specular_anisotropic_gradient_lit_surface(LS, tangent, anisotropy, roughness, gradient), light_group, shadows, self_shadows); } /* META @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @size: default=0.49; @light_group: default=1; @shadows: default=true; @self_shadows: default=true; */ vec3 toon_shading(vec3 position, vec3 normal, float size, float gradient_size, float specularity, float offset, int light_group, bool shadows, bool self_shadows) { _LIT_SCENE_MACRO(toon_lit_surface(LS, size, gradient_size, specularity, offset), light_group, shadows, self_shadows); } #endif //NPR_SHADING_GLSL ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_Pipeline/NPR_Shading2.glsl ================================================ #ifndef NPR_SHADING2_GLSL #define NPR_SHADING2_GLSL #include "NPR_Lighting.glsl" #include "Shading/ShadingModels.glsl" /* META GLOBAL @meta: category=Shading; */ void _shadow_params(int enum_value, out bool shadows, out bool self_shadows) { #ifdef IS_MESH_SHADER { shadows = enum_value == 0 ? Settings.Receive_Shadow : enum_value < 3; self_shadows = enum_value == 0 ? Settings.Self_Shadow : enum_value == 1; } #else { shadows = enum_value < 2; self_shadows = enum_value == 0; } #endif } #ifdef IS_MESH_SHADER /* META @meta: label=Color Ramp; subcategory=NPR Diffuse; @base_color: default=vec3(0); @color: default=vec3(1); @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @light_groups: default=MATERIAL_LIGHT_GROUPS; @shadow_mode: subtype=ENUM(Multiply,Remap); default=0; @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #else /* META @meta: label=Color Ramp; subcategory=NPR Diffuse; @base_color: default=vec3(0); @color: default=vec3(1); @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @light_groups: default=ivec4(1,0,0,0); @shadow_mode: subtype=ENUM(Multiply,Remap); default=0; @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #endif vec3 NPR_diffuse_shading( vec3 base_color, vec3 color, sampler1D gradient, float offset, bool full_range, bool max_contribution, //int shadow_mode, int shadows, ivec4 light_groups, vec3 position, vec3 normal ) { bool shadow; bool self_shadow; _shadow_params(shadows, shadow, self_shadow); if(full_range) { self_shadow = false; } vec3 result = vec3(0,0,0); for (int i = 0; i < LIGHTS.lights_count; i++) { for(int group_index = 0; group_index < 4; group_index++) { if(LIGHT_GROUP_INDEX(i) != light_groups[group_index]) { continue; } Light L = LIGHTS.lights[i]; LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow); if(!full_range && LS.NoL < 0) { continue; } float lambert = LS.NoL; if(full_range) { lambert = map_range(LS.NoL, -1, 1, 0, 1); } lambert = saturate(lambert + offset); vec4 gradient_sample = texture(gradient, lambert); gradient_sample *= gradient_sample.a; vec3 diffuse = gradient_sample.rgb * LS.light_color * LS.shadow_multiply; /* vec3 diffuse; vec3 shadow_multiply = LS.shadow_multiply; if(shadow_mode == 0)//Multiply { diffuse = texture(gradient, lambert).rgb * shadow_multiply * LS.light_color; } else if(shadow_mode == 1)//Remap { if(full_range) { shadow_multiply = map_range_clamped(LS.shadow_multiply, vec3(0), vec3(1), vec3(0.5), vec3(1)); } diffuse = rgb_gradient(gradient, min(vec3(lambert), shadow_multiply)) * LS.light_color; } */ if(max_contribution) { result = max(result, diffuse); } else { result += diffuse; } } } return mix(base_color, color, result); } vec3 _map_gradient(float size, float gradient_size, vec3 uvw) { if(gradient_size > 0.0) { return map_range_clamped(uvw, vec3(1.0 - size), vec3(1.0 - size + gradient_size), vec3(0.0), vec3(1.0)); } else { return vec3(greaterThan(uvw, vec3(1.0 - size))); } } #ifdef IS_MESH_SHADER /* META @meta: label=Color Layer; subcategory=NPR Diffuse; @base_color: default=vec3(0); @color: default=vec3(1); @size: subtype=Slider; min=-0.0; max=1.0; default=1.0; @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @light_groups: default=MATERIAL_LIGHT_GROUPS; @shadow_mode: subtype=ENUM(Multiply,Remap); default=0; @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #else /* META @meta: label=Color Layer; subcategory=NPR Diffuse; @base_color: default=vec3(0); @color: default=vec3(1); @size: subtype=Slider; min=-0.0; max=1.0; default=1.0; @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @light_groups: default=ivec4(1,0,0,0); @shadow_mode: subtype=ENUM(Multiply,Remap); default=0; @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #endif vec3 NPR_diffuse_layer( vec3 base_color, vec3 color, float size, float gradient_size, float offset, bool full_range, //int shadow_mode, bool max_contribution, int shadows, ivec4 light_groups, vec3 position, vec3 normal ) { bool shadow; bool self_shadow; _shadow_params(shadows, shadow, self_shadow); if(full_range) { self_shadow = false; } size = saturate(size + offset); gradient_size = min(size, gradient_size); vec3 result = vec3(0,0,0); for (int i = 0; i < LIGHTS.lights_count; i++) { for(int group_index = 0; group_index < 4; group_index++) { if(LIGHT_GROUP_INDEX(i) != light_groups[group_index]) { continue; } Light L = LIGHTS.lights[i]; LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow); if(!full_range && LS.NoL < 0) { continue; } float lambert = LS.NoL; if(full_range) { lambert = map_range(LS.NoL, -1.0, 1.0, 0.0, 1.0); } vec3 diffuse = _map_gradient(size, gradient_size, vec3(lambert)) * LS.light_color * LS.shadow_multiply; /* vec3 diffuse; vec3 shadow_multiply = LS.shadow_multiply; if(shadow_mode == 0)//Multiply { diffuse = _map_gradient(size, gradient_size, vec3(lambert)) * shadow_multiply * LS.light_color; } else if(shadow_mode == 1)//Remap { if(full_range) { shadow_multiply = map_range_clamped(LS.shadow_multiply, vec3(0), vec3(1), vec3(0.5), vec3(1)); } diffuse = _map_gradient(size, gradient_size, min(vec3(lambert), shadow_multiply)) * LS.light_color; } */ if(max_contribution) { result = max(result, diffuse); } else { result += diffuse; } } } return mix(base_color, color, result); } #ifdef IS_MESH_SHADER /* META @meta: label=Color Ramp; subcategory=NPR Specular; @base_color: default=vec3(0); @color: default=vec3(1); @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5; @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5; @light_groups: default=MATERIAL_LIGHT_GROUPS; @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #else /* META @meta: label=Color Ramp; subcategory=NPR Specular; @base_color: default=vec3(0); @color: default=vec3(1); @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5; @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5; @light_groups: default=ivec4(1,0,0,0); @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #endif vec3 NPR_specular_shading( vec3 base_color, vec3 color, sampler1D gradient, float offset, float roughness, float anisotropy, bool max_contribution, int shadows, ivec4 light_groups, vec3 position, vec3 normal, vec3 tangent ) { bool shadow; bool self_shadow; _shadow_params(shadows, shadow, self_shadow); vec3 result = vec3(0,0,0); for (int i = 0; i < LIGHTS.lights_count; i++) { for(int group_index = 0; group_index < 4; group_index++) { if(LIGHT_GROUP_INDEX(i) != light_groups[group_index]) { continue; } Light L = LIGHTS.lights[i]; LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow); if(LS.NoL < 0) { continue; } float NoH = dot(normal, LS.H); vec3 bitangent = normalize(cross(normal, tangent)); float XoH = dot(LS.H, tangent); float YoH = dot(LS.H, bitangent); vec2 a = vec2(anisotropy, 1.0 - anisotropy) * roughness; float custom_ggx = (1.0 / (pow((XoH*XoH) / (a.x*a.x) + (YoH*YoH) / (a.y*a.y) + NoH*NoH, 3.0))); //minimal geometric shadowing custom_ggx *= 1.0 - pow(1.0 - LS.NoL, 5); custom_ggx = saturate(custom_ggx + offset); vec4 gradient_sample = texture(gradient, custom_ggx); gradient_sample *= gradient_sample.a; vec3 specular = gradient_sample.rgb * LS.light_color * LS.shadow_multiply; if(max_contribution) { result = max(result, specular); } else { result += specular; } } } return mix(base_color, color, result); } #ifdef IS_MESH_SHADER /* META @meta: label=Color Layer; subcategory=NPR Specular; @base_color: default=vec3(0); @color: default=vec3(1); @size: subtype=Slider; min=-0.0; max=1.0; default=1.0; @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5; @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5; @light_groups: default=MATERIAL_LIGHT_GROUPS; @shadows: subtype=ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #else /* META @meta: label=Color Layer; subcategory=NPR Specular; @base_color: default=vec3(0); @color: default=vec3(1); @size: subtype=Slider; min=-0.0; max=1.0; default=1.0; @gradient_size: subtype=Slider; min=0.0; max=1.0; default=0.1; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @tangent: subtype=Normal; default=radial_tangent(NORMAL, vec3(0,0,1)); @offset: subtype=Slider; min=-1.0; max=1.0; default=0.0; @anisotropy: subtype=Slider; min=0.0; max=1.0; default=0.5; @roughness: subtype=Slider; min=0.0; max=1.0; default=0.5; @light_groups: default=ivec4(1,0,0,0); @shadows: subtype=ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows); default=0; */ #endif vec3 NPR_specular_layer( vec3 base_color, vec3 color, float size, float gradient_size, float offset, float roughness, float anisotropy, bool max_contribution, int shadows, ivec4 light_groups, vec3 position, vec3 normal, vec3 tangent ) { bool shadow; bool self_shadow; _shadow_params(shadows, shadow, self_shadow); size = saturate(size + offset) * 0.99; gradient_size = min(size, gradient_size); vec3 result = vec3(0,0,0); for (int i = 0; i < LIGHTS.lights_count; i++) { for(int group_index = 0; group_index < 4; group_index++) { if(LIGHT_GROUP_INDEX(i) != light_groups[group_index]) { continue; } Light L = LIGHTS.lights[i]; LitSurface LS = npr_lit_surface(position, normal, ID.x, L, i, shadow, self_shadow); if(LS.NoL < 0) { continue; } float NoH = dot(normal, LS.H); vec3 bitangent = normalize(cross(normal, tangent)); float XoH = dot(LS.H, tangent); float YoH = dot(LS.H, bitangent); vec2 a = vec2(anisotropy, 1.0 - anisotropy) * roughness; float custom_ggx = (1.0 / (pow((XoH*XoH) / (a.x*a.x) + (YoH*YoH) / (a.y*a.y) + NoH*NoH, 3.0))); //minimal geometric shadowing custom_ggx *= 1.0 - pow(1.0 - LS.NoL, 5); vec3 specular = _map_gradient(size, gradient_size, vec3(custom_ggx)) * LS.light_color * LS.shadow_multiply; if(max_contribution) { result = max(result, specular); } else { result += specular; } } } return mix(base_color, color, result); } #endif //NPR_SHADING2_GLSL ================================================ FILE: Malt/Pipelines/NPR_Pipeline/Shaders/NPR_ScreenShader.glsl ================================================ #define NO_UV_INPUT #define NO_VERTEX_COLOR_INPUT #define NO_MODEL_INPUT #include "NPR_Intellisense.glsl" #ifdef PIXEL_SHADER vec3 DEFERRED_TRUE_NORMAL; #define CUSTOM_TRUE_NORMAL DEFERRED_TRUE_NORMAL float DEFERRED_PIXEL_DEPTH; #define CUSTOM_PIXEL_DEPTH DEFERRED_PIXEL_DEPTH #endif #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D IN_NORMAL_DEPTH; uniform usampler2D IN_ID; uniform bool RENDER_LAYER_MODE = false; uniform bool DEFERRED_MODE = false; void SCREEN_SHADER(); void main() { PIXEL_SETUP_INPUT(); DEFERRED_TRUE_NORMAL = reconstruct_normal(IN_NORMAL_DEPTH, 3, screen_pixel()); DEFERRED_PIXEL_DEPTH = texelFetch(IN_NORMAL_DEPTH, screen_pixel(), 0).w; if(RENDER_LAYER_MODE) { vec4 normal_depth = texture(IN_NORMAL_DEPTH, UV[0]); if(DEFERRED_MODE && normal_depth.w == 1.0) { discard; } POSITION = screen_to_camera(UV[0], normal_depth.w); POSITION = transform_point(inverse(CAMERA), POSITION); NORMAL = normal_depth.xyz; ID = texture(IN_ID, UV[0]); } SCREEN_SHADER(); } #endif #include "NPR_Pipeline/NPR_Filters.glsl" #include "NPR_Pipeline/NPR_Shading.glsl" #include "NPR_Pipeline/NPR_Shading2.glsl" ================================================ FILE: Malt/Render/Common.py ================================================ import ctypes from Malt.GL.Shader import UBO class C_CommonBuffer(ctypes.Structure): _fields_ = [ ('CAMERA', ctypes.c_float*16), ('PROJECTION', ctypes.c_float*16), ('RESOLUTION', ctypes.c_int*2), ('SAMPLE_OFFSET', ctypes.c_float*2), ('SAMPLE_COUNT', ctypes.c_int), ('FRAME', ctypes.c_int), ('TIME', ctypes.c_float), ('__padding', ctypes.c_int), ] def bake_sample_offset(projection_matrix, sample_offset, resolution): offset_x = sample_offset[0] / resolution[0] offset_y = sample_offset[1] / resolution[1] if projection_matrix[-1] == 1.0: #Orthographic camera projection_matrix[12] += offset_x projection_matrix[13] += offset_y else: #Perspective camera projection_matrix[8] += offset_x projection_matrix[9] += offset_y class CommonBuffer(): def __init__(self): self.data = C_CommonBuffer() self.UBO = UBO() def load(self, scene, resolution, sample_offset=(0,0), sample_count=0, camera=None, projection = None): self.data.CAMERA = tuple(camera if camera else scene.camera.camera_matrix) self.data.PROJECTION = tuple(projection if projection else scene.camera.projection_matrix) self.data.RESOLUTION = resolution self.data.SAMPLE_OFFSET = sample_offset self.data.SAMPLE_COUNT = sample_count self.data.FRAME = scene.frame self.data.TIME = scene.time bake_sample_offset(self.data.PROJECTION, sample_offset, resolution) self.UBO.load_data(self.data) def bind(self, block): self.UBO.bind(block) def shader_callback(self, shader): if 'COMMON_UNIFORMS' in shader.uniform_blocks: self.bind(shader.uniform_blocks['COMMON_UNIFORMS']) ================================================ FILE: Malt/Render/DepthToCompositeDepth.py ================================================ from Malt.GL.GL import * from Malt.GL.Texture import Texture from Malt.GL.RenderTarget import RenderTarget _shader_src=''' #include "Passes/DepthToBlenderDepth.glsl" ''' _SHADER = None class CompositeDepth(): def __init__(self): self.t = None self.fbo = None self.shader = None def render(self, pipeline, common_buffer, depth_texture, depth_channel=0): if self.t is None or self.t.resolution != depth_texture.resolution: self.t = Texture(depth_texture.resolution, GL_R32F) self.fbo = RenderTarget([self.t]) if self.shader == None: global _SHADER if _SHADER is None: _SHADER = pipeline.compile_shader_from_source(_shader_src) self.shader = _SHADER self.shader.textures['DEPTH_TEXTURE'] = depth_texture self.shader.uniforms['DEPTH_CHANNEL'].set_value(depth_channel) common_buffer.shader_callback(self.shader) pipeline.draw_screen_pass(self.shader, self.fbo) return self.t ================================================ FILE: Malt/Render/Lighting.py ================================================ import math import ctypes import pyrr from Malt.GL.GL import * from Malt.GL.Shader import UBO from Malt.GL.Texture import TextureArray, CubeMapArray from Malt.GL.RenderTarget import ArrayLayerTarget, RenderTarget from Malt import Pipeline from Malt.Render.Common import bake_sample_offset _LIGHTS_BUFFER = None def get_lights_buffer(): global _LIGHTS_BUFFER if _LIGHTS_BUFFER is None: _LIGHTS_BUFFER = LightsBuffer() return _LIGHTS_BUFFER _SHADOWMAPS = None def get_shadow_maps(): global _SHADOWMAPS if _SHADOWMAPS is None: _SHADOWMAPS = ShadowMaps() return _SHADOWMAPS LIGHT_SUN = 1 LIGHT_POINT = 2 LIGHT_SPOT = 3 class C_Light(ctypes.Structure): _fields_ = [ ('color', ctypes.c_float*3), ('type', ctypes.c_int32), ('position', ctypes.c_float*3), ('radius', ctypes.c_float), ('direction', ctypes.c_float*3), ('spot_angle', ctypes.c_float), ('spot_blend', ctypes.c_float), ('type_index', ctypes.c_int32), ('__padding', ctypes.c_int32*2), ] MAX_SPOTS = 64 MAX_SUNS = 64 MAX_POINTS = 64 MAX_LIGHTS = 128 class C_LightsBuffer(ctypes.Structure): _fields_ = [ ('lights', C_Light*MAX_LIGHTS), ('lights_count', ctypes.c_int), ('cascades_count', ctypes.c_int), ('__padding', ctypes.c_int32*2), ('spot_matrices', ctypes.c_float*16*MAX_SPOTS), ('sun_matrices', ctypes.c_float*16*MAX_SUNS), ('point_matrices', ctypes.c_float*16*MAX_POINTS), ] class ShadowMaps(): def __init__(self): self.max_spots = 1 self.spot_resolution = 2048 self.spot_depth_t = None self.spot_fbos = [] self.max_suns = 1 self.sun_resolution = 2048 self.sun_depth_t = None self.sun_fbos = [] self.max_points = 1 self.point_resolution = 512 self.point_depth_t = None self.point_fbos = [] self.initialized = False def load(self, scene, spot_resolution, sun_resolution, point_resolution, sun_cascades): needs_setup = self.initialized is False self.initialized = True new_settings = (spot_resolution, sun_resolution, point_resolution) current_settings = (self.spot_resolution, self.sun_resolution, self.point_resolution) if new_settings != current_settings: self.spot_resolution = spot_resolution self.sun_resolution = sun_resolution self.point_resolution = point_resolution needs_setup = True spot_count = len([l for l in scene.lights if l.type == LIGHT_SPOT]) if spot_count > self.max_spots: self.max_spots = spot_count needs_setup = True sun_count = len([l for l in scene.lights if l.type == LIGHT_SUN]) sun_count = sun_count * sun_cascades if sun_count > self.max_suns: self.max_suns = sun_count needs_setup = True point_count = len([l for l in scene.lights if l.type == LIGHT_POINT]) if point_count > self.max_points: self.max_points = point_count needs_setup = True if needs_setup: self.setup() self.clear(spot_count, sun_count, point_count) def setup(self, create_fbos=True): self.spot_depth_t = TextureArray((self.spot_resolution, self.spot_resolution), self.max_spots, GL_DEPTH_COMPONENT32F) self.sun_depth_t = TextureArray((self.sun_resolution, self.sun_resolution), self.max_suns, GL_DEPTH_COMPONENT32F) self.point_depth_t = CubeMapArray((self.point_resolution, self.point_resolution), self.max_points, GL_DEPTH_COMPONENT32F) if create_fbos: self.spot_fbos = [] for i in range(self.spot_depth_t.length): self.spot_fbos.append(RenderTarget([], ArrayLayerTarget(self.spot_depth_t, i))) self.sun_fbos = [] for i in range(self.sun_depth_t.length): self.sun_fbos.append(RenderTarget([], ArrayLayerTarget(self.sun_depth_t, i))) self.point_fbos = [] for i in range(self.point_depth_t.length*6): self.point_fbos.append(RenderTarget([], ArrayLayerTarget(self.point_depth_t, i))) def clear(self, spot_count, sun_count, point_count): for i in range(spot_count): self.spot_fbos[i].clear(depth=1) for i in range(sun_count): self.sun_fbos[i].clear(depth=1) for i in range(point_count*6): self.point_fbos[i].clear(depth=1) def shader_callback(self, shader): shader.textures['SHADOWMAPS_DEPTH_SPOT'] = self.spot_depth_t shader.textures['SHADOWMAPS_DEPTH_SUN'] = self.sun_depth_t shader.textures['SHADOWMAPS_DEPTH_POINT'] = self.point_depth_t class LightsBuffer(): def __init__(self): self.data = C_LightsBuffer() self.UBO = UBO() self.spots = None self.suns = None self.points = None def load( self, scene, spot_resolution, sun_resolution, point_resolution, cascades_count, cascades_distribution_scalar, cascades_max_distance=1.0, sample_offset=(0,0) ): #TODO: Automatic distribution exponent basedd on FOV spot_count=0 sun_count=0 point_count=0 from collections import OrderedDict self.spots = OrderedDict() self.suns = OrderedDict() self.points = OrderedDict() for i, light in enumerate(scene.lights): self.data.lights[i].color = light.color self.data.lights[i].type = light.type self.data.lights[i].position = light.position self.data.lights[i].radius = light.radius self.data.lights[i].direction = light.direction self.data.lights[i].spot_angle = light.spot_angle self.data.lights[i].spot_blend = light.spot_blend if light.type == LIGHT_SPOT: self.data.lights[i].type_index = spot_count projection_matrix = make_projection_matrix(light.spot_angle,1,0.01,light.radius, sample_offset,(spot_resolution, spot_resolution)) spot_matrix = projection_matrix * pyrr.Matrix44(light.matrix) self.data.spot_matrices[spot_count] = flatten_matrix(spot_matrix) self.spots[light] = [(light.matrix, flatten_matrix(projection_matrix))] spot_count+=1 if light.type == LIGHT_SUN: self.data.lights[i].type_index = sun_count sun_matrix = pyrr.Matrix44(light.matrix) projection_matrix = [*scene.camera.projection_matrix] projection_matrix = pyrr.Matrix44(projection_matrix) view_matrix = projection_matrix * pyrr.Matrix44(scene.camera.camera_matrix) max_distance = cascades_max_distance if light.sun_max_distance != 0: max_distance = light.sun_max_distance cascades_matrices = get_sun_cascades(sun_matrix, projection_matrix, view_matrix, cascades_count, cascades_distribution_scalar, max_distance, sample_offset, sun_resolution) self.suns[light] = [] for i, cascade in enumerate(cascades_matrices): matrix = pyrr.Matrix44(cascade[1])*pyrr.Matrix44(cascade[0]) self.data.sun_matrices[sun_count * cascades_count + i] = flatten_matrix(matrix) self.suns[light].append(cascade) sun_count+=1 if light.type == LIGHT_POINT: self.data.lights[i].type_index = point_count cube_map_axes = [ (( 1, 0, 0),( 0,-1, 0)), ((-1, 0, 0),( 0,-1, 0)), (( 0, 1, 0),( 0, 0, 1)), (( 0,-1, 0),( 0, 0,-1)), (( 0, 0, 1),( 0,-1, 0)), (( 0, 0,-1),( 0,-1, 0)) ] matrices = [] position = pyrr.Vector3(light.position) offset_matrix = pyrr.Matrix44.from_translation(position) rotation_matrix = pyrr.Matrix44.from_eulers((sample_offset[0], sample_offset[1], 0.0)) for axes in cube_map_axes: front = pyrr.Matrix33.from_matrix44(rotation_matrix) * pyrr.Vector3(axes[0]) up = pyrr.Matrix33.from_matrix44(rotation_matrix) * pyrr.Vector3(axes[1]) matrices.append(pyrr.Matrix44.look_at(position, position + front, up)) self.data.point_matrices[point_count] = flatten_matrix((offset_matrix * rotation_matrix).inverse) projection_matrix = make_projection_matrix(math.pi / 2.0, 1.0, 0.01, light.radius, (0,0), (point_resolution, point_resolution)) self.points[light] = [] for i in range(6): self.points[light].append((flatten_matrix(matrices[i]), flatten_matrix(projection_matrix))) point_count+=1 self.data.lights_count = len(scene.lights) self.data.cascades_count = cascades_count self.UBO.load_data(self.data) def bind(self, block): self.UBO.bind(block) def shader_callback(self, shader): if 'SCENE_LIGHTS' in shader.uniform_blocks: self.bind(shader.uniform_blocks['SCENE_LIGHTS']) def flatten_matrix(matrix): return (ctypes.c_float * 16)(*[e for v in matrix for e in v]) #TODO: Hard-coded for Blender conventions for now def make_projection_matrix(fov, aspect_ratio, near, far, sample_offset, resolution): x_scale = 1.0 / math.tan(fov / 2.0) y_scale = x_scale * aspect_ratio matrix = [ x_scale, 0, 0, 0, 0, y_scale, 0, 0, 0, 0, (-(far + near)) / (far - near), -1, 0, 0, (-2.0 * far * near) / (far - near), 0 ] bake_sample_offset(matrix, sample_offset, resolution) return pyrr.Matrix44(matrix) def get_sun_cascades(sun_from_world_matrix, projection_matrix, view_from_world_matrix, cascades_count, cascades_distribution_scalar, cascades_max_distance, sample_offset, resolution): cascades = [] splits = [] n,f = 0,0 if projection_matrix[3][3] == 1.0: # ortho n = cascades_max_distance / 2 f = -cascades_max_distance / 2 else: # perspective clip_start = projection_matrix.inverse * pyrr.Vector4([0,0,-1,1]) clip_start /= clip_start.w n = clip_start.z f = -cascades_max_distance def lerp(a,b,f): f = max(0,min(f,1)) return a * (1.0 - f) + b * f for i in range(cascades_count+1): split_log = n * pow(f/n, i/cascades_count) split_uniform = n + (f-n) * (i/cascades_count) split = lerp(split_uniform, split_log, cascades_distribution_scalar) projected = projection_matrix * pyrr.Vector4([0,0,split,1]) projected = (projected / projected.w) * (1.0 if projected.w >= 0 else -1.0) splits.append(projected.z) for i in range(1, len(splits)): near = splits[i-1] far = splits[i] # Make the cascades overlap a bit if i > 1 : near = lerp(near, splits[i-1], 0.01) if i+1 < len(splits): far = lerp(far, splits[i+1], 0.01) cascades.append(sun_shadowmap_matrix(sun_from_world_matrix, view_from_world_matrix, near, far, sample_offset, resolution)) return cascades def frustum_corners(view_from_world_matrix, near, far): m = view_from_world_matrix.inverse corners = [] for x in (-1, 1): for y in (-1, 1): for z in (near, far): v = pyrr.Vector4([x, y, z, 1]) v = m * v v /= v.w corners.append(v) return corners def sun_shadowmap_matrix(sun_from_world_matrix, view_from_world_matrix, near, far, sample_offset, resolution): INFINITY = float('inf') aabb = { 'min': pyrr.Vector3([ INFINITY, INFINITY, INFINITY]), 'max': pyrr.Vector3([-INFINITY, -INFINITY, -INFINITY]) } for corner in frustum_corners(view_from_world_matrix, near, far): corner = sun_from_world_matrix * corner aabb['min'].x = min(aabb['min'].x, corner.x) aabb['min'].y = min(aabb['min'].y, corner.y) aabb['min'].z = min(aabb['min'].z, corner.z) aabb['max'].x = max(aabb['max'].x, corner.x) aabb['max'].y = max(aabb['max'].y, corner.y) aabb['max'].z = max(aabb['max'].z, corner.z) world_from_light_space = sun_from_world_matrix.inverse size = aabb['max'] - aabb['min'] aabb['min'] = world_from_light_space * pyrr.Vector4([*aabb['min'].tolist(), 1.0]) aabb['max'] = world_from_light_space * pyrr.Vector4([*aabb['max'].tolist(), 1.0]) center = (aabb['min'] + aabb['max']) / 2.0 center = pyrr.Vector3(center.tolist()[:3]) translate = pyrr.Matrix44.from_translation(center) matrix = translate * world_from_light_space o_x = sample_offset[0]/resolution o_y = sample_offset[1]/resolution scale = 1.0 / (size / 2.0) screen = [ scale[0], 0, 0, 0, 0, scale[1], 0, 0, 0, 0,-scale[2], 0, o_x, o_y, 0, 1 ] return flatten_matrix(matrix.inverse), screen ================================================ FILE: Malt/Render/Sampling.py ================================================ import math import random #Don't share state random = random.Random() #Rotated Grid Super Sampling pattern #https://en.wikipedia.org/wiki/Supersampling#Rotated_grid def get_RGSS_samples(grid_size, width=1.0): samples = [] for x in range(0, grid_size): for y in range(0, grid_size): _x = (x / grid_size) * 2.0 - 1.0 #(-1 ... +1 range) _y = (y / grid_size) * 2.0 - 1.0 #(-1 ... +1 range) angle = math.atan(1/2) sin = math.sin(angle) cos = math.cos(angle) r_x = _x * cos - _y * sin r_y = _x * sin + _y * cos scale = math.sqrt(5)/2 r_x *= scale #discard samples where radius > 1 if r_x * r_x + r_y * r_y <= 1: r_x *= width r_y *= width samples.append((r_x,r_y)) random.seed(0) #randomize the sampling order to get better early results samples = sorted(samples, key=lambda k: random.random()) #Make sure there's at least one sample if len(samples) == 0: samples = [(0,0)] return samples #Random sampling. Best results at high sample counts def get_random_samples(grid_size, width=1.0): random.seed(0) samples = [] for i in range(0, grid_size * grid_size): x = 2 y = 2 #discard samples where radius > 1 while x*x + y*y > 1.0: x = random.random() * 2.0 - 1.0 #(-1 ... +1 range) y = random.random() * 2.0 - 1.0 #(-1 ... +1 range) x *= width y *= width samples.append((x,y)) #Make sure there's at least one sample if len(samples) == 0: samples = [(0,0)] return samples ================================================ FILE: Malt/Render/readme.md ================================================ # Render Library Any *Pipeline* agnostic render utility that can't be implemented only as *GLSL* code should go here. 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)). *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. 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. ================================================ FILE: Malt/Scene.py ================================================ class Camera(): def __init__(self, camera_matrix, projection_matrix, parameters={}): self.camera_matrix = camera_matrix self.projection_matrix = projection_matrix self.parameters = parameters class Material(): def __init__(self, shader, parameters={}): self.shader = shader self.parameters = parameters class Mesh(): def __init__(self, mesh, parameters={}): self.mesh = mesh self.parameters = parameters class Object(): def __init__(self, matrix, mesh, material, parameters={}, mirror_scale=False, tags=[]): self.matrix = matrix self.mesh = mesh self.material = material self.parameters = parameters self.mirror_scale = mirror_scale self.tags = tags class Light(): def __init__(self): self.type = 0 self.color = (0,0,0) self.parameters = {} self.position = (0,0,0) self.direction = (0,0,0) self.sun_max_distance = 0 self.spot_angle = 0 self.spot_blend = 0 self.radius = 0 self.matrix = None class Scene(): def __init__(self): self.camera = None self.objects = [] self.lights = [] self.parameters = {} self.world_parameters = {} self.frame = 0 self.time = 0 self.batches = None self.shader_resources = {} class ShaderResource(): def shader_callback(self, shader): pass class TextureShaderResource(): def __init__(self, name, texture): self.name = name self.texture = texture def shader_callback(self, shader): if self.name in shader.textures.keys(): shader.textures[self.name] = self.texture ================================================ FILE: Malt/Shaders/Common/Color.glsl ================================================ #ifndef COMMON_COLOR_GLSL #define COMMON_COLOR_GLSL /* META GLOBAL @meta: category=Color; */ /* META @meta:doc= Blends the blend color as a layer over the base color.; @blend:default=vec4(0); doc= The blend color.; */ vec4 alpha_blend(vec4 base, vec4 blend) { if(blend.a <= 0) { return base; } else if(blend.a >= 1) { return blend; } float alpha = blend.a + base.a * (1.0 - blend.a); vec4 result = (blend * blend.a + base * base.a * (1.0 - blend.a)) / alpha; result.a = alpha; return result; } /* META @meta: label=Grayscale; */ float relative_luminance(vec3 color) { return dot(color, vec3(0.2126,0.7152,0.0722)); } /* META @meta: internal=true; */ float luma(vec3 color) { return dot(color, vec3(0.299,0.587,0.114)); } /* META @meta: label=Linear To sRGB; */ vec3 linear_to_srgb(vec3 linear) { vec3 low = linear * 12.92; vec3 high = 1.055 * pow(linear, vec3(1.0/2.4)) - 0.055; return mix(low, high, greaterThan(linear, vec3(0.0031308))); } /* META @meta: label=sRGB To Linear; */ vec3 srgb_to_linear(vec3 srgb) { vec3 low = srgb / 12.92; vec3 high = pow((srgb + 0.055)/1.055, vec3(2.4)); return mix(low, high, greaterThan(srgb, vec3(0.04045))); } /* META @meta: label=RGB To HSV; */ vec3 rgb_to_hsv(vec3 rgb) { rgb = linear_to_srgb(rgb); //http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, K.wz) : vec4(rgb.gb, K.xy); vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx); float d = q.x - min(q.w, q.y); float e = 1.0e-10; return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); } /* META @meta: label=HSV To RGB; @hsv:subtype=HSV; min=0.0; max=1.0; */ vec3 hsv_to_rgb(vec3 hsv) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(hsv.xxx + K.xyz) * 6.0 - K.www); return srgb_to_linear(max(hsv.z,0) * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), max(hsv.y,0))); } /* META @meta: label=HSV Edit; */ vec4 hsv_edit(vec4 color, float hue, float saturation, float value) { vec3 hsv = rgb_to_hsv(color.rgb); hsv += vec3(hue, saturation, value); return vec4(hsv_to_rgb(hsv), color.a); } /* META @meta: label=Bright/Contrast; */ vec4 bright_contrast(vec4 color, float brightness, float contrast) { float a = 1.0 + contrast; float b = brightness - contrast * 0.5; vec3 result = max(a * color.rgb + b, vec3(0.0)); return vec4(result, color.a); } /* META @meta: label=Gamma; @gamma: default=1.0; min=0.0; */ vec4 gamma_correction(vec4 color, float gamma) { vec3 result = max(pow(color.rgb, vec3(gamma)), vec3(0)); return vec4(result, color.a); } /* META @meta: label=Invert; @fac: subtype=Slider; min=0.0; max=1.0; */ vec4 color_invert(vec4 color, float fac) { vec4 inverted = vec4(1.0 - color.rgb, color.a); return mix(color, inverted, fac); } /* META @meta: internal=true; */ vec3 rgb_gradient(sampler1D gradient, vec3 uvw) { return vec3 ( texture(gradient, uvw.r).r, texture(gradient, uvw.g).g, texture(gradient, uvw.b).b ); } /* META @meta: internal=true; */ vec4 rgba_gradient(sampler1D gradient, vec4 uvw) { return vec4 ( texture(gradient, uvw.r).r, texture(gradient, uvw.g).g, texture(gradient, uvw.b).b, texture(gradient, uvw.a).a ); } #endif // COMMON_COLOR_GLSL ================================================ FILE: Malt/Shaders/Common/Hash.glsl ================================================ #ifndef COMMON_HASH_GLSL #define COMMON_HASH_GLSL /* META GLOBAL @meta: category=Math; internal=True; */ uvec4 _pcg4d(uvec4 v) { //http://www.jcgt.org/published/0009/03/02/ v = v * 1664525u + 1013904223u; v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; v ^= v >> 16u; v.x += v.y*v.w; v.y += v.z*v.x; v.z += v.x*v.y; v.w += v.y*v.z; return v; } vec4 _pcg4d(vec4 v) { uvec4 u = _pcg4d(floatBitsToUint(v)); return vec4(u) / float(0xffffffffU); } vec4 hash(float v){ return _pcg4d(vec4(v,0,0,0)); } vec4 hash(vec2 v){ return _pcg4d(vec4(v,0,0)); } vec4 hash(vec3 v){ return _pcg4d(vec4(v,0)); } /* META @meta: internal=false; @v:subtype=Data; */ vec4 hash(vec4 v){ return _pcg4d(v); } #endif // COMMON_HASH_GLSL ================================================ FILE: Malt/Shaders/Common/Mapping.glsl ================================================ #ifndef COMMON_MAPPING_GLSL #define COMMON_MAPPING_GLSL #include "Common.glsl" /* META GLOBAL @meta: category=Vector; internal=True; */ vec3 view_direction(); vec3 transform_normal(mat4 matrix, vec3 normal); /* META @meta: label=Matcap UV; @normal: subtype=Normal; default=NORMAL; */ vec2 matcap_uv(vec3 normal) { vec3 N = transform_normal(CAMERA, normal); vec3 I = transform_normal(CAMERA, -view_direction()); vec3 x = vec3(1,0,0); vec3 tangent = normalize(x - I * dot(x, I)); vec3 y = vec3(0,1,0); vec3 bitangent = normalize(y - I * dot(y, I)); vec3 screen_normal = vec3 ( dot(N, tangent), dot(N, bitangent), dot(N, I) ); screen_normal = normalize(screen_normal); return screen_normal.xy * 0.499 + 0.5; } /* META @meta: category=Texturing; @normal: subtype=Normal; default=NORMAL; */ vec4 sample_matcap(sampler2D matcap, vec3 normal) { return textureLod(matcap, matcap_uv(normal), 0); } /* META @meta: label=HDRI UV; @normal: subtype=Normal; default=NORMAL; */ vec2 hdri_uv(vec3 normal) { vec2 uv = vec2(atan(normal.y, normal.x), asin(normal.z)); vec2 inverse_atan = vec2(0.1591, 0.3183); return uv * inverse_atan + 0.5; } /* META @meta: category=Texturing; label=Sample HDRI; @normal: subtype=Normal; default=NORMAL; */ vec4 sample_hdri(sampler2D hdri, vec3 normal) { return textureLod(hdri, hdri_uv(normal), 0); } vec2 curve_view_mapping(vec2 uv, vec3 normal, vec3 tangent, vec3 incoming) { vec3 screen_bitangent = transform_normal(CAMERA, cross(incoming, tangent)); vec3 screen_normal = transform_normal(CAMERA, normal); float y_grad = dot(screen_bitangent, screen_normal); return vec2(uv.x, (y_grad + 1) * 0.5); } vec4 sample_flipbook(sampler2D tex, vec2 uv, ivec2 dimensions, int page, bool top_left) { int page_count = dimensions.x * dimensions.y; page = int(mod(page, page_count)); vec2 offset = vec2( mod(page, dimensions.x), floor(top_left ? dimensions.y - 1 - (page / dimensions.y) : (page / dimensions.y)) ); uv = (uv + offset) / vec2(dimensions); return texture(tex, uv); } float pingpong(float a, float b); vec4 flowmap(sampler2D tex, vec2 uv, vec2 flow, float progression, int samples) { vec4 result; float fraction = 1.0 / float(samples); for(int i = 0; i < samples; i++) { float flow_scale = fract(progression - i * fraction); vec4 color = texture(tex, uv - flow * flow_scale); float range = fract(progression - (float(i) / float(samples))) * samples; float p = pingpong(range, 1.0); float bounds = (range > 0.0 && range < 2.0)? 1.0 : 0.0; result += color * p * bounds; } return result; } #endif //COMMON_MAPPING_GLSL ================================================ FILE: Malt/Shaders/Common/Math.glsl ================================================ #ifndef COMMON_MATH_GLSL #define COMMON_MATH_GLSL /* META GLOBAL @meta: category=Math; internal=true; */ //C Standard constants #define M_E 2.71828182845904523536028747135266250 /* e */ #define M_LOG2E 1.44269504088896340735992468100189214 /* log2(e) */ #define M_LOG10E 0.434294481903251827651128918916605082 /* log10(e) */ #define M_LN2 0.693147180559945309417232121458176568 /* ln(2) */ #define M_LN10 2.30258509299404568401799145468436421 /* ln(10) */ #define M_PI 3.14159265358979323846264338327950288 /* pi */ #define M_PI_2 1.57079632679489661923132169163975144 /* pi/2 */ #define M_PI_4 0.785398163397448309615660845819875721 /* pi/4 */ #define M_1_PI 0.318309886183790671537767526745028724 /* 1/pi */ #define M_2_PI 0.636619772367581343075535053490057448 /* 2/pi */ #define M_2_SQRTPI 1.12837916709551257389615890312154517 /* 2/sqrt(pi) */ #define M_SQRT2 1.41421356237309504880168872420969808 /* sqrt(2) */ #define M_SQRT1_2 0.707106781186547524400844362104849039 /* 1/sqrt(2) */ #define GOLDEN_RATIO 1.618033988749894848204586834365638117 /* phi */ #define GOLDEN_ANGLE 2.399963229728652887367000547681316645 /* pi*(3-sqrt(5)) */ #define PI M_PI //These are defined as macros to make them work across different types. (Poor Man's Generics) #define saturate(value) clamp((value), 0, 1) float safe_mix(float a, float b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);} vec2 safe_mix(vec2 a, vec2 b, vec2 f) {return f == vec2(0) ? a : f == vec2(1) ? b : mix(a, b, f);} vec2 safe_mix(vec2 a, vec2 b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);} vec3 safe_mix(vec3 a, vec3 b, vec3 f) {return f == vec3(0) ? a : f == vec3(1) ? b : mix(a, b, f);} vec3 safe_mix(vec3 a, vec3 b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);} vec4 safe_mix(vec4 a, vec4 b, vec4 f) {return f == vec4(0) ? a : f == vec4(1) ? b : mix(a, b, f);} vec4 safe_mix(vec4 a, vec4 b, float f) {return f == 0.0 ? a : f == 1.0 ? b : mix(a, b, f);} #define map_range(value, from_min, from_max, to_min, to_max) (safe_mix((to_min), (to_max), ((value) - (from_min)) / ((from_max) - (from_min)))) #define map_range_clamped(value, from_min, from_max, to_min, to_max) clamp(map_range(value, from_min, from_max, to_min, to_max), min(to_min, to_max), max(to_max, to_min)) #define snap(value, range) (round((value) / (range)) * (range)) #define distort(base, distortion, fac) (base + (distortion - 0.5) * 2 * fac) #define vector_angle(a,b) (acos((dot(a,b))/(length(a) * length(b)))) #include "Common.glsl" float pingpong(float a, float b) { return (b != 0.0)? abs(fract((a - b) / (b * 2.0)) * b * 2.0 - b) : 0.0; } /* META @meta: subcategory=Random; */ vec4 random_per_object(float seed) { return hash(vec2(IO_ID.x, seed)); } /* META @meta: subcategory=Random; */ vec4 random_per_sample(float seed) { return hash(vec2(float(SAMPLE_COUNT), seed)); } vec2 screen_uv(); //FORWARD DECLARATION /* META @meta: subcategory=Random; */ vec4 random_per_pixel(float seed) { return hash(vec4(screen_uv(), float(SAMPLE_COUNT), seed)); } vec2 phyllotaxis_disk(float p, int total) { // https://en.wikipedia.org/wiki/Phyllotaxis // https://www.mathrecreation.com/2008/09/phyllotaxis-spirals.html // returns a point on a disk where all points are evenly spaced. The 'total' argument makes sure that your point ends up in a disk of radius=1 // this is a good approach of getting a spherical kernel with arbitrary sample count float r = p * GOLDEN_ANGLE; return vec2(cos(r), sin(r)) * vec2(pow(p / float(total), 0.5)); } #endif // COMMON_MATH_GLSL ================================================ FILE: Malt/Shaders/Common/Matrix.glsl ================================================ #ifndef COMMON_MATRIX_GLSL #define COMMON_MATRIX_GLSL /* META GLOBAL @meta: category=Math; subcategory=Matrix; */ #include "Common/Quaternion.glsl" /* META @meta: label=From Translation; @t: subtype=Vector; */ mat4 mat4_translation(vec3 t) { mat4 m = mat4(1.0); m[3] = vec4(t, 1); return m; } /* META @meta: label=From Quaternion; @q: subtype=Quaternion; default=vec4(0,0,0,1); */ mat4 mat4_rotation_from_quaternion(vec4 q) { return mat4(mat3( quaternion_transform(q, vec3(1,0,0)), quaternion_transform(q, vec3(0,1,0)), quaternion_transform(q, vec3(0,0,1)) )); } /* META @meta: label=From Euler; @e: subtype=Euler; */ mat4 mat4_rotation_from_euler(vec3 e) { mat4 x = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(1,0,0), e.x)); mat4 y = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(0,1,0), e.y)); mat4 z = mat4_rotation_from_quaternion(quaternion_from_axis_angle(vec3(0,0,1), e.z)); return z * y * x; } /* META @meta: label=From Scale; @s: subtype=Vector; default=vec3(1); */ mat4 mat4_scale(vec3 s) { return mat4(mat3(s.x,0,0, 0,s.y,0, 0,0,s.z)); } /* META @meta: label=Is Orthographic; @matrix: default=mat4(1); */ bool is_ortho(mat4 matrix) { return matrix[3][3] == 1.0; } #endif // COMMON_MATRIX_GLSL ================================================ FILE: Malt/Shaders/Common/Normal.glsl ================================================ #ifndef COMMON_NORMAL_GLSL #define COMMON_NORMAL_GLSL #include "Common.glsl" /* META GLOBAL @meta: category=Vector; internal=true; */ /* META @meta: category=Input; */ vec3 true_normal() { #ifndef CUSTOM_TRUE_NORMAL { #ifdef PIXEL_SHADER { return normalize(cross(dFdx(POSITION), dFdy(POSITION))); } #endif //PIXEL_SHADER return NORMAL; } #else { return CUSTOM_TRUE_NORMAL; } #endif //CUSTOM_TRUE_NORMAL } /* META @meta: category=Input; */ float facing() { return dot(NORMAL, -view_direction()); } /* META @meta: category=Input; */ bool is_front_facing() { #ifdef PIXEL_SHADER { return gl_FrontFacing; } #endif return facing() > 0; } /* META @meta: subcategory=Tangent; */ vec4 compute_tangent(vec2 uv) { // Copyright (c) 2020 mmikk. MIT License // http://jcgt.org/published/0009/03/04/ #ifdef PIXEL_SHADER { vec3 normal = normalize(NORMAL); vec3 position_dx = dFdx(POSITION); vec3 position_dy = dFdy(POSITION); vec3 sigma_x = position_dx - dot(position_dx, normal) * normal; vec3 sigma_y = position_dy - dot(position_dy, normal) * normal; float flip_sign = dot(position_dy, cross(normal, position_dx)) < 0 ? -1 : 1; vec2 uv_dx = dFdx(uv); vec2 uv_dy = dFdy(uv); float determinant = dot(uv_dx, vec2(uv_dy.y, -uv_dy.x)); float determinant_sign = determinant < 0.0 ? -1.0 : 1.0; // inverse represents (dXds, dYds), but we don't divide // by the determinant. Instead, we scale by the sign. vec2 inverse = determinant_sign * vec2(uv_dy.y, -uv_dx.y); vec3 tangent = sigma_x * inverse.x + sigma_y * inverse.y; if (abs(determinant) > 0.0) { tangent = normalize(tangent); } return vec4(tangent, (determinant_sign * flip_sign)); } #endif return vec4(0); } /* META @meta: subcategory=Tangent; */ vec3 get_tangent(int uv_index) { if(PRECOMPUTED_TANGENTS && uv_index == 0) return TANGENT; return compute_tangent(UV[uv_index]).xyz; } /* META @meta: subcategory=Tangent; */ vec3 get_bitangent(int uv_index) { if(PRECOMPUTED_TANGENTS && uv_index == 0) return BITANGENT; vec4 T = compute_tangent(UV[uv_index]); return normalize(cross(NORMAL, T.xyz) * T.w); } /* META @meta: subcategory=Tangent; */ mat3 get_TBN(int uv_index) { return mat3(get_tangent(uv_index), get_bitangent(uv_index), NORMAL); } /* META @tangent_normal: subtype=Vector; default='vec3(0.5, 0.5, 1.0)'; @TBN: label=TBN; default=get_TBN(0); */ vec3 tangent_to_world_normal(vec3 tangent_normal, mat3 TBN) { return normalize(TBN * (tangent_normal * 2 - 1)); } /* META @meta: category=Texturing; @TBN: default=get_TBN(0); @uv: default=UV[0]; label=UV; */ vec3 sample_normal_map_ex(sampler2D normal_texture, mat3 TBN, vec2 uv) { return tangent_to_world_normal(texture(normal_texture, uv).rgb, TBN); } /* META @meta: category=Texturing; label=Normal Map; @uv_index: min=0; max=3; @uv: default=UV[0]; */ vec3 sample_normal_map(sampler2D normal_texture, int uv_index, vec2 uv) { return sample_normal_map_ex(normal_texture, get_TBN(uv_index), uv); } /* META @meta: category=Vector; subcategory=Tangent; @normal: subtype=Normal; default=NORMAL; @axis: subtype=Normal; default=(0.0,0.0,1.0); */ vec3 radial_tangent(vec3 normal, vec3 axis) { float d = abs(dot(normal, axis)); float epsilon = 1e-5; if(d > 1.0 - epsilon) { // Avoid singularities when normal == axis. vec3 alternative_axis = normalize(axis + vec3(0.1, 0.2, 0.3)); axis = mix(axis, alternative_axis, map_range_clamped(d, 1.0 - epsilon, 1.0, 0.0, epsilon)); } return normalize(cross(axis, normal)); } /* META @meta: internal=false; @base_normal: subtype=Normal; default=NORMAL; @custom_normal: subtype=Normal; default=NORMAL; */ vec3 surface_gradient_from_normal(vec3 base_normal, vec3 custom_normal) { // Copyright (c) 2020 mmikk. MIT License // http://jcgt.org/published/0009/03/04/ const float epsilon = 1.192092896e-07f; float NoC = dot(base_normal, custom_normal); return (NoC * base_normal - custom_normal) / max(epsilon, abs(NoC)); } vec3 surface_gradient_from_normal(vec3 custom_normal) { return surface_gradient_from_normal(NORMAL, custom_normal); } /* META @meta: internal=false; @base_normal: subtype=Normal; default=NORMAL; @surface_gradient: subtype=Vector; default=vec3(0); */ vec3 normal_from_surface_gradient(vec3 base_normal, vec3 surface_gradient) { // Copyright (c) 2020 mmikk. MIT License // http://jcgt.org/published/0009/03/04/ return normalize(base_normal - surface_gradient); } vec3 normal_from_surface_gradient(vec3 surface_gradient) { return normal_from_surface_gradient(NORMAL, surface_gradient); } #endif //COMMON_NORMAL_GLSL ================================================ FILE: Malt/Shaders/Common/Quaternion.glsl ================================================ #ifndef COMMON_QUATERNION_GLSL #define COMMON_QUATERNION_GLSL /* META GLOBAL @meta: category=Math; subcategory=Quaternion; */ /* META @meta: label=From Axis Angle; @axis: subtype=Normal; @angle: subtype=Angle; default=0; */ vec4 quaternion_from_axis_angle(vec3 axis, float angle) { return vec4(axis * sin(0.5 * angle), cos(0.5 * angle)); } /* META @meta: label=From Vector Delta; @from: subtype=Normal; @to: subtype=Normal; */ vec4 quaternion_from_vector_delta(vec3 from, vec3 to) { return normalize(vec4(cross(from, to), 1.0 + dot(from, to))); } /* META @meta: label=Inverted; @a: label=Q; subtype=Quaternion; default=vec4(0,0,0,1); */ vec4 quaternion_inverted(vec4 a) { return vec4(-a.xyz, a.w); } /* META @meta: label=Multiply; @a: subtype=Quaternion; default=vec4(0,0,0,1); @b: subtype=Quaternion; default=vec4(0,0,0,1); */ vec4 quaternion_multiply(vec4 a, vec4 b) { return vec4 ( a.xyz * b.w + b.xyz * a.w + cross(a.xyz, b.xyz), a.w * b.w - dot(a.xyz, b.xyz) ); } /* META @meta: label=Transform; @a: label=Q; subtype=Quaternion; default=vec4(0,0,0,1); @vector: subtype=Vector; default=vec3(0); */ vec3 quaternion_transform(vec4 a, vec3 vector) { vec3 t = cross(a.xyz, vector) * 2.0; return vector + t * a.w + cross(a.xyz, t); } /* META @meta: label=Mix; @a: subtype=Quaternion; default=vec4(0,0,0,1); @b: subtype=Quaternion; default=vec4(0,0,0,1); @factor: subtype=Slider; default=0.5; min=0; max=1; */ vec4 quaternion_mix(vec4 a, vec4 b, float factor) { return normalize(mix(a, b, factor)); } #endif //COMMON_QUATERION_GLSL ================================================ FILE: Malt/Shaders/Common/Transform.glsl ================================================ #ifndef COMMON_TRANSFORM_GLSL #define COMMON_TRANSFORM_GLSL /* META GLOBAL @meta: category=Vector; internal=true; */ #include "Common.glsl" /* META @meta: subcategory=Matrix; internal=false; @matrix: default=mat4(1); @point: subtype=Vector; */ vec3 transform_point(mat4 matrix, vec3 point) { return (matrix * vec4(point, 1.0)).xyz; } /* META @meta: subcategory=Matrix; internal=false; @matrix: default=mat4(1); @point: subtype=Vector; */ vec3 project_point(mat4 matrix, vec3 point) { vec4 result = matrix * vec4(point, 1.0); return (result.xyz / result.w) * sign(result.w); } /* META @meta: subcategory=Matrix; internal=false; @matrix: default=mat4(1); @point: subtype=Vector; */ vec3 project_point_to_screen_coordinates(mat4 matrix, vec3 point) { //Assumes gl_DepthRange is 0...1 return map_range(project_point(matrix, point), vec3(-1), vec3(1), vec3(0), vec3(1)); } /* META @meta: subcategory=Matrix; internal=false; @matrix: default=mat4(1); @direction: subtype=Vector; */ vec3 transform_direction(mat4 matrix, vec3 direction) { return mat3(matrix) * direction; } /* META @meta: subcategory=Matrix; internal=false; @matrix: default=mat4(1); @normal: subtype=Normal; */ vec3 transform_normal(mat4 matrix, vec3 normal) { mat3 m = transpose(inverse(mat3(matrix))); return normalize(m * normal); } vec3 screen_to_camera(vec2 uv, float depth); vec3 camera_direction_to_screen_space(vec3 vector) { vec3 N = normalize(vector); vec3 I = -normalize(screen_to_camera(screen_uv(), 1)); vec3 x = vec3(1,0,0); vec3 tangent = normalize(x - I * dot(x, I)); vec3 y = vec3(0,1,0); vec3 bitangent = normalize(y - I * dot(y, I)); vec3 screen_normal = vec3 ( dot(N, tangent), dot(N, bitangent), dot(N, I) ); return normalize(screen_normal) * length(vector); } /* META @meta: category=Input; */ vec3 camera_position() { return transform_point(inverse(CAMERA), vec3(0,0,0)); } /* META @meta: category=Input; */ vec3 model_position() { return transform_point(MODEL, vec3(0,0,0)); } /* META @meta: category=Input; */ vec2 screen_uv() { #ifdef PIXEL_SHADER { return vec2(gl_FragCoord) / vec2(RESOLUTION); } #else { return project_point(PROJECTION * CAMERA, POSITION).xy * 0.5 + 0.5; } #endif //PIXEL_SHADER } ivec2 screen_pixel() { #ifdef PIXEL_SHADER { return ivec2(floor(gl_FragCoord.xy)); } #else { return ivec2(floor(screen_uv() * RESOLUTION)); } #endif } /* META @uv: default=UV[0]; */ vec3 screen_to_camera(vec2 uv, float depth) { vec3 clip_position = vec3(uv, depth) * 2.0 - 1.0; vec4 camera_position = inverse(PROJECTION) * vec4(clip_position, 1.0); camera_position /= camera_position.w; return camera_position.xyz; } /* META @meta: category=Input; */ vec3 view_direction() { return transform_normal(inverse(CAMERA), screen_to_camera(screen_uv(), 1)); } float pixel_depth() { #ifdef PIXEL_SHADER { #ifdef CUSTOM_PIXEL_DEPTH { return CUSTOM_PIXEL_DEPTH; } #else { return gl_FragCoord.z; } #endif } #endif return 0.0; } float depth_to_z(float depth) { return screen_to_camera(vec2(0,0), depth).z; } /* META @meta: label=Pixel Size in World Space; internal=false; @depth: default=pixel_depth(); */ float pixel_world_size_at(float depth) { vec2 uv = screen_uv(); vec2 offset = vec2(1.0 / RESOLUTION.x, 0); return distance(screen_to_camera(uv, depth), screen_to_camera(uv + offset, depth)); } float pixel_world_size() { #ifdef PIXEL_SHADER { return pixel_world_size_at(pixel_depth()); } #else { return pixel_world_size_at(project_point(PROJECTION * CAMERA, POSITION).z * 0.5 + 0.5); } #endif } vec3 _reconstruct_cs_position(sampler2D depth_texture, int depth_channel, ivec2 texel) { float depth = texelFetch(depth_texture, texel, 0)[depth_channel]; ivec2 size = textureSize(depth_texture, 0); vec2 uv = (vec2(texel) + vec2(0.5)) / vec2(size); return screen_to_camera(uv, depth); } vec3 reconstruct_normal(sampler2D depth_texture, int depth_channel, ivec2 texel) { vec3 t0 = _reconstruct_cs_position(depth_texture, depth_channel, texel); vec3 x1 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2(-1, 0)); vec3 x2 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2( 1, 0)); vec3 y1 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2( 0,-1)); vec3 y2 = _reconstruct_cs_position(depth_texture, depth_channel, texel + ivec2( 0, 1)); vec3 x = distance(x1.z, t0.z) < distance(x2.z, t0.z) ? x1 : x2; vec3 y = distance(y1.z, t0.z) < distance(y2.z, t0.z) ? y1 : y2; vec3 n = normalize(cross(x - t0, y - t0)); vec3 view_direction = screen_to_camera(screen_uv(), 1); n = dot(n, view_direction) < 0 ? n : -n; return transform_normal(inverse(CAMERA), n); } /* META @ray_origin: subtype=Vector; @ray_direction: subtype=Vector; @plane_position: subtype=Vector; @plane_normal: subtype=Normal; */ float ray_plane_intersection(vec3 ray_origin, vec3 ray_direction, vec3 plane_position, vec3 plane_normal) { float r_direction = dot(ray_direction, plane_normal); float r_origin = dot(ray_origin, plane_normal); float p_position = dot(plane_position, plane_normal); return (p_position - r_origin) / r_direction; } vec2 rotate_2d(vec2 p, float angle) { mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle)); return rot * p; } #endif //COMMON_TRANSFORM_GLSL ================================================ FILE: Malt/Shaders/Common.glsl ================================================ #ifndef COMMON_GLSL #define COMMON_GLSL #ifdef VERTEX_SHADER #define vertex_out out #else #define vertex_out in #endif vec3 POSITION; vec3 NORMAL; vec3 TANGENT; vec3 BITANGENT; vec2 UV[4]; vec4 COLOR[4]; uvec4 ID; vertex_out mat4 MODEL; layout(std140) uniform COMMON_UNIFORMS { uniform mat4 CAMERA; uniform mat4 PROJECTION; uniform ivec2 RESOLUTION; uniform vec2 SAMPLE_OFFSET; //Sample offset is already baked into PROJECTION. uniform int SAMPLE_COUNT; uniform int FRAME; uniform float TIME; }; uniform bool MIRROR_SCALE = false; uniform bool PRECOMPUTED_TANGENTS = false; uniform bvec4 COLOR_IS_SRGB = bvec4(false); #ifndef MAX_BATCH_SIZE // Assume at least 64kb of UBO storage (d3d11 requirement) and max element size of mat4 #define MAX_BATCH_SIZE 1000 #endif layout(std140) uniform BATCH_MODELS { uniform mat4 BATCH_MODEL[MAX_BATCH_SIZE]; }; layout(std140) uniform BATCH_IDS { //Use uvec4 so the uints are tightly packed uniform uvec4 BATCH_ID[MAX_BATCH_SIZE/4+1]; }; #define BATCH_ID(index) BATCH_ID[(index)/4][(index)%4] vertex_out vec3 IO_POSITION; vertex_out vec3 IO_NORMAL; vertex_out vec3 IO_TANGENT; vertex_out vec3 IO_BITANGENT; vertex_out vec2 IO_UV[4]; vertex_out vec4 IO_COLOR[4]; flat vertex_out uvec4 IO_ID; #include "Common/Color.glsl" #include "Common/Hash.glsl" #include "Common/Mapping.glsl" #include "Common/Math.glsl" #include "Common/Matrix.glsl" #include "Common/Normal.glsl" #include "Common/Quaternion.glsl" #include "Common/Transform.glsl" #ifdef VERTEX_SHADER layout (location = 0) in vec3 in_position; layout (location = 1) in vec3 in_normal; layout (location = 2) in vec4 in_tangent; layout (location = 3) in vec2 in_uv0; layout (location = 4) in vec2 in_uv1; layout (location = 5) in vec2 in_uv2; layout (location = 6) in vec2 in_uv3; layout (location = 7) in vec4 in_color0; layout (location = 8) in vec4 in_color1; layout (location = 9) in vec4 in_color2; layout (location = 10) in vec4 in_color3; void VERTEX_SETUP_OUTPUT() { gl_Position = PROJECTION * CAMERA * vec4(POSITION, 1); IO_POSITION = POSITION; IO_NORMAL = NORMAL; IO_TANGENT = TANGENT; IO_BITANGENT = BITANGENT; IO_UV = UV; IO_COLOR = COLOR; IO_ID = ID; } void DEFAULT_VERTEX_SHADER() { MODEL = BATCH_MODEL[gl_InstanceID]; ID = uvec4(BATCH_ID(gl_InstanceID),0,0,0); POSITION = transform_point(MODEL, in_position); NORMAL = transform_normal(MODEL, in_normal); if(PRECOMPUTED_TANGENTS) { TANGENT = transform_normal(MODEL, in_tangent.xyz); float mirror_scale = MIRROR_SCALE ? -1 : 1; BITANGENT = normalize(cross(NORMAL, TANGENT) * in_tangent.w) * mirror_scale; } UV[0]=in_uv0; UV[1]=in_uv1; UV[2]=in_uv2; UV[3]=in_uv3; COLOR[0]=in_color0; COLOR[1]=in_color1; COLOR[2]=in_color2; COLOR[3]=in_color3; for(int i = 0; i < 4; i++) { if(COLOR_IS_SRGB[i]) { COLOR[i].rgb = srgb_to_linear(COLOR[i].rgb); } } VERTEX_SETUP_OUTPUT(); } void DEFAULT_SCREEN_VERTEX_SHADER() { IO_POSITION = in_position; IO_UV[0] = in_position.xy * 0.5 + 0.5; gl_Position = vec4(in_position, 1); } #endif //VERTEX_SHADER #ifdef PIXEL_SHADER void PIXEL_SETUP_INPUT() { POSITION = IO_POSITION; NORMAL = normalize(IO_NORMAL) * (gl_FrontFacing ? 1.0 : -1.0); TANGENT = IO_TANGENT; BITANGENT = IO_BITANGENT; UV = IO_UV; COLOR = IO_COLOR; ID = IO_ID; } #endif //PIXEL_SHADER #endif //COMMON_GLSL ================================================ FILE: Malt/Shaders/Filters/AO.glsl ================================================ #ifndef AO_GLSL #define AO_GLSL #include "Common/Math.glsl" #include "Common/Transform.glsl" /* META @meta: internal=true; @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @samples: default=8; @radius: default=1.0; @distribution_exponent: default=5.0; @bias: default=0.01; */ float ao(sampler2D depth_texture, int depth_channel, vec3 position, vec3 normal, int samples, float radius, float distribution_exponent, float bias) { //Loosely based on https://learnopengl.com/Advanced-Lighting/SSAO float occlusion = 0; if(samples <= 0 || radius <= 0.0) { return 1.0; } for(int i = 0; i < samples; i++) { // Generate a random TBN matrix vec3 random_vec = random_per_pixel(i).xyz; random_vec.xyz = random_vec.xyz * 2.0 - 1.0; vec3 normal = transform_normal(CAMERA, normal); vec3 tangent = normalize(random_vec - normal * dot(random_vec, normal)); vec3 bitangent = cross(normal, tangent); mat3 TBN = mat3(tangent, bitangent, normal); vec3 random_offset = random_per_pixel(samples+i).xyz; // Make samples close to the center more likely, based on distribution exponent random_offset = normalize(random_offset) * pow(length(random_offset), distribution_exponent); random_offset *= radius; vec3 sample_offset = TBN * random_offset; vec3 sample_position = transform_point(CAMERA, position) + sample_offset; vec3 sample_uv = project_point_to_screen_coordinates(PROJECTION, sample_position); float sampled_depth = texture(depth_texture, sample_uv.xy)[depth_channel]; sampled_depth = screen_to_camera(sample_uv.xy, sampled_depth).z; float range_check = smoothstep(0.0, 1.0, radius / abs(sample_position.z - sampled_depth)); occlusion += (sampled_depth >= sample_position.z + bias ? 1.0 : 0.0) * range_check; } return 1.0 - (occlusion / samples); } #endif //AO_GLSL ================================================ FILE: Malt/Shaders/Filters/Bevel.glsl ================================================ #ifndef FILTERS_BEVEL_GLSL #define FILTERS_BEVEL_GLSL #include "Common/Math.glsl" #include "Common/Transform.glsl" /* META @meta: internal=true; @id: default=ID[0]; @samples: default=8; @radius: default=1.0; @distribution_exponent: default=5.0; @hard_bevel_max_dot: default=0.5; */ vec3 bevel ( sampler2D normal_texture, sampler2D depth_texture, int depth_channel, uint id, bool filter_by_id, usampler2D id_texture, int id_channel, int samples, float radius, float distribution_exponent, bool hard_bevel, float hard_bevel_max_dot ) { vec2 uv = screen_uv(); vec3 pixel_normal = texture(normal_texture, uv).xyz; float pixel_depth = texture(depth_texture, uv)[depth_channel]; vec3 pixel_position = screen_to_camera(uv, pixel_depth); float closest_distance = radius; vec3 normal = pixel_normal; float screen_radius = radius / pixel_world_size_at(pixel_depth); for(int i = 0; i < samples; i++) { vec2 offset = random_per_pixel(i).xy; if(length(offset) > 1) { offset = normalize(offset); } offset = offset * 2.0 - 1.0; if(distribution_exponent > 1) { offset = pow(abs(offset), vec2(distribution_exponent)) * sign(offset); } offset *= screen_radius; vec2 offset_uv = uv + (offset / vec2(RESOLUTION)); ivec2 offset_texel = ivec2(RESOLUTION * offset_uv); if(filter_by_id) { uint offset_id = texelFetch(id_texture, offset_texel, 0)[id_channel]; if (offset_id != id) { continue; } } vec3 offset_normal = texelFetch(normal_texture, offset_texel, 0).xyz; float offset_depth = texelFetch(depth_texture, offset_texel, 0)[depth_channel]; vec3 offset_position = screen_to_camera(offset_uv, offset_depth); float offset_distance = distance(pixel_position, offset_position); if(offset_distance < radius) { if(hard_bevel) { float offset_dot = dot(pixel_normal, offset_normal); if(offset_dot <= hard_bevel_max_dot) { if(offset_distance < closest_distance) { closest_distance = offset_distance; normal = offset_normal; } } } else { normal += offset_normal; } } } if(hard_bevel) { float mix_factor = 1.0 - (closest_distance / screen_radius); mix_factor = saturate(pow(mix_factor, distribution_exponent) * distribution_exponent); return normalize(mix(NORMAL, normalize(NORMAL + normalize(normal)), mix_factor)); } else { return normalize(normal); } } #endif //FILTERS_BEVEL_GLSL ================================================ FILE: Malt/Shaders/Filters/Blur.glsl ================================================ #ifndef BLUR_GLSL #define BLUR_GLSL /* META GLOBAL @meta: category=Filter; subcategory=Blur; */ /* META @input_texture: label=Texture; @uv: label=UV; default=UV[0]; @radius: default=5.0; min=0.0; */ vec4 box_blur(sampler2D input_texture, vec2 uv, float radius, bool circular) { if(radius <= 1.0) { return texture(input_texture, uv); } vec2 resolution = textureSize(input_texture, 0); vec4 result = vec4(0); float total_weight = 0.0; for(float x = -radius; x <= radius; x++) { for(float y = -radius; y <= radius; y++) { vec2 offset = vec2(x,y); if(!circular || length(offset) <= radius) { result += texture(input_texture, uv + offset / resolution); total_weight += 1.0; } } } return result / total_weight; } float _gaussian_weight(float x, float sigma) { float sigma2 = sigma * sigma; return (1.0 / sqrt(2*PI*sigma2)) * exp(-(x*x / 2.0*sigma2)); } float _gaussian_weight_2d(vec2 v, float sigma) { float sigma2 = sigma * sigma; return (1.0 / (2*PI*sigma2)) * exp(-(dot(v,v) / (2.0*sigma2))); } float _gaussian_weight_3d(vec3 v, float sigma) { return 0.39894*exp(-0.5*dot(v,v)/(sigma*sigma))/sigma; } /* META @input_texture: label=Texture; @uv: label=UV; default=UV[0]; @radius: default=5.0; min=0.0; @sigma: default=1.0; */ vec4 gaussian_blur(sampler2D input_texture, vec2 uv, float radius, float sigma) { if(radius <= 1.0 || sigma <= 0.0) { return texture(input_texture, uv); } vec2 resolution = textureSize(input_texture, 0); vec4 result = vec4(0); float total_weight = 0.0; for(float x = -radius; x <= radius; x++) { for(float y = -radius; y <= radius; y++) { vec2 offset = vec2(x,y); float weight = _gaussian_weight_2d(offset, sigma); result += texture(input_texture, uv + offset / resolution) * weight; total_weight += weight; } } return result / total_weight; } #include "Common/Math.glsl" /* META @input_texture: label=Texture; @uv: label=UV; default=UV[0]; @radius: default=5.0; @distribution_exponent: default=5.0; @samples: default=8; min=1; */ vec4 jitter_blur(sampler2D input_texture, vec2 uv, float radius, float distribution_exponent, int samples) { if(samples <= 0 || radius <= 0.0) { return texture(input_texture, uv); } vec2 resolution = textureSize(input_texture, 0); vec4 result = vec4(0); for(int i = 0; i < samples; i++) { vec4 random = random_per_pixel(i); float angle = random.x * PI * 2; float length = random.y; length = pow(length, distribution_exponent) * radius; float x = cos(angle) * length; float y = sin(angle) * length; vec2 offset = vec2(x,y) / resolution; result += texture(input_texture, uv + offset) / samples; } return result; } /* META @meta: internal=true; @uv: default=UV[0]; */ vec4 tent_blur(sampler2D tex, vec2 uv) { // Half pixel offset takes advantage of hardware texture interpolation to achieve a 3x3 gaussian blur // with just 4 samples instead of 9. // The resulting kernel looks like this: // 1 2 1 // 2 4 2 // 1 2 1 vec2 texel = 1.0 / textureSize(tex, 0); return (texture(tex, uv + texel * vec2(-0.5, -0.5)) + texture(tex, uv + texel * vec2(-0.5, +0.5)) + texture(tex, uv + texel * vec2(+0.5, -0.5)) + texture(tex, uv + texel * vec2(+0.5, +0.5))) / 4.0; } /* META @meta: label=Bilateral; @tex: label=Texture; @uv: label=UV; default=UV[0]; @radius: default=5; min=0; @sigma: default=10.0; min=0.0; @bsigma: label=BSigma; default=0.1; min=0.0; */ vec4 bilateral_blur(sampler2D input_texture, vec2 uv, float radius, float sigma, float bsigma) { // Similar to a gaussian blur but in addition to weighting the pixel distance it also weights the color difference. // Is good at preserving edges // Sigma -> controls pixel distance weight // BSigma -> controls color distance weight // https://people.csail.mit.edu/sparis/bf_course/course_notes.pdf vec2 texel = 1.0 / textureSize(input_texture, 0); float total_weight = 0.0; vec3 total_color = vec3(0.0); vec4 main_color = texture(input_texture, uv); if(radius <= 0.0 || sigma <= 0.0 || bsigma <= 0.0) { return main_color; } for(float u = -radius; u <= radius; u ++) { for(float v = -radius; v <= radius; v++) { vec2 o = vec2(u,v); vec3 color = texture(input_texture, uv + o * texel).rgb; float dist_weight = _gaussian_weight_2d(o, sigma); float diff_weight = _gaussian_weight_3d(color - main_color.rgb, bsigma); float weight = dist_weight * diff_weight; total_weight += weight; total_color += weight * color; } } return vec4(total_color / total_weight, main_color.a); } /* META @meta: label=Orientation-Aligned Bilateral; @tex: label=Texture; @uv: label=UV; default=UV[0]; @flow: default='vec2(0)'; @radius: default=6.0; min=0.0; @smoothness: default=0.55; min=0.0; */ vec4 OAB_blur(sampler2D input_texture, vec2 uv, vec2 flow, float radius, float smoothness) { // Orientation-aligned Bilateral Blur // Smoothes image while preserving edges by using the local structure of the texture if(radius <= 0.0 || smoothness <= 0.0) { return texture(input_texture, uv); } float D = 2 * radius * radius; float R = 2 * smoothness * smoothness; vec2 dir = flow; vec2 dir_abs = abs(dir); float d_max = 1.0 / max(dir_abs.x, dir_abs.y); dir *= 1.0 / textureSize(input_texture, 0); vec4 main_color = texture(input_texture, uv); vec3 total_color = main_color.rgb; float total_weight = 1.0; float half_width = 2.0 * radius; for(float d = -half_width; d <= half_width; d += d_max) { vec3 color = texture(input_texture, uv + d * dir).rgb; float difference = length(color - main_color.rgb); float dist_influence = exp(-d * d / D); float diff_influence = exp(-difference * difference / R); float local_weight = dist_influence * diff_influence; total_weight += local_weight; total_color += local_weight * color; } return vec4(total_color / total_weight, main_color.a); } #endif //BLUR_GLSL ================================================ FILE: Malt/Shaders/Filters/Curvature.glsl ================================================ #ifndef CURVATURE_GLSL #define CURVATURE_GLSL #include "Common/Math.glsl" /* META GLOBAL @meta: category=Filter; */ /* META @uv: label=UV; default=UV[0]; @width: default=1.0; @x: subtype=Normal; default=vec3(1,0,0); @y: subtype=Normal; default=vec3(0,1,0); */ float curvature(sampler2D normal_texture, vec2 uv, float width, vec3 x, vec3 y) { // x and y must be the screen x and y axis in the same coordinate space as the texture normals vec2 offset = vec2(width) / vec2(textureSize(normal_texture, 0)); vec3 l = texture(normal_texture, uv + vec2(-offset.x,0)).xyz; vec3 r = texture(normal_texture, uv + vec2( offset.x,0)).xyz; vec3 d = texture(normal_texture, uv + vec2(0,-offset.y)).xyz; vec3 u = texture(normal_texture, uv + vec2(0, offset.y)).xyz; if(width != 1.0) { l = normalize(l); r = normalize(r); d = normalize(d); u = normalize(u); } float curvature = (dot(u,y) - dot(d,y)) + (dot(r,x) - dot(l,x)); return map_range_clamped(curvature, -1, 1, 0, 1); } #include "Filters/Line.glsl" // Like curvature, but discard depth discontinuities /* META @meta: internal=true; @uv: default=UV[0]; @width: default=1.0; @x: subtype=Normal; default=vec3(1,0,0); @y: subtype=Normal; default=vec3(0,1,0); @depth_range: default=0.1; */ float surface_curvature(sampler2D normal_texture, sampler2D depth_texture, int depth_channel, vec2 uv, float width, vec3 x, vec3 y, float depth_range) { float curvature = curvature(normal_texture, uv, width, x, y); float delta_depth = _line_detection_depth(depth_texture, depth_channel, uv, width, LINE_DEPTH_MODE_ANY); delta_depth /= depth_range; delta_depth = clamp(delta_depth, 0, 1); return mix(curvature, 0.5, delta_depth); } #endif //CURVATURE_GLSL ================================================ FILE: Malt/Shaders/Filters/JumpFlood.glsl ================================================ #ifndef JUMP_FLOOD_GLSL #define JUMP_FLOOD_GLSL /* META @width: default=1.0; */ //input should be a texture where x and y are screen space uvs. //samples with values == vec(-1,-1) are ignored //More info : https://medium.com/@bgolus/the-quest-for-very-wide-outlines-ba82ed442cd9#c5bb vec4 jump_flood(sampler2D input_texture, vec2 uv, float width, bool third_channel_scale) { vec4 nearest = vec4(-1,-1,-1,-1); vec2 resolution = vec2(textureSize(input_texture, 0)); vec2 offset = vec2(width) / resolution; for(int x = -1; x <= 1; x++) { for(int y = -1; y <= 1; y++) { vec2 sample_uv = uv + vec2(x,y) * offset; vec4 sampled = texture(input_texture, sample_uv); if(sampled.xy != vec2(-1,-1)) //Check if it's valid { float stored_distance = distance(uv * resolution, nearest.xy * resolution); float sampled_distance = distance(uv * resolution, sampled.xy * resolution); if(third_channel_scale) { if(nearest.z > 0) { stored_distance /= nearest.z; } if(sampled.z > 0) { sampled_distance /= sampled.z; } } if(nearest.xy == vec2(-1,-1) || sampled_distance < stored_distance) { nearest = sampled; } } } } return nearest; } #endif //JUMP_FLOOD_GLSL ================================================ FILE: Malt/Shaders/Filters/Kuwahara.glsl ================================================ #ifndef KUWAHARA_GLSL #define KUWAHARA_GLSL /* META GLOBAL @meta: category=Filter; subcategory=Kuwahara; */ /* META @meta: label=Isotropic; @tex: label=Texture; @uv: default = UV[0]; label=UV; @size: default=5; min=0; */ vec4 kuwahara(sampler2D tex, vec2 uv, int size) { if(size <= 0) { return texture(tex, uv); } vec3 mean[4] = vec3[](vec3(0), vec3(0), vec3(0), vec3(0)); vec3 sigma[4] = vec3[](vec3(0), vec3(0), vec3(0), vec3(0)); vec2 offsets[4] = vec2[] ( vec2(-size, -size), vec2(-size, 0), vec2(0, -size), vec2(0, 0) ); vec3 color; vec2 texel = 1.0 / textureSize(tex, 0); float sigma_f; float minimum = 99.0; for(int i = 0; i < 4; i++) { float total_weight = 0.0; for(int u = 0; u <= ceil(size); u++) { for(int v = 0; v <= ceil(size); v++) { vec2 offset = (vec2(u,v) + offsets[i]) * texel; float weight = max(0, 1.0 - length(offset / texel) / float(size)); color = texture(tex, uv + offset).rgb; mean[i] += color * weight; sigma[i] += (color * color) * weight; total_weight += weight; } } mean[i] /= total_weight; sigma[i] = abs(sigma[i] / total_weight - mean[i] * mean[i]); } for(int i = 0; i < 4; i++) { sigma_f = sigma[i].r + sigma[i].g + sigma[i].b; if(sigma_f < minimum) { minimum = sigma_f; color = mean[i]; } } return vec4(color, texture(tex, uv).a); } /* META @meta: label=Anisotropic; @tex: label=Texture; @uv: label=UV; default=UV[0]; @direction: default='vec2(0.0, 0.0)'; @size: default=2.0; min=0.0; @samples: default=50; min=0; */ vec4 anisotropic_kuwahara(sampler2D tex, vec2 uv, vec2 direction, float size, int samples) { if(size <= 0.0 || samples <= 0) { return texture(tex, uv); } vec2 texel = 1.0 / textureSize(tex, 0); float a = atan(direction.y, direction.x); mat2 rot_mat = mat2(cos(a), -sin(a), sin(a), cos(a)); int segments = 8; vec3 mean[8] = vec3[](vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0)); vec3 sigma[8] = vec3[](vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0),vec3(0.0)); float total_weights[8] = float[](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); for(int i = 0; i < samples; i++) { vec2 offset = phyllotaxis_disk(float(i), samples); float l = length(offset); float rad = atan(offset.y, offset.x) / (2.0 * PI) + 0.5; int segment_index = int(floor(rad * segments)); float weight = exp(-(l * l) * 2.0); offset *= size * texel; offset.x *= length(direction) + 1.0; offset = rot_mat * offset; vec3 color = texture(tex, uv + offset).rgb; mean[segment_index] += color * weight; sigma[segment_index] += (color * color) * weight; total_weights[segment_index] += weight; } float sigma_f; float minimum = 99.0; vec3 result; for(int i = 0; i < segments; i++) { mean[i] /= total_weights[i]; sigma[i] = abs(sigma[i] / total_weights[i] - mean[i] * mean[i]); sigma_f = sigma[i].r + sigma[i].g + sigma[i].b; if(sigma_f < minimum) { minimum = sigma_f; result = mean[i]; } } return vec4(result, texture(tex, uv).a); } #endif //KUWAHARA_GLSL ================================================ FILE: Malt/Shaders/Filters/Line.glsl ================================================ #ifndef LINE_GLSL #define LINE_GLSL #define LINE_DEPTH_MODE_NEAR 0 #define LINE_DEPTH_MODE_FAR 1 #define LINE_DEPTH_MODE_ANY 2 /* META GLOBAL @meta: internal=true; */ void _sampling_pattern(out vec2 samples[4]) { samples = vec2[4]( vec2(-1,-1), vec2(-1,1), vec2(1,-1), vec2(1,1) ); samples = vec2[4]( vec2(-1, 0), vec2( 1, 0), vec2( 0,-1), vec2( 0, 1) ); } //TODO: Remove. Used by surface_curvature float _line_detection_depth(sampler2D depth_texture, int channel, vec2 uv, float pixel_width, int LINE_DEPTH_MODE) { vec2 offsets[4]; _sampling_pattern(offsets); vec2 offset = vec2(pixel_width) / vec2(textureSize(depth_texture, 0)); float depth = texture(depth_texture, uv)[channel]; depth = -depth_to_z(depth); float delta = 0.0; for(int i = 0; i < offsets.length(); i++) { float sampled_depth = texture(depth_texture, uv + offsets[i]*offset)[channel]; sampled_depth = -depth_to_z(sampled_depth); if ( LINE_DEPTH_MODE == LINE_DEPTH_MODE_ANY || LINE_DEPTH_MODE == LINE_DEPTH_MODE_NEAR && depth < sampled_depth || LINE_DEPTH_MODE == LINE_DEPTH_MODE_FAR && depth > sampled_depth ) { delta = max(delta, abs(depth - sampled_depth)); } } return delta; } struct LineDetectionOutput { float delta_distance; float delta_angle; bvec4 id_boundary; }; /* META @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; @true_normal: subtype=Normal; default=true_normal(); @width: default=1.0; @LINE_DEPTH_MODE: subtype=ENUM(Near, Far, Any); @uv: default=UV[0]; */ LineDetectionOutput line_detection( vec3 position, vec3 normal, vec3 true_normal, float width, int LINE_DEPTH_MODE, vec2 uv, sampler2D depth_texture, int depth_channel, sampler2D normal_texture, usampler2D id_texture ) { LineDetectionOutput result; result.delta_distance = 0.0; result.delta_angle = 1.0; result.id_boundary = bvec4(false); vec2 offsets[4]; _sampling_pattern(offsets); vec2 offset = vec2(width) / RESOLUTION; vec3 true_normal_camera = transform_normal(CAMERA, true_normal); float depth = texture(depth_texture, uv)[depth_channel]; position = transform_point(CAMERA, position); uvec4 id = texture(id_texture, uv); for(int i = 0; i < offsets.length(); i++) { vec2 sample_uv = uv + offsets[i]*offset; vec3 sampled_normal = texture(normal_texture, sample_uv).xyz; float sampled_depth = texture(depth_texture, sample_uv)[depth_channel]; vec3 sampled_position = screen_to_camera(sample_uv, sampled_depth); uvec4 sampled_id = texture(id_texture, sample_uv); float delta_distance = 0; if(is_ortho(PROJECTION)) { //TODO: Use ray-plane intersection here too. delta_distance = abs(sampled_position.z - position.z); delta_distance *= dot(true_normal, -view_direction()); } else { vec3 ray_origin = vec3(0); vec3 ray_direction = normalize(sampled_position); float expected_distance = ray_plane_intersection ( ray_origin, ray_direction, position, true_normal_camera ); delta_distance = abs(distance(sampled_position, ray_origin) - expected_distance); } if ( LINE_DEPTH_MODE == LINE_DEPTH_MODE_ANY || LINE_DEPTH_MODE == LINE_DEPTH_MODE_NEAR && depth <= sampled_depth || LINE_DEPTH_MODE == LINE_DEPTH_MODE_FAR && depth >= sampled_depth ) { result.delta_distance = max(result.delta_distance, delta_distance); result.delta_angle = min(result.delta_angle, dot(normal, sampled_normal)); for(int i = 0; i < 4; i++) { result.id_boundary[i] = result.id_boundary[i] || sampled_id[i] != id[i]; } } } result.delta_angle = acos(result.delta_angle); return result; } LineDetectionOutput line_detection_2( sampler2D depth_texture, int depth_channel, sampler2D normal_texture, usampler2D id_texture ) { ivec2 uv = screen_pixel(); LineDetectionOutput result; result.delta_distance = 0.0; result.delta_angle = 0.0; result.id_boundary = bvec4(false); float depth = texelFetch(depth_texture, uv, 0)[depth_channel]; vec3 normal = texelFetch(normal_texture, uv, 0).xyz;; vec3 position = screen_to_camera(screen_uv(), depth); vec3 true_normal = true_normal(); //Reconstructing the normal from depth provides more stable results across samples than the "real" true_normal //true_normal = reconstruct_normal(depth_texture, depth_channel, uv); vec3 average_normal = vec3(0); int radius = 1; for(int x = -radius; x <= radius; x++) { for(int y = -radius; y <= radius; y++) { ivec2 uv = uv + ivec2(x,y); if(uv != clamp(uv, ivec2(0), RESOLUTION)) { continue; } vec3 n = reconstruct_normal(depth_texture, depth_channel, uv); average_normal += n; } } true_normal = normalize(average_normal); uvec4 id = texelFetch(id_texture, uv, 0); vec3 true_normal_camera = transform_normal(CAMERA, true_normal); vec2 offsets[4]; _sampling_pattern(offsets); vec2 offset = vec2(1.0) / RESOLUTION; for(int i = 0; i < offsets.length(); i++) { ivec2 sample_uv = uv + ivec2(offsets[i]); if(sample_uv != clamp(sample_uv, ivec2(0), RESOLUTION)) { continue; } vec2 f_sample_uv = screen_uv() + offsets[i] * offset; vec3 sampled_normal = texelFetch(normal_texture, sample_uv, 0).xyz; float sampled_depth = texelFetch(depth_texture, sample_uv, 0)[depth_channel]; vec3 sampled_position = screen_to_camera(f_sample_uv , sampled_depth); uvec4 sampled_id = texelFetch(id_texture, sample_uv, 0); float delta_normal = dot(normal, sampled_normal); float plane_distance = dot(true_normal_camera, position); float offset_plane_distance = dot(true_normal_camera, sampled_position); // Scale by pixel world size so results are more stable at different distances and resolutions float delta_distance = abs(plane_distance - offset_plane_distance) / pixel_world_size_at(sampled_depth); /* Alternative depth computation { vec3 ray_origin = vec3(0); vec3 ray_direction = normalize(sampled_position); float expected_distance = ray_plane_intersection ( ray_origin, ray_direction, position, true_normal_camera ); delta_distance = distance(position, sampled_position) / distance(position, ray_origin + ray_direction * expected_distance); } //*/ if(depth <= sampled_depth) { result.delta_distance = max(result.delta_distance, delta_distance); result.delta_angle = max(result.delta_angle, 1.0 - delta_normal); for(int i = 0; i < 4; i++) { result.id_boundary[i] = result.id_boundary[i] || sampled_id[i] != id[i]; } } } return result; } struct LineExpandOutput { vec4 color; float depth; }; /* META @uv: default=UV[0]; @max_width: default=10; */ LineExpandOutput line_expand(vec2 uv, int max_width, sampler2D line_color_texture, sampler2D line_width_texture, int line_width_channel, float line_width_scale, sampler2D depth_texture, int depth_channel, usampler2D id_texture, int id_channel) { vec2 resolution = vec2(textureSize(line_color_texture,0)); int max_half_width = int(ceil(max_width / 2.0)); float depth = texture(depth_texture, uv)[depth_channel]; uint id = texture(id_texture, uv)[id_channel]; vec4 line_color = vec4(0); float line_depth = 1.0; float line_ldepth = -depth_to_z(line_depth); for(int x = -max_half_width; x <= max_half_width; x++) { for(int y = -max_half_width; y <= max_half_width; y++) { vec2 offset = vec2(x,y); float offset_length = length(offset) - 0.5; vec2 offset_uv = uv + offset / resolution; float offset_width = texture(line_width_texture, offset_uv)[line_width_channel] * line_width_scale; offset_width = min(offset_width, max_width); if(offset_width > 0 && offset_length <= offset_width / 2.0) { vec4 offset_line_color = texture(line_color_texture, offset_uv); float offset_line_depth = texture(depth_texture, offset_uv)[depth_channel]; float offset_line_ldepth = -depth_to_z(offset_line_depth); uint offset_line_id = texture(id_texture, offset_uv)[id_channel]; if(offset_length <= 0 && offset_width <= 1.0) { offset_line_color.a *= offset_width; } else { offset_line_color.a *= clamp(offset_width / 2.0 - offset_length, 0.0, 1.0); } float alpha = offset_line_color.a; float random_ldepth_offset = hash(vec4(offset, float(SAMPLE_COUNT), hash(uv).x)).x; offset_line_ldepth += random_ldepth_offset * offset_width * 0.5 * pixel_world_size_at(offset_line_depth); bool override = false; if(alpha == line_color.a && offset_line_ldepth < line_ldepth) { override = true; } if(alpha > line_color.a) { override = true; } if(offset_line_id != id && depth < offset_line_depth) { override = false; } if(override) { line_color = offset_line_color; line_ldepth = offset_line_ldepth; line_depth = offset_line_depth; } } } } return LineExpandOutput(line_color, line_depth); } #endif //LINE_GLSL ================================================ FILE: Malt/Shaders/Filters/Sharpen.glsl ================================================ #ifndef SHARPEN_GLSL #define SHARPEN_GLSL #include "Filters/Blur.glsl" /* META GLOBAL @meta: category=Filter; subcategory=Sharpen; */ vec4 _sharpen_common(sampler2D tex, vec2 uv, vec4 blurred, float sharpness) { vec4 base = texture(tex, uv); float scaler = 10.0; // Use scalar to put the range of the sharpness value mostly between 0-1 for convenience return base + (base - blurred) * sharpness * scaler; } /* META @meta: label=Box; @tex: label=Texture; @uv: label=UV; default = UV[0]; @radius: default=1.0; min=0.0; @sharpness: default = 0.3; min=0.0; */ vec4 box_sharpen(sampler2D tex, vec2 uv, float radius, bool circular, float sharpness) { vec4 blurred = box_blur(tex, uv, radius, circular); return _sharpen_common(tex, uv, blurred, sharpness); } /* META @meta: label=Gaussian; @tex: label=Texture; @uv: label=UV; default = UV[0]; @radius: default=1.0; min=0.0; @sigma: default=1.0; @sharpness: default = 0.3; min=0.0; */ vec4 gaussian_sharpen(sampler2D tex, vec2 uv, float radius, float sigma, float sharpness) { vec4 blurred = gaussian_blur(tex, uv, radius, sigma); return _sharpen_common(tex, uv, blurred, sharpness); } /* META @meta: label=Jitter; @tex: label=Texture; @uv: label=UV; default = UV[0]; @radius: default=1.0; min=0.0; @distribution_exponent: default=5.0; @samples: default=8; min=1; @sharpness: default = 0.3; min=0.0; */ vec4 jitter_sharpen(sampler2D tex, vec2 uv, float radius, float distribution_exponent, int samples, float sharpness) { vec4 blurred = jitter_blur(tex, uv, radius, distribution_exponent, samples); return _sharpen_common(tex, uv, blurred, sharpness); } #endif //SHARPEN_GLSL ================================================ FILE: Malt/Shaders/Filters/StructureTensor.glsl ================================================ #ifndef STRUCTURE_TENSOR_GLSL #define STRUCTURE_TENSOR_GLSL // Described in: https://en.wikipedia.org/wiki/Structure_tensor /* META @meta: category=Filter; internal=true; @uv: default=UV[0]; */ vec3 structure_tensor(sampler2D tex, vec2 uv) { vec2 texel = 1.0 / textureSize(tex, 0); // Stores the texel offset in x,y and the weight in z. the cells with weight 0 are ignored. // Notice that for v, the axes of the kernel are flipped to effectively rotate the kernel. // -1 0 1 // -2 0 2 // -1 0 1 vec3 sobel_kernel[6] = vec3[]( vec3(-1, -1, -1), vec3(+1, -1, +1), vec3(-1, +0, -2), vec3(+1, +0, +2), vec3(-1, +1, -1), vec3(+1, -1, +1) ); vec3 u = vec3(0.0); vec3 v = vec3(0.0); for(int i = 0; i < 6; i++) { vec3 k = sobel_kernel[i]; u += texture(tex, uv + texel * k.xy).xyz * k.z; v += texture(tex, uv + texel * k.yx).xyz * k.z; } u /= 4.0; v /= 4.0; return vec3( dot(u, u), dot(v, v), dot(u, v) ); } /* META @meta: internal=true; */ vec3 flow_from_structure(vec3 s) { 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)); vec2 d = vec2(s.x - l, s.z); return (length(d) > 0.0)? vec3(normalize(d), sqrt(l)) : vec3(0,1,0); } #endif //STRUCTURE_TENSOR_GLSL ================================================ FILE: Malt/Shaders/Intellisense/intellisense.glsl ================================================ //This file contains a series of C++ macros, structs and function declarations //to make GLSL autocompletion work with C++ autocompletion implementations #ifdef __INTELLISENSE__ //Define GLSL keywords #define in #define out #define inout #define uniform #define varying #define layout(index) #define discard #define uint unsigned int #define atomic_uint uint //Declare GLSL built-in types struct vec2 {}; struct vec3 {}; struct vec4 {}; struct dvec2 {}; struct dvec3 {}; struct dvec4 {}; struct ivec2 {}; struct ivec3 {}; struct ivec4 {}; struct uvec2 {}; struct uvec3 {}; struct uvec4 {}; struct bvec2 {}; struct bvec3 {}; struct bvec4 {}; struct mat2 {}; struct dmat2 {}; struct mat3 {}; struct dmat3 {}; struct mat4 {}; struct dmat4 {}; struct mat2x2 {}; struct dmat2x2 {}; struct mat2x3 {}; struct dmat2x3 {}; struct mat2x4 {}; struct dmat2x4 {}; struct mat3x2 {}; struct dmat3x2 {}; struct mat3x3 {}; struct dmat3x3 {}; struct mat3x4 {}; struct dmat3x4 {}; struct mat4x2 {}; struct dmat4x2 {}; struct mat4x3 {}; struct dmat4x3 {}; struct mat4x4 {}; struct dmat4x4 {}; struct sampler1D {}; struct isampler1D {}; struct usampler1D {}; struct sampler2D {}; struct isampler2D {}; struct usampler2D {}; struct sampler3D {}; struct isampler3D {}; struct usampler3D {}; struct sampler2DRect {}; struct isampler2DRect {}; struct usampler2DRect {}; struct sampler1DArray {}; struct isampler1DArray {}; struct usampler1DArray {}; struct sampler2DArray {}; struct isampler2DArray {}; struct usampler2DArray {}; struct samplerBuffer {}; struct isamplerBuffer {}; struct usamplerBuffer {}; struct sampler2DMS {}; struct isampler2DMS {}; struct usampler2DMS {}; struct sampler2DMSArray {}; struct isampler2DMSArray {}; struct usampler2DMSArray {}; struct samplerCube {}; struct isamplerCube {}; struct usamplerCube {}; struct samplerCubeArray {}; struct isamplerCubeArray {}; struct usamplerCubeArray {}; struct sampler2DDArray {}; struct isampler2DDArray {}; struct usampler2DDArray {}; struct samplerRect {}; struct isamplerRect {}; struct usamplerRect {}; //Declare GLSL standard library functions //return the absolute value of the parameter template T abs(T x); //return the absolute value of the parameter template I abs(I x); //return the absolute value of the parameter template D abs(D x); //return the arccosine of the parameter template T acos(T x); //return the arc hyperbolic cosine of the parameter template T acosh(T x); //check whether all elements of a boolean vector are true template bool all(bvec x); //check whether any element of a boolean vector is true template bool any(bvec x); //return the arcsine of the parameter template T asin(T x); //return the arc hyperbolic sine of the parameter template T asinh(T x); //return the arc-tangent of the parameters template T atan(T y, T x); //return the arc-tangent of the parameters template T atan(T y_over_x); //return the arc hyperbolic tangent of the parameter template T atanh(T x); //counts the number of 1 bits in an integer template I bitCount(I value); //counts the number of 1 bits in an integer template I bitCount(U value); //extract a range of bits from an integer template I bitfieldExtract(I value, int offset, int bits); //extract a range of bits from an integer template U bitfieldExtract(U value, int offset, int bits); //insert a range of bits into an integer template I bitfieldInsert(I base, I insert, int offset, int bits); //insert a range of bits into an integer template U bitfieldInsert(U base, U insert, int offset, int bits); //reverse the order of bits in an integer template I bitfieldReverse(I value); //reverse the order of bits in an integer template U bitfieldReverse(U value); //find the nearest integer that is greater than or equal to the parameter template T ceil(T x); //find the nearest integer that is greater than or equal to the parameter template D ceil(D x); //constrain a value to lie between two further values template T clamp(T x, T minVal, T maxVal); //constrain a value to lie between two further values template T clamp(T x, float minVal, float maxVal); //constrain a value to lie between two further values template D clamp(D x, D minVal, D maxVal); //constrain a value to lie between two further values template D clamp(D x, double minVal, double maxVal); //constrain a value to lie between two further values template I clamp(I x, I minVal, I maxVal); //constrain a value to lie between two further values template I clamp(I x, int minVal, int maxVal); //constrain a value to lie between two further values template U clamp(U x, U minVal, U maxVal); //constrain a value to lie between two further values template U clamp(U x, uint minVal, uint maxVal); //return the cosine of the parameter template T cos(T angle); //return the hyperbolic cosine of the parameter template T cosh(T x); //calculate the cross product of two vectors vec3 cross(vec3 x, vec3 y); //calculate the cross product of two vectors dvec3 cross(dvec3 x, dvec3 y); //convert a quantity in radians to degrees template T degrees(T radians); //calculate the determinant of a matrix float determinant(mat2 m); //calculate the determinant of a matrix float determinant(mat3 m); //calculate the determinant of a matrix float determinant(mat4 m); //calculate the determinant of a matrix double determinant(dmat2 m); //calculate the determinant of a matrix double determinant(dmat3 m); //calculate the determinant of a matrix double determinant(dmat4 m); //return the partial derivative of an argument with respect to x or y template T dFdx(T p); //return the partial derivative of an argument with respect to x or y template T dFdy(T p); //return the partial derivative of an argument with respect to x or y template T dFdxCoarse(T p); //return the partial derivative of an argument with respect to x or y template T dFdyCoarse(T p); //return the partial derivative of an argument with respect to x or y template T dFdxFine(T p); //return the partial derivative of an argument with respect to x or y template T dFdyFine(T p); //calculate the distance between two points template float distance(T p0, T p1); //calculate the distance between two points template double distance(D p0, D p1); //calculate the dot product of two vectors template float dot(T x, T y); //calculate the dot product of two vectors template double dot(D x, D y); //emit a vertex to a specified stream void EmitStreamVertex(int stream); //emit a vertex to the first vertex stream void EmitVertex(); //complete the current output primitive on the first vertex stream void EndPrimitive(); //complete the current output primitive on a specified stream void EndStreamPrimitive(int stream); //perform a component-wise equal-to comparison of two vectors template bvec equal(vec x, vec y); //perform a component-wise equal-to comparison of two vectors template bvec equal(ivec x, ivec y); //perform a component-wise equal-to comparison of two vectors template bvec equal(uvec x, uvec y); //return the natural exponentiation of the parameter template T exp(T x); //return 2 raised to the power of the parameter template T exp2(T x); //return a vector pointing in the same direction as another template T faceforward(T N, T I, T Nref); //return a vector pointing in the same direction as another template D faceforward(D N, D I, D Nref); //find the index of the least significant bit set to 1 in an integer template I findLSB(I value); //find the index of the least significant bit set to 1 in an integer template I findLSB(U value); //find the index of the most significant bit set to 1 in an integer template I findMSB(I value); //find the index of the most significant bit set to 1 in an integer template I findMSB(U value); //produce the encoding of a floating point value as an integer template I floatBitsToInt(T x); //produce the encoding of a floating point value as an integer template U floatBitsToUint(T x); //find the nearest integer less than or equal to the parameter template T floor(T x); //find the nearest integer less than or equal to the parameter template D floor(D x); //perform a fused multiply-add operation template T fma(T a, T b, T c); //perform a fused multiply-add operation template D fma(D a, D b, D c); //compute the fractional part of the argument template T fract(T x); //compute the fractional part of the argument template D fract(D x); //split a floating point number template T frexp(T x, out I exp); //split a floating point number template D frexp(D x, out I exp); //return the sum of the absolute value of derivatives in x and y template T fwidth(T p); //return the sum of the absolute value of derivatives in x and y template T fwidthCoarse(T p); //return the sum of the absolute value of derivatives in x and y template T fwidthFine(T p); //perform a component-wise greater-than comparison of two vectors template bvec greaterThan(vec x, vec y); //perform a component-wise greater-than comparison of two vectors template bvec greaterThan(ivec x, ivec y); //perform a component-wise greater-than comparison of two vectors template bvec greaterThan(uvec x, uvec y); //perform a component-wise greater-than-or-equal comparison of two vectors template bvec greaterThanEqual(vec x, vec y); //perform a component-wise greater-than-or-equal comparison of two vectors template bvec greaterThanEqual(ivec x, ivec y); //perform a component-wise greater-than-or-equal comparison of two vectors template bvec greaterThanEqual(uvec x, uvec y); //produce a floating point using an encoding supplied as an integer template T intBitsToFloat(I x); //produce a floating point using an encoding supplied as an integer template T uintBitsToFloat(U x); //sample a varying at the centroid of a pixel float interpolateAtCentroid(float interpolant); //sample a varying at the centroid of a pixel vec2 interpolateAtCentroid(vec2 interpolant); //sample a varying at the centroid of a pixel vec3 interpolateAtCentroid(vec3 interpolant); //sample a varying at the centroid of a pixel vec4 interpolateAtCentroid(vec4 interpolant); //sample a varying at specified offset from the center of a pixel float interpolateAtOffset(float interpolant, vec2 offset); //sample a varying at specified offset from the center of a pixel vec2 interpolateAtOffset(vec2 interpolant, vec2 offset); //sample a varying at specified offset from the center of a pixel vec3 interpolateAtOffset(vec3 interpolant, vec2 offset); //sample a varying at specified offset from the center of a pixel vec4 interpolateAtOffset(vec4 interpolant, vec2 offset); //sample a varying at the location of a specified sample float interpolateAtSample(float interpolant, int sample); //sample a varying at the location of a specified sample vec2 interpolateAtSample(vec2 interpolant, int sample); //sample a varying at the location of a specified sample vec3 interpolateAtSample(vec3 interpolant, int sample); //sample a varying at the location of a specified sample vec4 interpolateAtSample(vec4 interpolant, int sample); //calculate the inverse of a matrix mat2 inverse(mat2 m); //calculate the inverse of a matrix mat3 inverse(mat3 m); //calculate the inverse of a matrix mat4 inverse(mat4 m); //calculate the inverse of a matrix dmat2 inverse(dmat2 m); //calculate the inverse of a matrix dmat3 inverse(dmat3 m); //calculate the inverse of a matrix dmat4 inverse(dmat4 m); //return the inverse of the square root of the parameter template T inversesqrt(T x); //return the inverse of the square root of the parameter template D inversesqrt(D x); //determine whether the parameter is positive or negative infinity template B isinf(T x); //determine whether the parameter is positive or negative infinity template B isinf(D x); //determine whether the parameter is a number template B isnan(T x); //determine whether the parameter is a number template B isnan(D x); //assemble a floating point number from a value and exponent template T ldexp(T x, I exp); //assemble a floating point number from a value and exponent template D ldexp(D x, I exp); //calculate the length of a vector template float length(T x); //calculate the length of a vector template double length(D x); //perform a component-wise less-than comparison of two vectors template bvec lessThan(vec x, vec y); //perform a component-wise less-than comparison of two vectors template bvec lessThan(ivec x, ivec y); //perform a component-wise less-than comparison of two vectors template bvec lessThan(uvec x, uvec y); //perform a component-wise less-than-or-equal comparison of two vectors template bvec lessThanEqual(vec x, vec y); //perform a component-wise less-than-or-equal comparison of two vectors template bvec lessThanEqual(ivec x, ivec y); //perform a component-wise less-than-or-equal comparison of two vectors template bvec lessThanEqual(uvec x, uvec y); //return the natural logarithm of the parameter template T log(T x); //return the base 2 logarithm of the parameter template T log2(T x); //perform a component-wise multiplication of two matrices template mat matrixCompMult(mat x, mat y); //perform a component-wise multiplication of two matrices template dmat matrixCompMult(dmat x, dmat y); //return the greater of two values template T max(T x, T y); //return the greater of two values template T max(T x, float y); //return the greater of two values template D max(D x, D y); //return the greater of two values template D max(D x, double y); //return the greater of two values template I max(I x, I y); //return the greater of two values template I max(I x, int y); //return the greater of two values template U max(U x, U y); //return the greater of two values template U max(U x, uint y); //return the lesser of two values template T min(T x, T y); //return the lesser of two values template T min(T x, float y); //return the lesser of two values template D min(D x, D y); //return the lesser of two values template D min(D x, double y); //return the lesser of two values template I min(I x, I y); //return the lesser of two values template I min(I x, int y); //return the lesser of two values template U min(U x, U y); //return the lesser of two values template U min(U x, uint y); //linearly interpolate between two values template T mix(T x, T y, T a); //linearly interpolate between two values template T mix(T x, T y, float a); //linearly interpolate between two values template D mix(D x, D y, D a); //linearly interpolate between two values template D mix(D x, D y, double a); //linearly interpolate between two values template T mix(T x, T y, B a); //linearly interpolate between two values template D mix(D x, D y, B a); //linearly interpolate between two values template I mix(I x, I y, B a); //linearly interpolate between two values template U mix(U x, U y, B a); //linearly interpolate between two values template B mix(B x, B y, B a); //compute value of one parameter modulo another template T mod(T x, float y); //compute value of one parameter modulo another template T mod(T x, T y); //compute value of one parameter modulo another template D mod(D x, double y); //compute value of one parameter modulo another template D mod(D x, D y); //separate a value into its integer and fractional components template T modf(T x, out T i); //separate a value into its integer and fractional components template D modf(D x, out D i); //calculates the unit vector in the same direction as the original vector template T normalize(T v); //calculates the unit vector in the same direction as the original vector template D normalize(D v); //logically invert a boolean vector template bvec not(bvec x); //perform a component-wise not-equal-to comparison of two vectors template bvec notEqual(vec x, vec y); //perform a component-wise not-equal-to comparison of two vectors template bvec notEqual(ivec x, ivec y); //perform a component-wise not-equal-to comparison of two vectors template bvec notEqual(uvec x, uvec y); //calculate the outer product of a pair of vectors mat2 outerProduct(vec2 c, vec2 r); //calculate the outer product of a pair of vectors mat3 outerProduct(vec3 c, vec3 r); //calculate the outer product of a pair of vectors mat4 outerProduct(vec4 c, vec4 r); //calculate the outer product of a pair of vectors mat2x3 outerProduct(vec3 c, vec2 r); //calculate the outer product of a pair of vectors mat3x2 outerProduct(vec2 c, vec3 r); //calculate the outer product of a pair of vectors mat2x4 outerProduct(vec4 c, vec2 r); //calculate the outer product of a pair of vectors mat4x2 outerProduct(vec2 c, vec4 r); //calculate the outer product of a pair of vectors mat3x4 outerProduct(vec4 c, vec3 r); //calculate the outer product of a pair of vectors mat4x3 outerProduct(vec3 c, vec4 r); //calculate the outer product of a pair of vectors dmat2 outerProduct(dvec2 c, dvec2 r); //calculate the outer product of a pair of vectors dmat3 outerProduct(dvec3 c, dvec3 r); //calculate the outer product of a pair of vectors dmat4 outerProduct(dvec4 c, dvec4 r); //calculate the outer product of a pair of vectors dmat2x3 outerProduct(dvec3 c, dvec2 r); //calculate the outer product of a pair of vectors dmat3x2 outerProduct(dvec2 c, dvec3 r); //calculate the outer product of a pair of vectors dmat2x4 outerProduct(dvec4 c, dvec2 r); //calculate the outer product of a pair of vectors dmat4x2 outerProduct(dvec2 c, dvec4 r); //calculate the outer product of a pair of vectors dmat3x4 outerProduct(dvec4 c, dvec3 r); //calculate the outer product of a pair of vectors dmat4x3 outerProduct(dvec3 c, dvec4 r); //create a double-precision value from a pair of unsigned integers double packDouble2x32(uvec2 v); //convert two 32-bit floating-point quantities to 16-bit quantities and pack them into a single 32-bit integer uint packHalf2x16(vec2 v); //pack floating-point values into an unsigned integer uint packUnorm2x16(vec2 v); //pack floating-point values into an unsigned integer uint packSnorm2x16(vec2 v); //pack floating-point values into an unsigned integer uint packUnorm4x8(vec4 v); //pack floating-point values into an unsigned integer uint packSnorm4x8(vec4 v); //return the value of the first parameter raised to the power of the second template T pow(T x, T y); //convert a quantity in degrees to radians template T radians(T degrees); //calculate the reflection direction for an incident vector template T reflect(T I, T N); //calculate the reflection direction for an incident vector template D reflect(D I, D N); //calculate the refraction direction for an incident vector template T refract(T I, T N, float eta); //calculate the refraction direction for an incident vector template D refract(D I, D N, float eta); //find the nearest integer to the parameter template T round(T x); //find the nearest integer to the parameter template D round(D x); //find the nearest even integer to the parameter template T roundEven(T x); //find the nearest even integer to the parameter template D roundEven(D x); //extract the sign of the parameter template T sign(T x); //extract the sign of the parameter template I sign(I x); //extract the sign of the parameter template D sign(D x); //return the sine of the parameter template T sin(T angle); //return the hyperbolic sine of the parameter template T sinh(T x); //perform Hermite interpolation between two values template T smoothstep(T edge0, T edge1, T x); //perform Hermite interpolation between two values template T smoothstep(float edge0, float edge1, T x); //perform Hermite interpolation between two values template D smoothstep(D edge0, D edge1, D x); //perform Hermite interpolation between two values template D smoothstep(double edge0, double edge1, D x); //return the square root of the parameter template T sqrt(T x); //return the square root of the parameter template D sqrt(D x); //generate a step function by comparing two values template T step(T edge, T x); //generate a step function by comparing two values template T step(float edge, T x); //generate a step function by comparing two values template D step(D edge, D x); //generate a step function by comparing two values template D step(double edge, D x); //return the tangent of the parameter template T tan(T angle); //return the hyperbolic tangent of the parameter template T tanh(T x); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler1D sampler, int P, int lod); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler2D sampler, ivec2 P, int lod); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler3D sampler, ivec3 P, int lod); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler2DRect sampler, ivec2 P); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler1DArray sampler, ivec2 P, int lod); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler2DArray sampler, ivec3 P, int lod); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsamplerBuffer sampler, int P); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler2DMS sampler, ivec2 P, int sample); //perform a lookup of a single texel within a texture template gvec4 texelFetch(gsampler2DMSArray sampler, ivec3 P, int sample); //perform a lookup of a single texel within a texture with an offset template gvec4 texelFetchOffset(gsampler1D sampler, int P, int lod, int offset); //perform a lookup of a single texel within a texture with an offset template gvec4 texelFetchOffset(gsampler2D sampler, ivec2 P, int lod, ivec2 offset); //perform a lookup of a single texel within a texture with an offset template gvec4 texelFetchOffset(gsampler3D sampler, ivec3 P, int lod, ivec3 offset); //perform a lookup of a single texel within a texture with an offset template gvec4 texelFetchOffset(gsampler2DRect sampler, ivec2 P, ivec2 offset); //perform a lookup of a single texel within a texture with an offset template gvec4 texelFetchOffset(gsampler1DArray sampler, ivec2 P, int lod, ivec2 offset); //perform a lookup of a single texel within a texture with an offset template gvec4 texelFetchOffset(gsampler2DArray sampler, ivec3 P, int lod, ivec3 offset); //retrieves texels from a texture template gvec4 texture(gsampler1D sampler, float P); //retrieves texels from a texture template gvec4 texture(gsampler1D sampler, float P, float bias); //retrieves texels from a texture template gvec4 texture(gsampler2D sampler, vec2 P); //retrieves texels from a texture template gvec4 texture(gsampler2D sampler, vec2 P, float bias); //retrieves texels from a texture template gvec4 texture(gsampler3D sampler, vec3 P); //retrieves texels from a texture template gvec4 texture(gsampler3D sampler, vec3 P, float bias); //retrieves texels from a texture template gvec4 texture(gsamplerCube sampler, vec3 P); //retrieves texels from a texture template gvec4 texture(gsamplerCube sampler, vec3 P, float bias); //retrieves texels from a texture template gvec4 texture(gsampler1DArray sampler, vec2 P); //retrieves texels from a texture template gvec4 texture(gsampler1DArray sampler, vec2 P, float bias); //retrieves texels from a texture template gvec4 texture(gsampler2DArray sampler, vec3 P); //retrieves texels from a texture template gvec4 texture(gsampler2DArray sampler, vec3 P, float bias); //retrieves texels from a texture template gvec4 texture(gsamplerCubeArray sampler, vec4 P); //retrieves texels from a texture template gvec4 texture(gsamplerCubeArray sampler, vec4 P, float bias); //retrieves texels from a texture template gvec4 texture(gsampler2DRect sampler, vec2 P); //gathers four texels from a texture template gvec4 textureGather(gsampler2D sampler, vec2 P); //gathers four texels from a texture template gvec4 textureGather(gsampler2D sampler, vec2 P, int comp); //gathers four texels from a texture template gvec4 textureGather(gsampler2DArray sampler, vec3 P); //gathers four texels from a texture template gvec4 textureGather(gsampler2DArray sampler, vec3 P, int comp); //gathers four texels from a texture template gvec4 textureGather(gsamplerCube sampler, vec3 P); //gathers four texels from a texture template gvec4 textureGather(gsamplerCube sampler, vec3 P, int comp); //gathers four texels from a texture template gvec4 textureGather(gsamplerCubeArray sampler, vec4 P); //gathers four texels from a texture template gvec4 textureGather(gsamplerCubeArray sampler, vec4 P, int comp); //gathers four texels from a texture template gvec4 textureGather(gsampler2DRect sampler, vec3 P); //gathers four texels from a texture template gvec4 textureGather(gsampler2DRect sampler, vec3 P, int comp); //gathers four texels from a texture with offset template gvec4 textureGatherOffset(gsampler2D sampler, vec2 P, ivec2 offset); //gathers four texels from a texture with offset template gvec4 textureGatherOffset(gsampler2D sampler, vec2 P, ivec2 offset, int comp); //gathers four texels from a texture with offset template gvec4 textureGatherOffset(gsampler2DArray sampler, vec3 P, ivec2 offset); //gathers four texels from a texture with offset template gvec4 textureGatherOffset(gsampler2DArray sampler, vec3 P, ivec2 offset, int comp); //gathers four texels from a texture with offset template gvec4 textureGatherOffset(gsampler2DRect sampler, vec3 P, ivec2 offset); //gathers four texels from a texture with offset template gvec4 textureGatherOffset(gsampler2DRect sampler, vec3 P, ivec2 offset, int comp); //gathers four texels from a texture with an array of offsets template gvec4 textureGatherOffsets(gsampler2D sampler, vec2 P, ivec2 offsets[4]); //gathers four texels from a texture with an array of offsets template gvec4 textureGatherOffsets(gsampler2D sampler, vec2 P, ivec2 offsets[4], int comp); //gathers four texels from a texture with an array of offsets template gvec4 textureGatherOffsets(gsampler2DArray sampler, vec3 P, ivec2 offsets[4]); //gathers four texels from a texture with an array of offsets template gvec4 textureGatherOffsets(gsampler2DArray sampler, vec3 P, ivec2 offsets[4], int comp); //gathers four texels from a texture with an array of offsets template gvec4 textureGatherOffsets(gsampler2DRect sampler, vec3 P, ivec2 offsets[4]); //gathers four texels from a texture with an array of offsets template gvec4 textureGatherOffsets(gsampler2DRect sampler, vec3 P, ivec2 offsets[4], int comp); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsampler1D sampler, float P, float dPdx, float dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsampler2D sampler, vec2 P, vec2 dPdx, vec2 dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsampler3D sampler, vec3 P, vec3 dPdx, vec3 dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsamplerCube sampler, vec3 P, vec3 dPdx, vec3 dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsampler2DRect sampler, vec2 P, vec2 dPdx, vec2 dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsampler1DArray sampler, vec2 P, float dPdx, float dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsampler2DArray sampler, vec3 P, vec2 dPdx, vec2 dPdy); //perform a texture lookup with explicit gradients template gvec4 textureGrad(gsamplerCubeArray sampler, vec4 P, vec3 dPdx, vec3 dPdy); //perform a texture lookup with explicit gradients and offset template gvec4 textureGradOffset(gsampler1D sampler, float P, float dPdx, float dPdy, int offset); //perform a texture lookup with explicit gradients and offset template gvec4 textureGradOffset(gsampler2D sampler, vec2 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with explicit gradients and offset template gvec4 textureGradOffset(gsampler3D sampler, vec3 P, vec3 dPdx, vec3 dPdy, ivec3 offset); //perform a texture lookup with explicit gradients and offset template gvec4 textureGradOffset(gsampler2DRect sampler, vec2 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with explicit gradients and offset template gvec4 textureGradOffset(gsampler1DArray sampler, vec2 P, float dPdx, float dPdy, int offset); //perform a texture lookup with explicit gradients and offset template gvec4 textureGradOffset(gsampler2DArray sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsampler1D sampler, float P, float lod); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsampler2D sampler, vec2 P, float lod); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsampler3D sampler, vec3 P, float lod); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsamplerCube sampler, vec3 P, float lod); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsampler1DArray sampler, vec2 P, float lod); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsampler2DArray sampler, vec3 P, float lod); //perform a texture lookup with explicit level-of-detail template gvec4 textureLod(gsamplerCubeArray sampler, vec4 P, float lod); //perform a texture lookup with explicit level-of-detail and offset template gvec4 textureLodOffset(gsampler1D sampler, float P, float lod, int offset); //perform a texture lookup with explicit level-of-detail and offset template gvec4 textureLodOffset(gsampler2D sampler, vec2 P, float lod, ivec2 offset); //perform a texture lookup with explicit level-of-detail and offset template gvec4 textureLodOffset(gsampler3D sampler, vec3 P, float lod, ivec3 offset); //perform a texture lookup with explicit level-of-detail and offset template gvec4 textureLodOffset(gsampler1DArray sampler, vec2 P, float lod, int offset); //perform a texture lookup with explicit level-of-detail and offset template gvec4 textureLodOffset(gsampler2DArray sampler, vec3 P, float lod, ivec2 offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler1D sampler, float P, int offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler1D sampler, float P, int offset, float bias); //perform a texture lookup with offset template gvec4 textureOffset(gsampler2D sampler, vec2 P, ivec2 offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler2D sampler, vec2 P, ivec2 offset, float bias); //perform a texture lookup with offset template gvec4 textureOffset(gsampler3D sampler, vec3 P, ivec3 offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler3D sampler, vec3 P, ivec3 offset, float bias); //perform a texture lookup with offset template gvec4 textureOffset(gsampler2DRect sampler, vec2 P, ivec2 offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler1DArray sampler, vec2 P, int offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler1DArray sampler, vec2 P, int offset, float bias); //perform a texture lookup with offset template gvec4 textureOffset(gsampler2DArray sampler, vec3 P, ivec2 offset); //perform a texture lookup with offset template gvec4 textureOffset(gsampler2DArray sampler, vec3 P, ivec2 offset, float bias); //perform a texture lookup with projection template gvec4 textureProj(gsampler1D sampler, vec2 P); //perform a texture lookup with projection template gvec4 textureProj(gsampler1D sampler, vec2 P, float bias); //perform a texture lookup with projection template gvec4 textureProj(gsampler1D sampler, vec4 P); //perform a texture lookup with projection template gvec4 textureProj(gsampler1D sampler, vec4 P, float bias); //perform a texture lookup with projection template gvec4 textureProj(gsampler2D sampler, vec3 P); //perform a texture lookup with projection template gvec4 textureProj(gsampler2D sampler, vec3 P, float bias); //perform a texture lookup with projection template gvec4 textureProj(gsampler2D sampler, vec4 P); //perform a texture lookup with projection template gvec4 textureProj(gsampler2D sampler, vec4 P, float bias); //perform a texture lookup with projection template gvec4 textureProj(gsampler3D sampler, vec4 P); //perform a texture lookup with projection template gvec4 textureProj(gsampler3D sampler, vec4 P, float bias); //perform a texture lookup with projection template gvec4 textureProj(gsampler2DRect sampler, vec3 P); //perform a texture lookup with projection template gvec4 textureProj(gsampler2DRect sampler, vec4 P); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler1D sampler, vec2 P, float pDx, float pDy); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler1D sampler, vec4 P, float pDx, float pDy); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler2D sampler, vec3 P, vec2 pDx, vec2 pDy); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler2D sampler, vec4 P, vec2 pDx, vec2 pDy); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler3D sampler, vec4 P, vec3 pDx, vec3 pDy); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler2DRect sampler, vec3 P, vec2 pDx, vec2 pDy); //perform a texture lookup with projection and explicit gradients template gvec4 textureProjGrad(gsampler2DRect sampler, vec4 P, vec2 pDx, vec2 pDy); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler1D sampler, vec2 P, float dPdx, float dPdy, int offset); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler1D sampler, vec4 P, float dPdx, float dPdy, int offset); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler2D sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler2D sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler3D sampler, vec4 P, vec3 dPdx, vec3 dPdy, ivec3 offset); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler2DRect sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with projection, explicit gradients and offset template gvec4 textureProjGradOffset(gsampler2DRect sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset); //perform a texture lookup with projection and explicit level-of-detail template gvec4 textureProjLod(gsampler1D sampler, vec2 P, float lod); //perform a texture lookup with projection and explicit level-of-detail template gvec4 textureProjLod(gsampler1D sampler, vec4 P, float lod); //perform a texture lookup with projection and explicit level-of-detail template gvec4 textureProjLod(gsampler2D sampler, vec3 P, float lod); //perform a texture lookup with projection and explicit level-of-detail template gvec4 textureProjLod(gsampler2D sampler, vec4 P, float lod); //perform a texture lookup with projection and explicit level-of-detail template gvec4 textureProjLod(gsampler3D sampler, vec4 P, float lod); //perform a texture lookup with projection and explicit level-of-detail and offset template gvec4 textureProjLodOffset(gsampler1D sampler, vec2 P, float lod, int offset); //perform a texture lookup with projection and explicit level-of-detail and offset template gvec4 textureProjLodOffset(gsampler1D sampler, vec4 P, float lod, int offset); //perform a texture lookup with projection and explicit level-of-detail and offset template gvec4 textureProjLodOffset(gsampler2D sampler, vec3 P, float lod, ivec2 offset); //perform a texture lookup with projection and explicit level-of-detail and offset template gvec4 textureProjLodOffset(gsampler2D sampler, vec4 P, float lod, ivec2 offset); //perform a texture lookup with projection and explicit level-of-detail and offset template gvec4 textureProjLodOffset(gsampler3D sampler, vec4 P, float lod, ivec3 offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler1D sampler, vec2 P, int offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler1D sampler, vec2 P, int offset, float bias); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler1D sampler, vec4 P, int offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler1D sampler, vec4 P, int offset, float bias); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler2D sampler, vec3 P, ivec2 offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler2D sampler, vec3 P, ivec2 offset, float bias); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler2D sampler, vec4 P, ivec2 offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler2D sampler, vec4 P, ivec2 offset, float bias); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler3D sampler, vec4 P, ivec3 offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler3D sampler, vec4 P, ivec3 offset, float bias); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler2DRect sampler, vec3 P, ivec2 offset); //perform a texture lookup with projection and offset template gvec4 textureProjOffset(gsampler2DRect sampler, vec4 P, ivec2 offset); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsampler1D sampler); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsampler2D sampler); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsampler3D sampler); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsamplerCube sampler); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsampler1DArray sampler); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsampler2DDArray sampler); //compute the number of accessible mipmap levels of a texture template int textureQueryLevels(gsamplerCubeArray sampler); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsampler1D sampler, float P); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsampler2D sampler, vec2 P); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsampler3D sampler, vec3 P); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsamplerCube sampler, vec3 P); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsampler1DArray sampler, float P); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsampler2DDArray sampler, vec2 P); //compute the level-of-detail that would be used to sample from a texture template vec2 textureQueryLod(gsamplerCubeArray sampler, vec3 P); //return the number of samples of a texture template int textureSamples(gsampler2DMS sampler); //return the number of samples of a texture template int textureSamples(gsampler2DMSArray sampler); //retrieve the dimensions of a level of a texture template int textureSize(gsampler1D sampler, int lod); //retrieve the dimensions of a level of a texture template ivec2 textureSize(gsampler2D sampler, int lod); //retrieve the dimensions of a level of a texture template ivec3 textureSize(gsampler3D sampler, int lod); //retrieve the dimensions of a level of a texture template ivec2 textureSize(gsamplerCube sampler, int lod); //retrieve the dimensions of a level of a texture ivec3 textureSize(samplerCubeArray sampler, int lod); //retrieve the dimensions of a level of a texture template ivec2 textureSize(gsamplerRect sampler); //retrieve the dimensions of a level of a texture template ivec2 textureSize(gsampler1DArray sampler, int lod); //retrieve the dimensions of a level of a texture template ivec3 textureSize(gsampler2DArray sampler, int lod); //retrieve the dimensions of a level of a texture template int textureSize(gsamplerBuffer sampler); //retrieve the dimensions of a level of a texture template ivec2 textureSize(gsampler2DMS sampler); //retrieve the dimensions of a level of a texture template ivec3 textureSize(gsampler2DMSArray sampler); //calculate the transpose of a matrix mat2 transpose(mat2 m); //calculate the transpose of a matrix mat3 transpose(mat3 m); //calculate the transpose of a matrix mat4 transpose(mat4 m); //calculate the transpose of a matrix mat2x3 transpose(mat3x2 m); //calculate the transpose of a matrix mat2x4 transpose(mat4x2 m); //calculate the transpose of a matrix mat3x2 transpose(mat2x3 m); //calculate the transpose of a matrix mat3x4 transpose(mat4x3 m); //calculate the transpose of a matrix mat4x2 transpose(mat2x4 m); //calculate the transpose of a matrix mat4x3 transpose(mat3x4 m); //calculate the transpose of a matrix dmat2 transpose(dmat2 m); //calculate the transpose of a matrix dmat3 transpose(dmat3 m); //calculate the transpose of a matrix dmat4 transpose(dmat4 m); //calculate the transpose of a matrix dmat2x3 transpose(dmat3x2 m); //calculate the transpose of a matrix dmat2x4 transpose(dmat4x2 m); //calculate the transpose of a matrix dmat3x2 transpose(dmat2x3 m); //calculate the transpose of a matrix dmat3x4 transpose(dmat4x3 m); //calculate the transpose of a matrix dmat4x2 transpose(dmat2x4 m); //calculate the transpose of a matrix dmat4x3 transpose(dmat3x4 m); //find the truncated value of the parameter template T trunc(T x); //find the truncated value of the parameter template D trunc(D x); //add unsigned integers and generate carry template U uaddCarry(U x, U y, out U carry); //perform a 32- by 32-bit multiply to produce a 64-bit result template void umulExtended(U x, U y, out U msb, out U lsb); //perform a 32- by 32-bit multiply to produce a 64-bit result template void imulExtended(I x, I y, out I msb, out I lsb); //produce two unsigned integers containing the bit encoding of a double precision floating point value uvec2 unpackDouble2x32(double d); //convert two 16-bit floating-point values packed into a single 32-bit integer into a vector of two 32-bit floating-point quantities vec2 unpackHalf2x16(uint v); //unpack floating-point values from an unsigned integer vec2 unpackUnorm2x16(uint p); //unpack floating-point values from an unsigned integer vec2 unpackSnorm2x16(uint p); //unpack floating-point values from an unsigned integer vec4 unpackUnorm4x8(uint p); //unpack floating-point values from an unsigned integer vec4 unpackSnorm4x8(uint p); //subtract unsigned integers and generate borrow template U usubBorrow(U x, U y, out U borrow); #endif ================================================ FILE: Malt/Shaders/Lighting/Lighting.glsl ================================================ #ifndef COMMON_LIGHTING_GLSL #define COMMON_LIGHTING_GLSL #include "Common.glsl" #ifndef MAX_LIGHTS #define MAX_LIGHTS 128 #endif #define LIGHT_SUN 1 #define LIGHT_POINT 2 #define LIGHT_SPOT 3 /* META GLOBAL @meta: internal=true; */ struct Light { vec3 color; int type; vec3 position; float radius; vec3 direction; float spot_angle; float spot_blend; int type_index; }; #define MAX_SPOTS 64 #define MAX_SUNS 64 #define MAX_POINTS 64 struct SceneLights { Light lights[MAX_LIGHTS]; int lights_count; int cascades_count; mat4 spot_matrices[MAX_SPOTS]; mat4 sun_matrices[MAX_SUNS]; mat4 point_matrices[MAX_POINTS]; }; layout(std140) uniform SCENE_LIGHTS { SceneLights LIGHTS; }; uniform sampler2DArray SHADOWMAPS_DEPTH_SPOT; uniform sampler2DArray SHADOWMAPS_DEPTH_SUN; uniform samplerCubeArray SHADOWMAPS_DEPTH_POINT; struct LitSurface { vec3 N;// Surface normal vec3 L;// Surface to light direction (normalized) vec3 V;// Surface to camera (view) direction (normalized) vec3 R;// -L reflected on N vec3 H;// Halfway vector float NoL;// Dot product between N and L float P;// Power Scalar bool shadow; int cascade; vec3 shadow_multiply; vec3 light_color; }; struct ShadowData { vec3 light_uv; bool shadow; vec3 light_space; float depth; }; ShadowData spot_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias); ShadowData sun_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias, out int cascade); ShadowData point_shadow(vec3 position, Light light, samplerCubeArray shadowmap, float bias); /* META @position: subtype=Vector; default=POSITION; @normal: subtype=Normal; default=NORMAL; */ LitSurface lit_surface(vec3 position, vec3 normal, Light light, bool shadows) { LitSurface S; S.N = normal; if (light.type == LIGHT_SUN) { S.L = -light.direction; } else { S.L = normalize(light.position - position); } S.V = -view_direction(); S.R = reflect(-S.L, S.N); S.H = normalize(S.L + S.V); S.NoL = dot(S.N,S.L); S.P = 1.0; if (light.type != LIGHT_SUN) //Point or Spot { float normalized_distance = distance(position, light.position) / light.radius; normalized_distance = clamp(normalized_distance, 0, 1); S.P = 1.0 - normalized_distance; } if (light.type == LIGHT_SPOT) { float spot_angle = dot(light.direction, normalize(position - light.position)); spot_angle = acos(spot_angle); float end_angle = light.spot_angle / 2.0; float start_angle = end_angle - light.spot_blend; float delta_angle = end_angle - start_angle; float spot_scalar = clamp((spot_angle - start_angle) / delta_angle, 0, 1); S.P *= 1.0 - spot_scalar; } S.shadow = false; S.cascade = -1; if(shadows) { if(light.type_index >= 0) { float bias = 1e-5; if(light.type == LIGHT_SPOT) { S.shadow = spot_shadow(position, light, SHADOWMAPS_DEPTH_SPOT, bias).shadow; } if(light.type == LIGHT_SUN) { float bias = 1e-3; //bias *= 1.0 - abs(S.NoL); S.shadow = sun_shadow(position, light, SHADOWMAPS_DEPTH_SUN, bias, S.cascade).shadow; } if(light.type == LIGHT_POINT) { S.shadow = point_shadow(position, light, SHADOWMAPS_DEPTH_POINT, bias).shadow; } } } S.shadow_multiply = S.shadow ? vec3(0) : vec3(1); S.light_color = light.color * S.P * S.shadow_multiply; return S; } ShadowData spot_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias) { vec2 shadowmap_size = vec2(textureSize(shadowmap, 0)); ShadowData S; S.light_space = project_point(LIGHTS.spot_matrices[light.type_index], position); S.light_uv = S.light_space * 0.5 + 0.5; S.depth = texture(shadowmap, vec3(S.light_uv.xy, light.type_index)).x; S.shadow = S.depth < S.light_uv.z - bias && S.light_uv == clamp(S.light_uv, vec3(0), vec3(1)); //if(!S.shadow) S.depth = 0; return S; } ShadowData sun_shadow(vec3 position, Light light, sampler2DArray shadowmap, float bias, out int cascade) { vec2 shadowmap_size = vec2(textureSize(shadowmap, 0)); ShadowData S; S.shadow = false; for(int c = 0; c < LIGHTS.cascades_count; c++) { int index = light.type_index * LIGHTS.cascades_count + c; S.light_space = project_point(LIGHTS.sun_matrices[index], position); S.light_uv = S.light_space * 0.5 + 0.5; if(S.light_space == clamp(S.light_space, vec3(-0.99), vec3(0.99))) { S.depth = texture(shadowmap, vec3(S.light_uv.xy, index)).x; S.shadow = S.depth < S.light_uv.z - bias; cascade = c; break; } } return S; } ShadowData point_shadow(vec3 position, Light light, samplerCubeArray shadowmap, float bias) { vec2 shadowmap_size = vec2(textureSize(shadowmap, 0)); ShadowData S; S.light_space = transform_point(LIGHTS.point_matrices[light.type_index], position); S.light_uv = normalize(S.light_space); float cubemap_side_depth = max(abs(S.light_space.x), max(abs(S.light_space.y), abs(S.light_space.z))); float n = 0.01; //Near is hard-coded for point lights float f = light.radius; float buffer_depth = (f+n) / (f-n) - (2*f*n)/(f-n) / cubemap_side_depth; buffer_depth = (buffer_depth + 1.0) * 0.5; S.depth = texture(shadowmap, vec4(S.light_uv, light.type_index)).x; S.shadow = S.depth < buffer_depth - bias; if(!S.shadow) S.depth = 0; return S; } #endif //COMMON_LIGHTING_GLSL ================================================ FILE: Malt/Shaders/Node Utils/bool.glsl ================================================ #ifndef BOOL_GLSL #define BOOL_GLSL /* META GLOBAL @meta: internal = true; */ bool bool_and(bool a, bool b) { return a && b; } bool bool_or(bool a, bool b) { return a || b; } bool bool_not(bool b) { return !b; } bool bool_equal(bool a, bool b){ return a == b; } bool bool_not_equal(bool a, bool b){ return a != b; } bool if_else(bool condition, bool if_true, bool if_false){ return condition ? if_true : if_false; } #endif //BOOL_GLSL ================================================ FILE: Malt/Shaders/Node Utils/common.glsl ================================================ #ifndef NODE_UTILS_COMMON_GLSL #define NODE_UTILS_COMMON_GLSL /* META GLOBAL @meta: internal = true; */ vec3 surface_position() { return POSITION; } vec3 surface_normal() { return NORMAL; } vec3 surface_tangent(int index) { return get_tangent(index); } vec3 surface_bitangent(int index) { return get_bitangent(index); } vec2 surface_uv(int index) { return UV[index]; } vec4 surface_vertex_color(int index) { return COLOR[index]; } vec3 surface_original_position() { return IO_POSITION; } vec3 surface_original_normal() { return IO_NORMAL; } uvec4 object_id() { return ID; } uvec4 object_original_id() { return IO_ID; } mat4 model_matrix() { return MODEL; } mat4 camera_matrix() { return CAMERA; } mat4 projection_matrix() { return PROJECTION; } vec2 render_resolution() { return vec2(RESOLUTION); } vec2 sample_offset() { return SAMPLE_OFFSET; } int sample_count() { return SAMPLE_COUNT; } int current_frame() { return FRAME; } float current_time() { return TIME; } #endif // NODE_UTILS_COMMON_GLSL ================================================ FILE: Malt/Shaders/Node Utils/float.glsl ================================================ #ifndef FLOAT_GLSL #define FLOAT_GLSL /* META GLOBAL @meta: internal = true; */ float float_add(float a, float b){ return a+b; } float float_subtract(float a, float b){ return a-b; } float float_multiply(float a, float b){ return a*b; } float float_divide(float a, float b){ return a/b; } float float_modulo(float a, float b){ return mod(a,b); } float float_pow(float f, float e){ return pow(f, e); } float float_sqrt(float f){ return sqrt(f); } float float_round(float f){ return round(f); } float float_fract(float f){ return fract(f); } float float_floor(float f){ return floor(f); } float float_ceil(float f){ return ceil(f); } float float_clamp(float f, float min, float max){ return clamp(f, min, max); } float float_sign(float f){ return sign(f); } float float_abs(float f){ return abs(f); } float float_min(float a, float b){ return min(a,b); } float float_max(float a, float b){ return max(a,b); } float float_mix(float a, float b, float factor){ return mix(a,b,factor); } float float_sin(float f) { return sin(f); } float float_cos(float f) { return cos(f); } float float_tan(float f) { return tan(f); } float float_asin(float f) { return asin(f); } float float_acos(float f) { return acos(f); } float float_atan(float f) { return atan(f); } float float_degrees(float r) { return degrees(r); } float float_radians(float d) { return radians(d); } bool float_equal(float a, float b){ return a == b; } bool float_not_equal(float a, float b){ return a != b; } bool float_greater(float a, float b){ return a > b; } bool float_greater_or_equal(float a, float b){ return a >= b; } bool float_less(float a, float b){ return a < b; } bool float_less_or_equal(float a, float b){ return a <= b; } float float_if_else(bool condition, float if_true, float if_false){ return condition ? if_true : if_false; } #endif //FLOAT_GLSL ================================================ FILE: Malt/Shaders/Node Utils/int.glsl ================================================ #ifndef INT_GLSL #define INT_GLSL /* META GLOBAL @meta: internal = true; */ int int_add(int a, int b){ return a+b; } int int_subtract(int a, int b){ return a-b; } int int_multiply(int a, int b){ return a*b; } int int_divide(int a, int b){ return a/b; } int int_modulo(int a, int b){ return a%b; } int int_clamp(int i, int min, int max){ return clamp(i, min, max); } int int_sign(int i){ return sign(i); } int int_abs(int i){ return abs(i); } int int_min(int a, int b){ return min(a,b); } int int_max(int a, int b){ return max(a,b); } bool int_equal(int a, int b){ return a == b; } bool int_not_equal(int a, int b){ return a != b; } bool int_greater(int a, int b){ return a > b; } bool int_greater_or_equal(int a, int b){ return a >= b; } bool int_less(int a, int b){ return a < b; } bool int_less_or_equal(int a, int b){ return a <= b; } int int_if_else(bool condition, int if_true, int if_false){ return condition ? if_true : if_false; } #endif //INT_GLSL ================================================ FILE: Malt/Shaders/Node Utils/node_utils.glsl ================================================ #ifndef NODE_UTILS_GLSL #define NODE_UTILS_GLSL #include "Node Utils/common.glsl" #include "Node Utils/bool.glsl" #include "Node Utils/float.glsl" #include "Node Utils/int.glsl" #include "Node Utils/packing.glsl" #include "Node Utils/properties.glsl" #include "Node Utils/sampler.glsl" #include "Node Utils/vec2.glsl" #include "Node Utils/vec3.glsl" #include "Node Utils/vec4.glsl" // Basic common API #include "Common.glsl" #include "Filters/AO.glsl" #include "Filters/Bevel.glsl" #include "Filters/Blur.glsl" #include "Filters/Curvature.glsl" #include "Filters/Line.glsl" #include "Procedural/Noise.glsl" #include "Procedural/Cell_Noise.glsl" #include "Procedural/Fractal_Noise.glsl" #include "Shading/ShadingModels.glsl" #endif //NODE_UTILS_GLSL ================================================ FILE: Malt/Shaders/Node Utils/packing.glsl ================================================ #ifndef PACKING_GLSL #define PACKING_GLSL /* META GLOBAL @meta: internal = true; */ uvec4 pack_8bit(vec4 a, vec4 b, vec4 c, vec4 d) { return uvec4(packUnorm4x8(a), packUnorm4x8(b), packUnorm4x8(c), packUnorm4x8(d)); } void unpack_8bit(uvec4 packed_vector, out vec4 a, out vec4 b, out vec4 c, out vec4 d) { a = unpackUnorm4x8(packed_vector.x); b = unpackUnorm4x8(packed_vector.y); c = unpackUnorm4x8(packed_vector.z); d = unpackUnorm4x8(packed_vector.w); } #endif //PACKING_GLSL ================================================ FILE: Malt/Shaders/Node Utils/properties.glsl ================================================ #ifndef PROPERTIES_GLSL #define PROPERTIES_GLSL /* META GLOBAL @meta: internal = true; */ bool bool_property(bool b) { return b; } float float_property(float f) { return f; } int int_property(int i) { return i; } vec2 vec2_property(vec2 v) { return v; } /* META @v: subtype=Vector;*/ vec3 vec3_property(vec3 v) { return v; } /* META @v: subtype=Vector;*/ vec4 vec4_property(vec4 v) { return v; } vec3 vec3_color_property(vec3 v) { return v; } vec4 vec4_color_property(vec4 v) { return v; } #endif //PROPERTIES_GLSL ================================================ FILE: Malt/Shaders/Node Utils/sampler.glsl ================================================ #ifndef SAMPLER_GLSL #define SAMPLER_GLSL /* META GLOBAL @meta: internal = true; */ vec4 sampler1D_sample(sampler1D t, float u) { return texture(t, u); } int sampler1D_size(sampler1D t) { return textureSize(t, 0); } vec4 sampler1D_textel_fetch(sampler1D t, int u) { return texelFetch(t, u, 0); } /* META @uv: default=UV[0];*/ vec4 sampler2D_sample(sampler2D t, vec2 uv) { return texture(t, uv); } /* META @uv: default=UV[0];*/ vec4 sampler2D_sample_nearest(sampler2D t, vec2 uv){ return texelFetch(t, ivec2(textureSize(t, 0) * uv), 0); } ivec2 sampler2D_size(sampler2D t) { return textureSize(t, 0); } vec4 sampler2D_textel_fetch(sampler2D t, ivec2 uv) { return texelFetch(t, uv, 0); } #endif //SAMPLER_GLSL ================================================ FILE: Malt/Shaders/Node Utils/vec2.glsl ================================================ #ifndef VEC2_GLSL #define VEC2_GLSL /* META GLOBAL @meta: internal = true; */ vec2 vec2_add(vec2 a, vec2 b){ return a+b; } vec2 vec2_subtract(vec2 a, vec2 b){ return a-b; } vec2 vec2_multiply(vec2 a, vec2 b){ return a*b; } vec2 vec2_divide(vec2 a, vec2 b){ return a/b; } vec2 vec2_scale(vec2 v, float s){ return v*s; } vec2 vec2_modulo(vec2 a, vec2 b){ return mod(a,b); } vec2 vec2_pow(vec2 v, vec2 e){ return pow(v, e); } vec2 vec2_sqrt(vec2 v){ return sqrt(v); } vec2 vec2_round(vec2 v){ return round(v); } vec2 vec2_fract(vec2 v){ return fract(v); } vec2 vec2_floor(vec2 v){ return floor(v); } vec2 vec2_ceil(vec2 v){ return ceil(v); } vec2 vec2_clamp(vec2 v, vec2 min, vec2 max){ return clamp(v, min, max); } vec2 vec2_sign(vec2 v){ return sign(v); } vec2 vec2_abs(vec2 v){ return abs(v); } vec2 vec2_min(vec2 a, vec2 b){ return min(a,b); } vec2 vec2_max(vec2 a, vec2 b){ return max(a,b); } vec2 vec2_mix(vec2 a, vec2 b, vec2 factor){ return mix(a,b,factor); } vec2 vec2_mix_float(vec2 a, vec2 b, float factor){ return mix(a,b,factor); } vec2 vec2_normalize(vec2 v){ return normalize(v); } float vec2_length(vec2 v){ return length(v); } float vec2_distance(vec2 a, vec2 b){ return distance(a,b); } float vec2_dot_product(vec2 a, vec2 b){ return dot(a,b); } bool vec2_equal(vec2 a, vec2 b){ return a == b; } bool vec2_not_equal(vec2 a, vec2 b){ return a != b; } vec2 vec2_if_else(bool condition, vec2 if_true, vec2 if_false){ return condition ? if_true : if_false; } vec2 vec2_join(float x, float y) { return vec2(x,y);} void vec2_split(vec2 v, out float x, out float y){ x=v.x; y=v.y; } #endif //VEC2_GLSL ================================================ FILE: Malt/Shaders/Node Utils/vec3.glsl ================================================ #ifndef VEC3_GLSL #define VEC3_GLSL /* META GLOBAL @meta: internal = true; */ /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_add(vec3 a, vec3 b){ return a+b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_subtract(vec3 a, vec3 b){ return a-b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_multiply(vec3 a, vec3 b){ return a*b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_divide(vec3 a, vec3 b){ return a/b; } /*META @v: subtype=Vector;*/ vec3 vec3_scale(vec3 v, float s){ return v*s; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_modulo(vec3 a, vec3 b){ return mod(a,b); } /*META @v: subtype=Vector; @e: subtype=Vector;*/ vec3 vec3_pow(vec3 v, vec3 e){ return pow(v, e); } /*META @v: subtype=Vector;*/ vec3 vec3_sqrt(vec3 v){ return sqrt(v); } /*META @v: subtype=Vector;*/ vec3 vec3_round(vec3 v){ return round(v); } /*META @v: subtype=Vector;*/ vec3 vec3_fract(vec3 v){ return fract(v); } /*META @v: subtype=Vector;*/ vec3 vec3_floor(vec3 v){ return floor(v); } /*META @v: subtype=Vector;*/ vec3 vec3_ceil(vec3 v){ return ceil(v); } /*META @v: subtype=Vector; @min: subtype=Vector; @max: subtype=Vector;*/ vec3 vec3_clamp(vec3 v, vec3 min, vec3 max){ return clamp(v, min, max); } /*META @v: subtype=Vector;*/ vec3 vec3_sign(vec3 v){ return sign(v); } /*META @v: subtype=Vector;*/ vec3 vec3_abs(vec3 v){ return abs(v); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_min(vec3 a, vec3 b){ return min(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_max(vec3 a, vec3 b){ return max(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector; @factor: subtype=Vector;*/ vec3 vec3_mix(vec3 a, vec3 b, vec3 factor){ return mix(a,b,factor); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_mix_float(vec3 a, vec3 b, float factor){ return mix(a,b,factor); } /*META @v: subtype=Vector;*/ vec3 vec3_normalize(vec3 v){ return normalize(v); } /*META @v: subtype=Vector;*/ float vec3_length(vec3 v){ return length(v); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ float vec3_distance(vec3 a, vec3 b){ return distance(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ float vec3_dot_product(vec3 a, vec3 b){ return dot(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec3 vec3_cross_product(vec3 a, vec3 b){ return cross(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ bool vec3_equal(vec3 a, vec3 b){ return a == b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ bool vec3_not_equal(vec3 a, vec3 b){ return a != b; } /*META @if_true: subtype=Vector; @if_false: subtype=Vector;*/ vec3 vec3_if_else(bool condition, vec3 if_true, vec3 if_false){ return condition ? if_true : if_false; } vec3 vec3_join(float x, float y, float z) { return vec3(x,y,z);} /*META @v: subtype=Vector;*/ void vec3_split(vec3 v, out float x, out float y, out float z){ x=v.x; y=v.y; z=v.z; } #endif //VEC3_GLSL ================================================ FILE: Malt/Shaders/Node Utils/vec4.glsl ================================================ #ifndef VEC4_GLSL #define VEC4_GLSL /* META GLOBAL @meta: internal = true; */ /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_add(vec4 a, vec4 b){ return a+b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_subtract(vec4 a, vec4 b){ return a-b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_multiply(vec4 a, vec4 b){ return a*b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_divide(vec4 a, vec4 b){ return a/b; } /*META @v: subtype=Vector;*/ vec4 vec4_scale(vec4 v, float s){ return v*s; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_modulo(vec4 a, vec4 b){ return mod(a,b); } /*META @v: subtype=Vector; @e: subtype=Vector;*/ vec4 vec4_pow(vec4 v, vec4 e){ return pow(v, e); } /*META @v: subtype=Vector;*/ vec4 vec4_sqrt(vec4 v){ return sqrt(v); } /*META @v: subtype=Vector;*/ vec4 vec4_round(vec4 v){ return round(v); } /*META @v: subtype=Vector;*/ vec4 vec4_fract(vec4 v){ return fract(v); } /*META @v: subtype=Vector;*/ vec4 vec4_floor(vec4 v){ return floor(v); } /*META @v: subtype=Vector;*/ vec4 vec4_ceil(vec4 v){ return ceil(v); } /*META @v: subtype=Vector; @min: subtype=Vector; @max: subtype=Vector;*/ vec4 vec4_clamp(vec4 v, vec4 min, vec4 max){ return clamp(v, min, max); } /*META @v: subtype=Vector;*/ vec4 vec4_sign(vec4 v){ return sign(v); } /*META @v: subtype=Vector;*/ vec4 vec4_abs(vec4 v){ return abs(v); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_min(vec4 a, vec4 b){ return min(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_max(vec4 a, vec4 b){ return max(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector; @factor: subtype=Vector;*/ vec4 vec4_mix(vec4 a, vec4 b, vec4 factor){ return mix(a,b,factor); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ vec4 vec4_mix_float(vec4 a, vec4 b, float factor){ return mix(a,b,factor); } /*META @v: subtype=Vector;*/ vec4 vec4_normalize(vec4 v){ return normalize(v); } /*META @v: subtype=Vector;*/ float vec4_length(vec4 v){ return length(v); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ float vec4_distance(vec4 a, vec4 b){ return distance(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ float vec4_dot_product(vec4 a, vec4 b){ return dot(a,b); } /*META @a: subtype=Vector; @b: subtype=Vector;*/ bool vec4_equal(vec4 a, vec4 b){ return a == b; } /*META @a: subtype=Vector; @b: subtype=Vector;*/ bool vec4_not_equal(vec4 a, vec4 b){ return a != b; } /*META @if_true: subtype=Vector; @if_false: subtype=Vector;*/ vec4 vec4_if_else(bool condition, vec4 if_true, vec4 if_false){ return condition ? if_true : if_false; } vec4 vec4_join(float r, float g, float b, float a) { return vec4(r,g,b,a);} /*META @v: subtype=Vector;*/ 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; } #endif //VEC4_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Bool.glsl ================================================ #ifndef NODE_UTILS_2_BOOL_GLSL #define NODE_UTILS_2_BOOL_GLSL /* META GLOBAL @meta: category=Math; subcategory=Boolean Logic; */ /*META @meta: label=And;*/ bool Bool_and(bool a, bool b) { return a && b; } /*META @meta: label=Or;*/ bool Bool_or(bool a, bool b) { return a || b; } /*META @meta: label=Not;*/ bool Bool_not(bool b) { return !b; } /*META @meta: label=Equal;*/ bool Bool_equal(bool a, bool b){ return a == b; } /*META @meta: label=Not Equal;*/ bool Bool_not_equal(bool a, bool b){ return a != b; } /*META @meta: label=If Else; @a: label=If True; @b: label=If False; */ bool Bool_if_else(bool condition, bool a, bool b){ return condition ? a : b; } #endif //NODE_UTILS_2_BOOL_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Color.glsl ================================================ #ifndef NODE_UTILS_2_COLOR_GLSL #define NODE_UTILS_2_COLOR_GLSL /* META GLOBAL @meta: category=Color; */ /* META @meta: label=Gradient; subcategory=Color Gradient; @Coord: label=U; subtype=Slider; min=0.0; max=1.0; */ vec4 Color_Gradient_1d(sampler1D Color_Ramp, float Coord) { return texture(Color_Ramp, Coord); } /* META @meta: label=RGB Gradient; subcategory=Color Gradient; @Coord: label=UVW; subtype=Slider; min=0.0; max=1.0; */ vec3 Color_Gradient_3d(sampler1D Color_Ramp, vec3 Coord) { return rgb_gradient(Color_Ramp, Coord); } /* META @meta: label=RGBA Gradient; subcategory=Color Gradient; @Coord: label=UVWX; subtype=Slider; min=0.0; max=1.0; */ vec4 Color_Gradient_4d(sampler1D Color_Ramp, vec4 Coord) { return rgba_gradient(Color_Ramp, Coord); } #include "Node Utils 2/ColorBlend.glsl" #endif // NODE_UTILS_2_COLOR_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/ColorBlend.glsl ================================================ #ifndef COMMON_BLEND_MODES_GLSL #define COMMON_BLEND_MODES_GLSL #include "Common/Color.glsl" /* META GLOBAL @meta: category=Color; subcategory=Layer Blend; */ //Based on https://www.w3.org/TR/compositing-1 void _blend_prepare_inputs(inout vec4 base, inout vec4 blend, int mode, float opacity) { blend.a *= opacity; if(mode == 1 || mode == 3)//Clamp { base = saturate(base); blend = saturate(blend); } if(mode == 2 || mode == 3)//sRGB { base.rgb = linear_to_srgb(base.rgb); blend.rgb = linear_to_srgb(blend.rgb); } } //https://www.w3.org/TR/compositing-1/#generalformula vec4 _blend_common(vec4 base, vec4 blend, vec3 mixed, int mode) { if(mode == 1 || mode == 3)//Clamp { mixed = saturate(mixed); } vec4 result; vec3 color = (1.0 - base.a) * blend.rgb + base.a * mixed; result.rgb = color * blend.a + base.rgb * base.a * (1.0 - blend.a); result.a = blend.a + base.a * (1.0 - blend.a); if(mode == 2 || mode == 3)//sRGB { result.rgb = srgb_to_linear(result.rgb); } return result; } /* META @meta: label=Normal; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_normal(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); return _blend_common(base, blend, blend.rgb, mode); } /* META @meta: label=Add; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_add(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = (base + blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Multiply; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_multiply(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = (base * blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Overlay; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_overlay(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed; for(int i=0; i<3; i++) { if(base[i] <= 0.5) mixed[i] = blend[i] * 2.0 * base[i]; else mixed[i] = blend[i] + (2.0*base[i]-1.0) - (blend[i] * (2.0*base[i]-1.0)); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Screen; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_screen(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = (base + blend - (base * blend)).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Darken; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_darken(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = min(base, blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Lighten; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_lighten(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = max(base, blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Soft Light; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_soft_light(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed; for(int i=0; i<3; i++) { float d; if(base[i] <= 0.25) d = ((16.0*base[i] - 12.0) * base[i] + 4.0) * base[i]; else d = sqrt(base[i]); if(blend[i] <= 0.5) mixed[i] = base[i] - (1.0 - 2.0*blend[i]) * base[i] * (1.0 - base[i]); else mixed[i] = base[i] + (2.0*blend[i] - 1.0) * (d - base[i]); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Hard Light; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_hard_light(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed; for(int i=0; i<3; i++) { if(blend[i] <= 0.5) mixed[i] = base[i] * 2.0*blend[i]; else mixed[i] = base[i] + (2.0*blend[i]-1.0) - (base[i] * (2.0*blend[i]-1.0)); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Linear Light; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_linear_light(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = (base + (2.0 * blend) - 1.0).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Dodge; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_dodge(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed; for(int i=0; i<3; i++) { if(base[i] == 0) mixed[i] = 0; else if(blend[i] == 1) mixed[i] = 1; else mixed[i] = min(1.0, base[i] / (1.0 - blend[i])); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Burn; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_burn(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed; for(int i=0; i<3; i++) { if(base[i] == 1) mixed[i] = 1; else if(blend[i] == 0) mixed[i] = 0; else mixed[i] = 1 - min(1, (1.0 - base[i]) / blend[i]); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Subtract; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_subtract(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = (base - blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Difference; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_difference(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = abs(base - blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Divide; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_divide(float opacity, vec4 base, vec4 blend, int mode) { _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = (base / blend).rgb; return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Hue; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_hue(float opacity, vec4 base, vec4 blend, int mode) { vec3 base_hsv = rgb_to_hsv(base.rgb); vec3 blend_hsv = rgb_to_hsv(blend.rgb); vec3 mixed_hsv = base_hsv; mixed_hsv.x = blend_hsv.x; if(blend_hsv.y == 0) mixed_hsv.y = 0; _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = hsv_to_rgb(mixed_hsv); if(mode == 2 || mode == 3)//sRGB { mixed = linear_to_srgb(mixed); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Saturation; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_saturation(float opacity, vec4 base, vec4 blend, int mode) { vec3 base_hsv = rgb_to_hsv(base.rgb); vec3 blend_hsv = rgb_to_hsv(blend.rgb); vec3 mixed_hsv = base_hsv; if(base_hsv.y != 0) mixed_hsv.y = blend_hsv.y; _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = hsv_to_rgb(mixed_hsv); if(mode == 2 || mode == 3)//sRGB { mixed = linear_to_srgb(mixed); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Value; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_value(float opacity, vec4 base, vec4 blend, int mode) { vec3 base_hsv = rgb_to_hsv(base.rgb); vec3 blend_hsv = rgb_to_hsv(blend.rgb); vec3 mixed_hsv = base_hsv; mixed_hsv.z = blend_hsv.z; _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = hsv_to_rgb(mixed_hsv); if(mode == 2 || mode == 3)//sRGB { mixed = linear_to_srgb(mixed); } return _blend_common(base, blend, mixed, mode); } /* META @meta: label=Color; @blend: default=vec4(0); @opacity: subtype=Slider; min=0.0; max=1.0; default=1.0; @mode: subtype=ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp); */ vec4 blend_color(float opacity, vec4 base, vec4 blend, int mode) { vec3 base_hsv = rgb_to_hsv(base.rgb); vec3 blend_hsv = rgb_to_hsv(blend.rgb); vec3 mixed_hsv = base_hsv; mixed_hsv.xy = blend_hsv.xy; _blend_prepare_inputs(base, blend, mode, opacity); vec3 mixed = hsv_to_rgb(mixed_hsv); if(mode == 2 || mode == 3)//sRGB { mixed = linear_to_srgb(mixed); } return _blend_common(base, blend, mixed, mode); } #endif //COMMON_BLEND_MODES_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Filter.glsl ================================================ #ifndef NODE_UTILS_2_FILTER_GLSL #define NODE_UTILS_2_FILTER_GLSL /* META GLOBAL @meta: category=Filter; */ #endif //NODE_UTILS_2_FILTER_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Float.glsl ================================================ #ifndef NODE_UTILS_2_FLOAT_GLSL #define NODE_UTILS_2_FLOAT_GLSL /* META GLOBAL @meta: category=Math; subcategory=Float; */ /*META @meta: label=Add;*/ float Float_add(float a, float b){ return a+b; } /*META @meta: label=Subtract;*/ float Float_subtract(float a, float b){ return a-b; } /*META @meta: label=Multiply;*/ float Float_multiply(float a, float b){ return a*b; } /*META @meta: label=Divide;*/ float Float_divide(float a, float b){ return a/b; } /*META @meta: label=Map Range; @clamped: default=true; @value: default = 0.5; @from_min: default = 0.0; @from_max: default = 1.0; @to_min: default = 0.0; @to_max: default = 1.0; */ float Float_map_range(bool clamped, float value, float from_min, float from_max, float to_min, float to_max) { if(clamped) { return map_range_clamped(value, from_min, from_max, to_min, to_max); } else { return map_range(value, from_min, from_max, to_min, to_max); } } /*META @meta: label=Modulo;*/ float Float_modulo(float a, float b){ return mod(a,b); } /*META @meta: label=Power;*/ float Float_pow(float a, float b){ return pow(a, b); } /*META @meta: label=Square Root;*/ float Float_sqrt(float a){ return sqrt(a); } /*META @meta: label=Round;*/ float Float_round(float a){ return round(a); } /*META @meta: label=Fractional Part;*/ float Float_fract(float a){ return fract(a); } /*META @meta: label=Floor;*/ float Float_floor(float a){ return floor(a); } /*META @meta: label=Ceil;*/ float Float_ceil(float a){ return ceil(a); } /*META @meta: label=Clamp; @b: label=Min; @c: label=Max; */ float Float_clamp(float a, float b, float c){ return clamp(a, b, c); } /*META @meta: label=Sign;*/ float Float_sign(float a){ return sign(a); } /*META @meta: label=Absolute;*/ float Float_abs(float a){ return abs(a); } /*META @meta: label=Minimum;*/ float Float_min(float a, float b){ return min(a,b); } /*META @meta: label=Maximum;*/ float Float_max(float a, float b){ return max(a,b); } /*META @meta: label=Smooth Minimum; @s: min=0.0; */ /*META @meta: label=Mix;*/ float Float_mix(float a, float b, float fac){ return safe_mix(a,b,fac); } /*META @meta: label=Sine;*/ float Float_sin(float a) { return sin(a); } /*META @meta: label=Cosine;*/ float Float_cos(float a) { return cos(a); } /*META @meta: label=Tangent;*/ float Float_tan(float a) { return tan(a); } /*META @meta: label=Arcsine;*/ float Float_asin(float a) { return asin(a); } /*META @meta: label=Arcosine;*/ float Float_acos(float a) { return acos(a); } /*META @meta: label=Arctangent;*/ float Float_atan(float a) { return atan(a); } /*META @meta: label=Arctangent 2;*/ float Float_atan2(float a, float b) { return atan(a, b); } /*META @meta: label=Radians to Degrees;*/ float Float_degrees(float a) { return degrees(a); } /*META @meta: label=Degrees to Radians;*/ float Float_radians(float a) { return radians(a); } /*META @meta: label=Equal; @e: default=0.1; min=0.0; */ bool Float_equal(float a, float b, float e){ return abs(a - b) < abs(e); } /*META @meta: label=Not Equal; @e: default=0.1; min=0.0; */ bool Float_not_equal(float a, float b, float e){ return !Float_equal(a, b, e); } /*META @meta: label=Greater;*/ bool Float_greater(float a, float b){ return a > b; } /*META @meta: label=Greater or Equal;*/ bool Float_greater_or_equal(float a, float b){ return a >= b; } /*META @meta: label=Less;*/ bool Float_less(float a, float b){ return a < b; } /*META @meta: label=Less or Equal;*/ bool Float_less_or_equal(float a, float b){ return a <= b; } /*META @meta: label=If Else; @a: label=If True; @b: label=If False; */ float Float_if_else(bool condition, float a, float b){ return condition ? a : b; } #endif //NODE_UTILS_2_FLOAT_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Input.glsl ================================================ #ifndef NODE_UTILS_2_INPUT_GLSL #define NODE_UTILS_2_INPUT_GLSL #include "Shading/BRDF.glsl" /* META GLOBAL @meta: category=Input; */ #if !(defined(NO_POSITION_INPUT) && defined(NO_NORMAL_INPUT)) /* META @Coordinate_Space: label=Space; subtype=ENUM(Object,World,Camera,Screen); default=1; @Normal: default=NORMAL; @IOR: label=IOR; min=1.0; max=3.0; default=1.45; */ void Geometry( int Coordinate_Space, out vec3 Position, out vec3 Incoming, out vec3 Normal, out vec3 True_Normal, out bool Is_Backfacing, out float Facing, out float Fresnel, out vec3 Reflection, out vec3 Refraction, float IOR ) { Position = POSITION; Incoming = -view_direction(); Normal = NORMAL; True_Normal = true_normal(); Facing = dot(Normal, Incoming); Is_Backfacing = !is_front_facing(); float F0 = pow((1-IOR) / (1+IOR), 2); Fresnel = F_cook_torrance(Facing, F0); Reflection = reflect(view_direction(), Normal); Refraction = refract(Incoming, Normal, 1.0 / IOR); if(Coordinate_Space == 0) //Object { mat4 m = inverse(MODEL); Position = transform_point(m, Position); Incoming = transform_normal(m, Incoming); Normal = transform_normal(m, Normal); True_Normal = transform_normal(m, True_Normal); Reflection = transform_normal(m, Reflection); Refraction = transform_normal(m, Refraction); } if(Coordinate_Space == 2) //Camera { mat4 m = CAMERA; Position = transform_point(m, Position); Incoming = transform_normal(m, Incoming); Normal = transform_normal(m, Normal); True_Normal = transform_normal(m, True_Normal); Reflection = transform_normal(m, Reflection); Refraction = transform_normal(m, Refraction); } if(Coordinate_Space == 3) //Screen { Position = project_point_to_screen_coordinates(PROJECTION * CAMERA, Position); mat4 m = CAMERA; Incoming = camera_direction_to_screen_space(transform_normal(m, Incoming)); Normal = camera_direction_to_screen_space(transform_normal(m, Normal)); True_Normal = camera_direction_to_screen_space(transform_normal(m, True_Normal)); Reflection = camera_direction_to_screen_space(transform_normal(m, Reflection)); Refraction = camera_direction_to_screen_space(transform_normal(m, Refraction)); } } #endif //NO_POSITION_INPUT && NO_NORMAL_INPUT #ifndef NO_UV_INPUT /* META @meta: label=UV Map; @Index: min=0; max=3; @uv: label=UV; */ void UV_Map( int Index, out vec2 uv ) { uv = UV[Index]; } /* META @meta: subcategory=Tangent; label=UV Map; @UV_Index: min=0; max=3; */ void Tangent_UV_Map( int UV_Index, out vec3 Tangent, out vec3 Bitangent ) { Tangent = get_tangent(UV_Index); Bitangent = get_bitangent(UV_Index); } #endif //NO_GEOMETRY_UV #ifndef NO_NORMAL_INPUT /* META @meta: subcategory=Tangent; label=Radial; @Axis: subtype=ENUM(X,Y,Z); default=2; @Object_Space: default=true; */ void Tangent_Radial( int Axis, bool Object_Space, out vec3 Tangent, out vec3 Bitangent ) { vec3 normal = NORMAL; if(Object_Space) { normal = transform_normal(inverse(MODEL), normal); } vec3 _axis = vec3[3](vec3(1,0,0), vec3(0,1,0), vec3(0,0,1))[Axis]; Tangent = radial_tangent(NORMAL, _axis); Bitangent = normalize(cross(NORMAL, Tangent)) * (is_front_facing() ? 1 : -1); } /* META @meta: subcategory=Tangent; label=Procedural UV; */ void Tangent_Procedural_UV( vec2 UV, out vec3 Tangent, out vec3 Bitangent ) { vec4 t = compute_tangent(UV); Tangent = t.xyz; Bitangent = normalize(cross(NORMAL, Tangent)) * t.w; } #endif //NO_NORMAL_INPUT #ifndef NO_VERTEX_COLOR_INPUT /* META @Index: min=0; max=3; @uv: label=UV; */ void Vertex_Color( int Index, out vec4 Vertex_Color ) { Vertex_Color = COLOR[Index]; } #endif //NO_VERTEX_COLOR_INPUT #ifndef NO_ID_INPUT /* META @meta: label=Id; */ void ID_Node( out vec4 Object_Id, out vec4 Custom_Id_A, out vec4 Custom_Id_B, out vec4 Custom_Id_C ) { Object_Id = unpackUnorm4x8(ID.x); Custom_Id_A = unpackUnorm4x8(ID.y); Custom_Id_B = unpackUnorm4x8(ID.z); Custom_Id_C = unpackUnorm4x8(ID.w); } #endif //NO_ID_INPUT #ifndef NO_MODEL_INPUT void Object_Info( out vec3 Position, out mat4 Rotation, out vec3 Scale, out vec4 Id, out mat4 Matrix ) { Position = MODEL[3].xyz; vec3 i = MODEL[0].xyz; vec3 j = MODEL[1].xyz; vec3 k = MODEL[2].xyz; //TODO: Signed Scale? Scale = vec3(length(i), length(j), length(k)); i /= Scale.x; j /= Scale.y; k /= Scale.z; Rotation = mat4(mat3(i,j,k)); Id = unpackUnorm4x8(IO_ID.x); Matrix = MODEL; } #endif //NO_MODEL_INPUT #ifndef NO_CAMERA_INPUT /* META @Screen_UV: label=Screen UV; */ void Camera_Data( out vec3 View_Direction, out vec2 Screen_UV, out float Z_Depth, out float View_Distance, out vec3 Camera_Position, out mat4 Camera_Matrix, out mat4 Projection_Matrix, out bool Is_Orthographic ) { Screen_UV = screen_uv(); View_Direction = view_direction(); Z_Depth = -transform_point(CAMERA, POSITION).z; Camera_Position = camera_position(); View_Distance = distance(Camera_Position, POSITION); Camera_Matrix = CAMERA; Projection_Matrix = PROJECTION; Is_Orthographic = is_ortho(PROJECTION); } #endif //NO_CAMERA_INPUT void Render_Info( out vec2 Resolution, out int Current_Sample, out vec2 Sample_Offset ) { Resolution = RESOLUTION; Current_Sample = SAMPLE_COUNT; Sample_Offset = SAMPLE_OFFSET; } void Time_Info( out float Time, out int Frame ) { Time = TIME; Frame = FRAME; } void Random( float seed, out vec4 per_object, out vec4 per_sample, out vec4 per_pixel ) { per_object = random_per_object(seed); per_sample = random_per_sample(seed); per_pixel = random_per_pixel(seed); } #if !(defined(NO_NORMAL_INPUT) || defined(NO_UV_INPUT)) /* META @scale: default=vec2(1.0); @Uv: label=UV; */ void Curve_View_Mapping( vec2 scale, out vec2 Uv, out float Facing ) { Uv = curve_view_mapping(UV[0], NORMAL, get_tangent(0), -view_direction()) * scale; Facing = 1 - (abs(Uv.y - 0.5) * 2); } #endif //NO_NORMAL_INPUT || NO_UV_INPUT #endif //NODE_UTILS_2_INPUT_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Int.glsl ================================================ #ifndef NODE_UTILS_2_INT_GLSL #define NODE_UTILS_2_INT_GLSL /* META GLOBAL @meta: category=Math; subcategory=Integer; */ /*META @meta: label=Add;*/ int Int_add(int a, int b){ return a+b; } /*META @meta: label=Subtract;*/ int Int_subtract(int a, int b){ return a-b; } /*META @meta: label=Multiply;*/ int Int_multiply(int a, int b){ return a*b; } /*META @meta: label=Divide;*/ int Int_divide(int a, int b){ return a/b; } /*META @meta: label=Modulo;*/ int Int_modulo(int a, int b){ return a%b; } /*META @meta: label=Clamp; @b: label=Min; @c: label=Max; */ int Int_clamp(int a, int b, int c){ return clamp(a, b, c); } /*META @meta: label=Sign;*/ int Int_sign(int a){ return sign(a); } /*META @meta: label=Absolute;*/ int Int_abs(int a){ return abs(a); } /*META @meta: label=Minimum;*/ int Int_min(int a, int b){ return min(a,b); } /*META @meta: label=Maximum;*/ int Int_max(int a, int b){ return max(a,b); } /*META @meta: label=Equal;*/ bool Int_equal(int a, int b){ return a == b; } /*META @meta: label=Not Equal;*/ bool Int_not_equal(int a, int b){ return a != b; } /*META @meta: label=Greater;*/ bool Int_greater(int a, int b){ return a > b; } /*META @meta: label=Greater or Equal;*/ bool Int_greater_or_equal(int a, int b){ return a >= b; } /*META @meta: label=Less;*/ bool Int_less(int a, int b){ return a < b; } /*META @meta: label=Less or Equal;*/ bool Int_less_or_equal(int a, int b){ return a <= b; } /*META @meta: label=If Else; @a: label=If True; @b: label=If False; */ int Int_if_else(bool condition, int a, int b){ return condition ? a : b; } #endif //NODE_UTILS_2_INT_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Mat4.glsl ================================================ #ifndef NODE_UTILS_2_MAT4_GLSL #define NODE_UTILS_2_MAT4_GLSL /* META GLOBAL @meta: category=Math; subcategory=Matrix; */ /* META @meta: label=Inverse; @a: label=Matrix; default=mat4(1); */ mat4 Mat4_inverse(mat4 a) { return inverse(a); } /* META @meta: label=Multiply; @a: default=mat4(1); @b: default=mat4(1); */ mat4 Mat4_multiply(mat4 a, mat4 b) { return a * b; } #endif //NODE_UTILS_2_MAT4_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Parameters.glsl ================================================ #ifndef NODE_UTILS_2_PARAMETERS_GLSL #define NODE_UTILS_2_PARAMETERS_GLSL /*META GLOBAL @meta: category=Parameters; */ /* META @meta: label=Boolean; */ bool Bool_property(bool b) { return b; } /* META @meta: label=Float; */ float Float_property(float f) { return f; } /* META @meta: label=Integer; */ int Int_property(int i) { return i; } /* META @meta: label=Vector 2D; */ vec2 Vec2_property(vec2 v) { return v; } /* META @meta: label=Vector 3D; @v: subtype=Vector; */ vec3 Vec3_property(vec3 v) { return v; } /* META @meta: label=Vector 4D; @v: subtype=Vector; */ vec4 Vec4_property(vec4 v) { return v; } /* META @meta: label=RGB Color; @v: subtype=Color; */ vec3 Vec3_color_property(vec3 v) { return v; } /* META @meta: label=RGBA Color; @v: subtype=Color; */ vec4 Vec4_color_property(vec4 v) { return v; } #ifdef GL_ARB_bindless_texture /* META @meta: label=Color Ramp; */ sampler1D Sampler1D_property(sampler1D color_ramp) { return color_ramp; } /* META @meta: label=Image; */ sampler2D Sampler2D_property(sampler2D image) { return image; } #endif #endif //NODE_UTILS_2_PARAMETERS_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Texturing.glsl ================================================ #ifndef NODE_UTILS_2_TEXTURING_GLSL #define NODE_UTILS_2_TEXTURING_GLSL #include "Procedural/Fractal_Noise.glsl" #include "Procedural/Cell_Noise.glsl" /* META GLOBAL @meta: category=Texturing; */ /* META @UV: label=UV; default=UV[0]; @Smooth_Interpolation: default=true; */ void Image(sampler2D Image, vec2 UV, bool Smooth_Interpolation, out vec4 Color, out vec2 Resolution) { Resolution = vec2(textureSize(Image, 0)); if(Smooth_Interpolation) { Color = texture(Image, UV); } else { ivec2 texel = ivec2(mod(UV * Resolution, Resolution)); Color = texelFetch(Image, texel, 0); } } /* META @UV: label=UV; default=UV[0]; @UV_Index: label=UV Index; */ void Normal_Map(sampler2D Texture, vec2 UV, int UV_Index, out vec3 Normal) { Normal = sample_normal_map(Texture, UV_Index, UV); } /* META @tex: label=Texture; @uv: label=UV; default=UV[0]; @dimensions: default=(1,1); @page: min=0.0; @origin: subtype=ENUM(Top Left,Bottom Left); */ vec4 Flipbook(sampler2D tex, vec2 uv, ivec2 dimensions, float page, int origin) { int f = int(floor(page)); float frac = fract(page); vec4 c = sample_flipbook(tex, uv, dimensions, f, origin == 0 ? true : false); if(frac == 0.0) { return c; } return mix( c, sample_flipbook(tex, uv, dimensions, f + 1, origin == 0 ? true : false), fract(page) ); } /* META @tex: label=Texture; @uv: label=UV; default=UV[0]; @flow: default="vec2(0.0)"; @samples: default=2; min=1 ; */ vec4 Flowmap(sampler2D tex, vec2 uv, vec2 flow, float progression, int samples) { return flowmap(tex, uv, flow, progression, samples); } /* META @Normal: subtype=Normal; default=NORMAL; */ void Matcap(sampler2D Matcap, vec3 Normal, out vec4 Color, out vec2 UV) { UV = matcap_uv(Normal); Color = sample_matcap(Matcap, Normal); } /* META @meta: label=HDRI; @Normal: subtype=Normal; default=NORMAL; */ void Hdri(sampler2D Hdri, vec3 Normal, out vec4 Color, out vec2 UV) { UV = hdri_uv(Normal); Color = sample_hdri(Hdri, Normal); } /* META @meta: subcategory=Noise; label=Infinite; @coord: subtype=Vector; default=POSITION; @scale: default=5.0; @detail: default=3.0; min=1.0; max=16.0; @balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; */ vec4 Noise(vec3 coord, float seed, float scale, float detail, float balance) { detail = min(detail, 16); return fractal_noise(vec4(coord * scale, seed), detail, balance); } /* META @meta: subcategory=Noise; label=Tiled; @coord: subtype=Vector; default=POSITION; @scale: default=5.0; @detail: default=3.0; min=1.0; max=16.0; @balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; @tile_size: subtype=Vector; default=ivec4(5); min=2; */ vec4 Noise_Tiled(vec3 coord, float seed, float scale, float detail, float balance, ivec4 tile_size) { detail = min(detail, 16); tile_size = max(ivec4(2), tile_size); return fractal_noise(vec4(coord * scale, seed), detail, balance, tile_size); } /* META @meta: subcategory=Voronoi; label=Infinite; @coord: subtype=Vector; default=POSITION; @scale: default=5.0; */ void Voronoi( vec3 coord, float seed, float scale, out vec4 cell_color, out vec4 cell_position, out float cell_distance ) { CellNoiseResult result = cell_noise(vec4(coord * scale, seed)); cell_color = result.cell_color; cell_position = result.cell_position; cell_distance = result.cell_distance; } /* META @meta: subcategory=Voronoi; label=Tiled; @coord: subtype=Vector; default=POSITION; @scale: default=5.0; @tile_size: subtype=Vector; default=ivec4(5); min=2; */ void Voronoi_Tiled( vec3 coord, float seed, float scale, ivec4 tile_size, out vec4 cell_color, out vec4 cell_position, out float cell_distance ) { tile_size = max(ivec4(2), tile_size); CellNoiseResult result = cell_noise(vec4(coord * scale, seed), tile_size); cell_color = result.cell_color; cell_position = result.cell_position; cell_distance = result.cell_distance; } #include "Procedural/Bayer.glsl" /* META @size: subtype=ENUM(2x2,3x3,4x4,8x8); default=2; @texel: default=vec2(screen_pixel()); */ float bayer_pattern(int size, vec2 texel) { switch(size) { case 0: return bayer_2x2(ivec2(texel)); case 1: return bayer_3x3(ivec2(texel)); case 2: return bayer_4x4(ivec2(texel)); case 3: return bayer_8x8(ivec2(texel)); } return 0.0; } /* META @meta: subcategory=Gradient; label=Linear; @value: default=UV[0].x; */ float Linear_Gradient(float value) { return clamp(value, 0.0, 1.0); } /* META @meta: subcategory=Gradient; label=Quadratic; @value: default=UV[0].x; */ float Quadratic_Gradient(float value) { return clamp(value*value, 0.0, 1.0); } /* META @meta: subcategory=Gradient; label=Easing; @value: default=UV[0].x; */ float Easing_Gradient(float value) { float r = clamp(value, 0.0, 1.0); float t = r*r; return (3.0 * t - 2.0 * t * r); } /* META @meta: subcategory=Gradient; label=Diagonal; @value: label=UV; default=UV[0]; */ float Diagonal_Gradient(vec2 value) { return (value.x + value.y) * 0.5; } /* META @meta: subcategory=Gradient; label=Spherical; @value: label=Vector; subtype=Vector; default=POSITION; */ float Spherical_Gradient(vec3 value) { return max(0.999999 - length(value), 0.0); } /* META @meta: subcategory=Gradient; label=Quadratic Sphere; @value: label=Vector; subtype=Vector; default=POSITION; */ float Quadratic_Sphere_Gradient(vec3 value) { return pow(Spherical_Gradient(value), 2.0); } /* META @meta: subcategory=Gradient; label=Radial; @value: label=UV; default=UV[0]; */ float Radial_Gradient(vec2 value) { return atan(value.x, value.y) / (M_PI * 2) + 0.5; } /* META @meta: label=Wave; @mode: subtype=ENUM(Sine,Saw,Triangle); @value: label=Coord; default=UV[0].x; @scale: default=5.0;*/ float Wave_Texture(int mode, float value, float scale, float phase) { switch(mode) { case 0: value = value * scale * M_PI * 2.0 + phase; return sin(value - PI/2.0) * 0.5 + 0.5; case 1: value = value * scale + phase; return fract(value); case 2: value = value * scale + phase; return 1.0 - (abs(fract(value) - 0.5) * 2.0); } } #endif //NODE_UTILS_2_TEXTURING_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Vec2.glsl ================================================ #ifndef NODE_UTILS_2_VEC2_GLSL #define NODE_UTILS_2_VEC2_GLSL /* META GLOBAL @meta: category=Math; subcategory=Vector 2D; */ /* META @meta: label=Add; */ vec2 Vec2_add(vec2 a, vec2 b){ return a+b; } /* META @meta: label=Subtract; */ vec2 Vec2_subtract(vec2 a, vec2 b){ return a-b; } /* META @meta: label=Multiply; */ vec2 Vec2_multiply(vec2 a, vec2 b){ return a*b; } /* META @meta: label=Divide; */ vec2 Vec2_divide(vec2 a, vec2 b){ return a/b; } /* META @meta: label=Scale; */ vec2 Vec2_scale(vec2 a, float fac){ return a*fac; } /*META @meta: label=Map Range; @clamped: default=true; @a: label=UV; default = 'vec2(0.5)'; @from_min: default = vec2(0.0); @from_max: default = vec2(1.0); @to_min: default = vec2(0.0); @to_max: default = vec2(1.0); */ vec2 Vec2_map_range(bool clamped, vec2 a, vec2 from_min, vec2 from_max, vec2 to_min, vec2 to_max) { if(clamped) { return map_range_clamped(a, from_min, from_max, to_min, to_max); } else { return map_range(a, from_min, from_max, to_min, to_max); } } /* META @meta: label=Modulo; */ vec2 Vec2_modulo(vec2 a, vec2 b){ return mod(a,b); } /* META @meta: label=Power; */ vec2 Vec2_pow(vec2 a, vec2 b){ return pow(a, b); } /* META @meta: label=Square Root; */ vec2 Vec2_sqrt(vec2 a){ return sqrt(a); } /* META @meta: label=Distort; */ vec2 Vec2_distort(vec2 a, vec2 b, float fac) { return distort(a,b,fac); } /* META @meta: label=Round; */ vec2 Vec2_round(vec2 a){ return round(a); } /* META @meta: label=Fraction; */ vec2 Vec2_fract(vec2 a){ return fract(a); } /* META @meta: label=Floor; */ vec2 Vec2_floor(vec2 a){ return floor(a); } /* META @meta: label=Ceil; */ vec2 Vec2_ceil(vec2 a){ return ceil(a); } /* META @meta: label=Snap; */ vec2 Vec2_snap(vec2 a, vec2 b){ return snap(a,b);} /* META @meta: label=Clamp; @b: label=Min; @c: label=Max; */ vec2 Vec2_clamp(vec2 a, vec2 b, vec2 c){ return clamp(a, b, c); } /* META @meta: label=Sign; */ vec2 Vec2_sign(vec2 a){ return sign(a); } /* META @meta: label=Absolute; */ vec2 Vec2_abs(vec2 a){ return abs(a); } /* META @meta: label=Min; */ vec2 Vec2_min(vec2 a, vec2 b){ return min(a,b); } /* META @meta: label=Max; */ vec2 Vec2_max(vec2 a, vec2 b){ return max(a,b); } /* META @meta: label=Mix 2D; @c: label=Factor; */ vec2 Vec2_mix(vec2 a, vec2 b, vec2 c){ return safe_mix(a,b,c); } /* META @meta: label=Mix; */ vec2 Vec2_mix_float(vec2 a, vec2 b, float fac){ return safe_mix(a,b,fac); } /* META @meta: label=Normalize; */ vec2 Vec2_normalize(vec2 a){ return a != vec2(0) ? normalize(a) : vec2(0); } /* META @meta: label=Length; */ float Vec2_length(vec2 a){ return a != vec2(0) ? length(a) : 0; } /* META @meta: label=Distance; */ float Vec2_distance(vec2 a, vec2 b){ return a != b ? distance(a,b) : 0; } /* META @meta: label=Dot Product; */ float Vec2_dot_product(vec2 a, vec2 b){ return dot(a,b); } /* META @meta: label=Sine; */ vec2 Vec2_sin(vec2 a) { return sin(a); } /* META @meta: label=Cosine; */ vec2 Vec2_cos(vec2 a) { return cos(a); } /* META @meta: label=Tangent; */ vec2 Vec2_tan(vec2 a) { return tan(a); } /* META @meta: label=Rotate; */ vec2 Vec2_rotate(vec2 a, float angle) { return rotate_2d(a, angle); } /* META @meta: label=Angle; */ float Vec2_angle(vec2 a, vec2 b) { return vector_angle(a, b); } /* META @meta: label=Equal; */ bool Vec2_equal(vec2 a, vec2 b){ return a == b; } /* META @meta: label=Not Equal; */ bool Vec2_not_equal(vec2 a, vec2 b){ return a != b; } /* META @meta: label=If Else; @a: label=If True; @b: label=If False; */ vec2 Vec2_if_else(bool condition, vec2 a, vec2 b){ return condition ? a : b; } /* META @meta: label=Combine; */ vec2 Vec2_combine(float x, float y) { return vec2(x,y);} /* META @meta: label=Separate; */ void Vec2_separate(vec2 a, out float x, out float y){ x=a.x; y=a.y; } #endif //NODE_UTILS_2_VEC2_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Vec3.glsl ================================================ #ifndef NODE_UTILS_2_VEC3_GLSL #define NODE_UTILS_2_VEC3_GLSL /* META GLOBAL @meta: category=Math; subcategory=Vector 3D; */ /*META @meta: label=Add; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_add(vec3 a, vec3 b){ return a+b; } /*META @meta: label=Subtract; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_subtract(vec3 a, vec3 b){ return a-b; } /*META @meta: label=Multiply; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_multiply(vec3 a, vec3 b){ return a*b; } /*META @meta: label=Divide; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_divide(vec3 a, vec3 b){ return a/b; } /*META @meta: label=Scale; @a: subtype=Vector;*/ vec3 Vec3_scale(vec3 a, float fac){ return a*fac; } /*META @meta: label=Map Range; @clamped: default=true; @a: label=Vector; default = 'vec3(0.5)'; @from_min: subtype=Vector; default = vec3(0.0); @from_max: subtype=Vector; default = vec3(1.0); @to_min: subtype=Vector; default = vec3(0.0); @to_max: subtype=Vector; default = vec3(1.0); */ vec3 Vec3_map_range(bool clamped, vec3 a, vec3 from_min, vec3 from_max, vec3 to_min, vec3 to_max) { if(clamped) { return map_range_clamped(a, from_min, from_max, to_min, to_max); } else { return map_range(a, from_min, from_max, to_min, to_max); } } /*META @meta: label=Modulo; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_modulo(vec3 a, vec3 b){ return mod(a,b); } /*META @meta: label=Power; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_pow(vec3 a, vec3 b){ return pow(a, b); } /*META @meta: label=Square Root; @a: subtype=Vector;*/ vec3 Vec3_sqrt(vec3 a){ return sqrt(a); } /*META @meta: label=Distort; @a: subtype=Vector; @b: subtype=Vector; */ vec3 Vec3_distort(vec3 a, vec3 b, float fac) { return distort(a,b,fac); } /*META @meta: label=Round; @a: subtype=Vector;*/ vec3 Vec3_round(vec3 a){ return round(a); } /*META @meta: label=Fraction; @a: subtype=Vector;*/ vec3 Vec3_fract(vec3 a){ return fract(a); } /*META @meta: label=Floor; @a: subtype=Vector;*/ vec3 Vec3_floor(vec3 a){ return floor(a); } /*META @meta: label=Ceil; @a: subtype=Vector;*/ vec3 Vec3_ceil(vec3 a){ return ceil(a); } /*META @meta: label=Snap; @a: subtype=Vector; @b: subtype=Vector; */ vec3 Vec3_snap(vec3 a, vec3 b){ return snap(a,b); } /*META @meta: label=Clamp; @a: subtype=Vector; @b: label=Min; subtype=Vector; @c: label=Max; subtype=Vector;*/ vec3 Vec3_clamp(vec3 a, vec3 b, vec3 c){ return clamp(a, b, c); } /*META @meta: label=Sign; @a: subtype=Vector;*/ vec3 Vec3_sign(vec3 a){ return sign(a); } /*META @meta: label=Absolute; @a: subtype=Vector;*/ vec3 Vec3_abs(vec3 a){ return abs(a); } /*META @meta: label=Min; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_min(vec3 a, vec3 b){ return min(a,b); } /*META @meta: label=Max; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_max(vec3 a, vec3 b){ return max(a,b); } /*META @meta: label=Mix 3D; @a: subtype=Vector; @b: subtype=Vector; @c: label=Factor; subtype=Vector;*/ vec3 Vec3_mix(vec3 a, vec3 b, vec3 c){ return safe_mix(a,b,c); } /*META @meta: label=Mix; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_mix_float(vec3 a, vec3 b, float fac){ return safe_mix(a,b,fac); } /*META @meta: label=Normalize; @a: subtype=Vector;*/ vec3 Vec3_normalize(vec3 a){ return a != vec3(0) ? normalize(a) : vec3(0); } /*META @meta: label=Length; @a: subtype=Vector;*/ float Vec3_length(vec3 a){ return a != vec3(0) ? length(a) : 0; } /*META @meta: label=Distance; @a: subtype=Vector; @b: subtype=Vector;*/ float Vec3_distance(vec3 a, vec3 b){ return a != b ? distance(a,b) : 0; } /*META @meta: label=Dot Product; @a: subtype=Vector; @b: subtype=Vector;*/ float Vec3_dot_product(vec3 a, vec3 b){ return dot(a,b); } /*META @meta: label=Cross Product; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_cross_product(vec3 a, vec3 b){ return cross(a,b); } /*META @meta: label=Reflect; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_reflect(vec3 a, vec3 b){ return reflect(a,b); } /*META @meta: label=Refract; @a: subtype=Vector; @b: subtype=Vector;*/ vec3 Vec3_refract(vec3 a, vec3 b, float ior){ return refract(a,normalize(b),ior); } /*META @meta: label=Faceforward; @a: subtype=Vector; @b: subtype=Vector; @c: subtype=Vector; */ vec3 Vec3_faceforward(vec3 a, vec3 b, vec3 c){ return faceforward(a,b,c); } /* META @meta: label=Sine; @a: subtype=Vector; */ vec3 Vec3_sin(vec3 a) { return sin(a); } /* META @meta: label=Cosine; @a: subtype=Vector; */ vec3 Vec3_cos(vec3 a) { return cos(a); } /* META @meta: label=Tangent; @a: subtype=Vector; */ vec3 Vec3_tan(vec3 a) { return tan(a); } /* META @meta: label=Rotate Euler; @a: subtype=Vector; @euler: subtype=Euler; */ vec3 Vec3_rotate_euler(vec3 a, vec3 euler, bool invert) { mat4 m = mat4_rotation_from_euler(euler); if(invert) { m = inverse(m); } return transform_point(m, a); } /* META @meta: label=Rotate Axis Angle; @a: subtype=Vector; @b: label=Axis; subtype=Vector; default=vec3(0.0, 0.0, 1.0); */ vec3 Vec3_rotate_axis_angle(vec3 a, vec3 b, float angle) { mat4 m = mat4_rotation_from_quaternion(quaternion_from_axis_angle(b, angle)); return transform_point(m, a); } /* META @meta: label=Angle; @a: subtype=Vector; @b: subtype=Vector; */ float Vec3_angle(vec3 a, vec3 b) { return vector_angle(a, b); } /*META @meta: label=Equal; @a: subtype=Vector; @b: subtype=Vector;*/ bool Vec3_equal(vec3 a, vec3 b){ return a == b; } /*META @meta: label=Not Equal; @a: subtype=Vector; @b: subtype=Vector;*/ bool Vec3_not_equal(vec3 a, vec3 b){ return a != b; } /*META @meta: label=If Else; @a: label=If True; subtype=Vector; @b: label=If False; subtype=Vector;*/ vec3 Vec3_if_else(bool condition, vec3 a, vec3 b){ return condition ? a : b; } /* META @meta: label=Combine; */ vec3 Vec3_combine(float x, float y, float z) { return vec3(x,y,z);} /*META @meta: label=Separate; @a: subtype=Vector;*/ void Vec3_separate(vec3 a, out float x, out float y, out float z){ x=a.x; y=a.y; z=a.z; } #endif //NODE_UTILS_2_VEC3_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Vec4.glsl ================================================ #ifndef NODE_UTILS_2_VEC4_GLSL #define NODE_UTILS_2_VEC4_GLSL /* META GLOBAL @meta: category=Math; subcategory=Vector 4D; */ /*META @meta: label=Add; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_add(vec4 a, vec4 b){ return a+b; } /*META @meta: label=Subtract; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_subtract(vec4 a, vec4 b){ return a-b; } /*META @meta: label=Multiply; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_multiply(vec4 a, vec4 b){ return a*b; } /*META @meta: label=Divide; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_divide(vec4 a, vec4 b){ return a/b; } /*META @meta: label=Scale; @a: subtype=Vector;*/ vec4 Vec4_scale(vec4 a, float fac){ return a*fac; } /*META @meta: label=Map Range; @clamped: default=true; @a: label=Vector; default = 'vec4(0.5)'; @from_min: subtype=Vector; default = vec4(0.0); @from_max: subtype=Vector; default = vec4(1.0); @to_min: subtype=Vector; default = vec4(0.0); @to_max: subtype=Vector; default = vec4(1.0); */ vec4 Vec4_map_range(bool clamped, vec4 a, vec4 from_min, vec4 from_max, vec4 to_min, vec4 to_max) { if(clamped) { return map_range_clamped(a, from_min, from_max, to_min, to_max); } else { return map_range(a, from_min, from_max, to_min, to_max); } } /*META @meta: label=Modulo; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_modulo(vec4 a, vec4 b){ return mod(a,b); } /*META @meta: label=Power; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_pow(vec4 a, vec4 b){ return pow(a, b); } /*META @meta: label=Square Root; @a: subtype=Vector;*/ vec4 Vec4_sqrt(vec4 a){ return sqrt(a); } /*META @meta: label=Distort; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_distort(vec4 a, vec4 b, float fac) { return distort(a,b,fac); } /*META @meta: label=Round; @a: subtype=Vector;*/ vec4 Vec4_round(vec4 a){ return round(a); } /*META @meta: label=Fraction; @a: subtype=Vector;*/ vec4 Vec4_fract(vec4 a){ return fract(a); } /*META @meta: label=Floor; @a: subtype=Vector;*/ vec4 Vec4_floor(vec4 a){ return floor(a); } /*META @meta: label=Ceil; @a: subtype=Vector;*/ vec4 Vec4_ceil(vec4 a){ return ceil(a); } /*META @meta: label=Clamp; @a: subtype=Vector; @b: label=Min; subtype=Vector; @c: label=Max; subtype=Vector;*/ vec4 Vec4_clamp(vec4 a, vec4 b, vec4 c){ return clamp(a, b, c); } /*META @meta: label=Sign; @a: subtype=Vector;*/ vec4 Vec4_sign(vec4 a){ return sign(a); } /*META @meta: label=Absolute; @a: subtype=Vector;*/ vec4 Vec4_abs(vec4 a){ return abs(a); } /*META @meta: label=Min; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_min(vec4 a, vec4 b){ return min(a,b); } /*META @meta: label=Max; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_max(vec4 a, vec4 b){ return max(a,b); } /*META @meta: label=Mix 4D; @a: subtype=Vector; @b: subtype=Vector; @c: label=Factor; subtype=Vector;*/ vec4 Vec4_mix(vec4 a, vec4 b, vec4 c){ return safe_mix(a,b,c); } /*META @meta: label=Mix; @a: subtype=Vector; @b: subtype=Vector;*/ vec4 Vec4_mix_float(vec4 a, vec4 b, float fac){ return safe_mix(a,b,fac); } /*META @meta: label=Normalize; @a: subtype=Vector;*/ vec4 Vec4_normalize(vec4 a){ return a != vec4(0) ? normalize(a) : vec4(0); } /*META @meta: label=Length; @a: subtype=Vector;*/ float Vec4_length(vec4 a){ return a != vec4(0) ? length(a) : 0; } /*META @meta: label=Distance; @a: subtype=Vector; @b: subtype=Vector;*/ float Vec4_distance(vec4 a, vec4 b){ return a != b ? distance(a,b) : 0; } /*META @meta: label=Dot Product; @a: subtype=Vector; @b: subtype=Vector;*/ float Vec4_dot_product(vec4 a, vec4 b){ return dot(a,b); } /* META @meta: label=Sine; @a: subtype=Vector; */ vec4 Vec4_sin(vec4 a) { return sin(a); } /* META @meta: label=Cosine; @a: subtype=Vector; */ vec4 Vec4_cos(vec4 a) { return cos(a); } /* META @meta: label=Tangent; @a: subtype=Vector; */ vec4 Vec4_tan(vec4 a) { return tan(a); } /* META @meta: label=Angle; @a: subtype=Vector; @b: subtype=Vector; */ float Vec4_angle(vec4 a, vec4 b) { return vector_angle(a, b); } /*META @meta: label=Equal; @a: subtype=Vector; @b: subtype=Vector;*/ bool Vec4_equal(vec4 a, vec4 b){ return a == b; } /*META @meta: label=Not Equal; @a: subtype=Vector; @b: subtype=Vector;*/ bool Vec4_not_equal(vec4 a, vec4 b){ return a != b; } /*META @a: label=If True; subtype=Vector; @b: label=If False; subtype=Vector;*/ vec4 Vec4_if_else(bool condition, vec4 a, vec4 b){ return condition ? a : b; } /*META @meta: label=Combine; */ vec4 Vec4_combine(float r, float g, float b, float a) { return vec4(r,g,b,a);} /* META @meta: label=Combine Color; @c: subtype=Color; @a: subtype=Slider; min=0.0; max=1.0; default=1.0;*/ vec4 Vec4_combine_color(vec3 c, float a){ return vec4(c, a); } /*META @meta: label=Separate; @a: subtype=Vector; @w: label=A;*/ void Vec4_separate(vec4 a, out float r, out float g, out float b, out float w){ r=a.r; g=a.g; b=a.b; w=a.a; } /*META @meta: label=Separate Color; @a: subtype=Color; @w: label=A; */ void Vec4_separate_color(vec4 a, out vec3 c, out float w){ c=a.xyz; w=a.a; } #endif //NODE_UTILS_2_VEC4_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/Vector.glsl ================================================ #ifndef NODE_UTILS_2_VECTOR_GLSL #define NODE_UTILS_2_VECTOR_GLSL /* META GLOBAL @meta: category=Vector; */ /* META @Type: subtype=ENUM(Point,Vector,Normal); @From: subtype=ENUM(Object,World,Camera); @To: subtype=ENUM(Object,World,Camera,Screen); @Vector: subtype=Vector; */ void Transform( int Type, int From, int To, inout vec3 Vector ) { mat4 TRANSFORM_CONVERSION_TABLE[3*3] = mat4[3*3]( mat4(1), MODEL, CAMERA*MODEL, inverse(MODEL), mat4(1), CAMERA, inverse(CAMERA*MODEL), inverse(CAMERA), mat4(1) ); mat4 m = TRANSFORM_CONVERSION_TABLE[clamp(From,0,2)*3 + clamp(To,0,2)]; bool project = To == 3; if(Type==0)//Point { if(project) { m = PROJECTION * m; Vector = project_point_to_screen_coordinates(m, Vector); } else { Vector = transform_point(m, Vector); } } else { if(Type==1)//Vector { Vector = transform_direction(m, Vector); } if(Type==2)//Normal { Vector = transform_normal(m, Vector); } if (project) { Vector = camera_direction_to_screen_space(Vector); } } } /* META @meta: subcategory=Mapping; label=Point; @vector: subtype=Vector; default='vec3(0.0)'; @location: subtype=Vector; @rotation: subtype=Euler; @scale: subtype=Vector; default=vec3(1.0); */ vec3 mapping_point(vec3 vector, vec3 location, vec3 rotation, vec3 scale) { vec3 result = vector * scale; result = transform_point(mat4_rotation_from_euler(rotation), result); return result + location; } /* META @meta: subcategory=Mapping; label=Texture; @vector: subtype=Vector; default='vec3(0.0)'; @location: subtype=Vector; @rotation: subtype=Euler; @scale: subtype=Vector; default=vec3(1.0); */ vec3 mapping_texture(vec3 vector, vec3 location, vec3 rotation, vec3 scale) { vec3 result = vector - location; result = transform_point(inverse(mat4_rotation_from_euler(rotation)), result); return result / scale; } /* META @meta: subcategory=Mapping; label=Vector; @vector: subtype=Vector; default='vec3(0.0)'; @rotation: subtype=Euler; @scale: subtype=Vector; default=vec3(1.0); */ vec3 mapping_vector(vec3 vector, vec3 rotation, vec3 scale) { return transform_direction(mat4_rotation_from_euler(rotation), vector * scale); } /* META @meta: subcategory=Mapping; label=Normal; @vector: subtype=Vector; default='vec3(0.0)'; @rotation: subtype=Euler; @scale: subtype=Vector; default=vec3(1.0); */ vec3 mapping_normal(vec3 vector, vec3 rotation, vec3 scale) { return normalize(mapping_vector(vector, rotation, scale)); } #endif // COMMON_VECTOR_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/conversion.glsl ================================================ #ifndef NODE_UTILS_2_CONVERSION_GLSL #define NODE_UTILS_2_CONVERSION_GLSL /* META GLOBAL @meta: internal = true; */ //float float float_from_int(int i) { return float(i); } float float_from_uint(uint u) { return float(u); } float float_from_bool(bool b) { return float(b); } float float_from_vec2(vec2 v) { return (v.x + v.y)/2.0; } float float_from_vec3(vec3 v) { return (v.x + v.y + v.z)/3.0; } float float_from_vec4(vec4 c) { return (c.x + c.y + c.z)/3.0 * c.a; } //int int int_from_float(float f) { return int(f); } int int_from_uint(uint u) { return int(u); } int int_from_bool(bool b) { return int(b); } int int_from_vec2(vec2 v) { return int(float_from_vec2(v)); } int int_from_vec3(vec3 v) { return int(float_from_vec3(v)); } int int_from_vec4(vec4 c) { return int(float_from_vec4(c)); } //uint uint uint_from_float(float f) { return uint(f); } uint uint_from_int(int i) { return uint(i); } uint uint_from_bool(bool b) { return uint(b); } uint uint_from_vec2(vec2 v) { return uint(float_from_vec2(v)); } uint uint_from_vec3(vec3 v) { return uint(float_from_vec3(v)); } uint uint_from_vec4(vec4 c) { return uint(float_from_vec4(c)); } //bool bool bool_from_float(float f) { return bool(f); } bool bool_from_int(int i) { return bool(i); } bool bool_from_uint(uint u) { return bool(u); } bool bool_from_vec2(vec2 v) { return bool(float_from_vec2(v)); } bool bool_from_vec3(vec3 v) { return bool(float_from_vec3(v)); } bool bool_from_vec4(vec4 c) { return bool(float_from_vec4(c)); } //vec2 vec2 vec2_from_float(float f) { return vec2(f); } vec2 vec2_from_int(int i) { return vec2(i); } vec2 vec2_from_uint(uint u) { return vec2(u); } vec2 vec2_from_bool(bool b) { return vec2(b); } vec2 vec2_from_vec3(vec3 v) { return vec2(v.xy); } vec2 vec2_from_vec4(vec4 v) { return vec2(v.xy); } vec2 vec2_from_ivec2(ivec2 v) { return vec2(v); } vec2 vec2_from_ivec3(ivec3 v) { return vec2(v.xy); } vec2 vec2_from_ivec4(ivec4 v) { return vec2(v.xy); } vec2 vec2_from_uvec2(uvec2 v) { return vec2(v); } vec2 vec2_from_uvec3(uvec3 v) { return vec2(v.xy); } vec2 vec2_from_uvec4(uvec4 v) { return vec2(v.xy); } vec2 vec2_from_bvec2(bvec2 v) { return vec2(v); } vec2 vec2_from_bvec3(bvec3 v) { return vec2(v.xy); } vec2 vec2_from_bvec4(bvec4 v) { return vec2(v.xy); } //vec3 vec3 vec3_from_float(float f) { return vec3(f); } vec3 vec3_from_int(int i) { return vec3(i); } vec3 vec3_from_uint(uint u) { return vec3(u); } vec3 vec3_from_bool(bool b) { return vec3(b); } vec3 vec3_from_vec2(vec2 v) { return vec3(v, 0); } vec3 vec3_from_vec4(vec4 v) { return vec3(v.xyz); } vec3 vec3_from_ivec2(ivec2 v) { return vec3(v, 0); } vec3 vec3_from_ivec3(ivec3 v) { return vec3(v); } vec3 vec3_from_ivec4(ivec4 v) { return vec3(v.xyz); } vec3 vec3_from_uvec2(uvec2 v) { return vec3(v, 0); } vec3 vec3_from_uvec3(uvec3 v) { return vec3(v); } vec3 vec3_from_uvec4(uvec4 v) { return vec3(v.xyz); } vec3 vec3_from_bvec2(bvec2 v) { return vec3(v, 0); } vec3 vec3_from_bvec3(bvec3 v) { return vec3(v); } vec3 vec3_from_bvec4(bvec4 v) { return vec3(v.xyz); } //vec4 vec4 vec4_from_float(float f) { return vec4(f, f, f, 1); } vec4 vec4_from_int(int i) { return vec4(i, i, i, 1); } vec4 vec4_from_uint(uint u) { return vec4(u, u, u, 1); } vec4 vec4_from_bool(bool b) { return vec4(b, b, b, 1); } vec4 vec4_from_vec2(vec2 v) { return vec4(v, 0, 1); } vec4 vec4_from_vec3(vec3 v) { return vec4(v, 1); } vec4 vec4_from_ivec2(ivec2 v) { return vec4(v, 0, 1); } vec4 vec4_from_ivec3(ivec3 v) { return vec4(v, 1); } vec4 vec4_from_ivec4(ivec4 v) { return vec4(v); } vec4 vec4_from_uvec2(uvec2 v) { return vec4(v, 0, 1); } vec4 vec4_from_uvec3(uvec3 v) { return vec4(v, 1); } vec4 vec4_from_uvec4(uvec4 v) { return vec4(v); } vec4 vec4_from_bvec2(bvec2 v) { return vec4(v, 0, 1); } vec4 vec4_from_bvec3(bvec3 v) { return vec4(v, 1); } vec4 vec4_from_bvec4(bvec4 v) { return vec4(v); } //ivec2 ivec2 ivec2_from_float(float f) { return ivec2(f); } ivec2 ivec2_from_int(int i) { return ivec2(i); } ivec2 ivec2_from_uint(uint u) { return ivec2(u); } ivec2 ivec2_from_bool(bool b) { return ivec2(b); } ivec2 ivec2_from_vec2(vec2 v) { return ivec2(v); } ivec2 ivec2_from_vec3(vec3 v) { return ivec2(v.xy); } ivec2 ivec2_from_vec4(vec4 v) { return ivec2(v.xy); } ivec2 ivec2_from_ivec3(ivec3 v) { return ivec2(v.xy); } ivec2 ivec2_from_ivec4(ivec4 v) { return ivec2(v.xy); } ivec2 ivec2_from_uvec2(uvec2 v) { return ivec2(v); } ivec2 ivec2_from_uvec3(uvec3 v) { return ivec2(v.xy); } ivec2 ivec2_from_uvec4(uvec4 v) { return ivec2(v.xy); } ivec2 ivec2_from_bvec2(bvec2 v) { return ivec2(v); } ivec2 ivec2_from_bvec3(bvec3 v) { return ivec2(v.xy); } ivec2 ivec2_from_bvec4(bvec4 v) { return ivec2(v.xy); } //ivec3 ivec3 ivec3_from_float(float f) { return ivec3(f); } ivec3 ivec3_from_int(int i) { return ivec3(i); } ivec3 ivec3_from_uint(uint u) { return ivec3(u); } ivec3 ivec3_from_bool(bool b) { return ivec3(b); } ivec3 ivec3_from_vec2(vec2 v) { return ivec3(v, 0); } ivec3 ivec3_from_vec3(vec3 v) { return ivec3(v); } ivec3 ivec3_from_vec4(vec4 v) { return ivec3(v.xyz); } ivec3 ivec3_from_ivec2(ivec2 v) { return ivec3(v, 0); } ivec3 ivec3_from_ivec4(ivec4 v) { return ivec3(v.xyz); } ivec3 ivec3_from_uvec2(uvec2 v) { return ivec3(v, 0); } ivec3 ivec3_from_uvec3(uvec3 v) { return ivec3(v); } ivec3 ivec3_from_uvec4(uvec4 v) { return ivec3(v.xyz); } ivec3 ivec3_from_bvec2(bvec2 v) { return ivec3(v, 0); } ivec3 ivec3_from_bvec3(bvec3 v) { return ivec3(v); } ivec3 ivec3_from_bvec4(bvec4 v) { return ivec3(v.xyz); } //ivec4 ivec4 ivec4_from_float(float f) { return ivec4(f, f, f, 1); } ivec4 ivec4_from_int(int i) { return ivec4(i, i, i, 1); } ivec4 ivec4_from_uint(uint u) { return ivec4(u, u, u, 1); } ivec4 ivec4_from_bool(bool b) { return ivec4(b, b, b, 1); } ivec4 ivec4_from_vec2(vec2 v) { return ivec4(v, 0, 1); } ivec4 ivec4_from_vec3(vec3 v) { return ivec4(v, 1); } ivec4 ivec4_from_vec4(vec4 v) { return ivec4(v); } ivec4 ivec4_from_ivec2(ivec2 v) { return ivec4(v, 0, 1); } ivec4 ivec4_from_ivec3(ivec3 v) { return ivec4(v, 1); } ivec4 ivec4_from_uvec2(uvec2 v) { return ivec4(v, 0, 1); } ivec4 ivec4_from_uvec3(uvec3 v) { return ivec4(v, 1); } ivec4 ivec4_from_uvec4(uvec4 v) { return ivec4(v); } ivec4 ivec4_from_bvec2(bvec2 v) { return ivec4(v, 0, 1); } ivec4 ivec4_from_bvec3(bvec3 v) { return ivec4(v, 1); } ivec4 ivec4_from_bvec4(bvec4 v) { return ivec4(v); } //uvec2 uvec2 uvec2_from_float(float f) { return uvec2(f); } uvec2 uvec2_from_int(int i) { return uvec2(i); } uvec2 uvec2_from_uint(uint u) { return uvec2(u); } uvec2 uvec2_from_bool(bool b) { return uvec2(b); } uvec2 uvec2_from_vec2(vec2 v) { return uvec2(v); } uvec2 uvec2_from_vec3(vec3 v) { return uvec2(v.xy); } uvec2 uvec2_from_vec4(vec4 v) { return uvec2(v.xy); } uvec2 uvec2_from_ivec2(ivec2 v) { return uvec2(v); } uvec2 uvec2_from_ivec3(ivec3 v) { return uvec2(v.xy); } uvec2 uvec2_from_ivec4(ivec4 v) { return uvec2(v.xy); } uvec2 uvec2_from_uvec3(uvec3 v) { return uvec2(v.xy); } uvec2 uvec2_from_uvec4(uvec4 v) { return uvec2(v.xy); } uvec2 uvec2_from_bvec2(bvec2 v) { return uvec2(v); } uvec2 uvec2_from_bvec3(bvec3 v) { return uvec2(v.xy); } uvec2 uvec2_from_bvec4(bvec4 v) { return uvec2(v.xy); } //uvec3 uvec3 uvec3_from_float(float f) { return uvec3(f); } uvec3 uvec3_from_int(int i) { return uvec3(i); } uvec3 uvec3_from_uint(uint u) { return uvec3(u); } uvec3 uvec3_from_bool(bool b) { return uvec3(b); } uvec3 uvec3_from_vec2(vec2 v) { return uvec3(v, 0); } uvec3 uvec3_from_vec3(vec3 v) { return uvec3(v); } uvec3 uvec3_from_vec4(vec4 v) { return uvec3(v.xyz); } uvec3 uvec3_from_ivec2(ivec2 v) { return uvec3(v, 0); } uvec3 uvec3_from_ivec3(ivec3 v) { return uvec3(v); } uvec3 uvec3_from_ivec4(ivec4 v) { return uvec3(v.xyz); } uvec3 uvec3_from_uvec2(uvec2 v) { return uvec3(v, 0); } uvec3 uvec3_from_uvec4(uvec4 v) { return uvec3(v.xyz); } uvec3 uvec3_from_bvec2(bvec2 v) { return uvec3(v, 0); } uvec3 uvec3_from_bvec3(bvec3 v) { return uvec3(v); } uvec3 uvec3_from_bvec4(bvec4 v) { return uvec3(v.xyz); } //uvec4 uvec4 uvec4_from_float(float f) { return uvec4(f, f, f, 1); } uvec4 uvec4_from_int(int i) { return uvec4(i, i, i, 1); } uvec4 uvec4_from_uint(uint u) { return uvec4(u, u, u, 1); } uvec4 uvec4_from_bool(bool b) { return uvec4(b, b, b, 1); } uvec4 uvec4_from_vec2(vec2 v) { return uvec4(v, 0, 1); } uvec4 uvec4_from_vec3(vec3 v) { return uvec4(v, 1); } uvec4 uvec4_from_vec4(vec4 v) { return uvec4(v); } uvec4 uvec4_from_ivec2(ivec2 v) { return uvec4(v, 0, 1); } uvec4 uvec4_from_ivec3(ivec3 v) { return uvec4(v, 1); } uvec4 uvec4_from_ivec4(ivec4 v) { return uvec4(v); } uvec4 uvec4_from_uvec2(uvec2 v) { return uvec4(v, 0, 1); } uvec4 uvec4_from_uvec3(uvec3 v) { return uvec4(v, 1); } uvec4 uvec4_from_bvec2(bvec2 v) { return uvec4(v, 0, 1); } uvec4 uvec4_from_bvec3(bvec3 v) { return uvec4(v, 1); } uvec4 uvec4_from_bvec4(bvec4 v) { return uvec4(v); } //bvec2 bvec2 bvec2_from_float(float f) { return bvec2(f); } bvec2 bvec2_from_int(int i) { return bvec2(i); } bvec2 bvec2_from_uint(uint u) { return bvec2(u); } bvec2 bvec2_from_bool(bool b) { return bvec2(b); } bvec2 bvec2_from_vec2(vec2 v) { return bvec2(v); } bvec2 bvec2_from_vec3(vec3 v) { return bvec2(v.xy); } bvec2 bvec2_from_vec4(vec4 v) { return bvec2(v.xy); } bvec2 bvec2_from_ivec2(ivec2 v) { return bvec2(v); } bvec2 bvec2_from_ivec3(ivec3 v) { return bvec2(v.xy); } bvec2 bvec2_from_ivec4(ivec4 v) { return bvec2(v.xy); } bvec2 bvec2_from_uvec2(uvec2 v) { return bvec2(v); } bvec2 bvec2_from_uvec3(uvec3 v) { return bvec2(v.xy); } bvec2 bvec2_from_uvec4(uvec4 v) { return bvec2(v.xy); } bvec2 bvec2_from_bvec3(bvec3 v) { return bvec2(v.xy); } bvec2 bvec2_from_bvec4(bvec4 v) { return bvec2(v.xy); } //bvec3 bvec3 bvec3_from_float(float f) { return bvec3(f); } bvec3 bvec3_from_int(int i) { return bvec3(i); } bvec3 bvec3_from_uint(uint u) { return bvec3(u); } bvec3 bvec3_from_bool(bool b) { return bvec3(b); } bvec3 bvec3_from_vec2(vec2 v) { return bvec3(v, 0); } bvec3 bvec3_from_vec3(vec3 v) { return bvec3(v); } bvec3 bvec3_from_vec4(vec4 v) { return bvec3(v.xyz); } bvec3 bvec3_from_ivec2(ivec2 v) { return bvec3(v, 0); } bvec3 bvec3_from_ivec3(ivec3 v) { return bvec3(v); } bvec3 bvec3_from_ivec4(ivec4 v) { return bvec3(v.xyz); } bvec3 bvec3_from_uvec2(uvec2 v) { return bvec3(v, 0); } bvec3 bvec3_from_uvec3(uvec3 v) { return bvec3(v); } bvec3 bvec3_from_uvec4(uvec4 v) { return bvec3(v.xyz); } bvec3 bvec3_from_bvec2(bvec2 v) { return bvec3(v, 0); } bvec3 bvec3_from_bvec4(bvec4 v) { return bvec3(v.xyz); } //bvec4 bvec4 bvec4_from_float(float f) { return bvec4(f); } bvec4 bvec4_from_int(int i) { return bvec4(i); } bvec4 bvec4_from_uint(uint u) { return bvec4(u); } bvec4 bvec4_from_bool(bool b) { return bvec4(b); } bvec4 bvec4_from_vec2(vec2 v) { return bvec4(v, 0, 0); } bvec4 bvec4_from_vec3(vec3 v) { return bvec4(v, 0); } bvec4 bvec4_from_vec4(vec4 v) { return bvec4(v); } bvec4 bvec4_from_ivec2(ivec2 v) { return bvec4(v, 0, 0); } bvec4 bvec4_from_ivec3(ivec3 v) { return bvec4(v, 0); } bvec4 bvec4_from_ivec4(ivec4 v) { return bvec4(v); } bvec4 bvec4_from_uvec2(uvec2 v) { return bvec4(v, 0, 0); } bvec4 bvec4_from_uvec3(uvec3 v) { return bvec4(v, 0); } bvec4 bvec4_from_uvec4(uvec4 v) { return bvec4(v); } bvec4 bvec4_from_bvec2(bvec2 v) { return bvec4(v, 0, 0); } bvec4 bvec4_from_bvec3(bvec3 v) { return bvec4(v, 0); } #endif //NODE_UTILS_2_CONVERSION_GLSL ================================================ FILE: Malt/Shaders/Node Utils 2/node_utils_2.glsl ================================================ #ifndef NODE_UTILS_2_GLSL #define NODE_UTILS_2_GLSL #include "Node Utils 2/conversion.glsl" #include "Node Utils 2/Bool.glsl" #include "Node Utils 2/Float.glsl" #include "Node Utils 2/Int.glsl" #include "Node Utils 2/Mat4.glsl" #include "Node Utils 2/Vec2.glsl" #include "Node Utils 2/Vec3.glsl" #include "Node Utils 2/Vec4.glsl" #include "Node Utils 2/Input.glsl" #include "Node Utils 2/Color.glsl" #include "Node Utils 2/Vector.glsl" #include "Node Utils 2/Parameters.glsl" #include "Node Utils 2/Texturing.glsl" #include "Node Utils 2/Filter.glsl" // Basic common API #include "Common.glsl" #include "Filters/AO.glsl" #include "Filters/Bevel.glsl" #include "Filters/Blur.glsl" #include "Filters/Sharpen.glsl" #include "Filters/Curvature.glsl" #include "Filters/Kuwahara.glsl" #include "Filters/Line.glsl" #include "Filters/StructureTensor.glsl" #include "Procedural/Noise.glsl" #include "Procedural/Cell_Noise.glsl" #include "Procedural/Fractal_Noise.glsl" #include "Shading/ShadingModels.glsl" #endif //NODE_UTILS_GLSL ================================================ FILE: Malt/Shaders/Passes/BlendTexture.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D blend_texture; layout (location = 0) out vec4 OUT_COLOR; void main() { PIXEL_SETUP_INPUT(); vec4 color = texture(blend_texture, UV[0]); color.rgb *= color.a; OUT_COLOR = color; } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/BlendTransparency.glsl ================================================ #include "Common.glsl" #include "Common/Color.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D IN_BACK[8]; uniform sampler2D IN_FRONT[8]; layout (location = 0) out vec4 OUT_RESULT_0; layout (location = 1) out vec4 OUT_RESULT_1; layout (location = 2) out vec4 OUT_RESULT_2; layout (location = 3) out vec4 OUT_RESULT_3; layout (location = 4) out vec4 OUT_RESULT_4; layout (location = 5) out vec4 OUT_RESULT_5; layout (location = 6) out vec4 OUT_RESULT_6; layout (location = 7) out vec4 OUT_RESULT_7; void main() { PIXEL_SETUP_INPUT(); ivec2 uv = ivec2(gl_FragCoord.xy); OUT_RESULT_0 = alpha_blend(texelFetch(IN_BACK[0], uv, 0), texelFetch(IN_FRONT[0], uv, 0)); OUT_RESULT_1 = alpha_blend(texelFetch(IN_BACK[1], uv, 0), texelFetch(IN_FRONT[1], uv, 0)); OUT_RESULT_2 = alpha_blend(texelFetch(IN_BACK[2], uv, 0), texelFetch(IN_FRONT[2], uv, 0)); OUT_RESULT_3 = alpha_blend(texelFetch(IN_BACK[3], uv, 0), texelFetch(IN_FRONT[3], uv, 0)); OUT_RESULT_4 = alpha_blend(texelFetch(IN_BACK[4], uv, 0), texelFetch(IN_FRONT[4], uv, 0)); OUT_RESULT_5 = alpha_blend(texelFetch(IN_BACK[5], uv, 0), texelFetch(IN_FRONT[5], uv, 0)); OUT_RESULT_6 = alpha_blend(texelFetch(IN_BACK[6], uv, 0), texelFetch(IN_FRONT[6], uv, 0)); OUT_RESULT_7 = alpha_blend(texelFetch(IN_BACK[7], uv, 0), texelFetch(IN_FRONT[7], uv, 0)); } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/CopyTextures.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D IN[8]; uniform sampler2D IN_DEPTH; layout (location = 0) out vec4 OUT_0; layout (location = 1) out vec4 OUT_1; layout (location = 2) out vec4 OUT_2; layout (location = 3) out vec4 OUT_3; layout (location = 4) out vec4 OUT_4; layout (location = 5) out vec4 OUT_5; layout (location = 6) out vec4 OUT_6; layout (location = 7) out vec4 OUT_7; void main() { PIXEL_SETUP_INPUT(); ivec2 uv = ivec2(gl_FragCoord.xy); OUT_0 = texelFetch(IN[0], uv, 0); OUT_1 = texelFetch(IN[1], uv, 0); OUT_2 = texelFetch(IN[2], uv, 0); OUT_3 = texelFetch(IN[3], uv, 0); OUT_4 = texelFetch(IN[4], uv, 0); OUT_5 = texelFetch(IN[5], uv, 0); OUT_6 = texelFetch(IN[6], uv, 0); OUT_7 = texelFetch(IN[7], uv, 0); gl_FragDepth = texelFetch(IN_DEPTH, uv, 0).x; } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/DepthToBlenderDepth.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D DEPTH_TEXTURE; uniform int DEPTH_CHANNEL; layout (location = 0) out vec4 OUT_RESULT; void main() { PIXEL_SETUP_INPUT(); float depth = texture(DEPTH_TEXTURE, UV[0])[DEPTH_CHANNEL]; vec3 camera = screen_to_camera(UV[0], depth); OUT_RESULT.r = -camera.z; } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/JumpFlood.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER #include "Filters/JumpFlood.glsl" uniform sampler2D input_texture; uniform float width; layout (location = 0) out vec4 OUT_RESULT; void main() { PIXEL_SETUP_INPUT(); vec2 uv = screen_uv(); OUT_RESULT = jump_flood(input_texture, uv, width, true); } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/LineComposite.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER #include "Filters/Line.glsl" layout (location = 0) out vec4 OUT_RESULT; uniform sampler2D color_texture; uniform sampler2D depth_texture; uniform int depth_channel; uniform usampler2D id_texture; uniform int id_channel; uniform sampler2D line_color_texture; uniform sampler2D line_width_texture; uniform int line_width_channel; uniform float line_width_scale = 1.0; uniform int brute_force_range = 10; void main() { PIXEL_SETUP_INPUT(); vec2 uv = UV[0]; vec4 line_color = line_expand( uv, brute_force_range, line_color_texture, line_width_texture, line_width_channel, line_width_scale, depth_texture, depth_channel, id_texture, id_channel ).color; vec4 color = texture(color_texture, uv); OUT_RESULT = alpha_blend(color, line_color); } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/Unpack8bitTextures.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform usampler2D IN_PACKED; layout (location = 0) out vec4 OUT_A; layout (location = 1) out vec4 OUT_B; layout (location = 2) out vec4 OUT_C; layout (location = 3) out vec4 OUT_D; void main() { PIXEL_SETUP_INPUT(); uvec4 packed_pixel = texture(IN_PACKED, UV[0]); OUT_A = unpackUnorm4x8(packed_pixel.r); OUT_B = unpackUnorm4x8(packed_pixel.g); OUT_C = unpackUnorm4x8(packed_pixel.b); OUT_D = unpackUnorm4x8(packed_pixel.a); } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Passes/sRGBConversion.glsl ================================================ #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_SCREEN_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER uniform sampler2D input_texture; uniform bool to_srgb; uniform bool convert = true; layout (location = 0) out vec4 OUT_COLOR; void main() { PIXEL_SETUP_INPUT(); vec4 color = texture(input_texture, UV[0]); if(convert) { if(to_srgb) { color.rgb = linear_to_srgb(color.rgb); } else { color.rgb = srgb_to_linear(color.rgb); } } OUT_COLOR = color; } #endif //PIXEL_SHADER ================================================ FILE: Malt/Shaders/Procedural/Bayer.glsl ================================================ #ifndef BAYER_GLSL #define BAYER_GLSL // Based on: https://en.wikipedia.org/wiki/Ordered_dithering /* META GLOBAL @meta: internal=true; */ int _bayer_index(ivec2 texel, int size) { texel = texel % ivec2(size); return texel.x + size*texel.y; } float bayer_2x2(ivec2 texel) { int matrix[4] = int[4]( 0,2, 3,1 ); return float(matrix[_bayer_index(texel, 2)]) / 4.0; } float bayer_3x3(ivec2 texel) { int matrix[9] = int[9]( 0,7,3, 6,5,2, 4,1,8 ); return float(matrix[_bayer_index(texel, 3)]) / 9.0; } float bayer_4x4(ivec2 texel) { int matrix[16] = int[16]( 0,8,2,10, 12,4,14,6, 3,11,1,9, 15,7,13,5 ); return float(matrix[_bayer_index(texel, 4)]) / 16.0; } float bayer_8x8(ivec2 texel) { int matrix[64] = int[64]( 0,32,8,40,2,34,10,42, 48,16,56,24,50,18,58,26, 12,44,4,36,14,46,6,38, 60,28,53,20,62,30,54,22, 3,35,11,43,1,33,9,41, 51,19,59,27,49,17,57,25, 15,47,7,39,13,45,5,37, 63,31,55,23,61,29,53,21 ); return float(matrix[_bayer_index(texel, 8)]) / 64.0; } #endif //BAYER_GLSL ================================================ FILE: Malt/Shaders/Procedural/Cell_Noise.glsl ================================================ #ifndef PROCEDURAL_CELL_NOISE_GLSL #define PROCEDURAL_CELL_NOISE_GLSL #include "Common/Hash.glsl" /* META GLOBAL @meta: category=Texturing; internal=true; */ struct CellNoiseResult { vec4 cell_color; vec4 cell_position; float cell_distance; }; /* META @coord: subtype=Vector; default=vec4(POSITION,0); @tile_size: subtype=Vector; default=vec4(1); */ CellNoiseResult cell_noise_ex(vec4 coord, bool tile, vec4 tile_size) { //Kept for backward compatibility CellNoiseResult result; #define DIMENSIONS 4 #define T vec4 if(tile) { #define TILE 1 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position = r_cell_position; result.cell_distance = r_cell_distance; } else { #define TILE 0 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position = r_cell_position; result.cell_distance = r_cell_distance; } return result; } /* META @coord: subtype=Vector; default=vec4(POSITION,0); */ CellNoiseResult cell_noise(vec4 coord) { CellNoiseResult result; #define DIMENSIONS 4 #define T vec4 #define TILE 0 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=vec4(POSITION,0); @tile_size: default=ivec4(5); */ CellNoiseResult cell_noise(vec4 coord, ivec4 tile_size) { CellNoiseResult result; #define DIMENSIONS 4 #define T vec4 #define TILE 1 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=vec3(POSITION); */ CellNoiseResult cell_noise(vec3 coord) { CellNoiseResult result; #define DIMENSIONS 3 #define T vec3 #define TILE 0 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position.xyz = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=POSITION; @tile_size: default=ivec3(5); */ CellNoiseResult cell_noise(vec3 coord, ivec3 tile_size) { CellNoiseResult result; #define DIMENSIONS 3 #define T vec3 #define TILE 1 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position.xyz = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=POSITION.xy; */ CellNoiseResult cell_noise(vec2 coord) { CellNoiseResult result; #define DIMENSIONS 2 #define T vec2 #define TILE 0 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position.xy = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=POSITION.xy; @tile_size: default=ivec2(5); */ CellNoiseResult cell_noise(vec2 coord, ivec2 tile_size) { CellNoiseResult result; #define DIMENSIONS 2 #define T vec2 #define TILE 1 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position.xy = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=POSITION.x; */ CellNoiseResult cell_noise(float coord) { CellNoiseResult result; #define DIMENSIONS 1 #define T float #define TILE 0 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position.x = r_cell_position; result.cell_distance = r_cell_distance; return result; } /* META @coord: subtype=Vector; default=POSITION.x; @tile_size: default=5; */ CellNoiseResult cell_noise(float coord, int tile_size) { CellNoiseResult result; #define DIMENSIONS 1 #define T float #define TILE 1 #include "Cell_Noise.inl" result.cell_color = r_cell_color; result.cell_position.x = r_cell_position; result.cell_distance = r_cell_distance; return result; } #undef DIMENSIONS #undef T #undef TILE #endif // PROCEDURAL_CELL_NOISE_GLSL ================================================ FILE: Malt/Shaders/Procedural/Cell_Noise.inl ================================================ //Should define T, DIMENSIONS and TILE //Should declare coord and tile_size (if TILE) T real_coord = coord; #if TILE != 0 T _tile_size = round(T(tile_size)); coord = mod(coord, _tile_size); #endif T real_origin = floor(real_coord) + 0.5; T origin = floor(coord) + 0.5; T delta = fract(coord); vec4 r_cell_color; T r_cell_position; float r_cell_distance = 10; #if DIMENSIONS == 4 const ivec4 D = ivec4(1); #elif DIMENSIONS == 3 const ivec4 D = ivec4(1,1,1,0); #elif DIMENSIONS == 2 const ivec4 D = ivec4(1,1,0,0); #elif DIMENSIONS == 1 const ivec4 D = ivec4(1,0,0,0); #endif for(int w = -D.w; w <= D.w; w++) { for(int z = -D.z; z <= D.z; z++) { for(int y = -D.y; y <= D.y; y++) { for(int x = -D.x; x <= D.x; x++) { T offset = T(vec4(x,y,z,w)); T real_corner = real_origin + offset; T corner = origin + offset; #if TILE != 0 corner = mod(corner, _tile_size); #endif vec4 corner_hash = hash(corner); T real_cell_position = real_corner + (T(corner_hash) - 0.5); T cell_position = corner + (T(corner_hash) - 0.5); float cell_distance = distance(real_coord, real_cell_position); if(cell_distance < r_cell_distance) { r_cell_color = corner_hash; r_cell_position = real_cell_position; r_cell_distance = cell_distance; } } } } } ================================================ FILE: Malt/Shaders/Procedural/Fractal_Noise.glsl ================================================ #ifndef PROCEDURAL_FRACTAL_NOISE_GLSL #define PROCEDURAL_FRACTAL_NOISE_GLSL #include "Noise.glsl" /* META GLOBAL @meta: category=Texturing; @meta: internal=true; */ /* META @coord: subtype=Vector; default=vec4(POSITION,0); @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; @tile_size: default=ivec4(5); */ vec4 fractal_noise(vec4 coord, float detail, float detail_balance, ivec4 tile_size) { #define TILE 1 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=vec4(POSITION,0); @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; */ vec4 fractal_noise(vec4 coord, float detail, float detail_balance) { #define TILE 0 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION; @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; @tile_size: default=ivec3(5); */ vec4 fractal_noise(vec3 coord, float detail, float detail_balance, ivec3 tile_size) { #define TILE 1 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION; @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; */ vec4 fractal_noise(vec3 coord, float detail, float detail_balance) { #define TILE 0 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.xy; @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; @tile_size: default=ivec2(5); */ vec4 fractal_noise(vec2 coord, float detail, float detail_balance, ivec2 tile_size) { #define TILE 1 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.xy; @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; */ vec4 fractal_noise(vec2 coord, float detail, float detail_balance) { #define TILE 0 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.x; @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; @tile_size: default=5; */ vec4 fractal_noise(float coord, float detail, float detail_balance, int tile_size) { #define TILE 1 #include "Fractal_Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.x; @detail: default=3.0; min=1.0; @detail_balance: subtype=Slider; min=0.0; max=1.0; default = 0.5; */ vec4 fractal_noise(float coord, float detail, float detail_balance) { #define TILE 0 #include "Fractal_Noise.inl" return color; } #undef TILE // Keep for backward compatibility vec4 fractal_noise_ex(vec4 coord, int octaves, bool tile, vec4 tile_size) { if(tile) { return fractal_noise(coord, float(octaves), 0.5, ivec4(tile_size)); } else { return fractal_noise(coord, float(octaves), 0.5); } } vec4 fractal_noise(vec4 coord, int octaves) { return fractal_noise(coord, float(octaves), 0.5); } #endif // PROCEDURAL_FRACTAL_NOISE_GLSL ================================================ FILE: Malt/Shaders/Procedural/Fractal_Noise.inl ================================================ //Should define TILE //Should declare coord and tile_size (if TILE) vec4 color = vec4(0); float weight = 1.0; float total_weight = 0.0; detail_balance = clamp(detail_balance, 0.00001, 1.0); detail = max(1.0, detail); for (int i = 0; i < ceil(detail); i++) { #if TILE != 0 vec4 noise = noise(coord, tile_size); #else vec4 noise = noise(coord); #endif float octave_weight = (i + 1 > floor(detail))? mod(detail, 1.0) : 1.0; weight *= detail_balance * 2 * octave_weight; color += weight * noise; total_weight += weight; coord *= 2.0; #if TILE != 0 tile_size *= 2; #endif } color /= total_weight; ================================================ FILE: Malt/Shaders/Procedural/Noise.glsl ================================================ #ifndef PROCEDURAL_NOISE_GLSL #define PROCEDURAL_NOISE_GLSL #include "Common/Hash.glsl" /* META GLOBAL @meta: internal=true; */ /* META @coord: subtype=Vector; default=vec4(POSITION,0); @tile_size: subtype=Vector; default=vec4(1); */ vec4 noise_ex(vec4 coord, bool tile, vec4 tile_size) { //Kept for backward compatibility #define DIMENSIONS 4 #define T vec4 if(tile) { #define TILE 1 #include "Noise.inl" return color; } else { #define TILE 0 #include "Noise.inl" return color; } } /* META @coord: subtype=Vector; default=vec4(POSITION,0); @tile_size: subtype=Vector; default=ivec4(5); */ vec4 noise(vec4 coord, ivec4 tile_size) { #define DIMENSIONS 4 #define T vec4 #define TILE 1 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=vec4(POSITION,0); */ vec4 noise(vec4 coord) { #define DIMENSIONS 4 #define T vec4 #define TILE 0 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION; @tile_size: subtype=Vector; default=ivec3(5); */ vec4 noise(vec3 coord, ivec3 tile_size) { #define DIMENSIONS 3 #define T vec3 #define TILE 1 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION; */ vec4 noise(vec3 coord) { #define DIMENSIONS 3 #define T vec3 #define TILE 0 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.xy; @tile_size: subtype=Vector; default=ivec2(5); */ vec4 noise(vec2 coord, ivec2 tile_size) { #define DIMENSIONS 2 #define T vec2 #define TILE 1 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.xy; */ vec4 noise(vec2 coord) { #define DIMENSIONS 2 #define T vec2 #define TILE 0 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.x; @tile_size: subtype=Vector; default=5; */ vec4 noise(float coord, int tile_size) { #define DIMENSIONS 1 #define T float #define TILE 1 #include "Noise.inl" return color; } /* META @coord: subtype=Vector; default=POSITION.x; */ vec4 noise(float coord) { #define DIMENSIONS 1 #define T float #define TILE 0 #include "Noise.inl" return color; } #undef DIMENSIONS #undef T #undef TILE #endif // PROCEDURAL_NOISE_GLSL ================================================ FILE: Malt/Shaders/Procedural/Noise.inl ================================================ //Should define T, DIMENSIONS and TILE //Should declare coord and tile_size (if TILE) #if TILE != 0 T _tile_size = round(tile_size); coord = mod(coord, _tile_size); #endif T origin = floor(coord); T delta = fract(coord); //quintic interpolation factor T factor = delta*delta*delta*(delta*(delta*6.0-15.0)+10.0); #if DIMENSIONS == 4 const ivec4 D = ivec4(1); #elif DIMENSIONS == 3 const ivec4 D = ivec4(1,1,1,0); #elif DIMENSIONS == 2 const ivec4 D = ivec4(1,1,0,0); #elif DIMENSIONS == 1 const ivec4 D = ivec4(1,0,0,0); #endif vec4 w_results[2]; vec4 z_results[2]; vec4 y_results[2]; vec4 x_results[2]; for(int w = 0; w <= D.w; w++) { for(int z = 0; z <= D.z; z++) { for(int y = 0; y <= D.y; y++) { for(int x = 0; x <= D.x; x++) { T offset = T(vec4(x,y,z,w)); T corner = origin + offset; #if TILE != 0 corner = mod(corner, _tile_size); #endif T corner_hash_r = T(hash(corner)) * 2.0 - 1.0; //(-1|+1) range T corner_hash_g = T(hash(corner_hash_r)) * 2.0 - 1.0; T corner_hash_b = T(hash(corner_hash_g)) * 2.0 - 1.0; T corner_hash_a = T(hash(corner_hash_b)) * 2.0 - 1.0; x_results[x].r = dot(corner_hash_r, delta-offset); x_results[x].g = dot(corner_hash_g, delta-offset); x_results[x].b = dot(corner_hash_b, delta-offset); x_results[x].a = dot(corner_hash_a, delta-offset); } #if DIMENSIONS >= 2 y_results[y] = mix(x_results[0], x_results[1], factor.x); #endif } #if DIMENSIONS >= 3 z_results[z] = mix(y_results[0], y_results[1], factor.y); #endif } #if DIMENSIONS >= 4 w_results[w] = mix(z_results[0], z_results[1], factor.z); #endif } vec4 color; #if DIMENSIONS == 4 color = mix(w_results[0], w_results[1], factor.w) * 0.5 + 0.5; #elif DIMENSIONS == 3 color = mix(z_results[0], z_results[1], factor.z) * 0.5 + 0.5; #elif DIMENSIONS == 2 color = mix(y_results[0], y_results[1], factor.y) * 0.5 + 0.5; #elif DIMENSIONS == 1 color = mix(x_results[0], x_results[1], factor) * 0.5 + 0.5; #endif ================================================ FILE: Malt/Shaders/SDF/SDF.glsl ================================================ #ifndef SDF_SDF_GLSL #define SDF_SDF_GLSL #include "Common/Transform.glsl" // SDF functions adapted from https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm /* META GLOBAL @meta: internal=true; */ float sdf_box(vec3 p, vec3 size) { vec3 q = abs(p) - (size / 2.0); return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); } float sdf_sphere(vec3 p, float radius) { return length(p) - radius; } float sdf_ellipsoid(vec3 p, vec3 radius) { float k0 = length(p / radius); float k1 = length(p / (radius * radius)); return k0 * (k0 - 1.0) / k1; } float sdf_cone(vec3 p, float radius, float height) { vec2 q = vec2(radius, -height); vec2 w = vec2(length(p.xy), p.z - height); vec2 a = w - (q * clamp(dot(w, q) / dot(q, q), 0.0, 1.0)); vec2 b = w - (q * vec2(clamp(w.x / q.x, 0.0, 1.0), 1.0)); float k = sign(q.y); float d = min(dot(a, a),dot(b, b)); float s = max(k * ((w.x * q.y) - (w.y * q.x)), k * (w.y - q.y)); return sqrt(d) * sign(s); } float sdf_capsule(vec3 p, vec3 a, vec3 b, float radius) { vec3 pa = p - a; vec3 ba = b - a; float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - (ba * h)) - radius; } float sdf_smooth(float a, float radius) { return a - radius; } float sdf_union(float a, float b) { return min(a, b); } float sdf_difference(float a, float b) { return max(a, -b); } float sdf_intersection(float a, float b) { return max(a, b); } float sdf_union_smooth(float a, float b, float smooth_size) { float h = clamp(0.5 + 0.5 * (b - a) / smooth_size, 0.0, 1.0); return mix(b, a, h) - smooth_size * h * (1.0 - h); } float sdf_difference_smooth(float a, float b, float smooth_size) { float h = clamp(0.5 - 0.5 * (b + a) / smooth_size, 0.0, 1.0); return mix(a, -b, h) + smooth_size * h * (1.0 - h); } float sdf_intersection_smooth(float a, float b, float smooth_size) { float h = clamp(0.5 - 0.5 * (b - a) / smooth_size, 0.0, 1.0); return mix(b, a, h) + smooth_size * h * (1.0 - h); } struct RayMarchResult { bool hit; int step; float depth; vec3 position; vec3 normal; }; float scene_sdf(vec3 p); // To include this file and call this function you must define your own scene_sdf function RayMarchResult raymarch_scene(vec3 ray_start, vec3 ray_end, int max_steps, float min_precision) { RayMarchResult r = RayMarchResult(false, 0, 0, vec3(0), vec3(0)); vec3 ray = normalize(ray_end - ray_start); float ray_length = distance(ray_start, ray_end); for (r.step = 0; r.step < max_steps; r.step++) { float signed_distance = scene_sdf(ray_start + min(r.depth, ray_length) * ray); if (signed_distance < min_precision) { break; } if (r.depth > ray_length) { return r; } r.depth += signed_distance; } r.hit = true; r.position = ray_start + r.depth * ray; float projected_depth = project_point(PROJECTION * CAMERA, r.position).z; float offset_scale = pixel_world_size_at(projected_depth); // https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm vec2 k = vec2(1,-1); r.normal = normalize ( k.xyy * scene_sdf(r.position + k.xyy * offset_scale) + k.yyx * scene_sdf(r.position + k.yyx * offset_scale) + k.yxy * scene_sdf(r.position + k.yxy * offset_scale) + k.xxx * scene_sdf(r.position + k.xxx * offset_scale) ); return r; } #endif //SDF_SDF_GLSL ================================================ FILE: Malt/Shaders/Shading/BRDF.glsl ================================================ #ifndef BRDF_GLSL #define BRDF_GLSL // The following formulas follow the naming conventions explained in the LitSurface struct declaration (Lighing.glsl) // X is for tangent and Y for bitangent. (ie. XoH means dot(tangent, halfway_vector)) // (a) parameter stands for roughness factor (0..1) // Dot products should be clamped to (MIN_DOT..1) //Division by PI has been factored out for a more intuitive artistic workflow //https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/ //UTILS #define MIN_DOT 1e-10 /* META @meta: internal=true; */ float safe_dot(vec3 a, vec3 b) { return clamp(dot(a,b), MIN_DOT, 1.0); } /* META @meta: internal=true; */ float roughness_to_shininess(float roughness) { return 2.0 / pow(max(0.1, roughness), 3); } // DIFFUSE BRDFs /* META @meta: internal=true; */ float BRDF_lambert(float NoL) { return NoL; } float F_schlick(float VoH, float F0, float F90); //Forward declaration, definition in Fresnel section /* META @meta: internal=true; */ float BRDF_burley(float NoL, float NoV, float VoH, float a) { //https://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf float f90 = 0.5 + 2.0 * a * VoH*VoH; return F_schlick(NoL, 1.0, f90) * F_schlick(NoV, 1.0, f90) * NoL; } /* META @meta: internal=true; */ float BRDF_oren_nayar(float NoL, float NoV, float LoV, float a) { //https://mimosa-pudica.net/improved-oren-nayar.html float s = LoV - NoL * NoV; float t = s <= 0 ? 1.0 : max(NoL, NoV); float A = 1.0 - 0.5 * (a*a / (a*a + 0.33) + 0.17 * (a*a / (a*a + 0.13))); float B = 0.45 * (a*a / (a*a + 0.09)); return NoL * (A + B * (s / t)); } // SPECULAR BRDFs //http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html /* META @meta: internal=true; */ float BRDF_specular_cook_torrance(float D, float F, float G, float NoL, float NoV) { return (D * F * G) / (4.0 * NoL * NoV) * NoL * PI; } // Specular Normal Distribution Functions /* META @meta: internal=true; */ float D_phong(float VoR, float a) { return pow(VoR, roughness_to_shininess(a)); } /* META @meta: internal=true; */ float D_blinn_phong(float NoH, float a) { return pow(NoH, roughness_to_shininess(a)); } /* META @meta: internal=true; */ float D_ward(float NoL, float NoV, float NoH, float XoH, float YoH, float aX, float aY) { float e = -2.0 * ((pow(XoH / aX, 2) + pow(YoH / aY, 2)) / (1.0 + NoH)); return (1.0 / sqrt(NoL * NoV)) * (NoL / (4.0 * PI * aX * aY)) * exp(e); } /* META @meta: internal=true; */ float D_beckmann(float NoH, float a) { return (1.0 / (PI * a*a * pow(NoH, 4.0))) * exp((NoH*NoH - 1.0) / (a*a * NoH*NoH)); } /* META @meta: internal=true; */ float D_GGX(float NoH, float a) { return (a*a) / (PI * pow(NoH*NoH * (a*a - 1.0) + 1.0, 2.0)); } /* META @meta: internal=true; */ float D_GGX_anisotropic(float NoH, float XoH, float YoH, float ax, float ay) { return (1.0 / (PI * ax*ay)) * (1.0 / (pow((XoH*XoH) / (ax*ax) + (YoH*YoH) / (ay*ay) + NoH*NoH, 2.0))); } // Specular Geometric Shadowing Functions /* META @meta: internal=true; */ float G_implicit(float NoL, float NoV) { return NoL*NoV; } /* META @meta: internal=true; */ float G_neumann(float NoL, float NoV) { return (NoL*NoV) / max(NoL, NoV); } /* META @meta: internal=true; */ float G_cook_torrance(float NoH, float NoV, float NoL, float VoH) { return min(1.0, min((2.0 * NoH * NoV) / VoH, (2.0 * NoH * NoL) / VoH)); } /* META @meta: internal=true; */ float G_kelemen(float NoL, float NoV, float VoH) { return (NoL*NoV) / VoH*VoH; } float _G1_beckmann(float NoLV, float a) { float c = NoLV / (a * sqrt(1.0 - NoLV*NoLV)); if(c >= 1.6) return 1.0; return (3.535*c + 2.181*c*c) / (1.0 + 2.276*c + 2.577*c*c); } /* META @meta: internal=true; */ float G_beckmann(float NoL, float NoV, float a) { return _G1_beckmann(NoL, a) * _G1_beckmann(NoV, a); } float _G1_GGX(float NoLV, float a) { return (2 * NoLV) / (NoLV + (sqrt(a*a + (1 - a*a) * NoLV*NoLV))); } /* META @meta: internal=true; */ float G_GGX(float NoL, float NoV, float a) { return _G1_GGX(NoL, a) * _G1_GGX(NoV, a); } // Specular Fresnel Functions /* META @meta: internal=true; */ float F_schlick(float VoNH, float F0, float F90) { // https://en.wikipedia.org/wiki/Schlick%27s_approximation return F0 + (F90 - F0) * pow(1.0 - VoNH, 5.0); } /* META @meta: internal=true; */ float F_cook_torrance(float VoNH, float F0) { float n = (1.0 + sqrt(F0)) / (1.0 - sqrt(F0)); float c = VoNH; float g = sqrt(n*n + c*c - 1.0); float A = (g - c) / (g + c); float B = ((g + c) * c - 1.0) / ((g - c) * c + 1.0); return 0.5 * A*A * (1.0 + B*B); } #endif //BRDF_GLSL ================================================ FILE: Malt/Shaders/Shading/ShadingModels.glsl ================================================ #ifndef SHADING_MODELS_GLSL #define SHADING_MODELS_GLSL #include "Shading/BRDF.glsl" /* META GLOBAL @meta: category=Shading; internal=true; */ vec3 diffuse_lit_surface(LitSurface LS) { return clamp(LS.NoL, 0, 1) * LS.light_color * LS.shadow_multiply; } vec3 _diffuse_half_lit_surface_common(LitSurface LS) { vec3 diffuse = vec3(map_range_clamped(LS.NoL, -1, 1, 0, 1)); vec3 shadow = map_range_clamped(LS.shadow_multiply, vec3(0),vec3(1),vec3(0.5),vec3(1)); return min(diffuse, shadow); } vec3 diffuse_half_lit_surface(LitSurface LS) { return _diffuse_half_lit_surface_common(LS) * LS.light_color; } vec3 diffuse_gradient_lit_surface(LitSurface LS, sampler1D gradient) { return rgb_gradient(gradient, _diffuse_half_lit_surface_common(LS)) * LS.light_color; } float _specular_shadowing(float NoL, float specular) { return clamp(specular * (1.0 - pow(1.0 - max(NoL, 0), 20.0)), 0, 1); } float _specular_common_lit_surface(LitSurface LS, float roughness) { float VoR = dot(LS.V, LS.R); VoR = _specular_shadowing(LS.NoL, VoR); return pow(VoR, roughness_to_shininess(roughness)); } vec3 specular_lit_surface(LitSurface LS, float roughness) { return _specular_common_lit_surface(LS, roughness) * LS.light_color * LS.shadow_multiply; } vec3 specular_gradient_lit_surface(LitSurface LS, float roughness, sampler1D gradient) { return texture(gradient, _specular_common_lit_surface(LS, roughness)).rgb * LS.light_color * LS.shadow_multiply; } float _specular_anisotropic_lit_surface_common(LitSurface LS, vec3 tangent, float anisotropy, float roughness) { vec2 a = vec2(anisotropy, 1.0 - anisotropy); a *= roughness; vec3 bitangent = normalize(cross(LS.N, tangent)); float NoL = max(dot(LS.N, LS.L), MIN_DOT); float NoV = max(dot(LS.N, LS.V), MIN_DOT); float NoH = max(dot(LS.H, LS.N), MIN_DOT); float XoH = dot(LS.H, tangent); float YoH = dot(LS.H, bitangent); float specular = D_ward(NoL, NoV, XoH, XoH, YoH, a.x, a.y); return _specular_shadowing(NoL, specular); } vec3 specular_anisotropic_lit_surface(LitSurface LS, vec3 tangent, float anisotropy, float roughness) { return _specular_anisotropic_lit_surface_common(LS, tangent, anisotropy, roughness) * LS.light_color * LS.shadow_multiply; } vec3 specular_anisotropic_gradient_lit_surface(LitSurface LS, vec3 tangent, float anisotropy, float roughness, sampler1D gradient) { return texture(gradient, _specular_anisotropic_lit_surface_common(LS, tangent, anisotropy, roughness)).rgb * LS.light_color * LS.shadow_multiply; } vec3 toon_lit_surface(LitSurface LS, float size, float gradient_size, float specularity, float offset) { float D = mix(LS.NoL, dot(LS.V, LS.R), specularity); float angle = acos(D); float delta = angle / PI; delta -= offset; gradient_size = min(size, gradient_size); float value = 1.0 - map_range_clamped(delta, size - gradient_size, size, 0.0, 1.0); float color_at_05 = 1.0 - map_range_clamped(0.5, size - gradient_size, size, 0.0, 1.0); return min(LS.shadow ? color_at_05 : 1.0, value) * LS.light_color; } /* META @meta: internal=false; @normal: subtype=Normal; default=NORMAL; @angle: default=0.0; @rim_length: default=2.0; @length_fallof: default=0.1; @thickness: default=0.1; @thickness_fallof: default=0.0; */ float rim_light(vec3 normal, float angle, float rim_length, float length_falloff, float thickness, float thickness_falloff) { vec2 angle_vec = vec2(cos(angle), sin(angle)); vec3 r = cross(transform_normal(CAMERA, -view_direction()), transform_normal(CAMERA, normal)); vec2 r2d = normalize(r.xy); float angle_dot = dot(r2d, angle_vec); angle_dot = angle_dot * 0.5 + 0.5; float facing_dot = dot(-view_direction(), normal); facing_dot = 1.0 - facing_dot; length_falloff = max(1e-6, length_falloff); thickness_falloff = max(1e-6, thickness_falloff); float angle_result = map_range_clamped(angle_dot, 1.0 - rim_length, 1.0 - (rim_length - length_falloff), 0.0, 1.0); float facing_result = map_range_clamped(facing_dot * angle_result, 1.0 - thickness, 1.0 - (thickness - thickness_falloff), 0.0, 1.0); return angle_result * facing_result; } #endif //SHADING_MODELS_GLSL ================================================ FILE: Malt/Shaders/readme.md ================================================ # Shader Library All *GLSL* code should go here. All shader files have their own [*include guard*](https://en.wikipedia.org/wiki/Include_guard) macro. *Include guards* are used instead of the *#pragma once* preprocessor directive to allow user code to override the Malt built-in code when needed. Whenever it makes sense, functions should be implemented in a pipeline/renderer agnostic way. Ideally it should be possible to copy-paste *Malt* functions into any other render engine with *GLSL* support. [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. The [Common](Common) folder contains 3d math and rendering utility functions. [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. [Lighting](Lighting) contains a basic lighting and shadow mapping implementation and it's intended to be extended by custom pipelines. [Shading](Shading) has an extensive collection of basic building blocks for implementing *BRDFs*. [Filters](Filters) is for texture processing functions, from *Blur* to *AO*. Standalone texture processing and other types of shaders that wouldn't benefit from a library-like interface are located in [Passes](Passes) Any pipeline specific code should go inside their own folder in [Pipelines](Pipelines). ================================================ FILE: Malt/SourceTranspiler.py ================================================ import textwrap #TODO: Send transpiler along graph types class SourceTranspiler(): @classmethod def get_source_name(self, name): name = name.replace('.','_').replace(' ', '_') name = '_' + ''.join(char for char in name if char.isalnum() or char == '_') while '__' in name: name = name.replace('__','_') return name @classmethod def asignment(self, name, asignment): pass @classmethod def declaration(self, type, size, name, initialization=None): pass @classmethod def global_reference(self, node_name, parameter_name): pass @classmethod def global_declaration(self, type, size, name, initialization=None): pass @classmethod def custom_io_reference(self, io, graph_io_type, name): pass @classmethod def preprocessor_wrap(self, define, content): return content @classmethod def custom_output_declaration(self, type, name, index, graph_io_type): pass @classmethod def parameter_reference(self, node_name, parameter_name, io_type): pass @classmethod def io_parameter_reference(self, parameter_name, io_type): return parameter_name @classmethod def is_instantiable_type(self, type): return True @classmethod def call(self, name, parameters=[], full_statement=False): pass @classmethod def result(self, result): pass @classmethod def scoped(self, code): pass class GLSLTranspiler(SourceTranspiler): @classmethod def asignment(self, name, asignment): return f'{name} = {asignment};\n' @classmethod def declaration(self, type, size, name, initialization=None): array = '' if size == 0 else f'[{size}]' asignment = f' = {initialization}' if initialization else '' return f'{type} {name}{array}{asignment};\n' @classmethod def global_reference(self, node_name, parameter_name): return f"U_0{node_name}_0_{self.get_source_name(parameter_name)}".replace('__','_') @classmethod def global_declaration(self, type, size, name, initialization=None): uniform_declaration = 'uniform ' if 'sampler' in type: uniform_declaration = 'OPTIONALLY_BINDLESS uniform ' return uniform_declaration + self.declaration(type, size, name, initialization) @classmethod def custom_io_reference(self, io, graph_io_type, name): return f"{io.upper()}_{graph_io_type.upper()}_{''.join(char.upper() for char in name if char.isalnum())}" @classmethod def preprocessor_wrap(self, define, content): if define is None: return content return textwrap.dedent('''\ #ifdef {} {} #endif //{} ''').format(define, content.strip(), define) @classmethod def custom_output_declaration(self, type, name, index, graph_io_type): return f"layout (location = {index}) out {type} {self.custom_io_reference('OUT', graph_io_type, name)};\n" @classmethod def parameter_reference(self, node_name, parameter_name, io_type): return f'{node_name}_0_{parameter_name}' @classmethod def is_instantiable_type(self, type): return type.startswith('sampler') == False @classmethod def call(self, function, name, parameters=[], post_parameter_initialization = ''): src = '' for i, parameter in enumerate(function['parameters']): if parameter['io'] in ['out','inout']: initialization = parameters[i] src_reference = self.parameter_reference(name, parameter['name'], parameter['io']) src += self.declaration(parameter['type'], parameter['size'], src_reference, initialization) parameters[i] = src_reference src += post_parameter_initialization initialization = f'{function["name"]}({",".join(parameters)})' if function['type'] != 'void' and self.is_instantiable_type(function['type']): src += self.declaration(function['type'], 0, self.parameter_reference(name, 'result', 'out'), initialization) else: src += initialization + ';\n' return src @classmethod def result(self, result): return f'return {result};\n' @classmethod def scoped(self, code): import textwrap code = textwrap.indent(code, '\t') return f'{{\n{code}}}\n' class PythonTranspiler(SourceTranspiler): @classmethod def asignment(self, name, asignment): return f'{name} = {asignment}\n' @classmethod def declaration(self, type, size, name, initialization=None): if initialization is None: initialization = 'None' return self.asignment(name, initialization) @classmethod def global_reference(self, node_name, parameter_name): return f'PARAMETERS["{node_name}"]["{parameter_name}"]' @classmethod def global_declaration(self, type, size, name, initialization=None): return '' return self.declaration(type, size, name, initialization) @classmethod def custom_io_reference(self, io, graph_io_type, name): return self.io_parameter_reference(name, io) @classmethod def custom_output_declaration(self, type, name, index, graph_io_type): return self.declaration(type, 0, self.io_parameter_reference(name, 'out')) @classmethod def parameter_reference(self, node_name, parameter_name, io_type): if io_type: return f'{node_name}_parameters["{io_type.upper()}"]["{parameter_name}"]' else: return f'{node_name}_parameters["{parameter_name}"]' @classmethod def io_parameter_reference(self, parameter_name, io_type): return f'{io_type.upper()}["{parameter_name}"]' @classmethod def call(self, function, name, parameters=[], post_parameter_initialization = ''): import textwrap src = '' src += textwrap.dedent(f''' {name}_parameters = {{ 'IN' : {{}}, 'OUT' : {{}}, }} ''') for i, parameter in enumerate(function['parameters']): initialization = parameters[i] if initialization is None: initialization = 'None' parameter_reference = self.parameter_reference(name, parameter['name'], parameter['io']) src += f'{parameter_reference} = {initialization}\n' src += post_parameter_initialization src += f'run_node("{name}", "{function["name"]}", {name}_parameters)\n' return src @classmethod def result(self, result): return f'return {result}\n' @classmethod def scoped(self, code): import textwrap code = textwrap.indent(code, '\t') return f'if True:\n{code}' ================================================ FILE: Malt/Utils.py ================================================ import logging class MaltLogger(): def __init__(self): self.last_msg = None self.repeated_msg = 0 def log(self, level, *args): if level < logging.root.level: return args = [str(arg) for arg in args] msg = ' '.join(args) if msg != self.last_msg: self.last_msg = msg self.repeated_msg = 0 logging.log(level, msg) else: self.repeated_msg += 1 if self.repeated_msg in (1, 10, 100, 1000): logging.log(level, '(Repeated {}+ times)'.format(self.repeated_msg)) def debug(self, *args): self.log(logging.DEBUG, *args) def info(self, *args): self.log(logging.INFO, *args) def warning(self, *args): self.log(logging.WARNING, *args) def error(self, *args): self.log(logging.ERROR, *args) def critical(self, *args): self.log(logging.CRITICAL, *args) LOG = MaltLogger() def dump_function(function): import textwrap, inspect name = function.__name__ function = textwrap.dedent(inspect.getsource(function)) return (name, function) def load_function(function): name, function = function f = {} exec(function, f) return f[name] def scan_dirs(path, file_callback): import os for e in os.scandir(path): if e.is_file(): file_callback(e) if e.is_dir(): scan_dirs(e, file_callback) def isinstance_str(object, class_name): classes = [object.__class__, *object.__class__.__bases__] for cls in classes: if cls.__name__ == class_name: return True return False import cProfile, io, pstats def profile_function(function): def profiled_function(*args, **kwargs): profiler = cProfile.Profile() profiling_data = io.StringIO() profiler.enable() result = function(*args, **kwargs) profiler.disable() stats = pstats.Stats(profiler, stream=profiling_data) stats.strip_dirs() stats.sort_stats(pstats.SortKey.CUMULATIVE) stats.print_stats() print('PROFILE FUNCTION: ', function.__name__) print(profiling_data.getvalue()) return result return profiled_function # https://numpy.org/doc/stable/reference/arrays.interface.html class Array_Interface(): def __init__(self, pointer, typestr, shape, read_only=False): self.__array_interface__ = { 'data': (pointer, read_only), 'typestr': typestr, 'shape': shape } class IBuffer(): def ctype(self): raise Exception('ctype() method not implemented') def __len__(self): raise Exception('__len__() method not implemented') def buffer(self): raise Exception('buffer() method not implemented') def size_in_bytes(self): import ctypes return ctypes.sizeof(self.ctype()) * len(self) def as_array_interface(self, shape=None): import ctypes type_map = { ctypes.c_float : 'f', ctypes.c_int : 'i', ctypes.c_uint : 'u', ctypes.c_bool : 'b', } if shape is None: shape = (len(self),) return Array_Interface( ctypes.addressof(self.buffer()), type_map[self.ctype()], shape ) def as_np_array(self, shape=None): import numpy as np return np.array(self.as_array_interface(shape), copy=False) ================================================ FILE: Malt/__init__.py ================================================ ================================================ FILE: Malt/readme.md ================================================ # Malt ## Introduction *Malt* is a fully customizable rendering framework written in **Python** and **OpenGL**. Its main goal is to support offline rendering for **animation** and **illustration**, with special care put into supporting the needs of **stylized, non photo-realistic rendering** and being **accesible** to technical artists and users without previous graphics programming experience. Therefore, while it's a **real-time renderer**, it **prioritizes image quality, flexibility and simplicity over rendering performance**. ## Malt Pipelines The core class in *Malt* is the [*Pipeline*](Pipeline.py). *Malt* allows to write completely custom render *Pipelines* while providing ready to use render utilities in the [*Shaders*](Shaders) and [*Render*](Render) libraries. *Malt* is meant to be used by a *Host* application, like [*BlenderMalt*](../BlenderMalt). The *Host* is responsible for preparing and sending the *Scene* data to *Malt*, including already loaded and ready to use [*Meshes*, *Shaders* and *Textures*](GL). ### Render *Pipelines* main task is to implement a *render* function. The *render* function takes a *Scene* and must return the rendered result as a *Texture*. ### Scene The [*Scene*](Scene.py) class makes as little assumptions as posible about the data needed by the *Pipeline*. Instead, the *Pipeline* can declare custom [*Parameters*](PipelineParameters.py) for each *Scene* object type. The host is responsible for exposing those parameters so users can edit them. ### Materials Additionaly, Pipelines are responsible for compiling *Materials*. A *Material* is a *Python Dictionary* of [Shaders](GL/Shader.py) with an arbitrary number of entries. The host is responsible for sending back scene objects with their respective materials and the shader parameters already setup. *Materials* should share the same source code for all the *Shaders* they generate by using conditional compilation through the *Preprocesor*. By default *Pipelines* declares the *VERTEX_SHADER* define when generating the *Vertex Shader* source code and *PIXEL_SHADER* for *Pixel/Fragment Shaders*. ### Implementing a Pipeline Custom *Pipelines* can be implemented by creating a new *class* that inherits the *Pipeline* *class*. The pipeline can then be loaded from the *Host* settings. The main functions to override are: * *\_\_init__*: *Pipeline Parameters* *(self.parameters)* should be registered here, in addition to any *Render Resource* like *Shaders* and *UBOs* needed by the *Pipeline*. > 💡 Since there can be multiple instances of the same *Pipeline*, it's a good practice to share resources between them when possible, to shorten *Pipeline* creation times and lower memory consumption. * *setup_render_targets*: Any resolution dependent resource, like *RenderTargets* should be created here. This function is called whenever the *Pipeline* resolution changes, including the first time *render* is called. * *compile_material_from_source*: This should return a dictionary of all the *Shaders* generated from a *Pipeline* *Material*. * *do_render*: This one is called by the *render* function after setup and as its name implies should render a whole frame and return it as a *Texture*. #### Minimal example ```Python class MiniPipeline(Pipeline): DEFAULT_SHADER = None def __init__(self): super().__init__() self.parameters.world['Background Color'] = Parameter((0.5,0.5,0.5,1), Type.FLOAT, 4) self.common_buffer = Common.CommonBuffer() if MiniPipeline.DEFAULT_SHADER is None: source = ''' #include "Common.glsl" #ifdef VERTEX_SHADER void main() { DEFAULT_VERTEX_SHADER(); } #endif #ifdef PIXEL_SHADER layout (location = 0) out vec4 RESULT; void main() { RESULT = vec4(1); } #endif ''' MiniPipeline.DEFAULT_SHADER = self.compile_material_from_source('mesh', source) self.default_shader = MiniPipeline.DEFAULT_SHADER def compile_material_from_source(self, material_type, source, include_paths=[]): return { 'MAIN_PASS' : self.compile_shader_from_source( source, include_paths, ['MAIN_PASS'] ) } def setup_render_targets(self, resolution): self.t_depth = Texture(resolution, GL_DEPTH_COMPONENT32F) self.t_main_color = Texture(resolution, GL_RGBA32F) self.fbo_main = RenderTarget([self.t_main_color], self.t_depth) def do_render(self, resolution, scene, is_final_render, is_new_frame): self.common_buffer.load(scene, resolution) UBOS = { 'COMMON_UNIFORMS' : self.common_buffer } self.fbo_main.clear([scene.world_parameters['Background Color']], 1) self.draw_scene_pass(self.fbo_main, scene.batches, 'MAIN_PASS', self.default_shader['MAIN_PASS'], UBOS) return { 'COLOR' : self.t_main_color } ``` For a complete example see the [NPR_Pipeline](Pipelines/NPR_Pipeline) ================================================ FILE: README.md ================================================ # Malt Malt is a fully customizable real-time rendering framework for animation and illustration. 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. [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) ## Features - **Free and Open Source**. MIT License. - **Real Time Rendering**. - **Complete *Blender* integration**. - **Built-in Pipeline for Stylized Non Photorealistic Rendering**. - **Code as a First Class Citizen** - Automatic reloading. - *VSCode* integration, including *GLSL* autocompletion. - Automatic generation of nodes from *GLSL* functions. - Automatic UI for *Shader* and *Pipeline* parameters. - 100% customizable *Python* Render Pipelines. ## Requirements - OpenGL 4.5 - Latest Blender stable release. - Windows or Linux > A dedicated Nvidia or AMD graphics card is highly recomended. ## Install - Go to [the latest Release page](https://github.com/bnpr/Malt/releases/tag/Release-latest). - Download the *BlenderMalt* version that matches your OS. - Open Blender. Go to *Preferences > Addons*, click on the *Install...* button and select *BlenderMalt.zip* from your downloads. *(It will take a few seconds)* - Tick the box in the *BlenderMalt* panel to enable it. > Altenatively, you can download the [Development version](https://github.com/bnpr/Malt/releases/tag/Development-latest) to test the latest features. ## Uninstall - Untick the box in *Preferences > Addons > BlenderMalt* to disable the addon. - Restart *Blender*. - Go back to *Preferences > Addons > BlenderMalt*, expand the panel and click the *Remove* button. ## First steps 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). The [Q&A section](https://github.com/bnpr/Malt/discussions/categories/q-a) is full of info as well. ## Developer Documentation [How to setup BlenderMalt for Development.](docs/Setup-BlenderMalt-for-Development.md) 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. The [Malt folder documentation](Malt#malt) is a good starting point. ================================================ FILE: __init__.py ================================================ # Some people are used to download the zipped repo from Github to install Blender addons. # This file is just to redirect those users to the right path. It's not distributed in the actual addon. bl_info = { "name": "Oops! You downloaded the wrong BlenderMalt file.", "description" : "Please, read the install intructions on Github or malt3d.com", "author" : "Miguel Pozo", "version": (1,0,0), "blender" : (3, 0, 0), "doc_url": "https://malt3d.com/documentation/getting started/#install", "tracker_url": "https://github.com/bnpr/Malt#install", "category": "Render" } def register(): pass def unregister(): pass ================================================ FILE: docs/Documentation/Getting Started.md ================================================ # Gettting Started ## Requirements - OpenGL 4.5 support. - Latest Blender stable release. > A dedicated Nvidia or AMD graphics card is highly recomended. ## Install - Go to [the latest Release page](https://github.com/bnpr/Malt/releases/tag/Release-latest). - Download the *BlenderMalt* version that matches your OS. - Open Blender. Go to *Preferences > Addons*, click on the *Install...* button and select *BlenderMalt.zip* from your downloads. *(This will take a few seconds)* - Tick the box in the *BlenderMalt* panel to enable it. > Altenatively, you can download the [Development version](https://github.com/bnpr/Malt/releases/tag/Development-latest) to test the latest features. ## Uninstall - Untick the box in *Preferences > Addons > BlenderMalt* to disable the addon. - Restart *Blender*. - Go back to *Preferences > Addons > BlenderMalt*, expand the panel and click the *Remove* button. ## Enable Malt ![img](2022-02-16-16-45-47.png) *Malt* is a separate render engine, just like *Cycles* and *EEVEE*. To enable it, select *Malt* in *Properties Panel > Render Properties > Render Engine*. > When *Malt* is enabled, a tiny black window will pop up. This is the process where the renderer runs. ![img2](2022-02-16-17-20-18.png) Feel free to ignore it, it's only there because hiding it can lower the process priority and impact the render performance. ## Sample Files Sample files can be found at [Github](https://github.com/bnpr/Malt/discussions/94). ![](2022-03-21-15-58-04.png) ## Pipelines *Malt* allows implementing custom *render pipelines* for advanced use cases. However, the built-in *NPR Pipeline* is a fully featured and highly customizable pipeline designed to cover most use cases. Most parts of this documentation apply only to the *NPR Pipeline*. > For building custom *render pipelines*, see the [Developer Documentation](https://github.com/bnpr/Malt/tree/Development/Malt#malt). ================================================ FILE: docs/Documentation/Graphs.md ================================================ # Graphs & Nodes *Pipelines* and *Plugins* declare their own *Node Tree* subtypes, a.k.a *Graph* types. Nodes have their own *Malt Node Tree* editor. ![](2022-02-23-17-03-18.png) *Pipelines* and *Plugins* can declare their own *Graph* types: ![](2022-02-23-17-11-03.png) > *Pipelines* and *Plugins* can also declare custom *Node Libraries*. There are 2 main groups of *Graph* types: *Shader Graphs* and *Render Graphs*. *Shader Graphs* compile to *GLSL* (*OpenGL Shading Language*), while *Render Graphs* compile to *Python* scripts. > These source files can be found in the *.malt-autogenerated* folder alongside their *.blend* file. *Shader Graphs* are always used from a *Material*. Multiple *Materials* can use the same *Node Tree* while overriding some parameters. > It's best to reuse *node trees* when possible for improved render performance and shader compilation times. ## Socket Types *Node socket* types keep the *GLSL* naming: - **bool** A value that can be either *true* or *false*. - **float** A positive or negative number that can have decimals. *(ie: -123.456, -1.0, 0.0, 1.0, 123.456)* - **int** A positive or negative number that can't have decimals. *(ie: -123, -1, 0, 1, 123)* - **uint** A positive number that can't have decimals. *(ie: 0, 1, 123)* - **vec2** A vector of 2 floats. Used for 2d coordinates, like *UVs*. - **vec3** A vector of 3 floats. Used for 3d coordinates, like *positions* and *normals*, and *RGB* colors. - **vec4** A vector of 4 floats. Used for *RGBA* colors. - **bvec(n)** A vector of (n) bools. - **ivec(n)** A vector of (n) ints. - **uvec(n)** A vector of (n) uints. - **mat4** A 4x4 *Matrix*. - **sampler1D** A 1D image (like a color gradient) that holds float values. - **sampler2D** A 2D image (like a texture) that holds float values. - **isampler(n)D** An (n) dimesional image that holds int values. - **usampler(n)D** An (n) dimesional image that holds uint values. You can find more details in the [OpenGL wiki](https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)). ### Structs In addition to basic types, you will also find custom types, a.k.a. *structs*. *Structs* are just groups of basic types and/or other *structs*. For example, a typical *PBR* material setup is composed of albedo *(vec4)*, normal *(vec3)*, roughness *(float)* and metalness *(float)*. So nodes that operate with these values could pack all of them in a single *PBR_Properties* socket, instead of having them as 4 individual sockets. *Struct sockets* always expose their subproperties so you can still override them individually if needed. So in the *PBR_Properties* example you would see these sockets: ![](2022-04-02-17-36-17.png) ### Arrays Arrays are lists of multiple values of the same type. They are expressed as `type[n]` where `n` is the number of values it stores. For example, a socket expressed as `random_colors: (vec4[8])` is a list of 8 values of type *vec4*. ### Opaque Types In the case of *Python Render Graphs* there are some types, like *Scene*, that can't be edited directly in the node editor. However, they can be edited from code in *custom nodes*. ### Automatic type conversion Conversion between types is handled automatically whenever there's an unambiguous mapping between the 2. ![](2022-04-02-17-35-20.png) You can add extra automatic conversions by adding functions with the signature: ```glsl typeB typeB_from_typeA(typeA value); ``` The full list of built-in conversion functions can be found at [Malt/Shaders/Node Utils/conversion.glsl](https://github.com/bnpr/Malt/blob/Development/Malt/Shaders/Node%20Utils/conversion.glsl) ## Node Types The main categories of nodes are *Functions*, *Structs*, *Inputs/Outputs* and *Other*. ![](2022-03-21-18-17-30.png) ### Functions This is the most common node type, they behave like a typical node where their inputs are used to compute their outputs. In the case of Shader nodes, they're auto-generated from GLSL functions. ### Structs Used to pack/unpack and modify struct values. In the case of Shader nodes, they're auto-generated from GLSL structs. > Struct nodes can be used to create a new struct from scratch, override the properties of an existing node or retrieving the value of individual properties: ![](2022-02-25-20-18-44.png) ### Other #### Array Used to retrieve an individual value from an array. ![](2022-04-02-17-34-35.png) #### Inline Code Allows you to write value assignment directly in code form. Useful to write math formulas and to workaround the node system limitations. ![](2022-03-21-18-05-53.png) ### Inputs/Outputs Every node system produces a result from some given intputs. Ie, a material computes the color of a surface given some geometry, and render nodes generate an image from the scene data. Each graph type has at least one set of input/output nodes, but some of them (like Mesh) have several. #### Custom IO Some IO nodes allow adding extra properties. These are usually known as AOVs (Arbitrary Output Values) in other render engines, but Malt supports custom inputs too. ![](2022-03-21-18-11-27.png) > Custom IO can be set up from the Input/Output node properties panel. > > Modifying the Custom IO will requires the recompilation of all graphs affected by it, so changes aren't applied automatically. You must click the *Reload* button to apply your changes. These *custom IO* properties are exposed as sockets in nodes that execute these *graphs*. For example, the *Screen Pass* node in *Render Graphs* will expose as sockets the *custom IO* from the *Screen Shader* it's using. ![](2022-03-21-18-12-34.png) Some graph types (like Mesh) share their custom IO properties across all the materials in a scene, while others (like Screen) can be diferent for each graph. ## Graph Types ### Mesh > [Mesh Graph Reference](../../reference/Mesh-graph) Mesh Graphs define the surface properties of an object. These are the equivalent to *Blender Material Nodes*. Mesh Graphs are always used as part of a Material attached to an Object. #### Passes & Outputs Multiple shader programs are generated from a single Mesh Graph: the Shadow Pass, the Pre Pass and the Main Pass. Each of them consisting of a vertex and a pixel shader. The vertex shaders are generated from the CUSTOM_VERTEX_SHADER Output and the DISPLACEMENT_SHADER Output. The Shadow Pass and the Pre Pass pixel shaders are generated from the PRE_PASS_PIXEL_SHADER Output and the DEPTH_OFFSET output. The Main Pass pixel shader is generated from the MAIN_PASS_PIXEL_SHADER Output and the DEPTH_OFFSET output. ```mermaid graph LR MG{Mesh Graph} classDef Graph fill:#4602bd, color:#fff, stroke-width:5px; class MG Graph; CVO[[COMMON VERTEX SHADER Output]] VDO[[VERTEX DISPLACEMENT SHADER Output]] PPO[[PRE PASS PIXEL SHADER Output]] MPO[[MAIN PASS PIXEL SHADER Output]] DOO[[DEPTH OFFSET Output]] classDef Output fill:#222, color:#fff, stroke-width:2px; class CVO,VDO,PPO,MPO,DOO Output; SP((Shadow Pass)) PP((Pre Pass)) MP((Main Pass)) classDef Pass fill:#d43c00, color:#fff, stroke-width:4px, stroke:#fff; class SP,PP,MP Pass; SMT(Shadow Maps) NDT(Normal & Depth) IDT(IDs) classDef Texture fill:#7f00d4, color:#fff, stroke-width:2px, stroke:#000; class SMT,NDT,IDT Texture; MG ===> CVO & VDO & PPO & MPO & DOO CVO & VDO & PPO ---> SP CVO & VDO & PPO & DOO --> PP CVO & VDO & MPO & DOO --> MP SP -.-> SMT -.-> PP & MP PP -.-> NDT & IDT -.-> MP ``` ### Light > [Light Graph Reference](../../reference/Light-graph) Light Graphs define the color projected by a light at any given point. While most lights don't need a custom shader, this can be useful for certain effects, like the light emited from a projector or a flashlight. Lights with custom shaders are more expensive to render. Light shaders don't take shadowmaps into account, shadows are handled by Mesh shaders. Light Graphs are always used as part of a Material attached to a Light. ### Screen > [Screen Graph Reference](../../reference/Screen-graph) Screen Graphs define screen-space shaders. The most common use case for screen-space shaders are post-processing effects, like color correction, or bloom. However they can also fit other use cases, like deferred shading or precalculating textures, like AO, before the Main Pass. Screen Graphs are always used as part of a Material, usually asigned to a Screen Pass node in a Render or Render Layer graph. ### Render & Render Layers > [Render Graph Reference](../../reference/Render-graph) > [Render Layer Graph Reference](../../reference/Render Layer-graph) Render nodes define the steps for rendering a frame, from preparing the shadowmaps to performing the image antialiasing. Most render engines only allow users to edit the render pipeline after the scene has been drawn, in the form of post-processing/compositing, but Malt exposes the full process for customization. *Render graphs* have the *RenderLayers* node, that executes a *Render Layer* graph. The scene geometry is drawn inside *Render Layer*, using *Depth Peeling*. #### Depth Peeling Real-time rendering often relies heavily on screen-space techniques, however this usually doesn't work with transparent objects. Malt renders opaque objects first, and then transparent objects in multiple overlapping layers (a.k.a. *Depth Peeling*), so every type of object can follow the same render path. However, it's important for screen-space shaders to avoid accidentaly covering back layers from the front ones. For this purpuse, the *Screen Node* has the *Layer Only* setting. ================================================ FILE: docs/Documentation/Plugins.md ================================================ # Plugins Render pipelines can be customized through plugins. Plugins can: - Add new node libraries to *Pipeline Graphs*. - Add new *Pipeline Parameters*. - Add new *PipelineGraph types*. Plugins can be installed globally in the *Addon Settings* or per world in the *World Panel*. A basic example can be found in [Development/plugins](https://github.com/bnpr/Malt/tree/Development/plugins) > The Plugins path should point to the plugins parent folder, then any plugin that you drop into that folder should be automatically loaded. > For example, the Plugins path shouldn't point to `C:/Malt-plugins/My-plugin-example/`, it should point to `C:/Malt-plugins/` ================================================ FILE: docs/Documentation/Settings.md ================================================ # Setup & Settings ## Addon Settings ![](2022-03-21-19-27-33.png) - **Open Session Log** >Opens the current session log in a text editor. - **Global Plugins** >The path to the *plugins* folder. See [Plugins](../Plugins) for more info. - **Show sockets in Material Panel** >Show node socket properties in the Material Panel by default. - **Max Viewport Render Framerate** >Framerate cap for the viewport. Limiting *Blender* framerate can improve *Malt* performance and animation playback stability. Set it to 0 to disable it. - **Auto setup VSCode** >On file save, setups a VSCode project on your *.blend* file folder. - **RenderDoc Path** >Path to the **renderdoccmd** executable, for [RenderDoc](https://renderdoc.org/) debugging. - **Debug Mode** >Include debug info in the *Session Logs*. Enabling it increases the log sizes and can negatively affect performance, don't enable it unless a developer asks you for it in a bug report. ## Pipeline Configuration Settings The *Pipeline Configuration Settings* contain the settings to select and setup the *pipeline* itself. In *Malt*, the *pipeline* configuration and render settings are part of the *World* properties. This allows sharing the same setup across *Scenes* and *.blend* files. ![](2022-02-16-17-08-23.png) - **Malt Pipeline** >The path to a custom *render pipeline*. If it's empty (the default), the *NPR Pipeline* will be used. >The button at the right is the *Reload Pipeline* operator, which fully restarts the renderer. - **Local Plugins** >The path to the *World* specific *plugins* folder. See [Plugins](../Plugins) for more info. - **Bit Depth (Viewport)** >The viewport image *bit depth*. Higher *bit depths* can yield better image quality (avoiding *banding* and *clamping*), but can bottleneck your *GPU<->CPU* bandwith at high resolutions. >Final renders are always sent to *Blender* as 32bit images for best quality, regardless of this setting. ### Material Settings - **Shader Source** >GLSL source file for the material. *(Ignored when a Node Tree is selected)* - **Node Tree** >*Node Tree* used for this material. The material panel will also show the parameters declared in its *Shader/Node Tree*. In code based materials, "ALL CAPS" uniforms and uniforms starting with an underscore "_" are treated as "private" and won't be shown in the material panel. For node based materials, visibility can be toggled from the node *UI*. ![](2022-03-21-19-36-40.png) ### Light Settings - **Color** >The light color if no custom shader is in use. - **Radius** >The area of effect radius for *Point* and *Spot Lights.* - **Angle** >*Spot light* cone angle. - **Blend** >*Spot light* cone gradient angle. ## Pipeline Settings The settings created by the active *pipeline* and *plugins* can be found in the *Properties Panel* inside a *Malt Settings* menu. [Pipeline Settings Reference](/reference/settings). ![](2022-02-16-17-05-31.png) ### Parameter Overrides *Malt* uses different performance profiles, allowing you to choose the appropiate performance/quality tradeoff for different tasks. > For example, you may want to use lower quality settings while modeling or animating, higher quality settings for material lookdev, and even higher for the final render. Each setting can have a different value for each profile. There are 3 performance profiles: - **Default** Used when the *Blender Viewport Shading Mode* is set to *Render* or when there's no active override. It's best to set Default settings at a quality level that provides a close result to the final render, while keeping a reasonable performance for viewport navigation. - **Preview** Used when the *Blender Viewport Shading Mode* is set to *Preview*. It's best to set Preview settings at a quality level that allows you to edit assets and play animations in real time. - **Final Render** Used for the final render (*F12*). For setting that are too heavy for the viewport, but needed for the final render quality. Any setting can be overridden for a specific profile by clicking on the *Override* button at its right. Otherwise the *Default* profile value will be used. ![](parameter-override.gif) ================================================ FILE: docs/Documentation/Tooling.md ================================================ # External Tools ## VSCode *Malt* has built-in integration with [VSCode](https://code.visualstudio.com), including a workaround to provide GLSL intellisense via C++ language servers. When *Malt* is the active render engine, it will auto-setup a *VSCode* workspace on the same folder you save your *.blend* file. >*(This feature can be disabled in the Addon Preferences)*
## RenderDoc *Malt* has built-in integration with [RenderDoc](https://renderdoc.org). It can be enabled by setting path to the **renderdoccmd** executable in the addon settings. 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. ![](2022-04-02-18-02-24.png) Captures can be triggered directly through the Blender UI: ![](2022-03-21-15-36-42.png) ================================================ FILE: docs/FAQ.md ================================================ # FAQ # What's the difference between Malt and BEER? | | Malt | BEER | |---|------|------| | **Target Audience** | Advanced users | Everyone | | **Worflow** | Code & Nodes | Layers/Stacks | | **Backend** | OpenGL | Malt | | **Project Lead** | [Miguel Pozo](https://twitter.com/pragma37) | [LightBWK](https://twitter.com/Lightbwk) | | **Main Developer** | [Miguel Pozo](https://twitter.com/pragma37) | *TBD* | | **Funding** | [Patreon](https://patreon.com/pragma37) | [BEER Dev Fund](https://blendernpr.org/beer/) | # Will Malt be ported to Vulkan? The most likely replacement for *OpenGL* in *Malt* is WebGPU Native, since it's easier to use than *Vulkan* and has better *MacOS* support. # Can Malt be used for games? Not at the moment. Malt main focus is animation and illustration and it prioritizes flexibility and image quality, which usually comes at a performance cost. # How can I help? - Share your *Malt* artworks! [*#malt3d*](https://twitter.com/hashtag/malt3d) - Make tutorials. - Implement new features. - Join the [Patreon](https://www.patreon.com/pragma37). ================================================ FILE: docs/From-Nodes-To-Code/From-Nodes-To-Code.md ================================================ # From Nodes To Code This tutorial will teach you how to make your own *Malt* shaders. *Malt* shaders are written in *GLSL* (OpenGL Shading Language), the shader programming language used by *OpenGL* and *Vulkan*. This tutorial assumes you are already familiar with *EEVEE* nodes. You may be surprised by how much of your knowledge about node based programming can easily be applied to code and how much more powerfull and convenient can be for advanced tasks. ## Before we start Shader files are just plain text, so any text editor works for writing them. But we can make our lifes much easier by using a code editor. *Malt* has built-in integration with [VSCode](https://code.visualstudio.com/download), it's free, cross-platform and open source so go ahead, download and install it!
After installation: 1. Open Blender, change the render engine to *Malt* and save the *.blend* file in a new folder. (When *Malt* is active it will auto-setup a *VSCode* workspace on the same folder you save your *.blend* file) 2. Open the folder in the OS file explorer and *Right Click > Open with Code*. 3. Create a new file and save it as ```malt-tutorial.mesh.glsl```. (or any othe name, but make sure it ends in ```.mesh.glsl```) 4. VSCode will tell you it has extensions for that file format and will ask you if you want to install them, say yes! 5. Select the shader file as the *Shader Source* for a new material (*Material Properties* Panel). Now we can make changes in our shader file and as soon as we save it (*Ctrl+S*) Malt will reload it. ## First Contact Let's start by looking at some basic *Malt* examples and what would be their *EEVEE* counterparts, so you can see how they compare to each other. Read the code, but don't worry if you don't really understand it, we will take a more detailed look at it later. To get a better feel of it, let's test this examples as we go. You could just copy-paste them, but you will get a better grasp by typing them yourself. It will also serve to get familiar with the extra goodies VSCode provides, like code auto-completion. If you miss-type something, you will see an error pop up in the Material panel inside *Blender* that will tell you what and where the error is. Try to play with the examples, modify them, break them on purpose and take a look at the errors. ### Starting Point (an empty shader)
```glsl #include "NPR_Pipeline.glsl" void COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO) { } ```
![](starting-point.png)
*Just an empty black shader.* *Even the most advanced procedural shader you can imagine needs this.* ### Flat Color
```glsl #include "NPR_Pipeline.glsl" uniform vec3 color = vec3(0,1,0); void COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO) { PO.color.rgb = color; } ```
![](flat-color.png)
*See how a new color property has appeared in your Material panel?* *Could you change the default green value ```vec3(0,1,0)``` in the code to red?* *What happens if you set ```vec3(0.5)``` as the default value?* > If you have edited a property in the UI, you can always *Right Click > Reset to Defaul Value* ### Textures
```glsl #include "NPR_Pipeline.glsl" uniform int uv_channel = 0; uniform sampler2D color_texture; void COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO) { vec2 texture_coordinates = S.uv[uv_channel]; vec4 sampled_color = texture(color_texture, texture_coordinates); PO.color = sampled_color; } ```
![](textures.png)
*In programming, the first element from a list is the number 0, so the first UV from your mesh is the number 0.* *It's not that weird if you think of it as the offset from the start of the list.* ### Lighting
```glsl #include "NPR_Pipeline.glsl" uniform vec3 color = vec3(0,1,0); void COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO) { PO.color.rgb = color * get_diffuse(); } ```
![](lighting.png)
*See how we can multiply colors just by using the multiply sign ```color * get_diffuse()``` ?* *The same goes for addition (+), subtraction (-) and division (/).* *Isn't that cool?* *You can even combine them in the same line and use parenthesis, like ```vec3 result = vec3(1) - (A+B+C) / 3.0;```.`* *Now compare it with the same formula in node form!* ### Recap It's time for something that looks a bit more like an actual material. Let's combine the previous examples!
```glsl #include "NPR_Pipeline.glsl" uniform vec3 ambient_color = vec3(0.5,0.5,0.5); uniform int uv_channel; uniform sampler2D color_texture; void COMMON_PIXEL_SHADER(Surface S, inout PixelOutput PO) { vec3 light_color = get_diffuse() + ambient_color; vec2 texture_coordinates = S.uv[uv_channel]; vec4 surface_color = texture(color_texture, texture_coordinates); vec3 result = surface_color.rgb * light_color; PO.color.rgb = result; } ```
![](all-together.png)
Woah! That was a lot of information, right? It's ok if you don't feel like you actually understand it as long as you have a rough intuition of what's going on. Now would be a good time to look at the [Shader Examples](https://github.com/bnpr/Malt/tree/master/Shader%20Examples). Each of them implements a single feature, why don't you try to mix some of them in the same shader? For example, a gradient material with outlines, rim lights and ambient occlusion. ## GLSL On the last chapter we have relied on examples and intuition. You could continue just by copy-pasting and combining examples, but you would always feel out of control. Luckily GLSL is a very small language, so there's not really a lot of details to learn. There are 2 main concepts you should understand, *Functions* and *Variables* . *Functions* are basically the same as nodes, they take some parameters and gives you a result. Instead of connecting *Functions* with wires, we give names to their results, so we can refer to them later. Those names where we store results are *Variables*. ### Comments Before we go further, let's start with an easy one. Comments! ```glsl // This is a comment! // Comments are ignored by the computer, they are for the people reading the code. // Any line that starts with 2 dashes "//" is a comment. ``` ### Variables *Variables* are like node sockets, they have a type, a name and a value. ```glsl // We create variables by writing their type, followed by their name and a semicolor: // type name; // The name can be whatever you want as long as it goes all together; // type ThisReallyLong_and_w3iRd0_name_is_OK_1234; // We can optionally assign them a value when we create it: // type name = value; // Once a variable has been created, you can change its value as many times as you want. // name = other_value; // The types we use the most are floats and vectors // Floats are just computer lingo for numbers. float some_number = 1; // They can be negative too some_number = -1; // Have decimals some_number = -1.5; // And can be as big as you want some_number = 176189672672868126.71671861876871678; // Floats can be added, subtracted, multiplied and divided float a = 1; float b = 2; some_number = a + b; // Now some_number is 3 some_number = a - b; // Now some_number is -1 some_number = a * b; // Now some_number is 2 some_number = a / b; // Now some_number is 0.5 // Vectors are a group of 2, 3 or 4 floats // We use them for positions and normals vec2 position_2d = vec2(1,2); vec3 position_3d = vec3(1,2,3); vec3 up_normal = vec3(0,0,1); // And colors too vec3 green = vec3(0,1,0); vec4 semi_transparent_red = vec4(1,0,0,0.5); // We can read their individual properties by typing the vector name followed by a dot (.) and the name of the property float x_position = position_3d.x; //1 float y_position = position_3d.y; //2 float z_position = position_3d.z; //3 // It also works for writing position_3d.x = 0; // Now position_3d is (0,2,3) // Since vectors are used for colors too, this is also valid: float red_channel = green.r; float green_channel = green.g; float blue_channel = green.b; float alpha_channel = semi_transparent_red.a; // Using rgba or xyzw as properties name is just a matter of preference, they are 100% equivalent vec3 some_color = vec3(1,2,3); some_color.x = 0; //Now some_color.r is 0 // Additionaly this kind of combinations are also valid position_2d = position_3d.xy; // (0, 2) some_color = semi_transparent_red.rgb; //(0,1,0) ``` ### Functions *Functions* are equivalent to nodes, they take some parameters and gives you a result. ```glsl // We declare functions by typing the type of value they return, // followed by its name and a pair of parenthesis. vec3 give_me_a_green_color() { vec3 green_color = vec3(0,1,0); // We return a value by typing the keyword "return" followed by the return value and a semicolor. return green_color; } // If a function doesn't return any value, its type is "void". // We will see later why functions that don't return a value can also be useful. void example_function() { //To use a function (a.k.a. call a function) we type its name followed by a pair of parenthesis. vec3 color_result = give_me_a_green_color(); // color_result is now (0,1,0) } // If a funtion takes input parameters, we write their type and name inside the parenthesis. // Each parameter must be separated by commas. float add_two_numbers(float first_number, float second_number) { float result = first_number + second_number; return result; } void another_example_function() { float A = 1; float B = 2; // For calling a function that takes parameters, we write their values separated by commas. float C = add_two_numbers(A, B); // C is now 3. } // Functions can also have output parameters void give_me_two_colors(inout vec3 first_result, inout vec3 second_result) { first_result = vec3(1,0,0); second_result = vec3(0,1,0); } void third_example() { vec3 A; vec3 B; give_me_two_colors(A, B); // Now A is (1,0,0) and B is (0,1,0). } ``` >If you reached here, congrats\! >You can share your questions and feedback in this [thread](https://github.com/bnpr/Malt/discussions/46)\. >I'm working on new chapters, meanwhile, [The Book of Shaders](https://thebookofshaders.com/) is a great resource\! ================================================ FILE: docs/Setup-BlenderMalt-for-Development.md ================================================ # How to Setup BlenderMalt for Development - Clone the repository. `git clone https://github.com/bnpr/Malt.git` - Set a user scripts folder for Blender if you don't have one. [*Blender > Preferences > File Paths > Scripts*](https://docs.blender.org/manual/en/latest/editors/preferences/file_paths.html) - Locate the *Python* executable from your *Blender* installation. For example: ```"C:\Program Files\Blender Foundation\Blender 2.93\2.93\python\bin\python.exe"``` - Run: ``` /scripts/setup_blender_addon.py --scripts-folder ``` > The setup script will try to compile the CBlenderMalt and the Bridge ipc libraries using CMake, > if you don't have the required toolchain you can just copy them from the Github release. Now if you restart *Blender* and go to *Preferences > Add-ons*, you should be able to enable *BlenderMalt*. ================================================ FILE: docs/extra/extra.css ================================================ /* https://github.com/squidfunk/mkdocs-material/issues/1635#issuecomment-811058692 */ [data-md-color-scheme="slate"] { --md-default-bg-color: #24272e; } /*Disable the edit button*/ .md-content__button { display: none; } .zoom { /*transition: transform ease-in-out 0.5s;*/ cursor: zoom-in; } .image-zoom-large { width: auto; max-width: 100vw; max-height: 100vh; /*transform: scale(1.5);*/ cursor: zoom-out; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); z-index: 100; position: absolute; left: 50%; transform: translate(-50%, 0); } ================================================ FILE: docs/extra/extra.js ================================================ /* https://github.com/squidfunk/mkdocs-material/issues/1635#issuecomment-811058692 */ document.querySelectorAll('.zoom').forEach(item => { item.addEventListener('click', function () { this.classList.toggle('image-zoom-large'); }) }); ================================================ FILE: docs/index.md ================================================ --- hide: #- toc #- navigation --- # Malt ***Malt*** is a fully ***customizable real-time rendering*** framework for animation and illustration. 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***. 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. ---
@pragma37
--- - Free and **Open Source**. *MIT License* - **Real Time Rendering** - Full **Blender** integration - Built-in Pipeline for **Stylized Non Photorealistic Rendering** - Stylized shading models - Light Groups - Line Rendering - Fully customizable through nodes *(Materials, Light shaders, Screen shaders and even the render pipeline itself)* - Code as a *First Class Citizen* - *Auto-reloading* for everything - **VSCode** (including **GLSL** *intellisense*) and **Renderdoc** integration - Automatic generation of nodes from plain GLSL functions - Automatic UI for Shader and Pipeline parameters - 100% customizable Python Render Pipelines ---
@pragma37
---
@Renato3xl
================================================ FILE: docs/overrides/partials/_footer.html ================================================ ================================================ FILE: docs/reference/Light-graph.md ================================================ # Light Graph Reference --- ## Input --- ### **Geometry** - **Inputs** - **Space** *: ( int | ENUM(Object,World,Camera,Screen) ) - default = 1* - **IOR** *: ( float ) - default = 1.45* - **Outputs** - **Position** *: ( vec3 )* - **Incoming** *: ( vec3 )* - **Normal** *: ( vec3 ) - default = NORMAL* - **True Normal** *: ( vec3 )* - **Is Backfacing** *: ( bool )* - **Facing** *: ( float )* - **Fresnel** *: ( float )* - **Reflection** *: ( vec3 )* - **Refraction** *: ( vec3 )* --- ### **Camera Data** - **Outputs** - **View Direction** *: ( vec3 )* - **Screen UV** *: ( vec2 )* - **Z Depth** *: ( float )* - **View Distance** *: ( float )* - **Camera Position** *: ( vec3 )* - **Camera Matrix** *: ( mat4 )* - **Projection Matrix** *: ( mat4 )* - **Is Orthographic** *: ( bool )* --- ### **Render Info** - **Outputs** - **Resolution** *: ( vec2 )* - **Current Sample** *: ( int )* - **Sample Offset** *: ( vec2 )* --- ### **Time Info** - **Outputs** - **Time** *: ( float )* - **Frame** *: ( int )* --- ### **Random** - **Inputs** - **Seed** *: ( float )* - **Outputs** - **Per Object** *: ( vec4 )* - **Per Sample** *: ( vec4 )* - **Per Pixel** *: ( vec4 )* --- ## Parameters --- ### **Boolean** - **Inputs** - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- ### **Float** - **Inputs** - **F** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **Integer** - **Inputs** - **I** *: ( int )* - **Outputs** - **result** *: ( int )* --- ### **Vector 2D** - **Inputs** - **V** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- ### **Vector 3D** - **Inputs** - **V** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- ### **Vector 4D** - **Inputs** - **V** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- ### **RGB Color** - **Inputs** - **V** *: ( vec3 | Color )* - **Outputs** - **result** *: ( vec3 )* --- ### **RGBA Color** - **Inputs** - **V** *: ( vec4 | Color )* - **Outputs** - **result** *: ( vec4 )* --- ### **Color Ramp** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **Outputs** - **result** *: ( sampler1D )* --- ### **Image** - **Inputs** - **Image** *: ( sampler2D )* - **Outputs** - **result** *: ( sampler2D )* --- ## Math --- ### **Hash** - **Inputs** - **V** *: ( vec4 | Data )* - **Outputs** - **result** *: ( vec4 )* --- ### **Quaternion** --- #### **From Axis Angle** - **Inputs** - **Axis** *: ( vec3 | Normal )* - **Angle** *: ( float | Angle )* - **Outputs** - **result** *: ( vec4 )* --- #### **From Vector Delta** - **Inputs** - **From** *: ( vec3 | Normal )* - **To** *: ( vec3 | Normal )* - **Outputs** - **result** *: ( vec4 )* --- #### **Inverted** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Transform** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Vector** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix** - **Inputs** - **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Factor** *: ( float | Slider ) - default = 0.5* - **Outputs** - **result** *: ( vec4 )* --- ### **Matrix** --- #### **From Translation** - **Inputs** - **T** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( mat4 )* --- #### **From Quaternion** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( mat4 )* --- #### **From Euler** - **Inputs** - **E** *: ( vec3 | Euler )* - **Outputs** - **result** *: ( mat4 )* --- #### **From Scale** - **Inputs** - **S** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( mat4 )* --- #### **Is Orthographic** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( bool )* --- #### **Inverse** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( mat4 )* --- #### **Multiply** - **Inputs** - **A** *: ( mat4 ) - default = mat4(1)* - **B** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( mat4 )* --- ### **Boolean Logic** --- #### **And** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Or** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Not** - **Inputs** - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Equal** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( bool )* - **If False** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- ### **Float** --- #### **Add** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Subtract** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Multiply** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Divide** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Value** *: ( float ) - default = 0.5* - **From Min** *: ( float )* - **From Max** *: ( float ) - default = 1.0* - **To Min** *: ( float )* - **To Max** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( float )* --- #### **Modulo** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Power** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Square Root** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Round** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Fractional Part** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Floor** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Ceil** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Clamp** - **Inputs** - **A** *: ( float )* - **Min** *: ( float )* - **Max** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Sign** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Absolute** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Minimum** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Maximum** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Mix** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Cosine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Tangent** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arcsine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arcosine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arctangent** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Radians to Degrees** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Degrees to Radians** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **E** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **E** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( bool )* --- #### **Greater** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Greater or Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Less** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Less or Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( float )* - **If False** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **Integer** --- #### **Add** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Subtract** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Multiply** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Divide** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Modulo** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Clamp** - **Inputs** - **A** *: ( int )* - **Min** *: ( int )* - **Max** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Sign** - **Inputs** - **A** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Absolute** - **Inputs** - **A** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Minimum** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Maximum** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Greater** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Greater or Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Less** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Less or Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( int )* - **If False** *: ( int )* - **Outputs** - **result** *: ( int )* --- ### **Vector 2D** --- #### **Add** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Divide** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Scale** - **Inputs** - **A** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **UV** *: ( vec2 ) - default = vec2(0.5)* - **From Min** *: ( vec2 ) - default = (0.0, 0.0)* - **From Max** *: ( vec2 ) - default = (1.0, 1.0)* - **To Min** *: ( vec2 ) - default = (0.0, 0.0)* - **To Max** *: ( vec2 ) - default = (1.0, 1.0)* - **Outputs** - **result** *: ( vec2 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Power** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Distort** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Round** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Floor** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Snap** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec2 )* - **Min** *: ( vec2 )* - **Max** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Sign** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Min** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Max** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Mix 2D** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Factor** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Mix** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Length** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Rotate** - **Inputs** - **A** *: ( vec2 )* - **Angle** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Angle** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec2 )* - **If False** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Combine** - **Inputs** - **X** *: ( float )* - **Y** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Separate** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **X** *: ( float )* - **Y** *: ( float )* --- ### **Vector 3D** --- #### **Add** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Divide** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Scale** - **Inputs** - **A** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Vector** *: ( vec3 ) - default = vec3(0.5)* - **From Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **From Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **To Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **To Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Power** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Distort** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Round** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Floor** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Snap** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec3 | Vector )* - **Min** *: ( vec3 | Vector )* - **Max** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Sign** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Min** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Max** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix 3D** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Factor** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Length** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Cross Product** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Reflect** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Refract** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Ior** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Faceforward** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **C** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Sine** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Rotate Euler** - **Inputs** - **A** *: ( vec3 | Vector )* - **Euler** *: ( vec3 | Euler )* - **Invert** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- #### **Rotate Axis Angle** - **Inputs** - **A** *: ( vec3 | Vector )* - **Axis** *: ( vec3 | Vector ) - default = (0.0, 0.0, 1.0)* - **Angle** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Angle** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec3 | Vector )* - **If False** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Combine** - **Inputs** - **X** *: ( float )* - **Y** *: ( float )* - **Z** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Separate** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **X** *: ( float )* - **Y** *: ( float )* - **Z** *: ( float )* --- ### **Vector 4D** --- #### **Add** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Divide** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Scale** - **Inputs** - **A** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Vector** *: ( vec4 ) - default = vec4(0.5)* - **From Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)* - **From Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)* - **To Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)* - **To Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Power** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Distort** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Round** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Floor** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec4 | Vector )* - **Min** *: ( vec4 | Vector )* - **Max** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Sign** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Min** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Max** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Mix 4D** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Factor** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Mix** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Length** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Angle** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Vec4 If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec4 | Vector )* - **If False** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Combine** - **Inputs** - **R** *: ( float )* - **G** *: ( float )* - **B** *: ( float )* - **A** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Combine Color** - **Inputs** - **C** *: ( vec3 | Color )* - **A** *: ( float | Slider ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- #### **Separate** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **R** *: ( float )* - **G** *: ( float )* - **B** *: ( float )* - **A** *: ( float )* --- #### **Separate Color** - **Inputs** - **A** *: ( vec4 | Color )* - **Outputs** - **C** *: ( vec3 )* - **A** *: ( float )* --- ## Vector --- ### **Surface Gradient From Normal** - **Inputs** - **Base Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Custom Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- ### **Normal From Surface Gradient** - **Inputs** - **Base Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Surface Gradient** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **Outputs** - **result** *: ( vec3 )* --- ### **Matrix** --- #### **Transform Point** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Project Point** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Project Point To Screen Coordinates** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Transform Direction** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Direction** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Transform Normal** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Normal** *: ( vec3 | Normal )* - **Outputs** - **result** *: ( vec3 )* --- ### **Pixel Size in World Space** - **Inputs** - **Depth** *: ( float ) - default = pixel_depth()* - **Outputs** - **result** *: ( float )* --- ### **Transform** - **Inputs** - **Type** *: ( int | ENUM(Point,Vector,Normal) )* - **From** *: ( int | ENUM(Object,World,Camera) )* - **To** *: ( int | ENUM(Object,World,Camera,Screen) )* - **Vector** *: ( vec3 | Vector )* - **Outputs** - **Vector** *: ( vec3 | Vector )* --- ### **Mapping** --- #### **Point** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Location** *: ( vec3 | Vector )* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Texture** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Location** *: ( vec3 | Vector )* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Vector** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Normal** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- ## Color --- ### **Alpha Blend** Blends the blend color as a layer over the base color. - **Inputs** - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* >The blend color. - **Outputs** - **result** *: ( vec4 )* --- ### **Grayscale** - **Inputs** - **Color** *: ( vec3 )* - **Outputs** - **result** *: ( float )* --- ### **Linear To sRGB** - **Inputs** - **Linear** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **sRGB To Linear** - **Inputs** - **Srgb** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **RGB To HSV** - **Inputs** - **Rgb** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **HSV To RGB** - **Inputs** - **Hsv** *: ( vec3 | HSV )* - **Outputs** - **result** *: ( vec3 )* --- ### **HSV Edit** - **Inputs** - **Color** *: ( vec4 )* - **Hue** *: ( float )* - **Saturation** *: ( float )* - **Value** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Bright/Contrast** - **Inputs** - **Color** *: ( vec4 )* - **Brightness** *: ( float )* - **Contrast** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Gamma** - **Inputs** - **Color** *: ( vec4 )* - **Gamma** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- ### **Invert** - **Inputs** - **Color** *: ( vec4 )* - **Fac** *: ( float | Slider )* - **Outputs** - **result** *: ( vec4 )* --- ### **Color Gradient** --- #### **Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **U** *: ( float | Slider )* - **Outputs** - **result** *: ( vec4 )* --- #### **RGB Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **UVW** *: ( vec3 | Slider )* - **Outputs** - **result** *: ( vec3 )* --- #### **RGBA Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **UVWX** *: ( vec4 | Slider )* - **Outputs** - **result** *: ( vec4 )* --- ### **Layer Blend** --- #### **Normal** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Add** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Overlay** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Screen** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Darken** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Lighten** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Soft Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Hard Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Linear Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Dodge** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Burn** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Subtract** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Difference** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Divide** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Hue** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Saturation** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Value** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Color** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- ## Texturing --- ### **Image** - **Inputs** - **Image** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Smooth Interpolation** *: ( bool ) - default = True* - **Outputs** - **Color** *: ( vec4 )* - **Resolution** *: ( vec2 )* --- ### **Normal Map** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **UV Index** *: ( int )* - **Outputs** - **Normal** *: ( vec3 )* --- ### **Flipbook** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Dimensions** *: ( ivec2 )* - **Page** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Flowmap** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Flow** *: ( vec2 ) - default = vec2(0.0)* - **Progression** *: ( float )* - **Samples** *: ( int ) - default = 2* - **Outputs** - **result** *: ( vec4 )* --- ### **Matcap** - **Inputs** - **Matcap** *: ( sampler2D )* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **Color** *: ( vec4 )* - **Uv** *: ( vec2 )* --- ### **HDRI** - **Inputs** - **Hdri** *: ( sampler2D )* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **Color** *: ( vec4 )* - **Uv** *: ( vec2 )* --- ### **Noise** --- #### **Infinite** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Detail** *: ( float ) - default = 3.0* - **Balance** *: ( float | Slider ) - default = 0.5* - **Outputs** - **result** *: ( vec4 )* --- #### **Tiled** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Detail** *: ( float ) - default = 3.0* - **Balance** *: ( float | Slider ) - default = 0.5* - **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)* - **Outputs** - **result** *: ( vec4 )* --- ### **Voronoi** --- #### **Infinite** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Outputs** - **Cell Color** *: ( vec4 )* - **Cell Position** *: ( vec4 )* - **Cell Distance** *: ( float )* --- #### **Tiled** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)* - **Outputs** - **Cell Color** *: ( vec4 )* - **Cell Position** *: ( vec4 )* - **Cell Distance** *: ( float )* --- ### **Bayer Pattern** - **Inputs** - **Size** *: ( int | ENUM(2x2,3x3,4x4,8x8) ) - default = 2* - **Texel** *: ( vec2 ) - default = vec2(screen_pixel())* - **Outputs** - **result** *: ( float )* --- ### **Gradient** --- #### **Linear** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Quadratic** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Easing** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Diagonal** - **Inputs** - **UV** *: ( vec2 ) - default = UV[0]* - **Outputs** - **result** *: ( float )* --- #### **Spherical** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = POSITION* - **Outputs** - **result** *: ( float )* --- #### **Quadratic Sphere** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = POSITION* - **Outputs** - **result** *: ( float )* --- #### **Radial** - **Inputs** - **UV** *: ( vec2 ) - default = UV[0]* - **Outputs** - **result** *: ( float )* --- ### **Wave** - **Inputs** - **Mode** *: ( int | ENUM(Sine,Saw,Triangle) )* - **Coord** *: ( float ) - default = UV[0].x* - **Scale** *: ( float ) - default = 5.0* - **Phase** *: ( float )* - **Outputs** - **result** *: ( float )* --- ## Shading --- ### **Rim Light** - **Inputs** - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Angle** *: ( float )* - **Rim Length** *: ( float ) - default = 2.0* - **Length Falloff** *: ( float )* - **Thickness** *: ( float ) - default = 0.1* - **Thickness Falloff** *: ( float )* - **Outputs** - **result** *: ( float )* --- ## Filter --- ### **Blur** --- #### **Box Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Circular** *: ( bool )* - **Outputs** - **result** *: ( vec4 )* --- #### **Gaussian Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Sigma** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- #### **Jitter Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Samples** *: ( int ) - default = 8* - **Outputs** - **result** *: ( vec4 )* --- #### **Bilateral** - **Inputs** - **Input Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5* - **Sigma** *: ( float ) - default = 10.0* - **BSigma** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( vec4 )* --- #### **Orientation-Aligned Bilateral** - **Inputs** - **Input Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Flow** *: ( vec2 ) - default = vec2(0)* - **Radius** *: ( float ) - default = 6.0* - **Smoothness** *: ( float ) - default = 0.55* - **Outputs** - **result** *: ( vec4 )* --- ### **Sharpen** --- #### **Box** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Circular** *: ( bool )* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- #### **Gaussian** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Sigma** *: ( float ) - default = 1.0* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- #### **Jitter** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Samples** *: ( int ) - default = 8* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- ### **Curvature** - **Inputs** - **Normal Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Width** *: ( float ) - default = 1.0* - **X** *: ( vec3 | Normal ) - default = (1.0, 0.0, 0.0)* - **Y** *: ( vec3 | Normal ) - default = (0.0, 1.0, 0.0)* - **Outputs** - **result** *: ( float )* --- ### **Kuwahara** --- #### **Isotropic** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Size** *: ( int ) - default = 5* - **Outputs** - **result** *: ( vec4 )* --- #### **Anisotropic** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Direction** *: ( vec2 ) - default = vec2(0.0, 0.0)* - **Size** *: ( float ) - default = 2.0* - **Samples** *: ( int ) - default = 50* - **Outputs** - **result** *: ( vec4 )* ================================================ FILE: docs/reference/Mesh-graph.md ================================================ # Mesh Graph Reference --- ## Input --- ### **Pass Info** - **Outputs** - **Is Main Pass** *: ( bool )* - **Is Pre Pass** *: ( bool )* - **Is Shadow Pass** *: ( bool )* --- ### **Geometry** - **Inputs** - **Space** *: ( int | ENUM(Object,World,Camera,Screen) ) - default = 1* - **IOR** *: ( float ) - default = 1.45* - **Outputs** - **Position** *: ( vec3 )* - **Incoming** *: ( vec3 )* - **Normal** *: ( vec3 ) - default = NORMAL* - **True Normal** *: ( vec3 )* - **Is Backfacing** *: ( bool )* - **Facing** *: ( float )* - **Fresnel** *: ( float )* - **Reflection** *: ( vec3 )* - **Refraction** *: ( vec3 )* --- ### **UV Map** - **Inputs** - **Index** *: ( int )* - **Outputs** - **UV** *: ( vec2 )* --- ### **Tangent** --- #### **UV Map** - **Inputs** - **Uv Index** *: ( int )* - **Outputs** - **Tangent** *: ( vec3 )* - **Bitangent** *: ( vec3 )* --- #### **Radial** - **Inputs** - **Axis** *: ( int | ENUM(X,Y,Z) ) - default = 2* - **Object Space** *: ( bool ) - default = True* - **Outputs** - **Tangent** *: ( vec3 )* - **Bitangent** *: ( vec3 )* --- #### **Procedural UV** - **Inputs** - **Uv** *: ( vec2 )* - **Outputs** - **Tangent** *: ( vec3 )* - **Bitangent** *: ( vec3 )* --- ### **Vertex Color** - **Inputs** - **Index** *: ( int )* - **Outputs** - **Vertex Color** *: ( vec4 )* --- ### **Id** - **Outputs** - **Object Id** *: ( vec4 )* - **Custom Id A** *: ( vec4 )* - **Custom Id B** *: ( vec4 )* - **Custom Id C** *: ( vec4 )* --- ### **Object Info** - **Outputs** - **Position** *: ( vec3 )* - **Rotation** *: ( mat4 )* - **Scale** *: ( vec3 )* - **Id** *: ( vec4 )* - **Matrix** *: ( mat4 )* --- ### **Camera Data** - **Outputs** - **View Direction** *: ( vec3 )* - **Screen UV** *: ( vec2 )* - **Z Depth** *: ( float )* - **View Distance** *: ( float )* - **Camera Position** *: ( vec3 )* - **Camera Matrix** *: ( mat4 )* - **Projection Matrix** *: ( mat4 )* - **Is Orthographic** *: ( bool )* --- ### **Render Info** - **Outputs** - **Resolution** *: ( vec2 )* - **Current Sample** *: ( int )* - **Sample Offset** *: ( vec2 )* --- ### **Time Info** - **Outputs** - **Time** *: ( float )* - **Frame** *: ( int )* --- ### **Random** - **Inputs** - **Seed** *: ( float )* - **Outputs** - **Per Object** *: ( vec4 )* - **Per Sample** *: ( vec4 )* - **Per Pixel** *: ( vec4 )* --- ### **Curve View Mapping** - **Inputs** - **Scale** *: ( vec2 ) - default = (1.0, 1.0)* - **Outputs** - **UV** *: ( vec2 )* - **Facing** *: ( float )* --- ## Parameters --- ### **Boolean** - **Inputs** - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- ### **Float** - **Inputs** - **F** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **Integer** - **Inputs** - **I** *: ( int )* - **Outputs** - **result** *: ( int )* --- ### **Vector 2D** - **Inputs** - **V** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- ### **Vector 3D** - **Inputs** - **V** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- ### **Vector 4D** - **Inputs** - **V** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- ### **RGB Color** - **Inputs** - **V** *: ( vec3 | Color )* - **Outputs** - **result** *: ( vec3 )* --- ### **RGBA Color** - **Inputs** - **V** *: ( vec4 | Color )* - **Outputs** - **result** *: ( vec4 )* --- ### **Color Ramp** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **Outputs** - **result** *: ( sampler1D )* --- ### **Image** - **Inputs** - **Image** *: ( sampler2D )* - **Outputs** - **result** *: ( sampler2D )* --- ## Math --- ### **Hash** - **Inputs** - **V** *: ( vec4 | Data )* - **Outputs** - **result** *: ( vec4 )* --- ### **Quaternion** --- #### **From Axis Angle** - **Inputs** - **Axis** *: ( vec3 | Normal )* - **Angle** *: ( float | Angle )* - **Outputs** - **result** *: ( vec4 )* --- #### **From Vector Delta** - **Inputs** - **From** *: ( vec3 | Normal )* - **To** *: ( vec3 | Normal )* - **Outputs** - **result** *: ( vec4 )* --- #### **Inverted** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Transform** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Vector** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix** - **Inputs** - **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Factor** *: ( float | Slider ) - default = 0.5* - **Outputs** - **result** *: ( vec4 )* --- ### **Matrix** --- #### **From Translation** - **Inputs** - **T** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( mat4 )* --- #### **From Quaternion** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( mat4 )* --- #### **From Euler** - **Inputs** - **E** *: ( vec3 | Euler )* - **Outputs** - **result** *: ( mat4 )* --- #### **From Scale** - **Inputs** - **S** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( mat4 )* --- #### **Is Orthographic** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( bool )* --- #### **Inverse** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( mat4 )* --- #### **Multiply** - **Inputs** - **A** *: ( mat4 ) - default = mat4(1)* - **B** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( mat4 )* --- ### **Boolean Logic** --- #### **And** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Or** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Not** - **Inputs** - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Equal** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( bool )* - **If False** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- ### **Float** --- #### **Add** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Subtract** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Multiply** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Divide** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Value** *: ( float ) - default = 0.5* - **From Min** *: ( float )* - **From Max** *: ( float ) - default = 1.0* - **To Min** *: ( float )* - **To Max** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( float )* --- #### **Modulo** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Power** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Square Root** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Round** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Fractional Part** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Floor** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Ceil** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Clamp** - **Inputs** - **A** *: ( float )* - **Min** *: ( float )* - **Max** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Sign** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Absolute** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Minimum** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Maximum** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Mix** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Cosine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Tangent** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arcsine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arcosine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arctangent** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Radians to Degrees** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Degrees to Radians** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **E** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **E** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( bool )* --- #### **Greater** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Greater or Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Less** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Less or Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( float )* - **If False** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **Integer** --- #### **Add** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Subtract** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Multiply** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Divide** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Modulo** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Clamp** - **Inputs** - **A** *: ( int )* - **Min** *: ( int )* - **Max** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Sign** - **Inputs** - **A** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Absolute** - **Inputs** - **A** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Minimum** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Maximum** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Greater** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Greater or Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Less** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Less or Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( int )* - **If False** *: ( int )* - **Outputs** - **result** *: ( int )* --- ### **Vector 2D** --- #### **Add** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Divide** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Scale** - **Inputs** - **A** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **UV** *: ( vec2 ) - default = vec2(0.5)* - **From Min** *: ( vec2 ) - default = (0.0, 0.0)* - **From Max** *: ( vec2 ) - default = (1.0, 1.0)* - **To Min** *: ( vec2 ) - default = (0.0, 0.0)* - **To Max** *: ( vec2 ) - default = (1.0, 1.0)* - **Outputs** - **result** *: ( vec2 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Power** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Distort** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Round** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Floor** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Snap** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec2 )* - **Min** *: ( vec2 )* - **Max** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Sign** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Min** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Max** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Mix 2D** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Factor** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Mix** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Length** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Rotate** - **Inputs** - **A** *: ( vec2 )* - **Angle** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Angle** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec2 )* - **If False** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Combine** - **Inputs** - **X** *: ( float )* - **Y** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Separate** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **X** *: ( float )* - **Y** *: ( float )* --- ### **Vector 3D** --- #### **Add** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Divide** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Scale** - **Inputs** - **A** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Vector** *: ( vec3 ) - default = vec3(0.5)* - **From Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **From Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **To Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **To Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Power** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Distort** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Round** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Floor** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Snap** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec3 | Vector )* - **Min** *: ( vec3 | Vector )* - **Max** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Sign** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Min** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Max** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix 3D** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Factor** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Length** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Cross Product** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Reflect** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Refract** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Ior** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Faceforward** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **C** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Sine** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Rotate Euler** - **Inputs** - **A** *: ( vec3 | Vector )* - **Euler** *: ( vec3 | Euler )* - **Invert** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- #### **Rotate Axis Angle** - **Inputs** - **A** *: ( vec3 | Vector )* - **Axis** *: ( vec3 | Vector ) - default = (0.0, 0.0, 1.0)* - **Angle** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Angle** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec3 | Vector )* - **If False** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Combine** - **Inputs** - **X** *: ( float )* - **Y** *: ( float )* - **Z** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Separate** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **X** *: ( float )* - **Y** *: ( float )* - **Z** *: ( float )* --- ### **Vector 4D** --- #### **Add** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Divide** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Scale** - **Inputs** - **A** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Vector** *: ( vec4 ) - default = vec4(0.5)* - **From Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)* - **From Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)* - **To Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)* - **To Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Power** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Distort** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Round** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Floor** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec4 | Vector )* - **Min** *: ( vec4 | Vector )* - **Max** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Sign** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Min** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Max** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Mix 4D** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Factor** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Mix** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Length** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Angle** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Vec4 If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec4 | Vector )* - **If False** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Combine** - **Inputs** - **R** *: ( float )* - **G** *: ( float )* - **B** *: ( float )* - **A** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Combine Color** - **Inputs** - **C** *: ( vec3 | Color )* - **A** *: ( float | Slider ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- #### **Separate** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **R** *: ( float )* - **G** *: ( float )* - **B** *: ( float )* - **A** *: ( float )* --- #### **Separate Color** - **Inputs** - **A** *: ( vec4 | Color )* - **Outputs** - **C** *: ( vec3 )* - **A** *: ( float )* --- ## Vector --- ### **Surface Gradient From Normal** - **Inputs** - **Base Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Custom Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- ### **Normal From Surface Gradient** - **Inputs** - **Base Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Surface Gradient** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **Outputs** - **result** *: ( vec3 )* --- ### **Matrix** --- #### **Transform Point** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Project Point** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Project Point To Screen Coordinates** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Transform Direction** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Direction** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Transform Normal** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Normal** *: ( vec3 | Normal )* - **Outputs** - **result** *: ( vec3 )* --- ### **Pixel Size in World Space** - **Inputs** - **Depth** *: ( float ) - default = pixel_depth()* - **Outputs** - **result** *: ( float )* --- ### **Bevel** --- #### **Soft Bevel** - **Inputs** - **Samples** *: ( int ) - default = 32* - **Radius** *: ( float ) - default = 0.02* - **Distribution Exponent** *: ( float ) - default = 2.0* - **Only Self** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- #### **Hard Bevel** - **Inputs** - **Samples** *: ( int ) - default = 32* - **Radius** *: ( float ) - default = 0.01* - **Max Dot** *: ( float ) - default = 0.75* - **Only Self** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- ### **Transform** - **Inputs** - **Type** *: ( int | ENUM(Point,Vector,Normal) )* - **From** *: ( int | ENUM(Object,World,Camera) )* - **To** *: ( int | ENUM(Object,World,Camera,Screen) )* - **Vector** *: ( vec3 | Vector )* - **Outputs** - **Vector** *: ( vec3 | Vector )* --- ### **Mapping** --- #### **Point** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Location** *: ( vec3 | Vector )* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Texture** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Location** *: ( vec3 | Vector )* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Vector** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Normal** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- ## Color --- ### **Alpha Blend** Blends the blend color as a layer over the base color. - **Inputs** - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* >The blend color. - **Outputs** - **result** *: ( vec4 )* --- ### **Grayscale** - **Inputs** - **Color** *: ( vec3 )* - **Outputs** - **result** *: ( float )* --- ### **Linear To sRGB** - **Inputs** - **Linear** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **sRGB To Linear** - **Inputs** - **Srgb** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **RGB To HSV** - **Inputs** - **Rgb** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **HSV To RGB** - **Inputs** - **Hsv** *: ( vec3 | HSV )* - **Outputs** - **result** *: ( vec3 )* --- ### **HSV Edit** - **Inputs** - **Color** *: ( vec4 )* - **Hue** *: ( float )* - **Saturation** *: ( float )* - **Value** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Bright/Contrast** - **Inputs** - **Color** *: ( vec4 )* - **Brightness** *: ( float )* - **Contrast** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Gamma** - **Inputs** - **Color** *: ( vec4 )* - **Gamma** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- ### **Invert** - **Inputs** - **Color** *: ( vec4 )* - **Fac** *: ( float | Slider )* - **Outputs** - **result** *: ( vec4 )* --- ### **Color Gradient** --- #### **Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **U** *: ( float | Slider )* - **Outputs** - **result** *: ( vec4 )* --- #### **RGB Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **UVW** *: ( vec3 | Slider )* - **Outputs** - **result** *: ( vec3 )* --- #### **RGBA Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **UVWX** *: ( vec4 | Slider )* - **Outputs** - **result** *: ( vec4 )* --- ### **Layer Blend** --- #### **Normal** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Add** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Overlay** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Screen** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Darken** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Lighten** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Soft Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Hard Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Linear Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Dodge** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Burn** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Subtract** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Difference** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Divide** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Hue** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Saturation** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Value** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Color** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- ## Texturing --- ### **Image** - **Inputs** - **Image** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Smooth Interpolation** *: ( bool ) - default = True* - **Outputs** - **Color** *: ( vec4 )* - **Resolution** *: ( vec2 )* --- ### **Normal Map** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **UV Index** *: ( int )* - **Outputs** - **Normal** *: ( vec3 )* --- ### **Flipbook** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Dimensions** *: ( ivec2 )* - **Page** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Flowmap** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Flow** *: ( vec2 ) - default = vec2(0.0)* - **Progression** *: ( float )* - **Samples** *: ( int ) - default = 2* - **Outputs** - **result** *: ( vec4 )* --- ### **Matcap** - **Inputs** - **Matcap** *: ( sampler2D )* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **Color** *: ( vec4 )* - **Uv** *: ( vec2 )* --- ### **HDRI** - **Inputs** - **Hdri** *: ( sampler2D )* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **Color** *: ( vec4 )* - **Uv** *: ( vec2 )* --- ### **Noise** --- #### **Infinite** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Detail** *: ( float ) - default = 3.0* - **Balance** *: ( float | Slider ) - default = 0.5* - **Outputs** - **result** *: ( vec4 )* --- #### **Tiled** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Detail** *: ( float ) - default = 3.0* - **Balance** *: ( float | Slider ) - default = 0.5* - **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)* - **Outputs** - **result** *: ( vec4 )* --- ### **Voronoi** --- #### **Infinite** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Outputs** - **Cell Color** *: ( vec4 )* - **Cell Position** *: ( vec4 )* - **Cell Distance** *: ( float )* --- #### **Tiled** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)* - **Outputs** - **Cell Color** *: ( vec4 )* - **Cell Position** *: ( vec4 )* - **Cell Distance** *: ( float )* --- ### **Bayer Pattern** - **Inputs** - **Size** *: ( int | ENUM(2x2,3x3,4x4,8x8) ) - default = 2* - **Texel** *: ( vec2 ) - default = vec2(screen_pixel())* - **Outputs** - **result** *: ( float )* --- ### **Gradient** --- #### **Linear** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Quadratic** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Easing** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Diagonal** - **Inputs** - **UV** *: ( vec2 ) - default = UV[0]* - **Outputs** - **result** *: ( float )* --- #### **Spherical** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = POSITION* - **Outputs** - **result** *: ( float )* --- #### **Quadratic Sphere** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = POSITION* - **Outputs** - **result** *: ( float )* --- #### **Radial** - **Inputs** - **UV** *: ( vec2 ) - default = UV[0]* - **Outputs** - **result** *: ( float )* --- ### **Wave** - **Inputs** - **Mode** *: ( int | ENUM(Sine,Saw,Triangle) )* - **Coord** *: ( float ) - default = UV[0].x* - **Scale** *: ( float ) - default = 5.0* - **Phase** *: ( float )* - **Outputs** - **result** *: ( float )* --- ## Shading --- ### **Ambient Occlusion** - **Inputs** - **Samples** *: ( int ) - default = 32* - **Radius** *: ( float ) - default = 1.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Contrast** *: ( float | Slider ) - default = 0.1* - **Bias** *: ( float | Slider ) - default = 0.01* - **Outputs** - **result** *: ( float )* --- ### **Curvature** --- #### **Curvature** - **Outputs** - **result** *: ( float )* --- #### **Surface Curvature** - **Inputs** - **Depth Range** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( float )* --- ### **Line Detection** - **Outputs** - **Delta Distance** *: ( float )* - **Delta Angle** *: ( float )* - **Is ID Boundary** *: ( vec4 )* --- ### **Line Width** - **Inputs** - **Width Scale** *: ( float ) - default = 4.0* - **Width Units** *: ( int | ENUM(Pixel,Screen,World) )* - **Depth Width** *: ( float | Slider ) - default = 1.0* - **Depth Threshold** *: ( float | Slider ) - default = 0.1* - **Depth Threshold Range** *: ( float | Slider )* - **Normal Width** *: ( float | Slider ) - default = 1.0* - **Normal Threshold** *: ( float | Slider ) - default = 0.5* - **Normal Threshold Range** *: ( float | Slider )* - **Id Boundary Width** *: ( vec4 | Slider ) - default = (1.0, 1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( float )* --- ### **Rim Light** - **Inputs** - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Angle** *: ( float )* - **Rim Length** *: ( float ) - default = 2.0* - **Length Falloff** *: ( float )* - **Thickness** *: ( float ) - default = 0.1* - **Thickness Falloff** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **NPR Diffuse** --- #### **Color Ramp** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Gradient** *: ( sampler1D )* - **Offset** *: ( float | Slider )* - **Full Range** *: ( bool )* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- #### **Color Layer** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Size** *: ( float | Slider ) - default = 1.0* - **Gradient Size** *: ( float | Slider ) - default = 0.1* - **Offset** *: ( float | Slider )* - **Full Range** *: ( bool )* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- ### **NPR Specular** --- #### **Color Ramp** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Gradient** *: ( sampler1D )* - **Offset** *: ( float | Slider )* - **Roughness** *: ( float | Slider ) - default = 0.5* - **Anisotropy** *: ( float | Slider ) - default = 0.5* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))* - **Outputs** - **result** *: ( vec3 )* --- #### **Color Layer** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Size** *: ( float | Slider ) - default = 1.0* - **Gradient Size** *: ( float | Slider ) - default = 0.1* - **Offset** *: ( float | Slider )* - **Roughness** *: ( float | Slider ) - default = 0.5* - **Anisotropy** *: ( float | Slider ) - default = 0.5* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Inherit from Material,Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = MATERIAL_LIGHT_GROUPS* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))* - **Outputs** - **result** *: ( vec3 )* --- ## Filter --- ### **Curvature** - **Inputs** - **Normal Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Width** *: ( float ) - default = 1.0* - **X** *: ( vec3 | Normal ) - default = (1.0, 0.0, 0.0)* - **Y** *: ( vec3 | Normal ) - default = (0.0, 1.0, 0.0)* - **Outputs** - **result** *: ( float )* --- ### **Blur** --- #### **Box Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Circular** *: ( bool )* - **Outputs** - **result** *: ( vec4 )* --- #### **Gaussian Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Sigma** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- #### **Jitter Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Samples** *: ( int ) - default = 8* - **Outputs** - **result** *: ( vec4 )* --- #### **Bilateral** - **Inputs** - **Input Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5* - **Sigma** *: ( float ) - default = 10.0* - **BSigma** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( vec4 )* --- #### **Orientation-Aligned Bilateral** - **Inputs** - **Input Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Flow** *: ( vec2 ) - default = vec2(0)* - **Radius** *: ( float ) - default = 6.0* - **Smoothness** *: ( float ) - default = 0.55* - **Outputs** - **result** *: ( vec4 )* --- ### **Sharpen** --- #### **Box** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Circular** *: ( bool )* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- #### **Gaussian** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Sigma** *: ( float ) - default = 1.0* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- #### **Jitter** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Samples** *: ( int ) - default = 8* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- ### **Kuwahara** --- #### **Isotropic** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Size** *: ( int ) - default = 5* - **Outputs** - **result** *: ( vec4 )* --- #### **Anisotropic** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Direction** *: ( vec2 ) - default = vec2(0.0, 0.0)* - **Size** *: ( float ) - default = 2.0* - **Samples** *: ( int ) - default = 50* - **Outputs** - **result** *: ( vec4 )* --- ## Node Tree --- ### **Pack ID** - **Inputs** - **Object ID** *: ( vec4 ) - default = unpackUnorm4x8(ID.x)* - **Custom ID A** *: ( vec4 ) - default = unpackUnorm4x8(ID.y)* - **Custom ID B** *: ( vec4 ) - default = unpackUnorm4x8(ID.z)* - **Custom ID C** *: ( vec4 ) - default = unpackUnorm4x8(ID.w)* - **Outputs** - **ID** *: ( uvec4 )* ================================================ FILE: docs/reference/Render Layer-graph.md ================================================ # Render Layer Graph Reference --- ## Render --- ### **LineRender** Expands the line up to the width especified in the *Line Width* texture and composites it on top of the *Color* texture. - **Inputs** - **Color** *: ( Texture )* - **Line Color** *: ( Texture )* - **Line Width** *: ( Texture )* - **Max Width** *: ( Int ) - default = 10* >The maximum width the shader can render. Increasing the value lowers the render performance. - **Line Scale** *: ( Float ) - default = 1.0* >Scale all Line Width values with this one. *(Useful for rendering at different resolutions)* - **Normal Depth** *: ( Texture )* - **ID** *: ( Texture )* - **Outputs** - **Color** *: ( Texture )* --- ### **SuperSamplingAA** Performs anti-aliasing by accumulating multiple render samples into a single texture. - **Inputs** - **Color** *: ( Texture )* - **Outputs** - **Color** *: ( Texture )* --- ### **MainPass** >Graph Type / Pass : *Mesh / MAIN_PASS_PIXEL_SHADER* Renders the scene geometry using the *Mesh Main Pass*. The node sockets are dynamic, based on the *Main Pass Custom IO*. If *Normal Depth/ID* is empty, the *Pre Pass* *Normal Depth/ID* will be used. - **Inputs** - **Scene** *: ( Scene )* - **Normal Depth** *: ( Texture )* - **ID** *: ( Texture )* --- ### **PrePass** >Graph Type / Pass : *Mesh / PRE_PASS_PIXEL_SHADER* Renders the scene geometry using the *Mesh Pre Pass*. The node sockets are dynamic, based on the *Pre Pass Custom IO*. If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used. - **Inputs** - **Scene** *: ( Scene )* - **Outputs** - **Scene** *: ( Scene )* - **Normal Depth** *: ( Texture )* - **ID** *: ( Texture )* --- ### **ScreenPass** >Graph Type / Pass : *Screen / SCREEN_SHADER* Renders a full screen shader pass. The node sockets are dynamic, based on the shader selected. If *Normal Depth/ID* is empty, the *PrePass* *Normal Depth/ID* will be used. - **Inputs** - **Layer Only** *: ( Bool ) - default = True* >Draw only on top of the current layer geometry, to avoid accidentally covering previous layers. - **Scene** *: ( Scene )* - **Normal Depth** *: ( Texture )* - **ID** *: ( Texture )* ================================================ FILE: docs/reference/Render-graph.md ================================================ # Render Graph Reference --- ## Render --- ### **LineRender** Expands the line up to the width especified in the *Line Width* texture and composites it on top of the *Color* texture. - **Inputs** - **Color** *: ( Texture )* - **Line Color** *: ( Texture )* - **Line Width** *: ( Texture )* - **Max Width** *: ( Int ) - default = 10* >The maximum width the shader can render. Increasing the value lowers the render performance. - **Line Scale** *: ( Float ) - default = 1.0* >Scale all Line Width values with this one. *(Useful for rendering at different resolutions)* - **Normal Depth** *: ( Texture )* - **ID** *: ( Texture )* - **Outputs** - **Color** *: ( Texture )* --- ### **SuperSamplingAA** Performs anti-aliasing by accumulating multiple render samples into a single texture. - **Inputs** - **Color** *: ( Texture )* - **Outputs** - **Color** *: ( Texture )* --- ### **RenderLayers** >Graph Type / Pass : *Render Layer / Render Layer* Renders the scene geometry, using multiple *depth peeling* layers for transparent objects. The node sockets are dynamic, based on the graph selected. - **Inputs** - **Scene** *: ( Scene )* - **Transparent Layers** *: ( Int ) - default = 4* >The maximum number of overlapping transparency layers. Incresing this values lowers performance. --- ### **SceneLighting** Renders the shadow maps and attaches them along the scene lights data to the *Scene* shader resources. - **Inputs** - **Scene** *: ( Scene )* - **Point Resolution** *: ( Int ) - default = 2048* >Shadowmap resolution for point lights *(for each cubemap side)*. - **Spot Resolution** *: ( Int ) - default = 2048* >Shadowmap resolution for spot lights. - **Sun Resolution** *: ( Int ) - default = 2048* >Shadowmap resolution for sun light lights *(for each cascade side)*. - **Sun Max Distance** *: ( Float ) - default = 100* >The maximum distance from the view origin at which objects will still cast shadows. The lower the value, the higher the perceived resolution. - **Sun CSM Count** *: ( Int ) - default = 4* >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. - **Sun CSM Distribution** *: ( Float ) - default = 0.9* >Interpolates the cascades distribution along the view distance between linear distribution *(at 0)* and logarithmic distribution *(at 1)*. The appropriate value depends on camera FOV and scene characteristics. - **Outputs** - **Scene** *: ( Scene )* --- ### **ScreenPass** >Graph Type / Pass : *Screen / SCREEN_SHADER* Renders a full screen shader pass. The node sockets are dynamic, based on the shader selected. ================================================ FILE: docs/reference/Screen-graph.md ================================================ # Screen Graph Reference --- ## Input --- ### **Geometry** - **Inputs** - **Space** *: ( int | ENUM(Object,World,Camera,Screen) ) - default = 1* - **IOR** *: ( float ) - default = 1.45* - **Outputs** - **Position** *: ( vec3 )* - **Incoming** *: ( vec3 )* - **Normal** *: ( vec3 ) - default = NORMAL* - **True Normal** *: ( vec3 )* - **Is Backfacing** *: ( bool )* - **Facing** *: ( float )* - **Fresnel** *: ( float )* - **Reflection** *: ( vec3 )* - **Refraction** *: ( vec3 )* --- ### **Tangent** --- #### **Radial** - **Inputs** - **Axis** *: ( int | ENUM(X,Y,Z) ) - default = 2* - **Object Space** *: ( bool ) - default = True* - **Outputs** - **Tangent** *: ( vec3 )* - **Bitangent** *: ( vec3 )* --- #### **Procedural UV** - **Inputs** - **Uv** *: ( vec2 )* - **Outputs** - **Tangent** *: ( vec3 )* - **Bitangent** *: ( vec3 )* --- ### **Id** - **Outputs** - **Object Id** *: ( vec4 )* - **Custom Id A** *: ( vec4 )* - **Custom Id B** *: ( vec4 )* - **Custom Id C** *: ( vec4 )* --- ### **Camera Data** - **Outputs** - **View Direction** *: ( vec3 )* - **Screen UV** *: ( vec2 )* - **Z Depth** *: ( float )* - **View Distance** *: ( float )* - **Camera Position** *: ( vec3 )* - **Camera Matrix** *: ( mat4 )* - **Projection Matrix** *: ( mat4 )* - **Is Orthographic** *: ( bool )* --- ### **Render Info** - **Outputs** - **Resolution** *: ( vec2 )* - **Current Sample** *: ( int )* - **Sample Offset** *: ( vec2 )* --- ### **Time Info** - **Outputs** - **Time** *: ( float )* - **Frame** *: ( int )* --- ### **Random** - **Inputs** - **Seed** *: ( float )* - **Outputs** - **Per Object** *: ( vec4 )* - **Per Sample** *: ( vec4 )* - **Per Pixel** *: ( vec4 )* --- ## Parameters --- ### **Boolean** - **Inputs** - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- ### **Float** - **Inputs** - **F** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **Integer** - **Inputs** - **I** *: ( int )* - **Outputs** - **result** *: ( int )* --- ### **Vector 2D** - **Inputs** - **V** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- ### **Vector 3D** - **Inputs** - **V** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- ### **Vector 4D** - **Inputs** - **V** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- ### **RGB Color** - **Inputs** - **V** *: ( vec3 | Color )* - **Outputs** - **result** *: ( vec3 )* --- ### **RGBA Color** - **Inputs** - **V** *: ( vec4 | Color )* - **Outputs** - **result** *: ( vec4 )* --- ### **Color Ramp** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **Outputs** - **result** *: ( sampler1D )* --- ### **Image** - **Inputs** - **Image** *: ( sampler2D )* - **Outputs** - **result** *: ( sampler2D )* --- ## Math --- ### **Hash** - **Inputs** - **V** *: ( vec4 | Data )* - **Outputs** - **result** *: ( vec4 )* --- ### **Quaternion** --- #### **From Axis Angle** - **Inputs** - **Axis** *: ( vec3 | Normal )* - **Angle** *: ( float | Angle )* - **Outputs** - **result** *: ( vec4 )* --- #### **From Vector Delta** - **Inputs** - **From** *: ( vec3 | Normal )* - **To** *: ( vec3 | Normal )* - **Outputs** - **result** *: ( vec4 )* --- #### **Inverted** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Transform** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Vector** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix** - **Inputs** - **A** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **B** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Factor** *: ( float | Slider ) - default = 0.5* - **Outputs** - **result** *: ( vec4 )* --- ### **Matrix** --- #### **From Translation** - **Inputs** - **T** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( mat4 )* --- #### **From Quaternion** - **Inputs** - **Q** *: ( vec4 | Quaternion ) - default = (0.0, 0.0, 0.0, 1.0)* - **Outputs** - **result** *: ( mat4 )* --- #### **From Euler** - **Inputs** - **E** *: ( vec3 | Euler )* - **Outputs** - **result** *: ( mat4 )* --- #### **From Scale** - **Inputs** - **S** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( mat4 )* --- #### **Is Orthographic** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( bool )* --- #### **Inverse** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( mat4 )* --- #### **Multiply** - **Inputs** - **A** *: ( mat4 ) - default = mat4(1)* - **B** *: ( mat4 ) - default = mat4(1)* - **Outputs** - **result** *: ( mat4 )* --- ### **Boolean Logic** --- #### **And** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Or** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Not** - **Inputs** - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Equal** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( bool )* - **B** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( bool )* - **If False** *: ( bool )* - **Outputs** - **result** *: ( bool )* --- ### **Float** --- #### **Add** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Subtract** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Multiply** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Divide** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Value** *: ( float ) - default = 0.5* - **From Min** *: ( float )* - **From Max** *: ( float ) - default = 1.0* - **To Min** *: ( float )* - **To Max** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( float )* --- #### **Modulo** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Power** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Square Root** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Round** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Fractional Part** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Floor** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Ceil** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Clamp** - **Inputs** - **A** *: ( float )* - **Min** *: ( float )* - **Max** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Sign** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Absolute** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Minimum** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Maximum** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Mix** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Cosine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Tangent** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arcsine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arcosine** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Arctangent** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Radians to Degrees** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Degrees to Radians** - **Inputs** - **A** *: ( float )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **E** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **E** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( bool )* --- #### **Greater** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Greater or Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Less** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **Less or Equal** - **Inputs** - **A** *: ( float )* - **B** *: ( float )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( float )* - **If False** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **Integer** --- #### **Add** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Subtract** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Multiply** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Divide** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Modulo** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Clamp** - **Inputs** - **A** *: ( int )* - **Min** *: ( int )* - **Max** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Sign** - **Inputs** - **A** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Absolute** - **Inputs** - **A** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Minimum** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Maximum** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( int )* --- #### **Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Greater** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Greater or Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Less** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **Less or Equal** - **Inputs** - **A** *: ( int )* - **B** *: ( int )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( int )* - **If False** *: ( int )* - **Outputs** - **result** *: ( int )* --- ### **Vector 2D** --- #### **Add** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Divide** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Scale** - **Inputs** - **A** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **UV** *: ( vec2 ) - default = vec2(0.5)* - **From Min** *: ( vec2 ) - default = (0.0, 0.0)* - **From Max** *: ( vec2 ) - default = (1.0, 1.0)* - **To Min** *: ( vec2 ) - default = (0.0, 0.0)* - **To Max** *: ( vec2 ) - default = (1.0, 1.0)* - **Outputs** - **result** *: ( vec2 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Power** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Distort** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Round** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Floor** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Snap** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec2 )* - **Min** *: ( vec2 )* - **Max** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Sign** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Min** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Max** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Mix 2D** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Factor** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Mix** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Length** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Rotate** - **Inputs** - **A** *: ( vec2 )* - **Angle** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Angle** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec2 )* - **B** *: ( vec2 )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec2 )* - **If False** *: ( vec2 )* - **Outputs** - **result** *: ( vec2 )* --- #### **Combine** - **Inputs** - **X** *: ( float )* - **Y** *: ( float )* - **Outputs** - **result** *: ( vec2 )* --- #### **Separate** - **Inputs** - **A** *: ( vec2 )* - **Outputs** - **X** *: ( float )* - **Y** *: ( float )* --- ### **Vector 3D** --- #### **Add** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Divide** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Scale** - **Inputs** - **A** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Vector** *: ( vec3 ) - default = vec3(0.5)* - **From Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **From Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **To Min** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **To Max** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Power** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Distort** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Round** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Floor** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Snap** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec3 | Vector )* - **Min** *: ( vec3 | Vector )* - **Max** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Sign** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Min** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Max** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix 3D** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Factor** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Mix** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Length** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Cross Product** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Reflect** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Refract** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Ior** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Faceforward** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **C** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Sine** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Rotate Euler** - **Inputs** - **A** *: ( vec3 | Vector )* - **Euler** *: ( vec3 | Euler )* - **Invert** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- #### **Rotate Axis Angle** - **Inputs** - **A** *: ( vec3 | Vector )* - **Axis** *: ( vec3 | Vector ) - default = (0.0, 0.0, 1.0)* - **Angle** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Angle** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec3 | Vector )* - **B** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec3 | Vector )* - **If False** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Combine** - **Inputs** - **X** *: ( float )* - **Y** *: ( float )* - **Z** *: ( float )* - **Outputs** - **result** *: ( vec3 )* --- #### **Separate** - **Inputs** - **A** *: ( vec3 | Vector )* - **Outputs** - **X** *: ( float )* - **Y** *: ( float )* - **Z** *: ( float )* --- ### **Vector 4D** --- #### **Add** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Subtract** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Divide** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Scale** - **Inputs** - **A** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Map Range** - **Inputs** - **Clamped** *: ( bool ) - default = True* - **Vector** *: ( vec4 ) - default = vec4(0.5)* - **From Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)* - **From Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)* - **To Min** *: ( vec4 | Vector ) - default = (0.0, 0.0, 0.0, 0.0)* - **To Max** *: ( vec4 | Vector ) - default = (1.0, 1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec4 )* --- #### **Modulo** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Power** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Square Root** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Distort** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Round** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Fraction** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Floor** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Ceil** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Clamp** - **Inputs** - **A** *: ( vec4 | Vector )* - **Min** *: ( vec4 | Vector )* - **Max** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Sign** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Absolute** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Min** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Max** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Mix 4D** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Factor** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Mix** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Fac** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Normalize** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Length** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Distance** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Dot Product** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Sine** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Cosine** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Tangent** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Angle** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( float )* --- #### **Equal** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Not Equal** - **Inputs** - **A** *: ( vec4 | Vector )* - **B** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( bool )* --- #### **Vec4 If Else** - **Inputs** - **Condition** *: ( bool )* - **If True** *: ( vec4 | Vector )* - **If False** *: ( vec4 | Vector )* - **Outputs** - **result** *: ( vec4 )* --- #### **Combine** - **Inputs** - **R** *: ( float )* - **G** *: ( float )* - **B** *: ( float )* - **A** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- #### **Combine Color** - **Inputs** - **C** *: ( vec3 | Color )* - **A** *: ( float | Slider ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- #### **Separate** - **Inputs** - **A** *: ( vec4 | Vector )* - **Outputs** - **R** *: ( float )* - **G** *: ( float )* - **B** *: ( float )* - **A** *: ( float )* --- #### **Separate Color** - **Inputs** - **A** *: ( vec4 | Color )* - **Outputs** - **C** *: ( vec3 )* - **A** *: ( float )* --- ## Vector --- ### **Surface Gradient From Normal** - **Inputs** - **Base Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Custom Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- ### **Normal From Surface Gradient** - **Inputs** - **Base Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Surface Gradient** *: ( vec3 | Vector ) - default = (0.0, 0.0, 0.0)* - **Outputs** - **result** *: ( vec3 )* --- ### **Matrix** --- #### **Transform Point** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Project Point** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Project Point To Screen Coordinates** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Point** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Transform Direction** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Direction** *: ( vec3 | Vector )* - **Outputs** - **result** *: ( vec3 )* --- #### **Transform Normal** - **Inputs** - **Matrix** *: ( mat4 ) - default = mat4(1)* - **Normal** *: ( vec3 | Normal )* - **Outputs** - **result** *: ( vec3 )* --- ### **Pixel Size in World Space** - **Inputs** - **Depth** *: ( float ) - default = pixel_depth()* - **Outputs** - **result** *: ( float )* --- ### **Bevel** --- #### **Soft Bevel** - **Inputs** - **Samples** *: ( int ) - default = 32* - **Radius** *: ( float ) - default = 0.02* - **Distribution Exponent** *: ( float ) - default = 2.0* - **Only Self** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- #### **Hard Bevel** - **Inputs** - **Samples** *: ( int ) - default = 32* - **Radius** *: ( float ) - default = 0.01* - **Max Dot** *: ( float ) - default = 0.75* - **Only Self** *: ( bool )* - **Outputs** - **result** *: ( vec3 )* --- ### **Transform** - **Inputs** - **Type** *: ( int | ENUM(Point,Vector,Normal) )* - **From** *: ( int | ENUM(Object,World,Camera) )* - **To** *: ( int | ENUM(Object,World,Camera,Screen) )* - **Vector** *: ( vec3 | Vector )* - **Outputs** - **Vector** *: ( vec3 | Vector )* --- ### **Mapping** --- #### **Point** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Location** *: ( vec3 | Vector )* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Texture** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Location** *: ( vec3 | Vector )* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Vector** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- #### **Normal** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = vec3(0.0)* - **Rotation** *: ( vec3 | Euler )* - **Scale** *: ( vec3 | Vector ) - default = (1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( vec3 )* --- ## Color --- ### **Alpha Blend** Blends the blend color as a layer over the base color. - **Inputs** - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* >The blend color. - **Outputs** - **result** *: ( vec4 )* --- ### **Grayscale** - **Inputs** - **Color** *: ( vec3 )* - **Outputs** - **result** *: ( float )* --- ### **Linear To sRGB** - **Inputs** - **Linear** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **sRGB To Linear** - **Inputs** - **Srgb** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **RGB To HSV** - **Inputs** - **Rgb** *: ( vec3 )* - **Outputs** - **result** *: ( vec3 )* --- ### **HSV To RGB** - **Inputs** - **Hsv** *: ( vec3 | HSV )* - **Outputs** - **result** *: ( vec3 )* --- ### **HSV Edit** - **Inputs** - **Color** *: ( vec4 )* - **Hue** *: ( float )* - **Saturation** *: ( float )* - **Value** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Bright/Contrast** - **Inputs** - **Color** *: ( vec4 )* - **Brightness** *: ( float )* - **Contrast** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Gamma** - **Inputs** - **Color** *: ( vec4 )* - **Gamma** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- ### **Invert** - **Inputs** - **Color** *: ( vec4 )* - **Fac** *: ( float | Slider )* - **Outputs** - **result** *: ( vec4 )* --- ### **Color Gradient** --- #### **Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **U** *: ( float | Slider )* - **Outputs** - **result** *: ( vec4 )* --- #### **RGB Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **UVW** *: ( vec3 | Slider )* - **Outputs** - **result** *: ( vec3 )* --- #### **RGBA Gradient** - **Inputs** - **Color Ramp** *: ( sampler1D )* - **UVWX** *: ( vec4 | Slider )* - **Outputs** - **result** *: ( vec4 )* --- ### **Layer Blend** --- #### **Normal** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Add** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Multiply** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Overlay** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Screen** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Darken** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Lighten** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Soft Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Hard Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Linear Light** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Dodge** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Burn** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Subtract** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Difference** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Divide** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Hue** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Saturation** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Value** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- #### **Color** - **Inputs** - **Opacity** *: ( float | Slider ) - default = 1.0* - **Base** *: ( vec4 )* - **Blend** *: ( vec4 ) - default = (0.0, 0.0, 0.0, 0.0)* - **Mode** *: ( int | ENUM(Linear,Linear Clamp,sRGB,sRGB Clamp) )* - **Outputs** - **result** *: ( vec4 )* --- ## Texturing --- ### **Image** - **Inputs** - **Image** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Smooth Interpolation** *: ( bool ) - default = True* - **Outputs** - **Color** *: ( vec4 )* - **Resolution** *: ( vec2 )* --- ### **Normal Map** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **UV Index** *: ( int )* - **Outputs** - **Normal** *: ( vec3 )* --- ### **Flipbook** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Dimensions** *: ( ivec2 )* - **Page** *: ( float )* - **Outputs** - **result** *: ( vec4 )* --- ### **Flowmap** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Flow** *: ( vec2 ) - default = vec2(0.0)* - **Progression** *: ( float )* - **Samples** *: ( int ) - default = 2* - **Outputs** - **result** *: ( vec4 )* --- ### **Matcap** - **Inputs** - **Matcap** *: ( sampler2D )* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **Color** *: ( vec4 )* - **Uv** *: ( vec2 )* --- ### **HDRI** - **Inputs** - **Hdri** *: ( sampler2D )* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **Color** *: ( vec4 )* - **Uv** *: ( vec2 )* --- ### **Noise** --- #### **Infinite** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Detail** *: ( float ) - default = 3.0* - **Balance** *: ( float | Slider ) - default = 0.5* - **Outputs** - **result** *: ( vec4 )* --- #### **Tiled** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Detail** *: ( float ) - default = 3.0* - **Balance** *: ( float | Slider ) - default = 0.5* - **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)* - **Outputs** - **result** *: ( vec4 )* --- ### **Voronoi** --- #### **Infinite** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Outputs** - **Cell Color** *: ( vec4 )* - **Cell Position** *: ( vec4 )* - **Cell Distance** *: ( float )* --- #### **Tiled** - **Inputs** - **Coord** *: ( vec3 | Vector ) - default = POSITION* - **Seed** *: ( float )* - **Scale** *: ( float ) - default = 5.0* - **Tile Size** *: ( ivec4 | Vector ) - default = (5, 5, 5, 5)* - **Outputs** - **Cell Color** *: ( vec4 )* - **Cell Position** *: ( vec4 )* - **Cell Distance** *: ( float )* --- ### **Bayer Pattern** - **Inputs** - **Size** *: ( int | ENUM(2x2,3x3,4x4,8x8) ) - default = 2* - **Texel** *: ( vec2 ) - default = vec2(screen_pixel())* - **Outputs** - **result** *: ( float )* --- ### **Gradient** --- #### **Linear** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Quadratic** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Easing** - **Inputs** - **Value** *: ( float ) - default = UV[0].x* - **Outputs** - **result** *: ( float )* --- #### **Diagonal** - **Inputs** - **UV** *: ( vec2 ) - default = UV[0]* - **Outputs** - **result** *: ( float )* --- #### **Spherical** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = POSITION* - **Outputs** - **result** *: ( float )* --- #### **Quadratic Sphere** - **Inputs** - **Vector** *: ( vec3 | Vector ) - default = POSITION* - **Outputs** - **result** *: ( float )* --- #### **Radial** - **Inputs** - **UV** *: ( vec2 ) - default = UV[0]* - **Outputs** - **result** *: ( float )* --- ### **Wave** - **Inputs** - **Mode** *: ( int | ENUM(Sine,Saw,Triangle) )* - **Coord** *: ( float ) - default = UV[0].x* - **Scale** *: ( float ) - default = 5.0* - **Phase** *: ( float )* - **Outputs** - **result** *: ( float )* --- ## Shading --- ### **Ambient Occlusion** - **Inputs** - **Samples** *: ( int ) - default = 32* - **Radius** *: ( float ) - default = 1.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Contrast** *: ( float | Slider ) - default = 0.1* - **Bias** *: ( float | Slider ) - default = 0.01* - **Outputs** - **result** *: ( float )* --- ### **Curvature** --- #### **Curvature** - **Outputs** - **result** *: ( float )* --- #### **Surface Curvature** - **Inputs** - **Depth Range** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( float )* --- ### **Line Detection** - **Outputs** - **Delta Distance** *: ( float )* - **Delta Angle** *: ( float )* - **Is ID Boundary** *: ( vec4 )* --- ### **Line Width** - **Inputs** - **Width Scale** *: ( float ) - default = 4.0* - **Width Units** *: ( int | ENUM(Pixel,Screen,World) )* - **Depth Width** *: ( float | Slider ) - default = 1.0* - **Depth Threshold** *: ( float | Slider ) - default = 0.1* - **Depth Threshold Range** *: ( float | Slider )* - **Normal Width** *: ( float | Slider ) - default = 1.0* - **Normal Threshold** *: ( float | Slider ) - default = 0.5* - **Normal Threshold Range** *: ( float | Slider )* - **Id Boundary Width** *: ( vec4 | Slider ) - default = (1.0, 1.0, 1.0, 1.0)* - **Outputs** - **result** *: ( float )* --- ### **Rim Light** - **Inputs** - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Angle** *: ( float )* - **Rim Length** *: ( float ) - default = 2.0* - **Length Falloff** *: ( float )* - **Thickness** *: ( float ) - default = 0.1* - **Thickness Falloff** *: ( float )* - **Outputs** - **result** *: ( float )* --- ### **NPR Diffuse** --- #### **Color Ramp** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Gradient** *: ( sampler1D )* - **Offset** *: ( float | Slider )* - **Full Range** *: ( bool )* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- #### **Color Layer** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Size** *: ( float | Slider ) - default = 1.0* - **Gradient Size** *: ( float | Slider ) - default = 0.1* - **Offset** *: ( float | Slider )* - **Full Range** *: ( bool )* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Outputs** - **result** *: ( vec3 )* --- ### **NPR Specular** --- #### **Color Ramp** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Gradient** *: ( sampler1D )* - **Offset** *: ( float | Slider )* - **Roughness** *: ( float | Slider ) - default = 0.5* - **Anisotropy** *: ( float | Slider ) - default = 0.5* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))* - **Outputs** - **result** *: ( vec3 )* --- #### **Color Layer** - **Inputs** - **Base Color** *: ( vec3 ) - default = (0.0, 0.0, 0.0)* - **Color** *: ( vec3 ) - default = (1.0, 1.0, 1.0)* - **Size** *: ( float | Slider ) - default = 1.0* - **Gradient Size** *: ( float | Slider ) - default = 0.1* - **Offset** *: ( float | Slider )* - **Roughness** *: ( float | Slider ) - default = 0.5* - **Anisotropy** *: ( float | Slider ) - default = 0.5* - **Max Contribution** *: ( bool )* - **Shadows** *: ( int | ENUM(Enable Shadows,Disable Self-Shadows,Disable Shadows) )* - **Light Groups** *: ( ivec4 ) - default = (1, 0, 0, 0)* - **Position** *: ( vec3 | Vector ) - default = POSITION* - **Normal** *: ( vec3 | Normal ) - default = NORMAL* - **Tangent** *: ( vec3 | Normal ) - default = radial_tangent(NORMAL, vec3(0,0,1))* - **Outputs** - **result** *: ( vec3 )* --- ## Filter --- ### **Curvature** - **Inputs** - **Normal Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Width** *: ( float ) - default = 1.0* - **X** *: ( vec3 | Normal ) - default = (1.0, 0.0, 0.0)* - **Y** *: ( vec3 | Normal ) - default = (0.0, 1.0, 0.0)* - **Outputs** - **result** *: ( float )* --- ### **Blur** --- #### **Box Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Circular** *: ( bool )* - **Outputs** - **result** *: ( vec4 )* --- #### **Gaussian Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Sigma** *: ( float ) - default = 1.0* - **Outputs** - **result** *: ( vec4 )* --- #### **Jitter Blur** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Samples** *: ( int ) - default = 8* - **Outputs** - **result** *: ( vec4 )* --- #### **Bilateral** - **Inputs** - **Input Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 5* - **Sigma** *: ( float ) - default = 10.0* - **BSigma** *: ( float ) - default = 0.1* - **Outputs** - **result** *: ( vec4 )* --- #### **Orientation-Aligned Bilateral** - **Inputs** - **Input Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Flow** *: ( vec2 ) - default = vec2(0)* - **Radius** *: ( float ) - default = 6.0* - **Smoothness** *: ( float ) - default = 0.55* - **Outputs** - **result** *: ( vec4 )* --- ### **Sharpen** --- #### **Box** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Circular** *: ( bool )* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- #### **Gaussian** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Sigma** *: ( float ) - default = 1.0* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- #### **Jitter** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Radius** *: ( float ) - default = 1.0* - **Distribution Exponent** *: ( float ) - default = 5.0* - **Samples** *: ( int ) - default = 8* - **Sharpness** *: ( float ) - default = 0.3* - **Outputs** - **result** *: ( vec4 )* --- ### **Kuwahara** --- #### **Isotropic** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Size** *: ( int ) - default = 5* - **Outputs** - **result** *: ( vec4 )* --- #### **Anisotropic** - **Inputs** - **Texture** *: ( sampler2D )* - **UV** *: ( vec2 ) - default = UV[0]* - **Direction** *: ( vec2 ) - default = vec2(0.0, 0.0)* - **Size** *: ( float ) - default = 2.0* - **Samples** *: ( int ) - default = 50* - **Outputs** - **result** *: ( vec4 )* ================================================ FILE: docs/reference/settings.md ================================================ # Malt Settings ## World - **Material** - **Default** *: ( Material )** = Malt - Default Mesh Material* >The default material, used for objects with no material assigned. - **Override** *: ( Material )** = None* >When set, overrides all scene materials with this one. - **Viewport** - **Resolution Scale** *: ( Float )** = 1.0* >A multiplier for the viewport resolution. It can be lowered to improve viewport performance or for specific styles, like *pixel art*. - **Smooth Interpolation** *: ( Bool )** = True* >The interpolation mode used when *Resolution Scale* is not 1. Toggles between *Nearest/Bilinear* interpolation. - **Samples** - **Grid Size** *: ( Int )** = 8* >The number of render samples per side in the sampling grid. The total number of samples is the square of this value minus the samples that fall outside the sampling radius. Higher values will provide cleaner renders at the cost of increased render times. - **Width** *: ( Float )** = 1.0* >The width (and height) of the sampling grid. Larger values will result in smoother/blurrier images while lower values will result in sharper/more aliased ones. Keep it withing the 1-2 range for best results. - **Render** *: ( Graph )** = Default Render* >The *Render Node Tree* used to render the scene. See [Render & Render Layers](#Render & Render Layers) for more info. ## Material - **Light Groups** - **Light** *: ( Int )** = [1, 0, 0, 0]* >The *Light Groups* (up to 4) that lit this material. - **Shadow** *: ( Int )** = [1, 0, 0, 0]* >The *Light Groups* (up to 4) that this material casts shadows on. ## Mesh - **double_sided** *: ( Bool )** = False* >Disables backface culling, so geometry is rendered from both sides. - **precomputed_tangents** *: ( Bool )** = False* >Load precomputed mesh tangents *(needed for improving normal mapping quality on low poly meshes)*. It's disabled by default since it slows down mesh loading in Blender. When disabled, the *tangents* are calculated on the fly from the *pixel shader*. ## Light - **Light Group** *: ( Int )** = 1* >Lights only affect materials with a matching *Light Group* value. - **Shader** *: ( Material )** = None* >When set, the *Material* with a custom *Light Shader* or *Light Node Tree* that will be used to render this light. ================================================ FILE: mkdocs/mkdocs.yml ================================================ site_name: Malt site_url: https://malt3d.com site_author: Miguel Pozo site_description: Malt Render Engine repo_name: bnpr/Malt repo_url: https://github.com/bnpr/Malt docs_dir: ../docs/ #edit_uri: "" nav: - 'index.md' - 'Documentation': - 'Documentation/Getting Started.md' - 'Documentation/Settings.md' - 'Documentation/Graphs.md' - 'Reference': - 'reference/settings.md' - 'reference/Mesh-graph.md' - 'reference/Screen-graph.md' - 'reference/Light-graph.md' - 'reference/Render-graph.md' - 'reference/Render Layer-graph.md' - 'Documentation/Plugins.md' - 'Documentation/Tooling.md' #- 'Developer Documentation' : 'https://github.com/bnpr/Malt/tree/Development/Malt#malt' - 'Discussions - Q&A' : 'https://github.com/bnpr/Malt/discussions' - 'Patreon' : 'https://patreon.com/pragma37' - 'Download' : 'https://github.com/bnpr/Malt/releases/tag/Release-latest' theme: name: material custom_dir: ../docs/overrides logo: images/cube-solid.svg favicon: images/cube-solid.svg features: - navigation.instant #- navigation.tracking - navigation.tabs #- toc.integrate palette: - scheme: slate primary: teal toggle: icon: material/toggle-switch-off-outline name: Switch to light mode - scheme: default primary: teal toggle: icon: material/toggle-switch name: Switch to dark mode extra_css: - extra/extra.css extra_javascript: - extra/extra.js markdown_extensions: - attr_list - pymdownx.highlight: use_pygments: true anchor_linenums: true - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format extra: generator: false social: - icon: fontawesome/brands/github link: https://github.com/bnpr/Malt - icon: fontawesome/brands/youtube link: https://www.youtube.com/channel/UCNjoz44PbgbQPqFrqy7Y4mg - icon: fontawesome/brands/twitter link: https://twitter.com/pragma37 - icon: fontawesome/brands/patreon link: https://www.patreon.com/pragma37 ================================================ FILE: mkdocs/netlify.toml ================================================ [build] publish = "site/" command = "mkdocs build" ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../docs/" ================================================ FILE: mkdocs/requirements.txt ================================================ mkdocs-material==8.2.8 ================================================ FILE: plugins/Experimental/RenderLayer/OpaqueLayer.py ================================================ from Malt.PipelineNode import PipelineNode from Malt.PipelineParameters import Parameter, Type class OpaqueLayer(PipelineNode): def __init__(self, pipeline): PipelineNode.__init__(self, pipeline) @classmethod def reflect_inputs(cls): return { 'Output Name' : Parameter('', Type.STRING) } @classmethod def reflect_outputs(cls): return { 'Opaque Layer' : Parameter('', Type.TEXTURE) } def execute(self, parameters): inputs = parameters['IN'] outputs = parameters['OUT'] output_name = inputs['Output Name'] render_layers_node = parameters['__GLOBALS__']['__RENDER_LAYERS__'] outputs['Opaque Layer'] = render_layers_node.opaque_targets.get(output_name) NODE = OpaqueLayer ================================================ FILE: plugins/Experimental/__init__.py ================================================ import os from Malt.PipelinePlugin import PipelinePlugin, isinstance_str class ExperimentalNodes(PipelinePlugin): @classmethod def poll_pipeline(self, pipeline): return isinstance_str(pipeline, 'NPR_Pipeline') @classmethod def register_graph_libraries(self, graphs): root = os.path.dirname(__file__) graphs['Render Layer'].add_library(os.path.join(root, 'RenderLayer')) PLUGIN = ExperimentalNodes ================================================ FILE: plugins/PluginExample/Shaders/PluginExample.glsl ================================================ #include "Common.glsl" #include "Common/Hash.glsl" vec4 instance_random_color() { return hash(ID[0]); } ================================================ FILE: plugins/PluginExample/__init__.py ================================================ import os from Malt.PipelinePlugin import PipelinePlugin, isinstance_str class PluginExample(PipelinePlugin): @classmethod def poll_pipeline(self, pipeline): return isinstance_str(pipeline, 'NPR_Pipeline') @classmethod def register_graph_libraries(self, graphs): library_path = os.path.join(os.path.dirname(__file__), 'Shaders') for graph in graphs.values(): if graph.language == 'GLSL': graph.add_library(library_path) PLUGIN = PluginExample ================================================ FILE: scripts/build_intellisense_glsl.py ================================================ import os, json, xmltodict, traceback, copy SKIP_TERMS = ['image','atomic','barrier','memory','shadow','noise'] DEFINES = ['in','out','inout','uniform','varying','layout(index)','discard','uint unsigned int','atomic_uint uint'] DEFINES = ''.join(['#define ' + define + '\n' for define in DEFINES]) GENERICS = { 'genType': ['float', 'vec2', 'vec3', 'vec4'], 'genDType':['double', 'dvec2', 'dvec3', 'dvec4'], 'genIType':['int', 'ivec2', 'ivec3', 'ivec4'], 'genUType':['uint', 'uvec2', 'uvec3', 'uvec4'], 'genBType':['bool', 'bvec2', 'bvec3', 'bvec4'], } #Start with types we don't want to define BUILTINS = [*GENERICS.keys(),'gvec4'] TYPES = '' FUNCTIONS = '' def add_type(type_name): global TYPES TYPES += 'struct ' + type_name + ' {};\n' def add_generic_type(type_name): if type_name.startswith('g') and type_name not in BUILTINS: BUILTINS.append(type_name) for replace in ['','i','u']: add_type(replace + type_name[1:]) for generic in GENERICS.values(): for _type in generic[1:]: add_type(_type) for mat_type in ['mat2','mat3','mat4','mat2x2','mat2x3','mat2x4','mat3x2','mat3x3','mat3x4','mat4x2','mat4x3','mat4x4']: add_type(mat_type) add_type('d'+mat_type) def output_function(signatures, docstring): generic_types = [] has_optional = False for _type, name in signatures: for term in SKIP_TERMS: if term in _type.lower() or term in name.lower(): return if _type.startswith('['): has_optional = True _type = _type.split(' ')[-1] if _type.startswith('g') or _type.endswith('vec') or _type.endswith('mat'): if _type not in generic_types: generic_types.append(_type) resolve_generics(signatures, generic_types, has_optional, docstring) def resolve_generics(signatures, generic_types, has_optional, docstring): if has_optional: for remove in [True, False]: _signatures = copy.deepcopy(signatures) if remove: _signatures.pop() else: _signatures[-1][0]= _signatures[-1][0].replace('[','').replace(']','') _signatures[-1][1]= _signatures[-1][1].replace('[','').replace(']','') resolve_generics(_signatures, copy.deepcopy(generic_types), False, docstring) else: string = '//' + docstring + '\n' if len(generic_types) > 0: string += 'template<' while len(generic_types) > 0: _type = generic_types.pop(0) add_generic_type(_type) string += 'typename '+_type if len(generic_types) > 0: string += ', ' string += '> ' func = signatures.pop(0) string += func[0] + ' ' + func[1] + '(' while len(signatures) > 0: param = signatures.pop(0) string += param[0] + ' ' + param[1] if len(signatures) > 0: string += ', ' string += ');' global FUNCTIONS FUNCTIONS += string + '\n' #print(string) for entry in os.listdir('gl4'): if entry.endswith('.xml'): with open(os.path.join('gl4', entry), 'r') as xml: xml = xml.read() try: d = xmltodict.parse(xml) if d['refentry']['refsynopsisdiv']['title'] == 'Declaration': try: print(entry,'---------------------------------------------------------') docstring = d['refentry']['refnamediv']['refpurpose'] synops = d['refentry']['refsynopsisdiv']['funcsynopsis'] if isinstance(synops, list) == False: synops = [synops] for synop in synops: functions = synop['funcprototype'] if isinstance(functions, list) == False: functions = [functions] for function in functions: name = function['funcdef']['function'] return_type = function['funcdef']['#text'] signatures = [[return_type, name]] declaration = return_type + ' ' + name + '(' parameters = function['paramdef'] if isinstance(parameters, str) == False: #not void if isinstance(parameters, list) == False: parameters = [parameters] for parameter in parameters: param_type = parameter['#text'] param_name = parameter['parameter'] signatures.append([param_type, param_name]) declaration += param_type + ' ' + param_name + ', ' if declaration.endswith(', '): declaration = declaration[:-2] declaration += ');' print(signatures) output_function(signatures, docstring) except Exception as e: print('exception') #print(json.dumps(d['refentry']['refsynopsisdiv'], indent=4)) print(traceback.format_exc()) except Exception as e: #print('FAILED', entry) pass SHORTEN = True if SHORTEN: FUNCTIONS = FUNCTIONS.replace('genType', 'T') FUNCTIONS = FUNCTIONS.replace('genDType', 'D') FUNCTIONS = FUNCTIONS.replace('genIType', 'I') FUNCTIONS = FUNCTIONS.replace('genUType', 'U') FUNCTIONS = FUNCTIONS.replace('genBType', 'B') FINAL_FILE = f''' //This file contains a series of C++ macros, structs and function declarations //to make GLSL autocompletion work with C++ autocompletion implementations #ifdef __INTELLISENSE__ //Define GLSL keywords {DEFINES} //Declare GLSL built-in types {TYPES} //Declare GLSL standard library functions {FUNCTIONS} #endif ''' with open('intellisense.glsl', 'w') as f: f.write(FINAL_FILE) ================================================ FILE: scripts/format.py ================================================ def scan_dirs(path, file_callback): import os for e in os.scandir(path): if e.is_file(): extension = e.name.split('.')[-1] if extension in ('py','glsl','h','c','cpp'): file_callback(e) if e.is_dir(): if e.name.startswith('.') or e.name.startswith('__'): continue scan_dirs(e, file_callback) def fix_whitespace(path): with open(path, 'r+') as f: result = '' lines = f.readlines() for i, line in enumerate(lines): new_line = '' if line.isspace(): while i < len(lines) - 1: i += 1 found_next_line = False if lines[i].isspace() == False: for c in lines[i]: if c.isspace(): new_line += c else: break found_next_line = True else: continue if found_next_line: break else: new_line = line.rstrip() result += new_line result += '\n' result = result.splitlines(keepends=True) while len(result) > 1 and result[-1].isspace(): while result[-1].isspace(): result.pop() result = ''.join(result) f.seek(0) f.truncate() f.write(result) import sys scan_dirs(sys.argv[1], fix_whitespace) ================================================ FILE: scripts/generate_conversion_nodes.py ================================================ result = '' base_types = ('float','int','uint','bool') vector_types = ('vec','ivec','uvec','bvec') for to_base_type in base_types: result += f'//{to_base_type}\n' for from_base_type in base_types: if from_base_type == to_base_type: continue result += '/* META @meta: internal=true; */\n' param = from_base_type[:1] result += f'{to_base_type} {to_base_type}_from_{from_base_type}({from_base_type} {param}) {{ return {to_base_type}({param}); }}\n' result += '\n' for to_vector_type in vector_types: for to_vector_len in range(2,5): to_vector = f'{to_vector_type}{to_vector_len}' result += f'//{to_vector}\n' for base_type in base_types: result += '/* META @meta: internal=true; */\n' param = base_type[:1] conversion = f'{to_vector}({param})' if to_vector_len == 4 and to_vector_type != 'bvec': conversion = f'{to_vector}({param}, {param}, {param}, 1)' result += f'{to_vector} {to_vector}_from_{base_type}({base_type} {param}) {{ return {conversion}; }}\n' for from_vector_type in vector_types: for from_vector_len in range(2,5): if from_vector_type == to_vector_type and from_vector_len == to_vector_len: continue from_vector = f'{from_vector_type}{from_vector_len}' param = 'v' if to_vector_len < from_vector_len: param += '.xyzw'[:to_vector_len+1] if to_vector_len > from_vector_len: for i in range(from_vector_len, to_vector_len): if to_vector_type != 'bvec' and to_vector_len == 4 and i == to_vector_len - 1: param += ', 1' else: param += ', 0' conversion = f'{to_vector}({param})' result += '/* META @meta: internal=true; */\n' result += f'{to_vector} {to_vector}_from_{from_vector}({from_vector} v) {{ return {conversion}; }}\n' result += '\n' print(result) ================================================ FILE: scripts/generate_license_dependencies_full.py ================================================ import os TOP_DIR = os.path.join(os.path.dirname(__file__), os.pardir) dependencies = open(os.path.join(TOP_DIR, 'LICENSE - DEPENDENCIES')).read() result = '' for dependency in dependencies.split('\n\n'): lines = dependency.splitlines() for i, line in enumerate(lines): if i < 3: result += line else: url = line.replace('github.com', 'raw.githubusercontent.com').replace('blob/','') from urllib.request import urlopen content = urlopen(url).read().decode('utf-8') result+=content result+='\n' result += '*'*80 result+='\n' open(os.path.join(TOP_DIR, 'LICENSE - DEPENDENCIES (FULL TEXT)'), 'w').write(result) ================================================ FILE: scripts/get_glslang.py ================================================ import os, stat, platform, shutil, zipfile current_dir = os.path.dirname(os.path.realpath(__file__)) gl_folder = os.path.join(current_dir, '..', 'Malt', 'GL') zip_file = os.path.join(current_dir, 'glslang.zip') glslang_url = { 'Windows' : "https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Release.zip", 'Linux' : "https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-linux-Release.zip", 'Darwin' : "https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-osx-Release.zip", } glslang_url = glslang_url[platform.system()] zipped_path = 'bin/glslangValidator' target_path = os.path.join(gl_folder, '.glslang') if platform.system() == 'Windows': zipped_path += '.exe' target_path += '.exe' import urllib.request urllib.request.urlretrieve(glslang_url, zip_file) with zipfile.ZipFile(zip_file) as z: with z.open(zipped_path) as zip, open(target_path, 'wb') as unzip: shutil.copyfileobj(zip, unzip) # Set as executable (Needed on Linux) os.chmod(target_path, os.stat(target_path).st_mode | stat.S_IEXEC) os.remove(zip_file) ================================================ FILE: scripts/install_dependencies.py ================================================ import os, subprocess, sys, shutil, stat current_dir = os.path.dirname(os.path.realpath(__file__)) malt_folder = os.path.join(current_dir, '..', 'Malt') try: subprocess.check_call([sys.executable, '-m', 'pip', '--version']) except: subprocess.check_call([sys.executable, '-m', 'ensurepip']) os.environ.pop("PIP_REQ_TRACKER", None) py_version = str(sys.version_info[0])+str(sys.version_info[1]) malt_dependencies_path = os.path.join(malt_folder, '.Dependencies-{}'.format(py_version)) dependencies = ['glfw', 'PyOpenGL', 'PyOpenGL_accelerate', 'Pyrr', 'xxhash'] for dependency in dependencies: try: subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', dependency, '--target', malt_dependencies_path]) except: print('ERROR: pip install {} failed.'.format(dependency)) import traceback traceback.print_exc() from shutil import copytree copytree(os.path.join(current_dir, "PatchDependencies"), malt_dependencies_path, dirs_exist_ok=True) #make sure mcpp has executable permissions for str in ['Linux', 'Darwin']: mcpp = os.path.join(malt_dependencies_path, f'mcpp-{str}') os.chmod(mcpp, os.stat(mcpp).st_mode | stat.S_IEXEC) #Remove numpy since Blender already ships with it #Remove bin to avoid AVs false positives for e in os.listdir(malt_dependencies_path): if e.startswith('numpy') or e == 'bin': path = os.path.join(malt_dependencies_path, e) if os.path.isdir(path): shutil.rmtree(path) elif os.path.isfile(path): os.remove(path) #subprocess.check_call([sys.executable, os.path.join(current_dir, 'get_glslang.py')]) ================================================ FILE: scripts/print_pixel_formats.py ================================================ GL_ENUMS = {} GL_NAMES = {} if True: #create new scope to import OpenGL from OpenGL import GL for e in dir(GL): if e.startswith('GL_'): GL_ENUMS[getattr(GL, e)] = e GL_NAMES[e] = getattr(GL, e) from OpenGL.GL import * def print_format_prop(format, prop): read = glGetInternalformativ(GL_TEXTURE_2D, format, prop, 1) print(GL_ENUMS[format], GL_ENUMS[prop], GL_ENUMS[read]) def print_format_props(format): print_format_prop(format, GL_READ_PIXELS) print_format_prop(format, GL_READ_PIXELS_FORMAT) print_format_prop(format, GL_READ_PIXELS_TYPE) print_format_prop(format, GL_TEXTURE_IMAGE_FORMAT) print_format_prop(format, GL_TEXTURE_IMAGE_TYPE) print_format_props(GL_RGB8) print_format_props(GL_RGBA8) print_format_props(GL_RGB16F) print_format_props(GL_RGBA16F) print_format_props(GL_RGB32F) print_format_props(GL_RGBA32F) ================================================ FILE: scripts/settings.json ================================================ { "files.associations": { "*.pyx": "python", "*.glsl": "cpp" }, "cmake.configureOnOpen": false, "C_Cpp.default.includePath": ["Malt\\Shaders"], "C_Cpp.autoAddFileAssociations": true, "C_Cpp.default.cppStandard": "c++03", "C_Cpp.default.compilerPath": "", "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": true, "C_Cpp.errorSquiggles": "Disabled", } ================================================ FILE: scripts/setup_blender_addon.py ================================================ import os, sys, platform, subprocess, shutil, pathlib import argparse parser = argparse.ArgumentParser() parser.add_argument('--scripts-folder', type=pathlib.Path, help='Create a symlink pointing to the addon.') parser.add_argument('--copy-modules', action='store_true', help='Copy Malt and Bridge instead of making a symlink. (Needed for zipping in Linux)') ARGS = parser.parse_args() current_dir = os.path.dirname(os.path.realpath(__file__)) main_dir = os.path.realpath(os.path.join(current_dir, '..')) blender_malt_folder = os.path.join(main_dir, 'BlenderMalt') bridge_folder = os.path.join(main_dir, 'Bridge') malt_folder = os.path.join(main_dir, 'Malt') def build_lib(path): subprocess.check_call([sys.executable, 'build.py'], cwd=path) import stat def delete_read_only(func, path, exc_info): os.chmod(path, stat.S_IWRITE) os.remove(path) shutil.rmtree(os.path.join(path, '.build'), onerror=delete_read_only) build_lib(os.path.join(blender_malt_folder, 'CBlenderMalt')) build_lib(os.path.join(malt_folder, 'GL', 'GLSLParser')) build_lib(os.path.join(bridge_folder, 'ipc')) build_lib(os.path.join(bridge_folder, 'renderdoc')) subprocess.check_call([sys.executable, os.path.join(current_dir, 'install_dependencies.py')]) def ensure_dir(path): if os.path.exists(path) == False: os.mkdir(path) def make_link(point_from, point_to): if os.path.exists(point_from): print('Already linked:', point_from, '<--->', point_to) return if platform.system() == 'Windows': import _winapi _winapi.CreateJunction(point_to, point_from) else: os.symlink(point_to, point_from, True) def make_copy(copy_to, copy_from): from shutil import copytree copytree(copy_from, copy_to) import_path = os.path.join(blender_malt_folder, '.MaltPath') ensure_dir(import_path) setup_modules = make_link if ARGS.copy_modules: setup_modules = make_copy setup_modules(os.path.join(import_path, 'Malt'), os.path.join(main_dir, 'Malt')) setup_modules(os.path.join(import_path, 'Bridge'), os.path.join(main_dir, 'Bridge')) if ARGS.scripts_folder: addons_folder = os.path.join(ARGS.scripts_folder, 'addons') ensure_dir(addons_folder) make_link(os.path.join(addons_folder, 'BlenderMalt'), blender_malt_folder)