├── src ├── python │ ├── menu.py │ ├── init.py │ └── sdf │ │ ├── path_march.py │ │ ├── __init__.py │ │ ├── utils.py │ │ ├── ray_march.py │ │ ├── material.py │ │ ├── light.py │ │ ├── knob_manager.py │ │ └── primitive.py ├── blink │ ├── include │ │ ├── colour.h │ │ ├── aovs.h │ │ ├── camera.h │ │ ├── conversion.h │ │ ├── random.h │ │ ├── lights.h │ │ ├── sdfModifications.h │ │ └── objectInteraction.h │ └── kernels │ │ ├── decode_alpha.blink │ │ ├── random.blink │ │ ├── variance.blink │ │ ├── normalize.blink │ │ ├── minMax.blink │ │ └── hdri_irradiance.blink └── gizmos │ ├── sdf_light.gizmo │ ├── sdf_noise.gizmo │ ├── sdf_material.gizmo │ └── sdf_primitive.gizmo ├── LICENSE ├── .gitignore ├── examples ├── mandelbulb.nk ├── mandelbox.nk ├── nested_dielectrics.nk ├── lenses.nk ├── room.nk └── snowman.nk └── README.md /src/python/menu.py: -------------------------------------------------------------------------------- 1 | """""" 2 | 3 | 4 | _toolbar = nuke.toolbar("Nodes") 5 | 6 | _sdf_menu = _toolbar.addMenu("SDF") 7 | _sdf_menu.addCommand("sdf_primitive", "nuke.createNode('sdf_primitive')") 8 | _sdf_menu.addCommand("sdf_light", "nuke.createNode('sdf_light')") 9 | _sdf_menu.addCommand("sdf_material", "nuke.createNode('sdf_material')") 10 | _sdf_menu.addCommand("sdf_noise", "nuke.createNode('sdf_noise')") 11 | _sdf_menu.addCommand("path_march", "nuke.createNode('path_march')") 12 | _sdf_menu.addCommand("ray_march", "nuke.createNode('ray_march')") 13 | -------------------------------------------------------------------------------- /src/python/init.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | _file_dir = os.path.dirname(__file__) 5 | _parent_dir = os.path.dirname(_file_dir) 6 | 7 | # This will overwrite your FN_BLINK_INCLUDE_PATHS if you have one set up 8 | # but I cannot find a delimiter to get nuke reading multiple paths and 9 | # there is only one reference to this in all of google :( 10 | # Either move the blink files to your path location or let me know 11 | # how to fix this. 12 | os.environ["FN_BLINK_INCLUDE_PATHS"] = os.path.join(_parent_dir, "blink", "include") 13 | 14 | nuke.pluginAddPath(_file_dir) 15 | nuke.pluginAddPath(os.path.join(_parent_dir, "gizmos")) 16 | -------------------------------------------------------------------------------- /src/python/sdf/path_march.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Knob management for sdf lights. 7 | 8 | # Add on knob changed callback to sdf_light group: 9 | nuke.toNode("path_march").knob("knobChanged").setValue( 10 | "__import__('sdf.path_march', fromlist='PathMarch').PathMarch().handle_knob_changed()" 11 | ) 12 | """ 13 | from .knob_manager import KnobChangedCallbacks, KnobManager 14 | 15 | 16 | class PathMarch(KnobManager): 17 | """Knob manager for the ray marcher.""" 18 | -------------------------------------------------------------------------------- /src/python/sdf/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Module for managing the callbacks for the SDF nodes.""" 7 | 8 | 9 | __author__ = "Owen Bulka" 10 | __copyright__ = "Copyright 2022" 11 | __credits__ = ["Owen Bulka"] 12 | __license__ = "MIT" 13 | __version__ = "1.0.0" 14 | __maintainer__ = "Owen Bulka" 15 | __email__ = "obulka@hotmail.com" 16 | __status__ = "Development" 17 | 18 | 19 | __all__ = [ 20 | "knob_manager", 21 | "light", 22 | "primitive", 23 | "ray_march", 24 | "utils", 25 | ] 26 | -------------------------------------------------------------------------------- /src/blink/include/colour.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Colour functions 9 | // 10 | 11 | 12 | /** 13 | * ACES tone mapping curve fit to go from HDR to LDR 14 | * https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve 15 | * 16 | * @arg colour: The linear colour to tone map. 17 | * 18 | * @returns: The tone mapped colour. 19 | */ 20 | inline float3 ACESToneMap(const float3 &colour) 21 | { 22 | return saturate( 23 | (colour * (2.51f * colour + 0.03f)) / (colour * (2.43f * colour + 0.59f) + 0.14f) 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/blink/kernels/decode_alpha.blink: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | #include "conversion.h" 8 | 9 | 10 | kernel DecodeAlpha : ImageComputationKernel 11 | { 12 | Image src; // the input image 13 | Image dst; // the output image 14 | 15 | 16 | /** 17 | * Decode the multiple values stored in the alpha channel. 18 | */ 19 | void process() 20 | { 21 | SampleType(src) srcPixel = src(); 22 | 23 | const int2 decoded = decodeTwoValuesFromUint(srcPixel.w); 24 | dst() = float4(decoded.x, decoded.y, 0, 0); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Owen Bulka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/python/sdf/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """This module contains general purpose utility functions""" 7 | 8 | 9 | def float_to_8bit_colour(colour_value): 10 | """Convert a floating point value to 8bit 11 | 12 | Args: 13 | colour_value (float): The value to convert. 14 | 15 | Returns: 16 | int: The 8bit colour value. 17 | """ 18 | return int(max(0, min(round(colour_value * 255.), 255))) 19 | 20 | 21 | def rgb_to_hex(rgb_value): 22 | """Convert a floating point rgb value to hex. 23 | 24 | Args: 25 | rgb_value (list(float)): The value to convert. 26 | 27 | Returns: 28 | int: The hex colour value. 29 | """ 30 | return int( 31 | "0x{0:02x}{1:02x}{2:02x}{3:02x}".format( 32 | float_to_8bit_colour(rgb_value[0]), 33 | float_to_8bit_colour(rgb_value[1]), 34 | float_to_8bit_colour(rgb_value[2]), 35 | 255, 36 | ), 37 | 0, 38 | ) 39 | -------------------------------------------------------------------------------- /src/blink/kernels/random.blink: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | #include "math.h" 8 | #include "random.h" 9 | 10 | 11 | kernel Random : ImageComputationKernel 12 | { 13 | Image seed; // the input image 14 | Image dst; // the output image 15 | 16 | 17 | param: 18 | float2 _inclusiveRange; 19 | 20 | local: 21 | float __rangeLength; 22 | 23 | 24 | /** 25 | * Give the parameters labels and default values. 26 | */ 27 | void define() 28 | { 29 | defineParam(_inclusiveRange, "Inclusive Range", float2(0, 1)); 30 | } 31 | 32 | 33 | /** 34 | * Initialize the local variables. 35 | */ 36 | void init() 37 | { 38 | __rangeLength = _inclusiveRange.y - _inclusiveRange.x; 39 | } 40 | 41 | 42 | /** 43 | * Compute a random pixel value. 44 | */ 45 | void process() 46 | { 47 | dst() = __rangeLength * random(seed()) + _inclusiveRange.x; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/python/sdf/ray_march.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Knob management for sdf lights. 7 | 8 | # Add on knob changed callback to sdf_light group: 9 | nuke.toNode("ray_march").knob("knobChanged").setValue( 10 | "__import__('sdf.ray_march', fromlist='RayMarch').RayMarch().handle_knob_changed()" 11 | ) 12 | """ 13 | from .knob_manager import KnobChangedCallbacks, KnobManager 14 | 15 | 16 | class RayMarch(KnobManager): 17 | """Knob manager for the ray marcher.""" 18 | 19 | soften_shadows_knob_name = "soften_shadows" 20 | shadow_hardness_knob_name = "hdri_shadow_hardness" 21 | 22 | _knob_changed_callbacks = KnobChangedCallbacks(KnobManager._knob_changed_callbacks) 23 | 24 | 25 | @_knob_changed_callbacks.register(soften_shadows_knob_name) 26 | def _soften_shadows_changed(self): 27 | """Dynamically enable/disable the shadow hardness knob depending 28 | on whether or not shadow softening has been enabled. 29 | """ 30 | self._node.knob(self.shadow_hardness_knob_name).setEnabled(self._knob.value()) 31 | -------------------------------------------------------------------------------- /src/python/sdf/material.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Knob management for sdf primitives. 7 | 8 | # Add on knob changed callback to sdf_primitive group: 9 | nuke.toNode("sdf_material").knob("knobChanged").setValue( 10 | "__import__('sdf.material', fromlist='SDFMaterial').SDFMaterial().handle_knob_changed()" 11 | ) 12 | """ 13 | from collections import OrderedDict 14 | 15 | from .knob_manager import KnobChangedCallbacks, KnobManager 16 | from .utils import rgb_to_hex 17 | 18 | 19 | class SDFMaterial(KnobManager): 20 | """Knob manager for primitive shapes in signed distance fields.""" 21 | 22 | colour_knob_names = ( 23 | "colour", 24 | "specular_colour", 25 | "extinction_colour", 26 | "scattering_colour", 27 | "emission_colour", 28 | ) 29 | 30 | _knob_changed_callbacks = KnobChangedCallbacks(KnobManager._knob_changed_callbacks) 31 | 32 | 33 | @_knob_changed_callbacks.register_multiple(colour_knob_names) 34 | def _colour_changed(self): 35 | """Change the node colour to match the object for easier ID.""" 36 | self._node.knob("tile_color").setValue(rgb_to_hex(self._knob.value())) 37 | -------------------------------------------------------------------------------- /src/blink/kernels/variance.blink: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | #include "math.h" 8 | 9 | 10 | kernel Variance : ImageComputationKernel 11 | { 12 | Image src; // the input image 13 | Image dst; // the output image 14 | 15 | param: 16 | float2 _range; 17 | 18 | local: 19 | float __numSamples; 20 | 21 | 22 | /** 23 | * Give the parameters labels and default values. 24 | */ 25 | void define() 26 | { 27 | defineParam(_range, "Range", float2(3, 3)); 28 | } 29 | 30 | 31 | /** 32 | * Initialize the local variables. 33 | */ 34 | void init() 35 | { 36 | src.setRange(-_range.x, -_range.y, _range.x, _range.y); 37 | 38 | __numSamples = (2.0f * _range.x + 1.0f) * (2.0f * _range.y + 1.0f); 39 | } 40 | 41 | 42 | /** 43 | * Compute the variance of a pixel. 44 | * 45 | * @arg pos: The x, and y location we are currently processing. 46 | */ 47 | void process(int2 pos) 48 | { 49 | float4 mean = float4(0); 50 | for (int yOffset=-_range.y; yOffset <= _range.y; yOffset++) 51 | { 52 | for (int xOffset=-_range.x; xOffset <= _range.x; xOffset++) 53 | { 54 | mean += src(xOffset, yOffset) / __numSamples; 55 | } 56 | } 57 | 58 | float4 sumOfSquares = float4(0); 59 | for (int yOffset=-_range.y; yOffset <= _range.y; yOffset++) 60 | { 61 | for (int xOffset=-_range.x; xOffset <= _range.x; xOffset++) 62 | { 63 | const float4 sample = src(xOffset, yOffset); 64 | 65 | const float4 deviation = src(xOffset, yOffset) - mean; 66 | 67 | sumOfSquares += deviation * deviation / (__numSamples - 1.0f); 68 | } 69 | } 70 | 71 | dst() = sumOfSquares; 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/blink/kernels/normalize.blink: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | #include "math.h" 8 | 9 | 10 | kernel Normalize : ImageComputationKernel 11 | { 12 | Image src; // the input image 13 | Image minMax; // the input image 14 | Image dst; // the output image 15 | 16 | 17 | param: 18 | int _normalizationMethod; 19 | bool _clampToStdDev; 20 | 21 | 22 | /** 23 | * Give the parameters labels and default values. 24 | */ 25 | void define() 26 | { 27 | defineParam(_normalizationMethod, "Normalization Method", 0); 28 | defineParam(_clampToStdDev, "Clamp To Standard Deviation", false); 29 | } 30 | 31 | 32 | /** 33 | * The normalized values. 34 | */ 35 | void process() 36 | { 37 | float minValue = minMax(0, 0, 0); 38 | float maxValue = minMax(0, 0, 1); 39 | const float meanValue = minMax(0, 0, 2); 40 | const float standardDeviationValue = minMax(0, 0, 3); 41 | 42 | float4 srcValue; 43 | if (_clampToStdDev) 44 | { 45 | const float lowerBound = meanValue - standardDeviationValue; 46 | const float upperBound = meanValue + standardDeviationValue; 47 | 48 | srcValue = clamp(src(), lowerBound, upperBound); 49 | minValue = clamp(minValue, lowerBound, upperBound); 50 | maxValue = clamp(maxValue, lowerBound, upperBound); 51 | } 52 | else 53 | { 54 | srcValue = src(); 55 | } 56 | 57 | float4 output; 58 | 59 | if (_normalizationMethod == 0 && minValue != maxValue) 60 | { 61 | dst() = (srcValue - minValue) / (maxValue - minValue); 62 | } 63 | else if (_normalizationMethod == 1 && standardDeviationValue != 0.0f) 64 | { 65 | dst() = (srcValue - meanValue) / standardDeviationValue; 66 | } 67 | else 68 | { 69 | dst() = 0.0f; 70 | } 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /src/blink/kernels/minMax.blink: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | #include "math.h" 8 | 9 | 10 | kernel MinMaxMeanDeviation : ImageComputationKernel 11 | { 12 | Image src; // the input image 13 | Image dst; // the output image 14 | 15 | param: 16 | float _maximum; 17 | 18 | local: 19 | float __numSamples; 20 | 21 | 22 | /** 23 | * Give the parameters labels and default values. 24 | */ 25 | void define() 26 | { 27 | defineParam(_maximum, "Inclusive Range", 99999.9f); 28 | } 29 | 30 | 31 | /** 32 | * Initialize the local variables. 33 | */ 34 | void init() 35 | { 36 | __numSamples = 3.0f * src.bounds.width() * src.bounds.height(); 37 | } 38 | 39 | 40 | /** 41 | * Compute the min max and standard deviation of the image. 42 | * 43 | * @arg pos: The x, and y location we are currently processing. 44 | */ 45 | void process(int2 pos) 46 | { 47 | if (length(float2(pos.x, pos.y)) > 0) 48 | { 49 | dst() = 0; 50 | return; 51 | } 52 | 53 | float minValue = FLT_MAX; 54 | float maxValue = 0.0f; 55 | float mean = 0.0f; 56 | 57 | for (int y=src.bounds.y1; y < src.bounds.y2; y++) 58 | { 59 | for (int x=src.bounds.x1; x < src.bounds.x2; x++) 60 | { 61 | const float3 pixelValue = float3( 62 | src(x, y, 0), 63 | src(x, y, 1), 64 | src(x, y, 2) 65 | ); 66 | 67 | minValue = min(minValue, minComponent(pixelValue)); 68 | maxValue = max(maxValue, maxComponent(pixelValue)); 69 | 70 | mean += sumComponent(pixelValue) / __numSamples; 71 | } 72 | } 73 | 74 | float standardDeviation = 0.0f; 75 | 76 | for (int y=src.bounds.y1; y < src.bounds.y2; y++) 77 | { 78 | for (int x=src.bounds.x1; x < src.bounds.x2; x++) 79 | { 80 | const float3 pixelValue = float3( 81 | src(x, y, 0), 82 | src(x, y, 1), 83 | src(x, y, 2) 84 | ); 85 | 86 | const float deviation = sumComponent(fabs(pixelValue - mean)); 87 | 88 | standardDeviation += min(deviation * deviation, _maximum) / __numSamples; 89 | } 90 | } 91 | 92 | dst() = float4(minValue, maxValue, mean, sqrt(standardDeviation)); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /src/blink/include/aovs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Functions for returning AOVs 9 | // 10 | 11 | #define BEAUTY_AOV 0 12 | #define WORLD_POSITION_AOV 1 13 | #define LOCAL_POSITION_AOV 2 14 | #define NORMAL_AOV 3 15 | #define DEPTH_AOV 4 16 | #define STATS_AOV 5 17 | 18 | 19 | /** 20 | * Get the pixel value for the chosen AOV when we are exiting early. 21 | * 22 | * @arg aovType: The selected AOV type. 23 | * @arg worldPosition: The world position at the time of exit. 24 | * @arg localPosition: The local position at the time of exit. 25 | * @arg normal: The surface normal at the time of exit. 26 | * @arg depth: The depth at the time of exit. 27 | * @arg objectId: The object ID that was last hit. 28 | * 29 | * @returns: The pixel colour for the AOV. 30 | */ 31 | inline float4 earlyExitAOVs( 32 | const int aovType, 33 | const float3 &worldPosition, 34 | const float3 &localPosition, 35 | const float3 &normal, 36 | const float depth, 37 | const float objectId) 38 | { 39 | if (aovType == WORLD_POSITION_AOV) 40 | { 41 | return float4(worldPosition.x, worldPosition.y, worldPosition.z, objectId); 42 | } 43 | if (aovType == LOCAL_POSITION_AOV) 44 | { 45 | return float4(localPosition.x, localPosition.y, localPosition.z, objectId); 46 | } 47 | if (aovType == NORMAL_AOV) 48 | { 49 | return float4(normal.x, normal.y, normal.z, objectId); 50 | } 51 | return float4(depth, 0, 0, objectId); 52 | } 53 | 54 | 55 | /** 56 | * Get the pixel value for the chosen AOV when we are exiting after 57 | * completing all bounces. 58 | * 59 | * @arg aovType: The selected AOV type. 60 | * @arg iterations: The number of iterations while tracing. 61 | * @arg bounces: The number of bounces while tracing. 62 | * @arg objectId: The object ID that was last hit. 63 | * @arg rayColour: The accumulated ray colour. 64 | * 65 | * @returns: The pixel colour for the AOV. 66 | */ 67 | inline float4 finalAOVs( 68 | const int aovType, 69 | const float iterations, 70 | const float bounces, 71 | const float objectId, 72 | const float4 &rayColour) 73 | { 74 | if (aovType == STATS_AOV) 75 | { 76 | return float4(iterations, bounces, 0, objectId); 77 | } 78 | return float4(rayColour.x, rayColour.y, rayColour.z, objectId); 79 | } 80 | 81 | 82 | /** 83 | * Get the pixel value for the chosen AOV when we have missed everything 84 | * in the scene. 85 | * 86 | * @arg aovType: The selected AOV type. 87 | * @arg iterations: The number of iterations while tracing. 88 | * @arg bounces: The number of bounces while tracing. 89 | * @arg objectId: The object ID that was last hit. 90 | * 91 | * @returns: The pixel colour for the AOV. 92 | */ 93 | inline float4 rayMissAOVs( 94 | const int aovType, 95 | const float iterations, 96 | const float bounces, 97 | const float objectId) 98 | { 99 | if (aovType == STATS_AOV) 100 | { 101 | return float4(iterations, bounces, 0, objectId); 102 | } 103 | return float4(0); 104 | } 105 | -------------------------------------------------------------------------------- /src/blink/kernels/hdri_irradiance.blink: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | #include "math.h" 8 | 9 | 10 | kernel HDRIrradiance : ImageComputationKernel 11 | { 12 | Image hdri; // the input image 13 | Image dst; // the output image 14 | 15 | param: 16 | int2 _samples; 17 | 18 | local: 19 | float2 __hdriPixelSize; 20 | float3 __up; 21 | float2 __sampleStep; 22 | 23 | 24 | /** 25 | * Give the parameters labels and default values. 26 | */ 27 | void define() 28 | { 29 | defineParam(_samples, "Samples", int2(100, 50)); 30 | } 31 | 32 | 33 | /** 34 | * Initialize the local variables. 35 | */ 36 | void init() 37 | { 38 | __hdriPixelSize = float2(hdri.bounds.width() / (2.0f * PI), hdri.bounds.height() / PI); 39 | __up = float3(0, 1, 0); 40 | 41 | __sampleStep = float2( 42 | 2.0f * PI / (float) _samples.x, 43 | PI / (2.0f * (float) _samples.y) 44 | ); 45 | } 46 | 47 | 48 | /** 49 | * Get the value of hdri the ray would hit at infinite distance 50 | * 51 | * @arg rayDirection: The direction of the ray. 52 | * 53 | * @returns: The colour of the pixel in the direction of the ray. 54 | */ 55 | float4 readHDRIValue(float3 rayDirection) 56 | { 57 | const float2 angles = cartesionUnitVectorToSpherical(rayDirection); 58 | 59 | // Why does bilinear give nans? :( 60 | return hdri( 61 | round(__hdriPixelSize.x * angles.x) - 1, 62 | round(hdri.bounds.height() - (__hdriPixelSize.y * angles.y)) - 1 63 | ); 64 | } 65 | 66 | 67 | /** 68 | * Compute the irradiance of a pixel. 69 | * 70 | * @arg pos: The x, and y location we are currently processing. 71 | */ 72 | void process(int2 pos) 73 | { 74 | const float2 uvPosition = pixelsToUV( 75 | float2(pos.x, pos.y), 76 | float2(hdri.bounds.width(), hdri.bounds.height()) 77 | ); 78 | const float3 direction = sphericalUnitVectorToCartesion( 79 | uvPositionToAngles(uvPosition) 80 | ); 81 | 82 | const float3 tangentRight = normalize(cross(__up, direction)); 83 | const float3 tangentUp = normalize(cross(direction, tangentRight)); 84 | 85 | float4 irradiance = float4(0); 86 | 87 | for (float theta = 0.0f; theta < 2.0f * PI; theta += __sampleStep.x) 88 | { 89 | for (float phi = PI / 2.0f; phi > 0.0f; phi -= __sampleStep.y) 90 | { 91 | const float3 tangent = sphericalUnitVectorToCartesion(float2(theta, phi)); 92 | const float3 sampleDirection = ( 93 | tangent.x * tangentRight 94 | + tangent.z * tangentUp 95 | + tangent.y * direction 96 | ); 97 | 98 | irradiance += readHDRIValue(sampleDirection) * cos(phi) * sin(phi); 99 | } 100 | } 101 | 102 | dst() = PI * irradiance / (float) (_samples.x * _samples.y); 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nuke_scripts 2 | build 3 | images 4 | *.nk~ 5 | *.autosave 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | share/python-wheels/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | MANIFEST 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .nox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *.cover 55 | *.py,cover 56 | .hypothesis/ 57 | .pytest_cache/ 58 | cover/ 59 | 60 | # Translations 61 | *.mo 62 | *.pot 63 | 64 | # Django stuff: 65 | *.log 66 | local_settings.py 67 | db.sqlite3 68 | db.sqlite3-journal 69 | 70 | # Flask stuff: 71 | instance/ 72 | .webassets-cache 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | .pybuilder/ 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | # For a library or package, you might want to ignore these files since the code is 93 | # intended to run in multiple environments; otherwise, check them in: 94 | # .python-version 95 | 96 | # pipenv 97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 100 | # install all needed dependencies. 101 | #Pipfile.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/#use-with-ide 116 | .pdm.toml 117 | 118 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 119 | __pypackages__/ 120 | 121 | # Celery stuff 122 | celerybeat-schedule 123 | celerybeat.pid 124 | 125 | # SageMath parsed files 126 | *.sage.py 127 | 128 | # Environments 129 | .env 130 | .venv 131 | env/ 132 | venv/ 133 | ENV/ 134 | env.bak/ 135 | venv.bak/ 136 | 137 | # Spyder project settings 138 | .spyderproject 139 | .spyproject 140 | 141 | # Rope project settings 142 | .ropeproject 143 | 144 | # mkdocs documentation 145 | /site 146 | 147 | # mypy 148 | .mypy_cache/ 149 | .dmypy.json 150 | dmypy.json 151 | 152 | # Pyre type checker 153 | .pyre/ 154 | 155 | # pytype static type analyzer 156 | .pytype/ 157 | 158 | # Cython debug symbols 159 | cython_debug/ 160 | -------------------------------------------------------------------------------- /examples/mandelbulb.nk: -------------------------------------------------------------------------------- 1 | #! /usr/local/Nuke13.2v2/libnuke-13.2.2.so -nx 2 | version 13.2 v2 3 | define_window_layout_xml { 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | Root { 34 | inputs 0 35 | name /home/ob1/software/nuke/dev/raymarch/examples/mandelbulb.nk 36 | frame 14 37 | format "1024 778 0 0 1024 778 1 1K_Super_35(full-ap)" 38 | proxy_type scale 39 | proxy_format "1024 778 0 0 1024 778 1 1K_Super_35(full-ap)" 40 | colorManagement Nuke 41 | workingSpaceLUT linear 42 | monitorLut sRGB 43 | monitorOutLUT rec709 44 | int8Lut sRGB 45 | int16Lut sRGB 46 | logLut Cineon 47 | floatLut linear 48 | } 49 | sdf_light { 50 | inputs 0 51 | name sdf_light6 52 | tile_color 0xffffffff 53 | label point 54 | xpos 21015 55 | ypos 1060 56 | dimension_x {0 1 3} 57 | intensity 2 58 | colour {1 1 1} 59 | } 60 | sdf_light { 61 | name sdf_light2 62 | tile_color 0xffffffff 63 | label point 64 | xpos 21015 65 | ypos 1098 66 | dimension_x {1 2 3} 67 | intensity 2 68 | colour {1 1 1} 69 | } 70 | sdf_light { 71 | name sdf_light1 72 | tile_color 0xffffffff 73 | label "ambient occlusion" 74 | xpos 21015 75 | ypos 1136 76 | type "ambient occlusion" 77 | dimension_x {5 0 0} 78 | intensity 0.15 79 | colour {1 1 1} 80 | } 81 | push 0 82 | push 0 83 | Axis2 { 84 | inputs 0 85 | name Axis1 86 | xpos 21129 87 | ypos 1035 88 | } 89 | Camera2 { 90 | translate {0.05 0 1.1} 91 | rotate {12 0 0} 92 | focal 7.5 93 | name Camera1 94 | xpos 21129 95 | ypos 1121 96 | } 97 | Dot { 98 | name Dot3 99 | xpos 21153 100 | ypos 1206 101 | } 102 | sdf_material { 103 | inputs 0 104 | name sdf_material1 105 | xpos 20739 106 | ypos 1143 107 | emission_colour {1 1 1} 108 | } 109 | push 0 110 | push 0 111 | sdf_primitive { 112 | inputs 3 113 | name sdf_primitive2 114 | label mandelbulb 115 | xpos 20888 116 | ypos 1137 117 | shape mandelbulb 118 | dimension_x 15 119 | dimension_y 10 120 | dimension_z 4 121 | dimension_w 1 122 | rotate {65 50 0} 123 | } 124 | Dot { 125 | name Dot2 126 | xpos 20922 127 | ypos 1205 128 | } 129 | ray_march { 130 | inputs 5 131 | name ray_march1 132 | xpos 21015 133 | ypos 1281 134 | max_paths_per_pixel 3 135 | max_bounces 0 136 | max_light_sampling_bounces 1 137 | equiangular_samples 0 138 | format 0 139 | } 140 | HueShift { 141 | color_saturation 0.82 142 | hue_rotation -30 143 | name HueShift3 144 | xpos 21015 145 | ypos 1307 146 | } 147 | Dot { 148 | name Dot1 149 | selected true 150 | xpos 21049 151 | ypos 1391 152 | } 153 | -------------------------------------------------------------------------------- /examples/mandelbox.nk: -------------------------------------------------------------------------------- 1 | #! /usr/local/Nuke13.2v2/libnuke-13.2.2.so -nx 2 | version 13.2 v2 3 | define_window_layout_xml { 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | Root { 34 | inputs 0 35 | name /home/ob1/software/nuke/dev/raymarch/examples/mandelbox.nk 36 | frame 107 37 | last_frame 400 38 | format "1024 778 0 0 1024 778 1 1K_Super_35(full-ap)" 39 | proxy_type scale 40 | proxy_format "1024 778 0 0 1024 778 1 1K_Super_35(full-ap)" 41 | colorManagement Nuke 42 | workingSpaceLUT linear 43 | monitorLut sRGB 44 | monitorOutLUT rec709 45 | int8Lut sRGB 46 | int16Lut sRGB 47 | logLut Cineon 48 | floatLut linear 49 | } 50 | StickyNote { 51 | inputs 0 52 | name StickyNote1 53 | label "Increase the number of paths to clean up the\ndefocused noise. You can also disable the\n'enable depth of field' knob and use the depth\nAOV with a ZDefocus node" 54 | xpos 20842 55 | ypos 1103 56 | } 57 | sdf_light { 58 | inputs 0 59 | name sdf_light4 60 | label point 61 | xpos 20724 62 | ypos 907 63 | dimension_x {3 0 3} 64 | dimension_y 2 65 | intensity 2 66 | } 67 | sdf_light { 68 | name sdf_light3 69 | label point 70 | xpos 20724 71 | ypos 945 72 | dimension_x {1 1 2} 73 | intensity 2 74 | } 75 | sdf_light { 76 | name sdf_light5 77 | label "ambient occlusion" 78 | xpos 20724 79 | ypos 983 80 | type "ambient occlusion" 81 | dimension_x {5 0 0} 82 | intensity 0.1 83 | falloff 4 84 | } 85 | push 0 86 | push 0 87 | Axis2 { 88 | inputs 0 89 | name Axis2 90 | xpos 20856 91 | ypos 829 92 | } 93 | Camera2 { 94 | translate {{curve x1 0 x400 -0.1} 0 2.5} 95 | focal 17.8 96 | focal_point 0.38 97 | fstop 10 98 | name Camera2 99 | xpos 20856 100 | ypos 930 101 | } 102 | Dot { 103 | name Dot4 104 | xpos 20880 105 | ypos 1052 106 | } 107 | sdf_material { 108 | inputs 0 109 | name sdf_material1 110 | xpos 20446 111 | ypos 989 112 | } 113 | push 0 114 | push 0 115 | sdf_primitive { 116 | inputs 3 117 | name sdf_primitive1 118 | label mandelbox 119 | xpos 20587 120 | ypos 983 121 | shape mandelbox 122 | dimension_x -1.75 123 | dimension_y 17 124 | dimension_z 0.03137584 125 | dimension_w {{curve x1 0.71666667 x400 0.8}} 126 | rotate {45 15 0} 127 | } 128 | Dot { 129 | name Dot2 130 | xpos 20621 131 | ypos 1053 132 | } 133 | ray_march { 134 | inputs 5 135 | name ray_march3 136 | xpos 20724 137 | ypos 1126 138 | max_paths_per_pixel 5 139 | max_bounces 0 140 | enable_dof true 141 | max_light_sampling_bounces 1 142 | equiangular_samples 0 143 | format 0 144 | } 145 | HueShift { 146 | saturation 0.62 147 | color_saturation 0.82 148 | hue_rotation -30 149 | name HueShift1 150 | xpos 20724 151 | ypos 1152 152 | } 153 | Dot { 154 | name Dot1 155 | selected true 156 | xpos 20758 157 | ypos 1230 158 | } 159 | -------------------------------------------------------------------------------- /src/python/sdf/light.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Knob management for sdf lights. 7 | 8 | # Add on knob changed callback to sdf_light group: 9 | nuke.toNode("sdf_light").knob("knobChanged").setValue( 10 | "__import__('sdf.light', fromlist='SDFLight').SDFLight().handle_knob_changed()" 11 | ) 12 | 13 | # Add on node create callback to sdf_light group: 14 | nuke.toNode("sdf_light").knob("onCreate").setValue( 15 | "__import__('sdf.light', fromlist='SDFLight').SDFLight().handle_node_created()" 16 | ) 17 | """ 18 | from collections import OrderedDict 19 | 20 | from .knob_manager import KnobChangedCallbacks, SDFGeoKnobManager 21 | from .utils import rgb_to_hex 22 | 23 | 24 | class SDFLight(SDFGeoKnobManager): 25 | """Knob manager for light shapes in signed distance fields.""" 26 | 27 | type_knob_name = "type" 28 | soften_shadows_knob_name = "soften_shadows" 29 | shadow_hardness_knob_name = "shadow_hardness" 30 | falloff_knob_name = "falloff" 31 | colour_knob_name = "colour" 32 | 33 | _knob_changed_callbacks = KnobChangedCallbacks(SDFGeoKnobManager._knob_changed_callbacks) 34 | 35 | _dimensional_axes = ("x", "y") 36 | 37 | dimensional_knob_defaults = { 38 | "directional": OrderedDict([ 39 | ( 40 | "direction", 41 | { 42 | "default": [0., -1., 0.], 43 | "tooltip": "The direction of the light.", 44 | }, 45 | ), 46 | ]), 47 | "point": OrderedDict([ 48 | ( 49 | "position", 50 | { 51 | "default": [0., 1., 0.], 52 | "tooltip": "The position of the light.", 53 | }, 54 | ), 55 | ]), 56 | "ambient": OrderedDict(), 57 | "ambient occlusion": OrderedDict([ 58 | ( 59 | "iterations", 60 | { 61 | "default": 5., 62 | "range": (1., 10.), 63 | "tooltip": "The number of iterations to compute.", 64 | }, 65 | ), 66 | ]), 67 | } 68 | 69 | def _update_ambient_dependent_knobs(self): 70 | """Enable/disable the knobs that are not used by ambient lights""" 71 | not_ambient = "ambient" not in self._knob.value() 72 | 73 | self._node.knob(self.falloff_knob_name).setEnabled(not_ambient) 74 | 75 | soften_shadows_knob = self._node.knob(self.soften_shadows_knob_name) 76 | soften_shadows_knob.setEnabled(not_ambient) 77 | self._node.knob(self.shadow_hardness_knob_name).setEnabled( 78 | not_ambient and soften_shadows_knob.value(), 79 | ) 80 | 81 | @_knob_changed_callbacks.register(colour_knob_name) 82 | def _colour_changed(self): 83 | """Change the node colour to match the object for easier ID.""" 84 | self._node.knob("tile_color").setValue(rgb_to_hex(self._knob.value())) 85 | 86 | @_knob_changed_callbacks.register(type_knob_name) 87 | def _type_changed(self): 88 | """Dynamically enable/disable and change the labels/tooltips/values 89 | of the context knobs when the selected type has changed. 90 | """ 91 | self._dropdown_context_changed( 92 | self.dimensional_knob_defaults, 93 | self.dimensional_context_knobs, 94 | set_node_label=True, 95 | ) 96 | self._update_ambient_dependent_knobs() 97 | 98 | @_knob_changed_callbacks.register(soften_shadows_knob_name) 99 | def _soften_shadows_changed(self): 100 | """Dynamically enable/disable the shadow hardness knob depending 101 | on whether or not shadow softening has been enabled. 102 | """ 103 | self._node.knob(self.shadow_hardness_knob_name).setEnabled(self._knob.value()) 104 | 105 | def _dropdown_context_changed(self, default_dict, context_knobs, set_node_label=False): 106 | """Dynamically enable/disable and change the labels/tooltips/values 107 | of the context knobs dependent on dropdown knobs. 108 | 109 | Args: 110 | default_dict (dict(str, OrderedDict(str, dict()))): The 111 | default values for the context sensitive knobs. 112 | 113 | context_knobs (list(nuke.Knob)): The knobs that change with 114 | the context. 115 | 116 | set_node_label (bool): Set the node's label to the context 117 | value if True. 118 | """ 119 | if "ambient" in self._knob.value(): 120 | context_knobs = context_knobs[::-1] 121 | 122 | super(SDFLight, self)._dropdown_context_changed( 123 | default_dict, 124 | context_knobs, 125 | set_node_label=set_node_label, 126 | ) 127 | -------------------------------------------------------------------------------- /src/python/sdf/knob_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Knob management for primitive shapes in signed distance fields.""" 7 | import logging 8 | 9 | import nuke 10 | 11 | 12 | _LOGGER = logging.getLogger(__file__) 13 | 14 | 15 | class KnobChangedCallbacks(dict): 16 | """Class to register knob callbacks using decorators.""" 17 | 18 | def register(self, knob_name): 19 | """Register the decorated function as a callback of the knob 20 | given by knob_name. 21 | 22 | Args: 23 | knob_name (str): The name of the knob this is a callback 24 | for. 25 | """ 26 | def decorated(method): 27 | self[knob_name] = method 28 | return method 29 | return decorated 30 | 31 | def register_multiple(self, knob_names): 32 | """Register the decorated function as a callback of the knobs 33 | given by knob_names. 34 | 35 | Args: 36 | knob_name (list(str)): The name of the knobs this is a 37 | callback for. 38 | """ 39 | def decorated(method): 40 | for knob_name in knob_names: 41 | self[knob_name] = method 42 | return method 43 | return decorated 44 | 45 | 46 | class KnobManager(object): 47 | """Knob manager for primitive shapes in signed distance fields.""" 48 | 49 | _knob_changed_callbacks = KnobChangedCallbacks() 50 | 51 | def __init__(self): 52 | """Initialize the manager""" 53 | self._node = nuke.thisNode() 54 | self._knob = nuke.thisKnob() 55 | 56 | def handle_node_created(self): 57 | """Setup a newly created node""" 58 | self._input_changed() 59 | 60 | def handle_knob_changed(self): 61 | """Handle a knob changed event""" 62 | knob_name = self._knob.name() 63 | try: 64 | type(self)._knob_changed_callbacks.get(knob_name)(self) 65 | except TypeError: 66 | _LOGGER.debug("No callbacks for knob: %s", knob_name) 67 | 68 | @_knob_changed_callbacks.register("inputChange") 69 | def _input_changed(self): 70 | """Called when the input has changed""" 71 | 72 | 73 | class SDFGeoKnobManager(KnobManager): 74 | """Knob manager for primitive shapes in signed distance fields.""" 75 | 76 | sibling_input_index = 0 77 | children_input_index = 1 78 | 79 | _knob_changed_callbacks = KnobChangedCallbacks(KnobManager._knob_changed_callbacks) 80 | 81 | _dimensional_axes = ("x", "y", "z") 82 | _dimensional_knob_prefix = "dimension_" 83 | 84 | def __init__(self): 85 | """Initialize the manager""" 86 | super(SDFGeoKnobManager, self).__init__() 87 | 88 | self._knob_names_only_enabled_if_parent = set() 89 | 90 | @property 91 | def dimensional_context_knobs(self): 92 | """list(nuke.Knob): The context knobs for an sdf node's 93 | dimensional parameters. 94 | """ 95 | return [ 96 | self._node.knob(self._dimensional_knob_prefix + axis) 97 | for axis in self._dimensional_axes 98 | ] 99 | 100 | @property 101 | def has_children(self): 102 | """bool: True if a node is connected to the 'children' input.""" 103 | return self._node.input(self.children_input_index) is not None 104 | 105 | @_knob_changed_callbacks.register("inputChange") 106 | def _input_changed(self): 107 | """Enable/disable the knobs that only apply if this object has 108 | children. 109 | """ 110 | has_child_input = self.has_children 111 | for knob_name in self._knob_names_only_enabled_if_parent: 112 | self._node.knob(knob_name).setEnabled(has_child_input) 113 | 114 | def _dropdown_context_changed(self, default_dict, context_knobs, set_node_label=False): 115 | """Dynamically enable/disable and change the labels/tooltips/values 116 | of the context knobs dependent on dropdown knobs. 117 | 118 | Args: 119 | default_dict (dict(str, OrderedDict(str, dict()))): The 120 | default values for the context sensitive knobs. 121 | 122 | context_knobs (list(nuke.Knob)): The knobs that change with 123 | the context. 124 | 125 | set_node_label (bool): Set the node's label to the context 126 | value if True. 127 | """ 128 | new_value = self._knob.value() 129 | 130 | if set_node_label: 131 | self._node.knob("label").setValue(new_value) 132 | 133 | default_values = default_dict.get(new_value, {}) 134 | 135 | for (knob_name, knob_values), context_knob in zip( 136 | default_values.items(), 137 | context_knobs, 138 | ): 139 | print("Visible", context_knob) 140 | context_knob.setVisible(True) 141 | context_knob.setLabel(knob_name) 142 | context_knob.setValue(knob_values["default"]) 143 | context_knob.setTooltip(knob_values["tooltip"]) 144 | if "range" in knob_values: 145 | context_knob.setRange(*knob_values["range"]) 146 | 147 | for context_knob in context_knobs[len(default_values):]: 148 | print("Not Visible", context_knob) 149 | context_knob.setVisible(False) 150 | -------------------------------------------------------------------------------- /examples/nested_dielectrics.nk: -------------------------------------------------------------------------------- 1 | #! /usr/local/Nuke13.2v2/libnuke-13.2.2.so -nx 2 | version 13.2 v2 3 | define_window_layout_xml { 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | Root { 34 | inputs 0 35 | name /home/ob1/software/nuke/dev/raymarch/examples/nested_dielectrics.nk 36 | frame 1100 37 | first_frame 1001 38 | last_frame 1111 39 | format "1280 720 0 0 1280 720 1 HD_720" 40 | colorManagement Nuke 41 | workingSpaceLUT linear 42 | monitorLut sRGB 43 | monitorOutLUT rec709 44 | int8Lut sRGB 45 | int16Lut sRGB 46 | logLut Cineon 47 | floatLut linear 48 | } 49 | sdf_noise { 50 | inputs 0 51 | name sdf_noise1 52 | xpos 498 53 | ypos 262 54 | size 5 55 | translation {1 0 0} 56 | octaves 7 57 | lacunarity 4.6 58 | gain 0.4 59 | gamma 0.445 60 | black_point 0.01 61 | white_point 0.09 62 | scattering true 63 | extinction true 64 | } 65 | Dot { 66 | name Dot1 67 | xpos 532 68 | ypos 357 69 | } 70 | push 0 71 | push 0 72 | CheckerBoard2 { 73 | inputs 0 74 | format "2048 1024 0 0 2048 1024 1 2K_LatLong" 75 | boxsize 32 76 | centerlinewidth 0 77 | name CheckerBoard1 78 | xpos 730 79 | ypos 169 80 | } 81 | Grade { 82 | white 0.1 83 | name Grade1 84 | xpos 730 85 | ypos 241 86 | } 87 | Reformat { 88 | format "2048 1024 0 0 2048 1024 1 2K_LatLong" 89 | name Reformat1 90 | xpos 730 91 | ypos 335 92 | } 93 | Axis2 { 94 | inputs 0 95 | rotate {0 -5 0} 96 | name Axis2 97 | xpos 626 98 | ypos 149 99 | } 100 | Camera3 { 101 | translate {0 0 6} 102 | focal 19.4 103 | name Camera1 104 | xpos 626 105 | ypos 245 106 | } 107 | Dot { 108 | name Dot4 109 | xpos 650 110 | ypos 353 111 | } 112 | sdf_material { 113 | inputs 0 114 | name sdf_material1 115 | xpos 237 116 | ypos 347 117 | emission 15 118 | } 119 | push 0 120 | sdf_material { 121 | inputs 0 122 | name sdf_material2 123 | tile_color 0x1ab2ffff 124 | xpos 237 125 | ypos 259 126 | specular_colour {0.1 0.7 1} 127 | transmission 1 128 | extinction_colour {0.1 0.7 1} 129 | extinction_coefficient 0.235 130 | scattering_coefficient 0.01 131 | } 132 | sdf_material { 133 | inputs 0 134 | name sdf_material3 135 | tile_color 0xe64c1aff 136 | xpos 235 137 | ypos 189 138 | specular_colour {0.3 0.1 0.7} 139 | transmission 1 140 | refractive_index 1 141 | extinction_colour {0.9 0.3 0.1} 142 | extinction_coefficient 3.95 143 | } 144 | push 0 145 | sdf_material { 146 | inputs 0 147 | name sdf_material4 148 | tile_color 0xe600e6ff 149 | xpos 233 150 | ypos 118 151 | specular_colour {0.4 0.5 0.1} 152 | transmission 1 153 | refractive_index 1 154 | extinction_colour {0.9 0 0.9} 155 | extinction_coefficient 2.7 156 | } 157 | push 0 158 | sdf_material { 159 | inputs 0 160 | name sdf_material5 161 | tile_color 0x4ce680ff 162 | xpos 235 163 | ypos 48 164 | transmission 1 165 | refractive_index 1 166 | extinction_colour {0.3 0.9 0.5} 167 | extinction_coefficient 1.65 168 | } 169 | sdf_material { 170 | inputs 0 171 | name sdf_material6 172 | tile_color 0xff3333ff 173 | xpos 236 174 | ypos -19 175 | specular_colour {1 0.2 0.2} 176 | transmission 1 177 | refractive_index 1.16 178 | extinction_colour {1 0.2 0.2} 179 | extinction_coefficient 6.4 180 | } 181 | push 0 182 | push 0 183 | sdf_primitive { 184 | inputs 3 185 | name sdf_primitive7 186 | label sphere 187 | xpos 374 188 | ypos -25 189 | dimension_x 0.3 190 | dimension_y 0.75 191 | dimension_z 0.5 192 | translate {0.1 0.3 0.1} 193 | blend_strength 0.805 194 | } 195 | push 0 196 | sdf_primitive { 197 | inputs 3 198 | name sdf_primitive6 199 | label sphere 200 | xpos 374 201 | ypos 42 202 | dimension_x 0.9 203 | dimension_y 0.75 204 | dimension_z 0.5 205 | translate {0.5 -0.5 0} 206 | blend_strength 0.805 207 | } 208 | sdf_primitive { 209 | inputs 3 210 | name sdf_primitive5 211 | label octahedron 212 | xpos 374 213 | ypos 112 214 | shape octahedron 215 | dimension_y 0.75 216 | translate {-0.7 -0.5 0.3} 217 | rotate {5 45 0} 218 | blend_strength 0.805 219 | } 220 | sdf_primitive { 221 | inputs 3 222 | name sdf_primitive4 223 | label cylinder 224 | xpos 374 225 | ypos 183 226 | shape cylinder 227 | dimension_x 0.3 228 | dimension_y 0.75 229 | translate {0 0.7 -0.3} 230 | rotate {13 5 15} 231 | blend_strength 0.805 232 | } 233 | push 0 234 | sdf_primitive { 235 | inputs 3 236 | name sdf_primitive3 237 | label sphere 238 | xpos 374 239 | ypos 253 240 | dimension_x 1.9 241 | translate {0.75 0 0} 242 | blend_strength 0.805 243 | } 244 | sdf_primitive { 245 | inputs 3 246 | name sdf_primitive2 247 | label sphere 248 | xpos 374 249 | ypos 341 250 | dimension_x 0.4 251 | translate {-2.5 0 0} 252 | blend_strength 0.095 253 | } 254 | Dot { 255 | name Dot3 256 | xpos 408 257 | ypos 450 258 | } 259 | ray_march { 260 | inputs 6 261 | name ray_march2 262 | xpos 730 263 | ypos 446 264 | min_paths_per_pixel 0 265 | ray_distance 20 266 | max_bounces 9 267 | hit_tolerance 0.001 268 | max_light_sampling_bounces 9 269 | equiangular_samples 14 270 | extinction_coefficient 0.7 271 | scattering_coefficient 1 272 | hdri_offset_angle 90 273 | variance_range 2 274 | format 0 275 | } 276 | -------------------------------------------------------------------------------- /src/gizmos/sdf_light.gizmo: -------------------------------------------------------------------------------- 1 | Gizmo { 2 | onCreate "__import__('sdf.light', fromlist='SDFLight').SDFLight().handle_node_created()" 3 | knobChanged "__import__('sdf.light', fromlist='SDFLight').SDFLight().handle_knob_changed()" 4 | tile_color 0xffcc80ff 5 | label point 6 | addUserKnob {20 User l "SDF LIght"} 7 | addUserKnob {4 type t "The type of light." M {ambient "ambient occlusion" directional point}} 8 | type point 9 | addUserKnob {13 dimension_x l position t "The position of the light." R 1 10} 10 | dimension_x {0 1 0} 11 | addUserKnob {7 dimension_y l iterations t "The number of iterations to compute." +HIDDEN R 1 10} 12 | dimension_y 5 13 | addUserKnob {7 intensity t "The intensity of the light." R 0 10} 14 | intensity 1 15 | addUserKnob {7 falloff t "The power of the light's intensity falloff. Two is physically accurate." R 0 5} 16 | falloff 2 17 | addUserKnob {18 colour t "The colour of the light."} 18 | colour {1 0.8 0.5} 19 | addUserKnob {7 shadow_hardness l "shadow hardness" t "The hardness of the shadows, this is a non-physically accurate effect, but can look nice." +DISABLED R 1 100} 20 | shadow_hardness 1 21 | addUserKnob {6 soften_shadows l "soften shadows" t "Soften the shadows using a non-phisically accurate effect." -STARTLINE} 22 | addUserKnob {26 ""} 23 | addUserKnob {26 info l "" +STARTLINE T "v2.1.0 - (c) Owen Bulka - 2022"} 24 | } 25 | Input { 26 | inputs 0 27 | name siblings 28 | xpos 803 29 | ypos 30 30 | } 31 | Dot { 32 | name Dot1 33 | xpos 837 34 | ypos 488 35 | } 36 | add_layer {sdf_light sdf_light.light_pos_dir_x sdf_light.light_pos_dir_y sdf_light.light_pos_dir_z sdf_light.intensity} 37 | Constant { 38 | inputs 0 39 | channels sdf_light 40 | color {{parent.dimension_y} 0 0 {parent.intensity}} 41 | format "1 1 0 0 1 1 1 1x1" 42 | name light1 43 | xpos 229 44 | ypos 143 45 | } 46 | Constant { 47 | inputs 0 48 | channels sdf_light 49 | color {{parent.dimension_x.x} {parent.dimension_x.y} {parent.dimension_x.z} {parent.intensity}} 50 | format "1 1 0 0 1 1 1 1x1" 51 | name light 52 | xpos 363 53 | ypos 11 54 | } 55 | Switch { 56 | inputs 2 57 | which {{"parent.type < 2"}} 58 | name Switch1 59 | xpos 363 60 | ypos 172 61 | } 62 | Dot { 63 | name Dot9 64 | xpos 397 65 | ypos 324 66 | } 67 | add_layer {sdf_light_properties sdf_light_properties.colour_r sdf_light_properties.colour_g sdf_light_properties.colour_b sdf_light_properties.type} 68 | Constant { 69 | inputs 0 70 | channels sdf_light_properties 71 | color {{parent.colour.r} {parent.colour.g} {parent.colour.b} {"parent.soften_shadows ? -parent.type : parent.type"}} 72 | format "1 1 0 0 1 1 1 1x1" 73 | name light_props 74 | xpos 501 75 | ypos 11 76 | } 77 | Copy { 78 | inputs 2 79 | channels all 80 | name Copy1 81 | xpos 501 82 | ypos 380 83 | } 84 | add_layer {sdf_light_properties1 sdf_light_properties1.shadow_hardness sdf_light_properties1.falloff} 85 | Constant { 86 | inputs 0 87 | channels sdf_light_properties1 88 | color {{parent.shadow_hardness} {parent.falloff} 0 0} 89 | format "1 1 0 0 1 1 1 1x1" 90 | name light_props1 91 | xpos 646 92 | ypos 12 93 | } 94 | Copy { 95 | inputs 2 96 | channels all 97 | name Copy2 98 | xpos 642 99 | ypos 380 100 | } 101 | Group { 102 | inputs 2 103 | name sdf_merge 104 | xpos 642 105 | ypos 484 106 | disable {{"!(\[exists parent.input0] && !(parent.input0.disable && !\[exists parent.input0.input0]))"}} 107 | addUserKnob {20 User} 108 | addUserKnob {26 warning l Warning: T "Only merge lights with lights, and primitives with primitives"} 109 | } 110 | Reformat { 111 | inputs 0 112 | type "to box" 113 | box_width 1 114 | box_height 1 115 | box_fixed true 116 | name Reformat1 117 | xpos -119 118 | ypos 440 119 | } 120 | Input { 121 | inputs 0 122 | name Input2 123 | xpos 158 124 | ypos -28 125 | number 1 126 | } 127 | Dot { 128 | name Dot2 129 | xpos 192 130 | ypos -2 131 | } 132 | set N12a1f990 [stack 0] 133 | Dot { 134 | name Dot3 135 | xpos 329 136 | ypos -2 137 | } 138 | Dot { 139 | name Dot4 140 | xpos 329 141 | ypos 391 142 | } 143 | Input { 144 | inputs 0 145 | name Input1 146 | xpos 0 147 | ypos -32 148 | } 149 | Dot { 150 | name Dot1 151 | xpos 34 152 | ypos -6 153 | } 154 | set Nb8b58f0 [stack 0] 155 | Dot { 156 | name Dot6 157 | xpos -98 158 | ypos -6 159 | } 160 | Dot { 161 | name Dot5 162 | xpos -98 163 | ypos 336 164 | } 165 | push $N12a1f990 166 | Crop { 167 | box {0 0 {Dot1.width+Dot2.width} 1} 168 | reformat true 169 | crop false 170 | name Crop3 171 | xpos 158 172 | ypos 69 173 | } 174 | Crop { 175 | box {0 0 {parent.Dot2.width} 1} 176 | name Crop4 177 | xpos 158 178 | ypos 95 179 | } 180 | Transform { 181 | translate {{parent.Dot1.width} 0} 182 | center {1 0.5} 183 | name Transform2 184 | xpos 158 185 | ypos 121 186 | } 187 | Crop { 188 | box {0 0 {width} {height}} 189 | crop false 190 | name Crop5 191 | xpos 158 192 | ypos 167 193 | } 194 | push $Nb8b58f0 195 | Crop { 196 | box {0 0 {Dot1.width+Dot2.width} 1} 197 | reformat true 198 | crop false 199 | name Crop6 200 | xpos 0 201 | ypos 66 202 | } 203 | Crop { 204 | box {0 0 {parent.Dot1.width} 1} 205 | name Crop7 206 | xpos 0 207 | ypos 92 208 | } 209 | Crop { 210 | box {0 0 {width} {height}} 211 | crop false 212 | name Crop8 213 | xpos 0 214 | ypos 164 215 | } 216 | Merge2 { 217 | inputs 2 218 | also_merge all 219 | name Merge1 220 | xpos 0 221 | ypos 262 222 | disable {{"\[exists parent.input1] ? 0:1"}} 223 | } 224 | Switch { 225 | inputs 2 226 | which {{"\[exists parent.input1] ? 0:1"}} 227 | name Switch2 228 | xpos 0 229 | ypos 332 230 | } 231 | Switch { 232 | inputs 2 233 | which {{"\[exists parent.input0] ? 0:1"}} 234 | name Switch1 235 | xpos 0 236 | ypos 387 237 | } 238 | Switch { 239 | inputs 2 240 | which {{"\[exists parent.input0] ? 0:\[exists parent.input1] ? 0:1"}} 241 | name Switch3 242 | xpos 0 243 | ypos 456 244 | } 245 | Dot { 246 | name out_dot 247 | xpos 36 248 | ypos 518 249 | } 250 | Output { 251 | name Output1 252 | xpos 2 253 | ypos 661 254 | } 255 | end_group 256 | Output { 257 | name Output1 258 | xpos 642 259 | ypos 562 260 | } 261 | end_group 262 | -------------------------------------------------------------------------------- /src/blink/include/camera.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Camera Utilities 9 | // 10 | 11 | 12 | /** 13 | * Compute the field of view from focal length. 14 | * 15 | * @arg focalLength: The focal length. 16 | * 17 | * @returns: The equivalent field of view. 18 | */ 19 | inline float fieldOfView(const float focalLength) 20 | { 21 | return 2 * atan(1 / focalLength); 22 | } 23 | 24 | 25 | /** 26 | * Compute the aspect ratio from image format. 27 | * 28 | * @arg height_: The height of the image. 29 | * @arg width_: The width of the image. 30 | * 31 | * @returns: The aspect ratio. 32 | */ 33 | inline float aspectRatio(const float height_, const float width_) 34 | { 35 | return height_ / width_; 36 | } 37 | 38 | 39 | /** 40 | * 41 | */ 42 | inline float fStopToAperture(const float fStop, const float focalLength) 43 | { 44 | return focalLength / fStop / 1000.0f; 45 | } 46 | 47 | 48 | /** 49 | * Create a projection matrix for a camera. 50 | * 51 | * @arg focalLength: The focal length of the camera. 52 | * @arg horizontalAperture: The horizontal aperture of the camera. 53 | * @arg aspect: The aspect ratio of the camera. 54 | * @arg nearPlane: The distance to the near plane of the camera. 55 | * @arg farPlane: The distance to the far plane of the camera. 56 | * 57 | * @returns: The camera's projection matrix. 58 | */ 59 | float4x4 projectionMatrix( 60 | const float focalLength, 61 | const float horizontalAperture, 62 | const float aspect, 63 | const float nearPlane, 64 | const float farPlane) 65 | { 66 | float farMinusNear = farPlane - nearPlane; 67 | return float4x4( 68 | 2 * focalLength / horizontalAperture, 0, 0, 0, 69 | 0, 2 * focalLength / horizontalAperture / aspect, 0, 0, 70 | 0, 0, -(farPlane + nearPlane) / farMinusNear, -2 * (farPlane * nearPlane) / farMinusNear, 71 | 0, 0, -1, 0 72 | ); 73 | } 74 | 75 | 76 | /** 77 | * Generate a ray out of a camera. 78 | * 79 | * @arg cameraWorldMatrix: The camera matrix. 80 | * @arg inverseProjectionMatrix: The inverse of the projection matrix. 81 | * @arg uvPosition: The UV position in the resulting image. 82 | * @arg rayOrigin: Will store the origin of the ray. 83 | * @arg rayDirection: Will store the direction of the ray. 84 | */ 85 | void createCameraRay( 86 | const float4x4 &cameraWorldMatrix, 87 | const float4x4 &inverseProjectionMatrix, 88 | const float2 &uvPosition, 89 | float3 &rayOrigin, 90 | float3 &rayDirection) 91 | { 92 | positionFromWorldMatrix(cameraWorldMatrix, rayOrigin); 93 | float4 direction = matmul( 94 | inverseProjectionMatrix, 95 | float4(uvPosition.x, uvPosition.y, 0, 1) 96 | ); 97 | matmul( 98 | cameraWorldMatrix, 99 | float4(direction.x, direction.y, direction.z, 0), 100 | direction 101 | ); 102 | rayDirection = normalize(float3(direction.x, direction.y, direction.z)); 103 | } 104 | 105 | 106 | /** 107 | * Generate a ray out of a camera. 108 | * 109 | * @arg cameraWorldMatrix: The camera matrix. 110 | * @arg inverseProjectionMatrix: The inverse of the projection matrix. 111 | * @arg uvPosition: The UV position in the resulting image. 112 | * @arg rayOrigin: Will store the origin of the ray. 113 | * @arg rayDirection: Will store the direction of the ray. 114 | */ 115 | void createCameraRay( 116 | const float4x4 &cameraWorldMatrix, 117 | const float4x4 &inverseProjectionMatrix, 118 | const float2 &uvPosition, 119 | const float aperture, 120 | const float focalDistance, 121 | const float3 &seed, 122 | float3 &rayOrigin, 123 | float3 &rayDirection) 124 | { 125 | createCameraRay( 126 | cameraWorldMatrix, 127 | inverseProjectionMatrix, 128 | uvPosition, 129 | rayOrigin, 130 | rayDirection 131 | ); 132 | 133 | const float4 cameraForward4 = matmul( 134 | cameraWorldMatrix, 135 | float4(0, 0, -1, 0) 136 | ); 137 | const float3 cameraForward = float3( 138 | cameraForward4.x, 139 | cameraForward4.y, 140 | cameraForward4.z 141 | ); 142 | const float4 cameraRight4 = matmul( 143 | cameraWorldMatrix, 144 | float4(1, 0, 0, 0) 145 | ); 146 | const float3 cameraRight = float3( 147 | cameraRight4.x, 148 | cameraRight4.y, 149 | cameraRight4.z 150 | ); 151 | const float4 cameraUp4 = matmul( 152 | cameraWorldMatrix, 153 | float4(0, 1, 0, 0) 154 | ); 155 | const float3 cameraUp = float3( 156 | cameraUp4.x, 157 | cameraUp4.y, 158 | cameraUp4.z 159 | ); 160 | 161 | const float3 focalPlanePoint = rayOrigin + cameraForward * focalDistance; 162 | const float3 focalPlaneNormal = -cameraForward; 163 | 164 | const float focalPointDistance = ( 165 | (dot(focalPlaneNormal, focalPlanePoint) - dot(rayOrigin, focalPlaneNormal)) 166 | / dot(rayDirection, focalPlaneNormal) 167 | ); 168 | const float3 focalPoint = rayOrigin + focalPointDistance * rayDirection; 169 | 170 | const float2 pointInUnitCircle = uniformPointInUnitCircle(seed); 171 | const float2 offset = pointInUnitCircle.x * aperture * float2( 172 | cos(pointInUnitCircle.y), 173 | sin(pointInUnitCircle.y) 174 | ); 175 | 176 | rayOrigin += cameraRight * offset.x + cameraUp * offset.y; 177 | rayDirection = normalize(focalPoint - rayOrigin); 178 | } 179 | 180 | 181 | /** 182 | * Generate a LatLong ray out of a camera. 183 | * 184 | * @arg cameraWorldMatrix: The camera matrix. 185 | * @arg uvPosition: The UV position in the resulting image. 186 | * @arg rayOrigin: Will store the origin of the ray. 187 | * @arg rayDirection: Will store the direction of the ray. 188 | */ 189 | void createLatLongCameraRay( 190 | const float4x4 &cameraWorldMatrix, 191 | const float2 &uvPosition, 192 | float3 &rayOrigin, 193 | float3 &rayDirection) 194 | { 195 | positionFromWorldMatrix(cameraWorldMatrix, rayOrigin); 196 | rayDirection = sphericalUnitVectorToCartesion(uvPositionToAngles(uvPosition)); 197 | 198 | float3x3 cameraRotation; 199 | rotationFromWorldMatrix(cameraWorldMatrix, cameraRotation); 200 | rayDirection = matmul(cameraRotation, rayDirection); 201 | } 202 | -------------------------------------------------------------------------------- /src/gizmos/sdf_noise.gizmo: -------------------------------------------------------------------------------- 1 | Gizmo { 2 | inputs 0 3 | tile_color 0xffffffff 4 | addUserKnob {20 User l "SDF Noise"} 5 | addUserKnob {7 size t "The size of the noise." R 1 300} 6 | size 1 7 | addUserKnob {4 type t "The noise type." M {turbulence fBm "" ""}} 8 | type fBm 9 | addUserKnob {13 translation t "Translate the noise by this amount."} 10 | addUserKnob {3 octaves t "The number of different frequencies to use."} 11 | octaves 10 12 | addUserKnob {7 lacunarity t "The per octave frequency multiplier." R 1 10} 13 | lacunarity 2 14 | addUserKnob {7 gain t "The per octave amplitude multiplier."} 15 | gain 0.5 16 | addUserKnob {7 gamma t "The result will be raised to 1 over this power."} 17 | gamma 0.5 18 | addUserKnob {19 low_frequency_scale l "low frequency scale"} 19 | low_frequency_scale {1 1 1 1} 20 | addUserKnob {19 high_frequency_scale l "high frequency scale"} 21 | high_frequency_scale {1 1 1 1} 22 | addUserKnob {19 low_frequency_translation l "low frequency translation"} 23 | low_frequency_translation {0 0 0 0} 24 | addUserKnob {19 high_frequency_translation l "high frequency translation"} 25 | high_frequency_translation {0 0 0 0} 26 | addUserKnob {7 black_point l "black point" t "The black point of the noise."} 27 | addUserKnob {7 white_point l "white point" t "The white point of the noise."} 28 | white_point 1 29 | addUserKnob {7 lift t "The amount to lift the noise."} 30 | addUserKnob {6 invert t "Invert the noise ie. white becomes black and vice versa." +STARTLINE} 31 | addUserKnob {26 ""} 32 | addUserKnob {6 diffuse t "Apply the noise to the diffuse material property." +STARTLINE} 33 | addUserKnob {6 specular t "Apply the noise to the specular material property." -STARTLINE} 34 | addUserKnob {6 transmission t "Apply the noise to the transmission material property." -STARTLINE} 35 | addUserKnob {6 emission t "Apply the noise to the emission material property." -STARTLINE} 36 | addUserKnob {6 specular_roughness l "specular roughness" t "Apply the noise to the specular roughness material property." +STARTLINE} 37 | addUserKnob {6 transmission_roughness l "transmission roughness" t "Apply the noise to the transmission roughness material property." -STARTLINE} 38 | addUserKnob {6 refractive_index l "refractive index" t "Apply the noise to the refractive index material property." +STARTLINE} 39 | addUserKnob {6 scattering t "Apply the noise to the scattering material property." +STARTLINE} 40 | addUserKnob {6 extinction t "Apply the noise to the extinction material property." -STARTLINE} 41 | addUserKnob {26 div0 l "" +STARTLINE} 42 | addUserKnob {26 info l "" +STARTLINE T "v2.1.0 - (c) Owen Bulka - 2022"} 43 | } 44 | add_layer {sdf_noise_params0 sdf_noise_params0.noise_options sdf_noise_params0.translationX sdf_noise_params0.translationY sdf_noise_params0.translationZ sdf_noise_params0.options sdf_noise_params0.x sdf_noise_params0.y sdf_noise_params0.z} 45 | Constant { 46 | inputs 0 47 | channels sdf_noise_params0 48 | color {{"(parent.extinction << 11) | (parent.scattering << 10) | (parent.invert << 9) | (parent.specular_roughness << 8) | (parent.transmission_roughness << 7) | (parent.refractive_index << 6) | (parent.emission << 5) | (parent.transmission << 4) | (parent.specular << 3) | (parent.diffuse << 2) | (parent.type << 1) | 1"} {-parent.translation.x} {-parent.translation.y} {-parent.translation.z}} 49 | format "1 1 0 0 1 1 1 1x1" 50 | name noise_params0 51 | xpos 862 52 | ypos 447 53 | } 54 | Dot { 55 | name Dot3 56 | xpos 896 57 | ypos 646 58 | } 59 | add_layer {sdf_noise_params1 sdf_noise_params1.octaves sdf_noise_params1.lacunarity sdf_noise_params1.gain sdf_noise_params1.gamma} 60 | Constant { 61 | inputs 0 62 | channels sdf_noise_params1 63 | color {{parent.octaves} {parent.lacunarity} {parent.gain} {parent.gamma}} 64 | format "1 1 0 0 1 1 1 1x1" 65 | name noise_params1 66 | xpos 987 67 | ypos 447 68 | } 69 | Copy { 70 | inputs 2 71 | channels all 72 | name Copy8 73 | xpos 987 74 | ypos 714 75 | } 76 | add_layer {sdf_noise_params2 sdf_noise_params2.size sdf_noise_params2.white_point sdf_noise_params2.black_point sdf_noise_params2.lift} 77 | Constant { 78 | inputs 0 79 | channels sdf_noise_params2 80 | color {{parent.size} {parent.white_point} {parent.black_point} {parent.lift}} 81 | format "1 1 0 0 1 1 1 1x1" 82 | name noise_params2 83 | xpos 1103 84 | ypos 449 85 | } 86 | Copy { 87 | inputs 2 88 | channels all 89 | name Copy1 90 | xpos 1103 91 | ypos 714 92 | } 93 | add_layer {sdf_noise_params3 sdf_noise_params3.lowFrequencyX sdf_noise_params3.lowFrequencyY sdf_noise_params3.lowFrequencyZ sdf_noise_params3.lowFrequencyW} 94 | Constant { 95 | inputs 0 96 | channels sdf_noise_params3 97 | color {{parent.low_frequency_scale} {parent.low_frequency_scale} {parent.low_frequency_scale} {parent.low_frequency_scale}} 98 | format "1 1 0 0 1 1 1 1x1" 99 | name noise_params3 100 | xpos 1225 101 | ypos 449 102 | } 103 | Copy { 104 | inputs 2 105 | channels all 106 | name Copy4 107 | xpos 1225 108 | ypos 714 109 | } 110 | add_layer {sdf_noise_params4 sdf_noise_params4.highFrequencyX sdf_noise_params4.highFrequencyY sdf_noise_params4.highFrequencyZ sdf_noise_params4.highFrequencyW} 111 | Constant { 112 | inputs 0 113 | channels sdf_noise_params4 114 | color {{parent.high_frequency_scale} {parent.high_frequency_scale} {parent.high_frequency_scale} {parent.high_frequency_scale}} 115 | format "1 1 0 0 1 1 1 1x1" 116 | name noise_params4 117 | xpos 1349 118 | ypos 450 119 | } 120 | Copy { 121 | inputs 2 122 | channels all 123 | name Copy3 124 | xpos 1349 125 | ypos 714 126 | } 127 | add_layer {sdf_noise_params5 sdf_noise_params5.lowFrequencyTranslationX sdf_noise_params5.lowFrequencyTranslationY sdf_noise_params5.lowFrequencyTranslationZ sdf_noise_params5.lowFrequencyTranslationW} 128 | Constant { 129 | inputs 0 130 | channels sdf_noise_params5 131 | color {{parent.low_frequency_translation} {parent.low_frequency_translation} {parent.low_frequency_translation} {parent.low_frequency_translation}} 132 | format "1 1 0 0 1 1 1 1x1" 133 | name noise_params5 134 | xpos 1477 135 | ypos 452 136 | } 137 | Copy { 138 | inputs 2 139 | channels all 140 | name Copy2 141 | xpos 1477 142 | ypos 714 143 | } 144 | add_layer {sdf_noise_params6 sdf_noise_params6.highFrequencyTranslationX sdf_noise_params6.highFrequencyTranslationY sdf_noise_params6.highFrequencyTranslationZ sdf_noise_params6.highFrequencyTranslationW} 145 | Constant { 146 | inputs 0 147 | channels sdf_noise_params6 148 | color {{parent.high_frequency_translation} {parent.high_frequency_translation} {parent.high_frequency_translation} {parent.high_frequency_translation}} 149 | format "1 1 0 0 1 1 1 1x1" 150 | name noise_params6 151 | xpos 1594 152 | ypos 453 153 | } 154 | Copy { 155 | inputs 2 156 | channels all 157 | name Copy5 158 | xpos 1594 159 | ypos 714 160 | } 161 | Output { 162 | name Output1 163 | xpos 1594 164 | ypos 818 165 | } 166 | end_group 167 | -------------------------------------------------------------------------------- /src/blink/include/conversion.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Converison functions 9 | // 10 | // These can be used to encode, and decode 8 16-bit channels in the 11 | // maximum of 4 32-bit channels defined by blink 12 | // 13 | 14 | 15 | /** 16 | * Convert a float vector to an int vector. 17 | * 18 | * @arg value: The value to convert. 19 | * 20 | * @returns: The converted vector. 21 | */ 22 | inline int2 floatToInt(const float2 value) 23 | { 24 | return int2( 25 | value.x, 26 | value.y 27 | ); 28 | } 29 | 30 | 31 | /** 32 | * Convert a float vector to an int vector. 33 | * 34 | * @arg value: The value to convert. 35 | * 36 | * @returns: The converted vector. 37 | */ 38 | inline int3 floatToInt(const float3 value) 39 | { 40 | return int3( 41 | value.x, 42 | value.y, 43 | value.z 44 | ); 45 | } 46 | 47 | 48 | /** 49 | * Convert a float vector to an int vector. 50 | * 51 | * @arg value: The value to convert. 52 | * 53 | * @returns: The converted vector. 54 | */ 55 | inline int4 floatToInt(const float4 value) 56 | { 57 | return int4( 58 | value.x, 59 | value.y, 60 | value.z, 61 | value.w 62 | ); 63 | } 64 | 65 | 66 | /** 67 | * Convert an int vector to a float vector. 68 | * 69 | * @arg value: The value to convert. 70 | * 71 | * @returns: The converted vector. 72 | */ 73 | inline float2 intToFloat(const int2 value) 74 | { 75 | return float2( 76 | value.x, 77 | value.y 78 | ); 79 | } 80 | 81 | 82 | /** 83 | * Convert an int vector to a float vector. 84 | * 85 | * @arg value: The value to convert. 86 | * 87 | * @returns: The converted vector. 88 | */ 89 | inline float3 intToFloat(const int3 value) 90 | { 91 | return float3( 92 | value.x, 93 | value.y, 94 | value.z 95 | ); 96 | } 97 | 98 | 99 | /** 100 | * Convert an int vector to a float vector. 101 | * 102 | * @arg value: The value to convert. 103 | * 104 | * @returns: The converted vector. 105 | */ 106 | inline float4 intToFloat(const int4 value) 107 | { 108 | return float4( 109 | value.x, 110 | value.y, 111 | value.z, 112 | value.w 113 | ); 114 | } 115 | 116 | 117 | /** 118 | * Convert a float to a uint without changing the bit values. 119 | * 120 | * @arg floatValue: The float value to convert. 121 | * 122 | * @returns: The uint value that has the same bit pattern. 123 | */ 124 | inline uint floatToUint(const float floatValue) 125 | { 126 | return *(uint*) &floatValue; 127 | } 128 | 129 | 130 | /** 131 | * Convert a uint to a float without changing the bit values. 132 | * 133 | * @arg uintValue: The uint value to convert. 134 | * 135 | * @returns: The float value that has the same bit pattern. 136 | */ 137 | inline float uintToFloat(const uint uintValue) 138 | { 139 | return *(float*) &uintValue; 140 | } 141 | 142 | 143 | /** 144 | * IEEE-754 16-bit floating-point format (without infinity): 1-5-10, 145 | * exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits 146 | * stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 147 | * 148 | * @arg halfValue: The half value to convert. 149 | * 150 | * @returns: The float value that has the same bit pattern. 151 | */ 152 | float halfToFloat(const uint halfValue) 153 | { 154 | // exponent 155 | const uint e = (halfValue & 0x7C00) >> 10; 156 | 157 | // mantissa 158 | const uint m = (halfValue & 0x03FF) << 13; 159 | 160 | // evil log2 bit hack to count leading zeros in denormalized format 161 | const uint v = floatToUint((float) m) >> 23; 162 | 163 | // sign : normalized : denormalized 164 | return uintToFloat( 165 | (halfValue & 0x8000) << 16 166 | | (e != 0) * ((e + 112) << 23 | m) 167 | | ((e == 0) & (m != 0)) 168 | * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000)) 169 | ); 170 | } 171 | 172 | 173 | /** 174 | * IEEE-754 16-bit floating-point format (without infinity): 1-5-10, 175 | * exp-15, +-131008.0, +-6.1035156E-5, +-5.9604645E-8, 3.311 digits 176 | * stackoverflow.com/questions/1659440/32-bit-to-16-bit-floating-point-conversion/60047308#60047308 177 | * 178 | * @arg floatValue: The float value to convert. 179 | * 180 | * @returns: The half value that has the same bit pattern. 181 | */ 182 | uint floatToHalf(const float floatValue) 183 | { 184 | // round-to-nearest-even: add last bit after truncated mantissa 185 | const uint b = floatToUint(floatValue) + 0x00001000; 186 | 187 | // exponent 188 | const uint e = (b & 0x7F800000) >> 23; 189 | 190 | // mantissa; in line below: 191 | // 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding 192 | const uint m = b & 0x007FFFFF; 193 | 194 | // sign : normalized : denormalized : saturate 195 | return ( 196 | (b & 0x80000000) >> 16 197 | | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) 198 | | ((e < 113) & (e > 101)) 199 | * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) 200 | | (e > 143) * 0x7FFF 201 | ); 202 | } 203 | 204 | 205 | /** 206 | * Encodes two 32-bit floats as 16-bit floats in a 32-bit uint. 207 | * 208 | * @arg value0: The first value to encode. 209 | * @arg value1: The second value to encode. 210 | * 211 | * @returns: The 32-bit uint with two 16-bit floats packed into it. 212 | */ 213 | uint encodeFloatsInUint(const float value0, const float value1) 214 | { 215 | return (floatToHalf(value0) << 16) + floatToHalf(value1); 216 | } 217 | 218 | 219 | /** 220 | * Decodes two 16-bit floats as 32-bit floats from a 32-bit uint. 221 | * 222 | * @arg value: The 32-bit uint with two 16-bit floats packed into it. 223 | * 224 | * @returns: The two decoded floats. 225 | */ 226 | float2 decodeFloatsFromUint(const uint value) 227 | { 228 | return float2( 229 | halfToFloat(value >> 16), 230 | halfToFloat(value & 0x0000FFFF) 231 | ); 232 | } 233 | 234 | 235 | /** 236 | * Encodes two 32-bit uints as 16-bit uints in a 32-bit uint. 237 | * 238 | * @arg value0: The first value to encode. 239 | * @arg value1: The second value to encode. 240 | * 241 | * @returns: The 32-bit uint with two 16-bit uints packed into it. 242 | */ 243 | uint encodeTwoValuesInUint(const uint value0, const uint value1) 244 | { 245 | return (value0 << 16) + (value1 & 0x0000FFFF); 246 | } 247 | 248 | 249 | /** 250 | * Decodes two 16-bit uints as 32-bit ints from a 32-bit uint. 251 | * 252 | * @arg value: The 32-bit uint with two 16-bit uints packed into it. 253 | * 254 | * @returns: The two decoded uints. 255 | */ 256 | int2 decodeTwoValuesFromUint(const uint value) 257 | { 258 | return int2( 259 | value >> 16, 260 | value & 0x0000FFFF 261 | ); 262 | } 263 | -------------------------------------------------------------------------------- /examples/lenses.nk: -------------------------------------------------------------------------------- 1 | #! /usr/local/Nuke13.2v2/libnuke-13.2.2.so -nx 2 | #write_info Write1 file:"/home/ob1/software/nuke/dev/raymarch/images/lenses_final/lenses_final.%04d.exr" format:"1280 720 1" chans:":rgba.red:rgba.green:rgba.blue:rgba.alpha:variance.blue:variance.green:variance.num_paths:variance.red:" framerange:"1001 1111" fps:"0" colorspace:"default" datatype:"16 bit half" transfer:"unknown" views:"main" colorManagement:"Nuke" 3 | version 13.2 v2 4 | define_window_layout_xml { 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | } 34 | Root { 35 | inputs 0 36 | name /home/ob1/software/nuke/dev/raymarch/examples/lenses.nk 37 | frame 1055 38 | first_frame 1001 39 | last_frame 1111 40 | format "1280 720 0 0 1280 720 1 HD_720" 41 | colorManagement Nuke 42 | workingSpaceLUT linear 43 | monitorLut sRGB 44 | monitorOutLUT rec709 45 | int8Lut sRGB 46 | int16Lut sRGB 47 | logLut Cineon 48 | floatLut linear 49 | } 50 | BackdropNode { 51 | inputs 0 52 | name BackdropNode1 53 | label "Set the read and write nodes to point to the\nsame sequence after rendering the first frame\nwithout any 'previous' input, this will create a\nfeedback loop allowing for adaptive sampling\nand allowing you to save and watch back the\nimprovement over time. The alternative to\nthis method of adaptive sampling is to simply\nplug the path_march node into another path_march\nnode's 'previous' input, but you will only save the\nfinal output." 54 | xpos 846 55 | ypos 24 56 | bdwidth 256 57 | bdheight 332 58 | } 59 | Read { 60 | inputs 0 61 | file_type exr 62 | file /home/ob1/software/nuke/dev/raymarch/images/lenses_final/lenses_final.####.exr 63 | format "1280 720 0 0 1280 720 1 HD_720" 64 | first 1001 65 | last 1100 66 | origfirst 1001 67 | origlast 1100 68 | origset true 69 | name Read1 70 | xpos 900 71 | ypos 198 72 | } 73 | FrameHold { 74 | firstFrame {{frame-1}} 75 | name FrameHold1 76 | xpos 900 77 | ypos 311 78 | } 79 | CheckerBoard2 { 80 | inputs 0 81 | format "2048 1024 0 0 2048 1024 1 2K_LatLong" 82 | centerlinewidth 0 83 | name CheckerBoard1 84 | xpos 703 85 | ypos 184 86 | } 87 | Grade { 88 | white 0.05 89 | name Grade1 90 | xpos 703 91 | ypos 265 92 | } 93 | Dot { 94 | name Dot2 95 | xpos 737 96 | ypos 335 97 | } 98 | Axis2 { 99 | inputs 0 100 | rotate {0 -5 0} 101 | name Axis2 102 | xpos 593 103 | ypos 140 104 | } 105 | Camera3 { 106 | translate {0 0 6} 107 | focal 19.4 108 | name Camera1 109 | xpos 593 110 | ypos 228 111 | } 112 | Dot { 113 | name Dot4 114 | xpos 617 115 | ypos 335 116 | } 117 | sdf_material { 118 | inputs 0 119 | name sdf_material10 120 | tile_color 0xffb24cff 121 | xpos 459 122 | ypos 232 123 | colour {0 0 0} 124 | transmission 1 125 | refractive_index 1.52 126 | extinction_colour {1 0.7 0.3} 127 | extinction_coefficient 0.585 128 | scattering_colour {1 0.7 0.3} 129 | } 130 | push 0 131 | sdf_material { 132 | inputs 0 133 | name sdf_material11 134 | tile_color 0xe64cffff 135 | xpos 284 136 | ypos 228 137 | colour {0 0 0} 138 | transmission 1 139 | refractive_index 2 140 | extinction_colour {0.9 0.3 1} 141 | extinction_coefficient 0.5 142 | scattering_colour {0.9 0.3 1} 143 | scattering_coefficient 1 144 | } 145 | push 0 146 | sdf_material { 147 | inputs 0 148 | name sdf_material5 149 | xpos -33 150 | ypos 320 151 | emission 5 152 | } 153 | sdf_material { 154 | inputs 0 155 | name sdf_material13 156 | tile_color 0x80ff4cff 157 | xpos -32 158 | ypos 163 159 | colour {0 0 0} 160 | transmission 1 161 | refractive_index 1.5 162 | extinction_colour {0.5 1 0.3} 163 | extinction_coefficient 1.5 164 | scattering_coefficient 1 165 | } 166 | set N841b6d0 [stack 0] 167 | push $N841b6d0 168 | push 0 169 | push 0 170 | sdf_primitive { 171 | inputs 3 172 | name sdf_primitive15 173 | label cylinder 174 | xpos 123 175 | ypos 157 176 | shape cylinder 177 | dimension_x 1 178 | dimension_y 1 179 | dimension_z 1 180 | edge_radius 0.08 181 | translate {0 0.5 0} 182 | rotate {90 0 0} 183 | blend_type "smooth subtraction" 184 | blend_strength 0.095 185 | } 186 | push 0 187 | sdf_primitive { 188 | inputs 3 189 | name sdf_primitive14 190 | label "rectangular prism" 191 | xpos 123 192 | ypos 229 193 | shape "rectangular prism" 194 | dimension_x 1 195 | dimension_y 2 196 | dimension_z 1 197 | edge_radius 0.08 198 | translate {0.5 0 0} 199 | rotate {0 0 90} 200 | blend_type "smooth subtraction" 201 | blend_strength 0.095 202 | } 203 | push 0 204 | sdf_primitive { 205 | inputs 3 206 | name sdf_primitive5 207 | label sphere 208 | xpos 123 209 | ypos 314 210 | dimension_x 0.3 211 | translate {-2 0 0} 212 | } 213 | sdf_primitive { 214 | inputs 3 215 | name sdf_primitive17 216 | label "cut sphere" 217 | xpos 284 218 | ypos 314 219 | shape "cut sphere" 220 | dimension_x 1 221 | dimension_y -0.7 222 | edge_radius 0.055 223 | rotate {0 0 -90} 224 | blend_type "smooth intersection" 225 | blend_strength 0.095 226 | } 227 | sdf_primitive { 228 | inputs 3 229 | name sdf_primitive11 230 | label "cut sphere" 231 | xpos 459 232 | ypos 314 233 | shape "cut sphere" 234 | dimension_x 1 235 | dimension_y -0.5 236 | edge_radius 0.07 237 | translate {2.5 0 0} 238 | rotate {0 0 -90} 239 | blend_type "smooth intersection" 240 | blend_strength 0.095 241 | } 242 | Dot { 243 | name Dot3 244 | xpos 493 245 | ypos 408 246 | } 247 | ray_march { 248 | inputs 4 249 | name ray_march1 250 | xpos 804 251 | ypos 404 252 | ray_distance 12 253 | max_bounces 8 254 | max_light_sampling_bounces 8 255 | light_sampling_bias 0.8 256 | equiangular_samples 9 257 | extinction_coefficient 0.15 258 | scattering_coefficient 1 259 | variance_range 3 260 | format 0 261 | } 262 | Write { 263 | channels all 264 | file /home/ob1/software/nuke/dev/raymarch/images/lenses_final/lenses_final.####.exr 265 | file_type exr 266 | first_part rgba 267 | version 51 268 | name Write1 269 | xpos 804 270 | ypos 445 271 | } 272 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlinkScript Ray/Path Marcher for Nuke 2 | 3 | This project contains five gizmos that allow you to ray march, and path march, a wide variety of shapes, including fractals, utilizing the GPU. 4 | 5 | ![nested_dielectrics_final](https://user-images.githubusercontent.com/21975584/185783312-db9a7f74-cf07-4655-b566-57b265afcf58.png) 6 | 7 | ![blended](https://user-images.githubusercontent.com/21975584/179328892-de242ee0-f3ec-4f9f-bcd4-eba3d76b6ea6.png) 8 | 9 | ![room_test](https://user-images.githubusercontent.com/21975584/186559860-cf376ee5-8875-4865-8a08-3c2e57a5587c.png) 10 | 11 | ![mandelbox_trap_test1_reduced](https://user-images.githubusercontent.com/21975584/164989275-4eb4791c-df89-4332-981d-aac79b607762.png) 12 | 13 | ![test_trap_bulb_done_reduced](https://user-images.githubusercontent.com/21975584/164989470-d13cabe2-8eb1-42de-becb-bd2672b14538.png) 14 | 15 |

16 | animated 17 |

18 | 19 | ## Setup 20 | 21 | Simply clone/download this repo and add the following line to your `init.py`: `nuke.pluginAddPath("/path/to/blink_raymarch/src/python")`, replacing "`/path/to`" with the actual path to the repository. The gizmos will appear in the nodes menu under the "SDF" group, and all callbacks and the `FN_BLINK_INCLUDE_PATHS` will be set up automatically. The project is also available at: http://www.nukepedia.com/blink/3d/blink-ray-marcher 22 | 23 | ## Requirements 24 | 25 | This project has been tested in Nuke 12.0v8, 12.1v5, and 13.0v2. Note that after Nuke 12.0 the VRAM cache limits are determined by the same settings as the CPU cache limits in Edit->Preferences->Performance->Caching, so if the limits exceed what your GPU can handle, then the VRAM will continue to fill up until Nuke either crashes or falls back to the CPU, which is very slow for these nodes. For that reason you will want to have a GPU. The above examples were rendered using an NVIDIA GeForce GTX 980 Ti. 26 | 27 | ## Gizmos 28 | 29 | ### ray_march 30 | 31 | This gizmo renders the scene using a ray marching algorithm, with support for: 32 | - global illumination 33 | - multiple importance sampled direct illumination 34 | - set the 'max light sampling bounces' to be > 0 to enable this 35 | - equi-angular sampling for participating media 36 | - includes volumetric caustics if you lower the 'light sampling bias' and increase the 'max light sampling bounces' knobs 37 | - increase the 'equi-angular samples' knob for clearer results when using an 'sdf_noise' node with 'scattering' enabled 38 | - adaptive sampling using a normalized variance AOV 39 | - plug a 'ray_march' node's output, or a previous render with different seeds, into the 'previous' input of another 'ray_march' node 40 | - set the minimum and maximum paths to trace, and the node will adaptively interpolate between the values 41 | - the first node in the chain will always trace the maximum paths 42 | - be sure to change the seed on each chained node 43 | - nested dielectrics 44 | - depth of field based on the camera input, simply check the 'enable dof' knob 45 | - hdr image based lighting 46 | 47 | The alpha channel contains unique ids for each object that is hit on the first bounce. 48 | 49 | You can input a standard nuke camera and the perspective projection, axes, and world space coordinates, will match that of Nuke's native scanline renderer, and general 3D system. 50 | 51 | The AOV options are: 52 | - beauty 53 | - normal 54 | - world position 55 | - local position 56 | - depth 57 | - stats 58 | 59 | The stats AOV gives you the average number of iterations, the average number of bounces, and the total number of paths traced per pixel in the r, g, and b channels, respectively. 60 | 61 | ### sdf_material 62 | 63 | This gizmo lets you set the surface properties of an object, and can be passed into an 'sdf_primitive' node in order to apply the surface material to the primitive. 64 | 65 | The material properties include: 66 | - diffuse colour 67 | - specular 68 | - specular roughness 69 | - specular colour 70 | - transmission 71 | - transmission roughness 72 | - absorption colour 73 | - refractive index 74 | - scattering coefficient 75 | - emission 76 | - emission colour 77 | 78 | ### sdf_noise 79 | 80 | This gizmo allows you to vary the material properties of an 'sdf_material' node, or blend between them. It allows for a position seeded, turbulence or fBm noise, with all the properties of Nuke's built-in noise node, plus the additional ability to modify the black and white points, and lift. You can also invert the noise, and select which material properties will be affected by it. This node can be passed into a material, and it will affect that material and that material only. You can also pass it directly into the 'path_march' node's 'noise' input for use with the global scattering coefficient. 81 | 82 | If you want the noise to appear to change with time you can add an expression to the 'low/high frequency translation' knob's alpha value, which animates the 4th dimension translation of the noise. 83 | 84 | ### sdf_primitive 85 | 86 | This gizmo lets you choose the shape, dimensions, and location of an object. It takes other sdf_primitives as inputs, and all nodes in the 'children' input will be positioned relative to it. The children will also interact with the shape according to your selection, allowing you to intersect, subtract, and blend the objects. You can also use any shape as a bounding volume of its children in order to improve performance. 87 | 88 | There are also controls for: 89 | - elongating about the x, y, and z axes 90 | - mirroring the object across the x, y, and z axes 91 | - repeating the object on a grid both finitely and infinitely with no extra memory cost 92 | - hollowing out the objects, which can be used in combination with intersections 93 | 94 | The current available shapes are: 95 | - sphere 96 | - ellipsoid 97 | - cut sphere 98 | - hollow sphere 99 | - death star 100 | - solid angle 101 | - rectangular prism 102 | - rectangular prism frame 103 | - rhombus 104 | - triangular prism 105 | - cylinder 106 | - infinite cylinder 107 | - plane 108 | - capsule 109 | - cone 110 | - infinite cone 111 | - capped cone 112 | - rounded cone 113 | - torus 114 | - capped torus 115 | - link 116 | - hexagonal prism 117 | - octahedron 118 | - mandelbulb 119 | - mandelbox 120 | 121 | ### sdf_light 122 | 123 | This gizmo allows you to light the scene with a few different light types, namely: 124 | - ambient 125 | - ambient occlusion 126 | - point 127 | - directional 128 | 129 | You can choose the colour, intensity, and falloff of the light. You can also soften the shadows with a slider. 130 | 131 | ## References 132 | - https://iquilezles.org/articles/distfunctions/ 133 | - http://blog.hvidtfeldts.net/index.php/2011/09/distance-estimated-3d-fractals-v-the-mandelbulb-different-de-approximations/ 134 | - https://blog.demofox.org/2020/05/25/casual-shadertoy-path-tracing-1-basic-camera-diffuse-emissive/ 135 | - https://www.researchgate.net/publication/354065092_Multiple_Importance_Sampling_101 136 | - https://www.arnoldrenderer.com/research/egsr2012_volume.pdf 137 | -------------------------------------------------------------------------------- /src/gizmos/sdf_material.gizmo: -------------------------------------------------------------------------------- 1 | Gizmo { 2 | knobChanged "__import__('sdf.material', fromlist='SDFMaterial').SDFMaterial().handle_knob_changed()" 3 | tile_color 0xffffffff 4 | addUserKnob {20 User l "SDF Material"} 5 | addUserKnob {18 colour l "diffuse colour" t "The diffuse colour of the shape."} 6 | colour {1 1 1} 7 | addUserKnob {6 diffuse_use_trap_colour l "use trap colour" t "When enabled the surface of the mandelbox will be coloured algorithmically, rather than using the specified surface colour. However, reducing the surface colour channels will remove colour from the corresponding trap colour channel." -STARTLINE} 8 | diffuse_use_trap_colour true 9 | addUserKnob {26 ""} 10 | addUserKnob {7 specular t "The amount of light that is reflected off the shape's surface. This plus 'transmission' should be less than or equal to one."} 11 | addUserKnob {7 specular_roughness l "specular roughness" t "The object's surface will scatter the specularly reflected light more the higher this value is."} 12 | addUserKnob {18 specular_colour l "specular colour" t "The specular colour."} 13 | specular_colour {1 1 1} 14 | addUserKnob {6 specular_use_trap_colour l "use trap colour" t "Use the trap colour for specualrly reflected light." -STARTLINE} 15 | specular_use_trap_colour true 16 | addUserKnob {26 ""} 17 | addUserKnob {7 transmission t "The amount of light that is transmitted through the shape's surface. This plus 'reflection' should be less than or equal to one."} 18 | addUserKnob {7 transmission_roughness l "transmission roughness" t "The object's surface will scatter the transmitted light more the higher this value is."} 19 | addUserKnob {7 refractive_index l "refractive index" t "The index of refraction of the object." R 1 4} 20 | refractive_index 1.33 21 | addUserKnob {6 do_refraction l "do refraction" t "Enable refraction. If disabled the surface will be invisible, and can be used with the scattering to create clouds. This can also be used with the 'is bound' option set on the sdf_primitive node to use it as an invisible bounding box." -STARTLINE} 22 | do_refraction true 23 | addUserKnob {18 extinction_colour l "extinction colour" t "The colour absorbed as light travels through the material."} 24 | extinction_colour {0 0 0} 25 | addUserKnob {6 extinction_use_trap_colour l "use trap colour" t "Absorb the trap colour during transmission." -STARTLINE} 26 | extinction_use_trap_colour true 27 | addUserKnob {7 extinction_coefficient l "extinction coefficient" t "The amount of energy lost as the light travells through the material." R 0 10} 28 | addUserKnob {18 scattering_colour l "scattering colour" t "The colour to be scatter as light is transmitted."} 29 | scattering_colour {1 1 1} 30 | addUserKnob {6 scattering_use_trap_colour l "use trap colour" t "Scatter the trap colour during transmission." -STARTLINE} 31 | scattering_use_trap_colour true 32 | addUserKnob {7 scattering_coefficient l "scattering coefficient" t "The amount of light to scatter."} 33 | addUserKnob {26 ""} 34 | addUserKnob {7 emission t "The amount of light emitted by the object." R 0 100} 35 | addUserKnob {18 emission_colour l "emission colour" t "The emission colour."} 36 | emission_colour {1 0.8 0.5} 37 | addUserKnob {6 emission_use_trap_colour l "use trap colour" t "Use the trap colour for emitted light." -STARTLINE} 38 | emission_use_trap_colour true 39 | addUserKnob {26 div0 l "" +STARTLINE} 40 | addUserKnob {26 info l "" +STARTLINE T "v2.1.0 - (c) Owen Bulka - 2022"} 41 | } 42 | Input { 43 | inputs 0 44 | name noise 45 | xpos 1170 46 | ypos 446 47 | } 48 | add_layer {sdf_colour sdf_colour.colour_r sdf_colour.colour_g sdf_colour.colour_b sdf_colour.colour_a} 49 | Constant { 50 | inputs 0 51 | channels sdf_colour 52 | color {{parent.colour.r} {parent.colour.g} {parent.colour.b} {parent.specular_roughness}} 53 | format "1 1 0 0 1 1 1 1x1" 54 | name diffuse_colour 55 | xpos 510 56 | ypos 417 57 | } 58 | Dot { 59 | name Dot3 60 | xpos 544 61 | ypos 618 62 | } 63 | add_layer {sdf_spec_colour sdf_spec_colour.r sdf_spec_colour.g sdf_spec_colour.b sdf_spec_colour.x} 64 | Constant { 65 | inputs 0 66 | channels sdf_spec_colour 67 | color {{parent.specular_colour.r} {parent.specular_colour.g} {parent.specular_colour.b} {parent.specular}} 68 | format "1 1 0 0 1 1 1 1x1" 69 | name spec_colour 70 | xpos 620 71 | ypos 417 72 | } 73 | Copy { 74 | inputs 2 75 | channels all 76 | name Copy8 77 | xpos 620 78 | ypos 674 79 | } 80 | add_layer {sdf_trans_colour sdf_trans_colour.r sdf_trans_colour.g sdf_trans_colour.b sdf_trans_colour.x} 81 | Constant { 82 | inputs 0 83 | channels sdf_trans_colour 84 | color {{"(1 - clamp(parent.extinction_colour.r, 0, 1)) * parent.extinction_coefficient"} {"(1 - clamp(parent.extinction_colour.g, 0, 1)) * parent.extinction_coefficient"} {"(1 - clamp(parent.extinction_colour.b, 0, 1)) * parent.extinction_coefficient"} {parent.transmission}} 85 | format "1 1 0 0 1 1 1 1x1" 86 | name trans_colour 87 | xpos 730 88 | ypos 417 89 | } 90 | Copy { 91 | inputs 2 92 | channels all 93 | name Copy9 94 | xpos 730 95 | ypos 674 96 | } 97 | add_layer {sdf_scattering_colour sdf_scattering_colour.red sdf_scattering_colour.green sdf_scattering_colour.blue} 98 | Constant { 99 | inputs 0 100 | channels sdf_scattering_colour 101 | color {{"parent.scattering_colour.r * parent.scattering_coefficient"} {"parent.scattering_colour.g * parent.scattering_coefficient"} {"parent.scattering_colour.b * parent.scattering_coefficient"} 0} 102 | format "1 1 0 0 1 1 1 1x1" 103 | name scattering_colour 104 | xpos 840 105 | ypos 417 106 | } 107 | Copy { 108 | inputs 2 109 | channels all 110 | name Copy1 111 | xpos 840 112 | ypos 674 113 | } 114 | add_layer {sdf_emm_colour sdf_emm_colour.r sdf_emm_colour.g sdf_emm_colour.b sdf_emm_colour.x} 115 | Constant { 116 | inputs 0 117 | channels sdf_emm_colour 118 | color {{"parent.emission_colour.r * parent.emission"} {"parent.emission_colour.g * parent.emission"} {"parent.emission_colour.b * parent.emission"} {parent.emission}} 119 | format "1 1 0 0 1 1 1 1x1" 120 | name emm_colour 121 | xpos 950 122 | ypos 417 123 | } 124 | Copy { 125 | inputs 2 126 | channels all 127 | name Copy10 128 | xpos 950 129 | ypos 674 130 | } 131 | add_layer {sdf_surface sdf_surface.reflection sdf_surface.transmission sdf_surface.emission sdf_surface.roughness} 132 | Constant { 133 | inputs 0 134 | channels sdf_surface 135 | color {{parent.refractive_index} {"(parent.diffuse_use_trap_colour ? 8192 : 0) | (parent.specular_use_trap_colour ? 16384 : 0) | (parent.extinction_use_trap_colour ? 32768 : 0) | (parent.emission_use_trap_colour ? 65536 : 0) | (parent.scattering_use_trap_colour ? 131072 : 0) | (parent.do_refraction ? 262144 : 0)"} {parent.transmission_roughness} 0} 136 | format "1 1 0 0 1 1 1 1x1" 137 | name surface 138 | xpos 1060 139 | ypos 417 140 | } 141 | Copy { 142 | inputs 2 143 | channels all 144 | name Copy5 145 | xpos 1060 146 | ypos 674 147 | } 148 | Copy { 149 | inputs 2 150 | channels all 151 | name Copy3 152 | xpos 1170 153 | ypos 674 154 | } 155 | Output { 156 | name Output1 157 | xpos 1170 158 | ypos 782 159 | } 160 | end_group 161 | -------------------------------------------------------------------------------- /examples/room.nk: -------------------------------------------------------------------------------- 1 | #! /usr/local/Nuke13.2v2/libnuke-13.2.2.so -nx 2 | #write_info Write1 file:"/home/ob1/software/nuke/dev/raymarch/images/room_test/room_test.%04d.exr" format:"1024 778 1" chans:":rgba.red:rgba.green:rgba.blue:rgba.alpha:variance.blue:variance.green:variance.num_paths:variance.red:" framerange:"1001 1100" fps:"0" colorspace:"default (linear)" datatype:"32 bit float" transfer:"unknown" views:"main" colorManagement:"Nuke" 3 | version 13.2 v2 4 | define_window_layout_xml { 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | } 34 | Root { 35 | inputs 0 36 | name /home/ob1/software/nuke/dev/raymarch/examples/room.nk 37 | frame 1060 38 | first_frame 1001 39 | last_frame 1100 40 | format "1024 778 0 0 1024 778 1 1K_Super_35(full-ap)" 41 | proxy_type scale 42 | proxy_format "1024 778 0 0 1024 778 1 1K_Super_35(full-ap)" 43 | colorManagement Nuke 44 | workingSpaceLUT linear 45 | monitorLut sRGB 46 | monitorOutLUT rec709 47 | int8Lut sRGB 48 | int16Lut sRGB 49 | logLut Cineon 50 | floatLut linear 51 | } 52 | Axis2 { 53 | inputs 0 54 | name Axis1 55 | xpos 1 56 | ypos -310 57 | } 58 | Camera2 { 59 | translate {0 1 2.9} 60 | rotate {-30 0 0} 61 | focal 8.4 62 | name Camera2 63 | xpos 1 64 | ypos -218 65 | } 66 | sdf_material { 67 | inputs 0 68 | name sdf_material6 69 | xpos -405 70 | ypos -73 71 | } 72 | sdf_material { 73 | inputs 0 74 | name sdf_material5 75 | tile_color 0xffff33ff 76 | xpos -413 77 | ypos -156 78 | colour {1 1 0.2} 79 | } 80 | push 0 81 | sdf_material { 82 | inputs 0 83 | name sdf_material2 84 | tile_color 0xff3333ff 85 | xpos -410 86 | ypos -194 87 | colour {1 0.2 0.2} 88 | } 89 | push 0 90 | sdf_material { 91 | inputs 0 92 | name sdf_material1 93 | tile_color 0x33ff33ff 94 | xpos -410 95 | ypos -234 96 | colour {0.2 1 0.2} 97 | } 98 | push 0 99 | sdf_material { 100 | inputs 0 101 | name sdf_material3 102 | tile_color 0x3333ffff 103 | xpos -409 104 | ypos -274 105 | colour {0.2 0.2 1} 106 | } 107 | push 0 108 | sdf_material { 109 | inputs 0 110 | name sdf_material4 111 | tile_color 0x33ffffff 112 | xpos -409 113 | ypos -312 114 | colour {0.2 1 1} 115 | } 116 | sdf_material { 117 | inputs 0 118 | name sdf_material16 119 | xpos -119 120 | ypos -392 121 | emission 3 122 | } 123 | push 0 124 | push 0 125 | sdf_primitive { 126 | inputs 3 127 | name sdf_primitive16 128 | label "rectangular prism" 129 | xpos -119 130 | ypos -318 131 | shape "rectangular prism" 132 | dimension_x 3 133 | dimension_y 0.1 134 | dimension_z 3 135 | } 136 | sdf_material { 137 | inputs 0 138 | name sdf_material17 139 | tile_color 0xff4c33ff 140 | xpos -411 141 | ypos -394 142 | transmission 1 143 | extinction_colour {1 0.3 0.2} 144 | extinction_coefficient 1 145 | } 146 | sdf_material { 147 | inputs 0 148 | name sdf_material18 149 | xpos -412 150 | ypos -462 151 | specular 1 152 | } 153 | push 0 154 | sdf_noise { 155 | inputs 0 156 | name sdf_noise1 157 | xpos -531 158 | ypos -534 159 | size 5 160 | octaves 4 161 | lacunarity 3.75 162 | white_point 0.09 163 | invert true 164 | specular true 165 | } 166 | sdf_material { 167 | name sdf_material8 168 | tile_color 0x1a4cffff 169 | xpos -411 170 | ypos -534 171 | colour {0.3 1 0.15} 172 | specular 1 173 | specular_roughness 0.3 174 | specular_colour {0.1 0.3 1} 175 | } 176 | push 0 177 | push 0 178 | sdf_primitive { 179 | inputs 3 180 | name sdf_primitive8 181 | label sphere 182 | xpos -271 183 | ypos -540 184 | dimension_x {{parent.sdf_primitive17.dimension_x/2}} 185 | translate {0 {"dimension_x + parent.sdf_primitive17.dimension_y / 2 + parent.sdf_primitive17.edge_radius"} 0} 186 | } 187 | sdf_primitive { 188 | inputs 3 189 | name sdf_primitive18 190 | label torus 191 | xpos -271 192 | ypos -468 193 | shape torus 194 | dimension_x 0.9 195 | dimension_y 0.4 196 | dimension_z 30 197 | translate {0 {"-parent.sdf_primitive17.dimension_y / 2 - parent.sdf_primitive17.edge_radius + dimension_y + cos(rotate.x*pi/180)"} {"parent.sdf_primitive17.dimension_z/2 + parent.sdf_primitive17.edge_radius + dimension_x * sin(-rotate.x*pi / 180) + dimension_y"}} 198 | rotate {-40 0 0} 199 | } 200 | push 0 201 | sdf_primitive { 202 | inputs 3 203 | name sdf_primitive17 204 | label "rectangular prism" 205 | xpos -274 206 | ypos -400 207 | shape "rectangular prism" 208 | dimension_x 1.3 209 | dimension_y 3 210 | dimension_z 1.3 211 | edge_radius 0.05 212 | translate {-1 {"dimension_y/2+ edge_radius"} -0.25} 213 | rotate {0 60 0} 214 | } 215 | sdf_primitive { 216 | inputs 3 217 | name sdf_primitive6 218 | label plane 219 | xpos -274 220 | ypos -318 221 | shape plane 222 | dimension_x 0 223 | dimension_y -1 224 | dimension_z 0 225 | translate {0 6 0} 226 | } 227 | sdf_primitive { 228 | inputs 3 229 | name sdf_primitive5 230 | label plane 231 | xpos -274 232 | ypos -280 233 | shape plane 234 | dimension_x 1 235 | dimension_y 0 236 | dimension_z 0 237 | translate {-3 0 0} 238 | } 239 | sdf_primitive { 240 | inputs 3 241 | name sdf_primitive4 242 | label plane 243 | xpos -274 244 | ypos -240 245 | shape plane 246 | dimension_x -1 247 | dimension_y 0 248 | dimension_z 0 249 | translate {3 0 0} 250 | } 251 | sdf_primitive { 252 | inputs 3 253 | name sdf_primitive3 254 | label plane 255 | xpos -274 256 | ypos -200 257 | shape plane 258 | dimension_x 0 259 | dimension_y 0 260 | dimension_z 1 261 | translate {0 0 -3} 262 | } 263 | sdf_primitive { 264 | inputs 3 265 | name sdf_primitive7 266 | label plane 267 | xpos -274 268 | ypos -162 269 | shape plane 270 | dimension_x 0 271 | dimension_y 0 272 | dimension_z -1 273 | translate {0 0 3} 274 | } 275 | push 0 276 | sdf_primitive { 277 | inputs 3 278 | name sdf_primitive1 279 | label plane 280 | xpos -274 281 | ypos -79 282 | shape plane 283 | dimension_x 0 284 | dimension_y 1 285 | dimension_z 0 286 | translate {0 -3 0} 287 | } 288 | ray_march { 289 | inputs 2 290 | name ray_march2 291 | xpos -9 292 | ypos -73 293 | ray_distance 30 294 | max_bounces 8 295 | max_light_sampling_bounces 8 296 | sample_all_lights false 297 | equiangular_samples 0 298 | format 0 299 | } 300 | Write { 301 | channels all 302 | file /home/ob1/software/nuke/dev/raymarch/images/room_test/room_test.####.exr 303 | file_type exr 304 | datatype "32 bit float" 305 | first_part rgba 306 | version 19 307 | name Write1 308 | xpos -9 309 | ypos -20 310 | } 311 | -------------------------------------------------------------------------------- /src/blink/include/random.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Randomization functions 9 | // 10 | 11 | 12 | // Some random constants on the interval [1, 2] 13 | #define RAND_CONST_0 1.571411510193971f 14 | #define RAND_CONST_1 1.268632820084931f 15 | #define RAND_CONST_2 1.7880365647937733f 16 | #define RAND_CONST_3 1.3546987471558234f 17 | #define RAND_CONST_4 1.4365958250848703f 18 | #define RAND_CONST_5 1.7045380669435368f 19 | #define RAND_CONST_6 1.2006950006793073f 20 | #define RAND_CONST_7 1.3877943854025474f 21 | #define RAND_CONST_8 1.2513180038618783f 22 | #define RAND_CONST_9 1.8584270278009565f 23 | #define RAND_CONST_10 1.1299747498069974f 24 | #define RAND_CONST_11 1.394137930742262f 25 | #define RAND_CONST_12 1.7779101864424334f 26 | #define RAND_CONST_13 1.8037481882044445f 27 | 28 | 29 | /** 30 | * Compute a Wang hash. 31 | * 32 | * @arg seed: The seed to hash. 33 | * 34 | * @returns: The hashed value. 35 | */ 36 | inline uint wangHash(uint seed) 37 | { 38 | seed = uint(seed ^ uint(61)) ^ uint(seed >> uint(16)); 39 | seed *= uint(9); 40 | seed = seed ^ (seed >> 4); 41 | seed *= uint(0x27d4eb2d); 42 | seed = seed ^ (seed >> 15); 43 | return seed; 44 | } 45 | 46 | 47 | /** 48 | * Compute a Wang hash. 49 | * 50 | * @arg seed: The seed to hash. 51 | * 52 | * @returns: The hashed value. 53 | */ 54 | inline int wangHash(int seed) 55 | { 56 | seed = int(seed ^ 61) ^ int(seed >> 16); 57 | seed *= 9; 58 | seed = seed ^ (seed >> 4); 59 | seed *= int(0x27d4eb2d); 60 | seed = seed ^ (seed >> 15); 61 | return seed; 62 | } 63 | 64 | 65 | /** 66 | * Get a random value on the interval [0, 1]. 67 | * 68 | * @arg seed: The random seed. 69 | * 70 | * @returns: A random value on the interval [0, 1]. 71 | */ 72 | inline float random(const float seed) 73 | { 74 | return fract(sin(seed * 91.3458f) * 47453.5453f); 75 | } 76 | 77 | 78 | /** 79 | * Get a random value on the interval [0, 1]. 80 | * 81 | * @arg seed: The random seed. 82 | * 83 | * @returns: A random value on the interval [0, 1]. 84 | */ 85 | inline float random(uint seed) 86 | { 87 | return float(wangHash(seed)) / 4294967296.0f; 88 | } 89 | 90 | 91 | /** 92 | * Get a random value on the interval [0, 1]. 93 | * 94 | * @arg seed: The random seed. 95 | * 96 | * @returns: A random value on the interval [0, 1]. 97 | */ 98 | inline float2 random(const float2 &seed) 99 | { 100 | return float2( 101 | random(seed.x), 102 | random(seed.y) 103 | ); 104 | } 105 | 106 | 107 | /** 108 | * Get a random value on the interval [0, 1]. 109 | * 110 | * @arg seed: The random seed. 111 | * 112 | * @returns: A random value on the interval [0, 1]. 113 | */ 114 | inline float3 random(const float3 &seed) 115 | { 116 | return float3( 117 | random(seed.x), 118 | random(seed.y), 119 | random(seed.z) 120 | ); 121 | } 122 | 123 | 124 | /** 125 | * Get a random value on the interval [0, 1]. 126 | * 127 | * @arg seed: The random seed. 128 | * 129 | * @returns: A random value on the interval [0, 1]. 130 | */ 131 | inline float4 random(const float4 &seed) 132 | { 133 | return float4( 134 | random(seed.x), 135 | random(seed.y), 136 | random(seed.z), 137 | random(seed.w) 138 | ); 139 | } 140 | 141 | 142 | /** 143 | * Create a random unit vector. 144 | * 145 | * @arg seed: The random seed. 146 | * 147 | * @returns: A random unit vector. 148 | */ 149 | float3 randomUnitVector(const float3 &seed) 150 | { 151 | const float z = random(seed.x) * 2.0f - 1.0f; 152 | const float a = random(seed.y) * 2.0f * PI; 153 | const float r = sqrt(1.0f - z * z); 154 | const float x = r * cos(a); 155 | const float y = r * sin(a); 156 | return normalize(float3(x, y, z)); 157 | } 158 | 159 | 160 | /** 161 | * Create a random unit vector in the hemisphere aligned along the 162 | * z-axis. 163 | * 164 | * @arg seed: The random seed. 165 | * 166 | * @returns: A random unit vector. 167 | */ 168 | float3 uniformDirectionInZHemisphere(const float3 &seed) 169 | { 170 | const float uniform = random(seed.x); 171 | const float r = sqrt(1.0f - uniform * uniform); 172 | const float phi = 2 * PI * random(seed.y); 173 | 174 | return float3(cos(phi) * r, sin(phi) * r, uniform); 175 | } 176 | 177 | 178 | /** 179 | * Create a random unit vector in the hemisphere aligned along the 180 | * given axis. 181 | * 182 | * @arg axis: The axis to align the hemisphere with. 183 | * @arg seed: The random seed. 184 | * 185 | * @returns: A random unit vector. 186 | */ 187 | float3 uniformDirectionInHemisphere(const float3 &axis, const float3 &seed) 188 | { 189 | return normalize(alignWithDirection( 190 | float3(0, 0, 1), 191 | axis, 192 | uniformDirectionInZHemisphere(seed) 193 | )); 194 | } 195 | 196 | 197 | /** 198 | * Create a random point that lies within the unit circle. 199 | * 200 | * @arg seed: The random seed. 201 | * 202 | * @returns: A random point, (radius, angle) in the unit circle. 203 | */ 204 | inline float2 uniformPointInUnitCircle(const float3 &seed) 205 | { 206 | return float2(sqrt(random(seed.x)), 2.0f * PI * random(seed.y)); 207 | } 208 | 209 | 210 | /** 211 | * Create a random unit vector in the hemisphere aligned along the 212 | * z-axis, with a distribution that is cosine weighted. 213 | * 214 | * @arg seed: The random seed. 215 | * 216 | * @returns: A random unit vector. 217 | */ 218 | float3 cosineDirectionInZHemisphere(const float3 &seed) 219 | { 220 | const float uniform = random(seed.x); 221 | const float r = sqrt(uniform); 222 | const float angle = 2 * PI * random(seed.y); 223 | 224 | const float x = r * cos(angle); 225 | const float y = r * sin(angle); 226 | 227 | return float3(x, y, sqrt(positivePart(1 - uniform))); 228 | } 229 | 230 | 231 | /** 232 | * Create a random unit vector in the hemisphere aligned along the 233 | * given axis, with a distribution that is cosine weighted. 234 | * 235 | * @arg axis: The axis to align the hemisphere with. 236 | * @arg seed: The random seed. 237 | * 238 | * @returns: A random unit vector. 239 | */ 240 | float3 cosineDirectionInHemisphere(const float3 &axis, const float3 &seed) 241 | { 242 | return normalize(alignWithDirection( 243 | float3(0, 0, 1), 244 | axis, 245 | cosineDirectionInZHemisphere(seed) 246 | )); 247 | } 248 | 249 | 250 | /** 251 | * Get a random direction within a solid angle oriented along the 252 | * z-axis. 253 | * 254 | * https://math.stackexchange.com/questions/56784/generate-a-random-direction-within-a-cone 255 | * 256 | * @arg angle: The angle from the axis to the conical surface. 257 | * @arg seed: The random seed. 258 | * 259 | * @returns: A random unit vector. 260 | */ 261 | inline float3 uniformDirectionInZSolidAngle(const float angle, const float3 &seed) 262 | { 263 | const float cosAngle = cos(angle); 264 | const float z = random(seed.x) * (1.0f - cosAngle) + cosAngle; 265 | const float phi = random(seed.y) * 2.0f * PI; 266 | 267 | const float rootOneMinusZSquared = sqrt(1.0f - z * z); 268 | 269 | return normalize(float3( 270 | rootOneMinusZSquared * cos(phi), 271 | rootOneMinusZSquared * sin(phi), 272 | z 273 | )); 274 | } 275 | 276 | 277 | /** 278 | * Get a random direction within a solid angle. 279 | * 280 | * @arg axis: The direction the solid angle is aligned with. 281 | * @arg angle: The angle from the axis to the conical surface. 282 | * @arg seed: The random seed. 283 | * 284 | * @returns: A random unit vector. 285 | */ 286 | float3 uniformDirectionInSolidAngle( 287 | const float3 &axis, 288 | const float angle, 289 | const float3 &seed) 290 | { 291 | return normalize(alignWithDirection( 292 | float3(0, 0, 1), 293 | axis, 294 | uniformDirectionInZSolidAngle(angle, seed) 295 | )); 296 | } 297 | -------------------------------------------------------------------------------- /examples/snowman.nk: -------------------------------------------------------------------------------- 1 | #! /usr/local/Nuke13.2v2/libnuke-13.2.2.so -nx 2 | version 13.2 v2 3 | define_window_layout_xml { 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | } 33 | Root { 34 | inputs 0 35 | name /home/ob1/software/nuke/dev/raymarch/examples/snowman.nk 36 | frame 48 37 | last_frame 63 38 | colorManagement Nuke 39 | workingSpaceLUT linear 40 | monitorLut sRGB 41 | monitorOutLUT rec709 42 | int8Lut sRGB 43 | int16Lut sRGB 44 | logLut Cineon 45 | floatLut linear 46 | } 47 | StickyNote { 48 | inputs 0 49 | name StickyNote1 50 | label "replace this with an HDR image" 51 | xpos 525 52 | ypos 30 53 | } 54 | StickyNote { 55 | inputs 0 56 | name StickyNote2 57 | label "enable the 'use precomputed irradiance'\nto get the instant smoothing of the example\non github" 58 | xpos 282 59 | ypos 411 60 | } 61 | sdf_light { 62 | inputs 0 63 | name sdf_light2 64 | label point 65 | xpos 180 66 | ypos 162 67 | dimension_x {3 3 3} 68 | dimension_y 2 69 | intensity 2 70 | shadow_hardness 100 71 | } 72 | sdf_light { 73 | name sdf_light1 74 | label point 75 | xpos 180 76 | ypos 200 77 | dimension_x {-1 3 3} 78 | dimension_y 3 79 | intensity 2 80 | shadow_hardness 41.5 81 | } 82 | push 0 83 | Constant { 84 | inputs 0 85 | channels rgb 86 | color {0.9 0.9 1 1} 87 | name Constant1 88 | xpos 421 89 | ypos 11 90 | } 91 | Grade { 92 | white 0.5 93 | name Grade1 94 | xpos 421 95 | ypos 83 96 | } 97 | Reformat { 98 | format "2048 1024 0 0 2048 1024 1 2K_LatLong" 99 | name Reformat1 100 | xpos 421 101 | ypos 155 102 | } 103 | Dot { 104 | name Dot4 105 | xpos 455 106 | ypos 311 107 | } 108 | Axis2 { 109 | inputs 0 110 | rotate {-20 0 0} 111 | name Axis1 112 | xpos 317 113 | ypos 101 114 | } 115 | Camera2 { 116 | translate {0 1 10} 117 | rotate {-2.9 0 0} 118 | focal 58.5 119 | name Camera1 120 | xpos 317 121 | ypos 178 122 | } 123 | Dot { 124 | name Dot5 125 | xpos 341 126 | ypos 309 127 | } 128 | sdf_material { 129 | inputs 0 130 | name ice 131 | xpos -201 132 | ypos 209 133 | specular 0.25 134 | } 135 | sdf_material { 136 | inputs 0 137 | name snow 138 | xpos -222 139 | ypos -271 140 | } 141 | Dot { 142 | name Dot3 143 | xpos -188 144 | ypos -227 145 | } 146 | set N9ac1b60 [stack 0] 147 | Dot { 148 | name Dot6 149 | xpos -90 150 | ypos -159 151 | } 152 | Dot { 153 | name Dot2 154 | xpos -90 155 | ypos -78 156 | } 157 | set N9acb800 [stack 0] 158 | Dot { 159 | name Dot1 160 | xpos -90 161 | ypos 44 162 | } 163 | push $N9acb800 164 | sdf_material { 165 | inputs 0 166 | name bbox 167 | xpos 117 168 | ypos -107 169 | specular_roughness 0.07 170 | transmission 1 171 | do_refraction false 172 | } 173 | push $N9ac1b60 174 | sdf_material { 175 | inputs 0 176 | name carrot 177 | tile_color 0xff4f00ff 178 | xpos -40 179 | ypos -430 180 | colour {1 0.3098039329 0} 181 | } 182 | push 0 183 | sdf_material { 184 | inputs 0 185 | name hat 186 | tile_color 0x1a1a1aff 187 | xpos -274 188 | ypos -403 189 | colour {0.1 0.1 0.1} 190 | transmission {{curve x1 0 x48 1}} 191 | } 192 | sdf_material { 193 | inputs 0 194 | name black 195 | tile_color 0x1a1a1aff 196 | xpos -490 197 | ypos -457 198 | colour {0.1 0.1 0.1} 199 | } 200 | set N9ce2a50 [stack 0] 201 | push 0 202 | sdf_material { 203 | inputs 0 204 | name jewel 205 | tile_color 0xff0000ff 206 | xpos -195 207 | ypos -618 208 | colour {1 0 0} 209 | specular 0.1 210 | } 211 | push 0 212 | push 0 213 | sdf_primitive { 214 | inputs 3 215 | name sdf_primitive5 216 | tile_color 0xff0000ff 217 | label octahedron 218 | xpos -195 219 | ypos -544 220 | shape octahedron 221 | dimension_x 0.15 222 | dimension_y 0.01 223 | translate {0 0.05 0} 224 | rotate {10 {"45 + frame*2"} 0} 225 | } 226 | sdf_primitive { 227 | inputs 3 228 | name sdf_primitive4 229 | tile_color 0x1a1a1aff 230 | label cylinder 231 | xpos -195 232 | ypos -463 233 | shape cylinder 234 | dimension_x 0.3 235 | dimension_y 0.01 236 | translate {0 -0.17 0} 237 | } 238 | push $N9ce2a50 239 | push 0 240 | push $N9ce2a50 241 | push 0 242 | push 0 243 | sdf_primitive { 244 | inputs 3 245 | name sdf_primitive7 246 | tile_color 0x1a1a1aff 247 | label sphere 248 | xpos -490 249 | ypos -357 250 | dimension_x 0.03 251 | dimension_y 0.01 252 | translate {0.1 0.1 0.25} 253 | } 254 | sdf_primitive { 255 | inputs 3 256 | name sdf_primitive14 257 | tile_color 0x1a1a1aff 258 | label sphere 259 | xpos -335 260 | ypos -357 261 | dimension_x 0.03 262 | dimension_y 0.01 263 | translate {-0.1 0.1 0.25} 264 | } 265 | sdf_primitive { 266 | inputs 3 267 | name sdf_primitive3 268 | tile_color 0x1a1a1aff 269 | label cylinder 270 | xpos -195 271 | ypos -357 272 | shape cylinder 273 | dimension_x 0.2 274 | dimension_y 0.5 275 | dimension_z 60 276 | translate {0 0.4 0} 277 | blend_strength 0.06 278 | } 279 | sdf_primitive { 280 | inputs 3 281 | name sdf_primitive1 282 | tile_color 0xff4f00ff 283 | label cone 284 | xpos -40 285 | ypos -357 286 | shape cone 287 | dimension_x 5 288 | dimension_y 0.33 289 | dimension_z 60 290 | translate {0 0 0.63} 291 | rotate {-90 0 0} 292 | blend_strength 0.06 293 | } 294 | push 0 295 | sdf_primitive { 296 | inputs 3 297 | name sdf_primitive15 298 | label sphere 299 | xpos -40 300 | ypos -237 301 | dimension_x 0.3 302 | dimension_y 0.1 303 | dimension_z 60 304 | translate {0 -0.2 0} 305 | rotate {{30*sin(.075*frame)} 0 0} 306 | blend_strength 0.06 307 | } 308 | push $N9ac1b60 309 | push 0 310 | push $N9ac1b60 311 | sdf_material { 312 | inputs 0 313 | name chrome 314 | xpos -555 315 | ypos -216 316 | specular 1 317 | } 318 | push 0 319 | push 0 320 | sdf_primitive { 321 | inputs 3 322 | name sdf_primitive11 323 | label "hollow sphere" 324 | xpos -414 325 | ypos -223 326 | shape "hollow sphere" 327 | dimension_x 0.3 328 | dimension_y 0.1 329 | dimension_z 0.05 330 | translate {0.62 0.4 0} 331 | rotate {{curve x1 30 x63 120} 0 -20} 332 | elongation {0.01 0 0} 333 | blend_strength 0.015 334 | } 335 | push 0 336 | sdf_primitive { 337 | inputs 3 338 | name sdf_primitive12 339 | label "capped torus" 340 | xpos -414 341 | ypos -113 342 | shape "capped torus" 343 | dimension_x 0.7 344 | dimension_y 0.1 345 | dimension_z 26 346 | translate {0 0.8 0.15} 347 | rotate {0 -10 130} 348 | elongation {0.05 0 0} 349 | elongate true 350 | blend_strength 0.015 351 | } 352 | sdf_primitive { 353 | inputs 3 354 | name sdf_primitive8 355 | label "capped torus" 356 | xpos -222 357 | ypos -113 358 | shape "capped torus" 359 | dimension_x 0.3 360 | dimension_y 0.1 361 | dimension_z 60 362 | translate {0.3 0.11 0.2} 363 | rotate {-90 -30 -30} 364 | blend_strength 0.06 365 | } 366 | sdf_primitive { 367 | inputs 3 368 | name sdf_primitive2 369 | label "rectangular prism" 370 | xpos -40 371 | ypos -113 372 | shape "rectangular prism" 373 | dimension_x {{curve x1 0.7 x63 0.9}} 374 | dimension_y 1 375 | dimension_z 1.3 376 | translate {0 0.8 0} 377 | rotate {0 {"30 * sin(.2*frame)"} 0} 378 | blend_strength 0.06 379 | is_bound true 380 | } 381 | push 0 382 | sdf_primitive { 383 | inputs 3 384 | name sdf_primitive13 385 | label sphere 386 | xpos -40 387 | ypos -12 388 | dimension_x 0.4 389 | translate {0 0.7 0} 390 | blend_type "smooth union" 391 | blend_strength 0.06 392 | } 393 | push 0 394 | sdf_primitive { 395 | inputs 3 396 | name sdf_primitive6 397 | label sphere 398 | xpos -40 399 | ypos 92 400 | translate {0 {.4-frame/70} 0} 401 | blend_type "smooth union" 402 | } 403 | push 0 404 | sdf_primitive { 405 | inputs 3 406 | name sdf_primitive9 407 | label plane 408 | xpos -40 409 | ypos 203 410 | shape plane 411 | dimension_x 0 412 | dimension_y 1 413 | dimension_z 0.01 414 | translate {0 -0.35 0} 415 | blend_type "smooth union" 416 | blend_strength {{.1+frame/50}} 417 | } 418 | Dot { 419 | name Dot7 420 | xpos -6 421 | ypos 311 422 | } 423 | ray_march { 424 | inputs 5 425 | name ray_march1 426 | selected true 427 | xpos 180 428 | ypos 417 429 | max_paths_per_pixel 5 430 | max_light_sampling_bounces 1 431 | equiangular_samples 0 432 | format 0 433 | } 434 | -------------------------------------------------------------------------------- /src/blink/include/lights.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Handle Various Lights 9 | // 10 | 11 | #define AMBIENT_LIGHT 0 12 | #define AMBIENT_OCCLUSION 1 13 | #define DIRECTIONAL_LIGHT 2 14 | #define POINT_LIGHT 3 15 | 16 | 17 | /** 18 | * Perform multiple importance sampling by combining probability 19 | * distribution functions. 20 | * 21 | * @arg emittance: The emissive values of the surface. 22 | * @arg throughput: The throughput of the ray. 23 | * @arg pdf0: The first PDF. 24 | * @arg pdf1: The second PDF. 25 | * 26 | * @returns: The multiple importance sampled colour. 27 | */ 28 | inline float4 multipleImportanceSample( 29 | const float4 &emittance, 30 | const float4 &throughput, 31 | const float pdf0, 32 | const float pdf1) 33 | { 34 | return emittance * throughput * balanceHeuristic(pdf0, pdf1); 35 | } 36 | 37 | 38 | /** 39 | * Get the probability distribution function for equi-angular sampling. 40 | * 41 | * @arg uniform: A uniform step distance along the ray. 42 | * @arg maxRayDistance: The maximum distance the ray can travel. 43 | * @arg rayOrigin: The origin of the ray. 44 | * @arg rayDirection: The direction of the ray. 45 | * @arg lightPosition: The position of the light. 46 | * @arg distance: The equi-angular distance. 47 | * 48 | * @returns: The probability distribution function. 49 | */ 50 | float sampleEquiangularPDF( 51 | const float uniform, 52 | const float maxRayDistance, 53 | const float3 &rayOrigin, 54 | const float3 &rayDirection, 55 | const float3 &lightPosition, 56 | float &distance) 57 | { 58 | // Get the coordinate of the closest point to the light along an 59 | // infinite ray 60 | const float delta = dot(lightPosition - rayOrigin, rayDirection); 61 | 62 | // Get distance this point is from light 63 | const float D = length(rayOrigin + delta * rayDirection - lightPosition); 64 | 65 | if (D == 0.0f) 66 | { 67 | distance = 0.0f; 68 | return 1.0f; 69 | } 70 | 71 | // Get the angle of the endpoints 72 | const float thetaA = atan2(-delta, D); 73 | const float thetaB = atan2(maxRayDistance - delta, D); 74 | 75 | // Take a sample 76 | const float t = D * tan(mix(thetaA, thetaB, uniform)); 77 | 78 | distance = delta + t; 79 | 80 | if (thetaA != thetaB) 81 | { 82 | return D / ((thetaB - thetaA) * (D * D + t * t)); 83 | } 84 | 85 | return 1.0f; 86 | } 87 | 88 | 89 | /** 90 | * Get the direction, and distance of a spherical light. 91 | * 92 | * @arg seed: The seed to use in randomization. 93 | * @arg surfaceNormal: The normal to the surface at the position we 94 | * are sampling the illumination of. 95 | * @arg lightDirection: Will store the direction to the light. 96 | * @arg distanceToLight: Will store the distance to the light. 97 | */ 98 | inline void hdriLightData( 99 | const float3 &seed, 100 | const float3 &surfaceNormal, 101 | float3 &lightDirection, 102 | float &distanceToLight) 103 | { 104 | lightDirection = cosineDirectionInHemisphere(surfaceNormal, seed); 105 | distanceToLight = 1.0f; 106 | } 107 | 108 | 109 | /** 110 | * Get the direction, and distance of a spherical light. 111 | * 112 | * @arg seed: The seed to use in randomization. 113 | * @arg pointOnSurface: The point on the surface to compute the 114 | * light intensity at. 115 | * @arg lightPosition: The position of the light. 116 | * @arg radius: The radius of the sphere. 117 | * @arg lightDirection: Will store the direction to the light. 118 | * @arg distanceToLight: Will store the distance to the light. 119 | * @arg visibleSurfaceArea: The surface area that is visible to the 120 | * position we are sampling from. 121 | */ 122 | inline void sphericalLightData( 123 | const float3 &seed, 124 | const float3 &pointOnSurface, 125 | const float3 &lightPosition, 126 | const float radius, 127 | float3 &lightDirection, 128 | float &distanceToLight, 129 | float &visibleSurfaceArea) 130 | { 131 | visibleSurfaceArea = 2.0f * PI * radius * radius; 132 | float3 lightNormal = uniformDirectionInHemisphere( 133 | normalize(pointOnSurface - lightPosition), 134 | seed 135 | ); 136 | lightDirection = lightPosition + lightNormal * radius - pointOnSurface; 137 | distanceToLight = length(lightDirection); 138 | lightDirection = normalize(lightDirection); 139 | } 140 | 141 | 142 | 143 | /** 144 | * Get the direction, and distance of a directional light. 145 | * 146 | * @arg direction: The direction the light is travelling. 147 | * @arg maxRayDistance: The maximum distance the ray can travel. 148 | * @arg lightDirection: Will store the direction to the light. 149 | * @arg distanceToLight: Will store the distance to the light. 150 | * @arg visibleSurfaceArea: The surface area that is visible to the 151 | * position we are sampling from. 152 | */ 153 | inline void directionalLightData( 154 | const float3 &direction, 155 | const float maxRayDistance, 156 | float3 &lightDirection, 157 | float &distanceToLight, 158 | float &visibleSurfaceArea) 159 | { 160 | visibleSurfaceArea = 2.0f * PI; 161 | distanceToLight = maxRayDistance; 162 | lightDirection = normalize(-direction); 163 | } 164 | 165 | 166 | /** 167 | * Get the direction, and distance of a point light. 168 | * 169 | * @arg pointOnSurface: The point on the surface to compute the 170 | * light intensity at. 171 | * @arg position: The position of the light. 172 | * @arg lightDirection: Will store the direction to the light. 173 | * @arg distanceToLight: Will store the distance to the light. 174 | * @arg visibleSurfaceArea: The surface area that is visible to the 175 | * position we are sampling from. 176 | */ 177 | inline void pointLightData( 178 | const float3 &pointOnSurface, 179 | const float3 &position, 180 | float3 &lightDirection, 181 | float &distanceToLight, 182 | float &visibleSurfaceArea) 183 | { 184 | visibleSurfaceArea = 0.0f; 185 | lightDirection = position - pointOnSurface; 186 | distanceToLight = length(lightDirection); 187 | lightDirection = normalize(lightDirection); 188 | } 189 | 190 | 191 | /** 192 | * Get the probability distribution function for the lights in the 193 | * scene. 194 | * 195 | * @arg numLights: The number of lights in the scene. 196 | * @arg visibleSurfaceArea: The surface area that is visible to the 197 | * position we are sampling from. 198 | * 199 | * @returns: The probability distribution function. 200 | */ 201 | inline float sampleLightsPDF(const float numLights, const float visibleSurfaceArea) 202 | { 203 | if (visibleSurfaceArea == 0.0f) 204 | { 205 | return 1.0f / numLights; 206 | } 207 | else 208 | { 209 | return 1.0f / numLights / visibleSurfaceArea; 210 | } 211 | } 212 | 213 | 214 | /** 215 | * Get the direction, distance, and intensity of a light. 216 | * 217 | * @arg intensity: The light intensity. 218 | * @arg falloff: The power of the falloff of the light. 219 | * @arg distanceToLight: The distance to the light. 220 | * 221 | * @returns: The light intensity. 222 | */ 223 | inline float lightIntensity( 224 | const float intensity, 225 | const float falloff, 226 | const float distanceToLight) 227 | { 228 | return intensity / pow(distanceToLight, falloff); 229 | } 230 | 231 | 232 | /** 233 | * Get the direction, distance, and intensity of a light. 234 | * 235 | * @arg pointOnSurface: The point on the surface to compute the 236 | * light intensity at. 237 | * @arg light: The light properties which depend on the light type. 238 | * @arg lightType: The type of light to compute the intensity for. 239 | * 0: directional 240 | * 1: point 241 | * 2: ambient 242 | * 3: ambient occlusion 243 | * @arg maxRayDistance: The maximum distance a ray can travel. 244 | * @arg distanceToLight: Will store the distance to the light. 245 | * @arg visibleSurfaceArea: The surface area that is visible to the 246 | * position we are sampling from. 247 | * @arg lightDirection: Will store the direction to the light. 248 | */ 249 | inline void getLightData( 250 | const float3 &pointOnSurface, 251 | const float3 &light, 252 | const int lightType, 253 | const float maxRayDistance, 254 | float &distanceToLight, 255 | float &visibleSurfaceArea, 256 | float3 &lightDirection) 257 | { 258 | if (lightType == DIRECTIONAL_LIGHT) 259 | { 260 | // directional 261 | directionalLightData( 262 | light, 263 | maxRayDistance, 264 | lightDirection, 265 | distanceToLight, 266 | visibleSurfaceArea 267 | ); 268 | } 269 | else if (lightType == POINT_LIGHT) 270 | { 271 | // point 272 | pointLightData( 273 | pointOnSurface, 274 | light, 275 | lightDirection, 276 | distanceToLight, 277 | visibleSurfaceArea 278 | ); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/blink/include/sdfModifications.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // SDF Modifiers 9 | // 10 | // These modify the ray position before computing the SD 11 | // 12 | 13 | #define FINITE_REPETITION 1 14 | #define INFINITE_REPETITION 2 15 | #define ELONGATE 4 16 | #define MIRROR_X 8 17 | #define MIRROR_Y 16 18 | #define MIRROR_Z 32 19 | #define HOLLOW 64 20 | 21 | 22 | /** 23 | * Infinitely repeat an object in the positive quadrant. 24 | * 25 | * @arg position: The position of the ray. 26 | * @arg spacing: The x, y, and z spacing with which to repeat the 27 | * object. 28 | * 29 | * @returns: The modified ray position that results in repetion. 30 | */ 31 | inline float3 infiniteRepetition(const float3 &position, const float3 &spacing) 32 | { 33 | return fmod(position + 0.5f * spacing, spacing) - 0.5f * spacing; 34 | } 35 | 36 | 37 | /** 38 | * Finitely repeat an object in the positive quadrant. 39 | * 40 | * @arg position: The position of the ray. 41 | * @arg limits: The x, y, and z limits of the repetition. 42 | * @arg spacing: The spacing with which to repeat the object. 43 | * 44 | * @returns: The modified ray position that results in repetion. 45 | */ 46 | inline float3 finiteRepetition( 47 | const float3 &position, 48 | const float3 &limits, 49 | const float spacing) 50 | { 51 | const int3 intLimits = round_(limits); 52 | const int3 repeat = clamp_(round_(fabs(position) / spacing), -intLimits, intLimits); 53 | return position - spacing * float3(repeat.x, repeat.y, repeat.z); 54 | } 55 | 56 | 57 | /** 58 | * Modify a ray to elongate an object. 59 | * 60 | * @arg position: The position of the ray. 61 | * @arg elongation: The amount to elongate the object on each respective 62 | * axis. 63 | * 64 | * @returns: The modified ray position that results in elongation. 65 | */ 66 | inline float3 elongate(const float3 &position, const float3 &elongation) 67 | { 68 | return position - clamp(position, -elongation, elongation); 69 | } 70 | 71 | 72 | /** 73 | * Mirror the object in the yz-plane. 74 | * 75 | * @arg position: The position of the ray. 76 | * 77 | * @returns: The modified ray position that results in mirroring. 78 | */ 79 | inline float3 mirrorX(const float3 &position) 80 | { 81 | return float3(fabs(position.x), position.y, position.z); 82 | } 83 | 84 | 85 | /** 86 | * Mirror the object in the xz-plane. 87 | * 88 | * @arg position: The position of the ray. 89 | * 90 | * @returns: The modified ray position that results in mirroring. 91 | */ 92 | inline float3 mirrorY(const float3 &position) 93 | { 94 | return float3(position.x, fabs(position.y), position.z); 95 | } 96 | 97 | 98 | /** 99 | * Mirror the object in the xy-plane. 100 | * 101 | * @arg position: The position of the ray. 102 | * 103 | * @returns: The modified ray position that results in mirroring. 104 | */ 105 | inline float3 mirrorZ(const float3 &position) 106 | { 107 | return float3(position.x, position.y, fabs(position.z)); 108 | } 109 | 110 | 111 | /** 112 | * Round the edges of an object. 113 | * 114 | * @arg distance: The distance the ray has travelled. 115 | * @arg radius: The radius of the edges. 116 | * 117 | * @returns: The modified distnace that results in edge rounding. 118 | */ 119 | inline float roundEdges(const float distance, const float radius) 120 | { 121 | return distance - radius; 122 | } 123 | 124 | 125 | /** 126 | * Round the edges of an object. 127 | * 128 | * @arg distance: The distance the ray has travelled. 129 | * @arg thickness: The thickness of the object's walls. 130 | * 131 | * @returns: The modified distnace that results in hollowing. 132 | */ 133 | inline float hollow(const float distance, const float thickness) 134 | { 135 | return fabs(distance) - thickness; 136 | } 137 | 138 | 139 | /** 140 | * Modify the position of a ray, resulting in various effects. 141 | * 142 | * @arg modifications: The modifications to perform. 143 | * Each bit will enable a modification: 144 | * bit 0: finite repetition 145 | * bit 1: infinite repetition 146 | * bit 2: elongation 147 | * bit 3: mirror x 148 | * bit 4: mirror y 149 | * bit 5: mirror z 150 | * @arg repetition: The values to use when repeating the ray. 151 | * @arg elongation: The values to use when elongating the ray. 152 | * @arg position: The position of the ray. 153 | */ 154 | void performShapeModification( 155 | const int modifications, 156 | const float4 &repetition, 157 | const float4 &elongation, 158 | float3 &position) 159 | { 160 | if (modifications & FINITE_REPETITION) 161 | { 162 | position = finiteRepetition( 163 | position, 164 | float3(repetition.x, repetition.y, repetition.z), 165 | repetition.w 166 | ); 167 | } 168 | else if (modifications & INFINITE_REPETITION) 169 | { 170 | position = infiniteRepetition( 171 | position, 172 | float3(repetition.x, repetition.y, repetition.z) 173 | ); 174 | } 175 | if (modifications & ELONGATE) 176 | { 177 | position = elongate( 178 | position, 179 | float3(elongation.x, elongation.y, elongation.z) 180 | ); 181 | } 182 | if (modifications & MIRROR_X) 183 | { 184 | position = mirrorX(position); 185 | } 186 | if (modifications & MIRROR_Y) 187 | { 188 | position = mirrorY(position); 189 | } 190 | if (modifications & MIRROR_Z) 191 | { 192 | position = mirrorZ(position); 193 | } 194 | } 195 | 196 | 197 | /** 198 | * Modify the distance a ray has travelled, resulting in various 199 | * effects. 200 | * 201 | * @arg modifications: The modifications to perform. 202 | * Each bit will enable a modification: 203 | * bit 6: hollowing 204 | * @arg edgeRadius: The radius to round the edges by. 205 | * @arg wallThickness: The thickness of the walls if hollowing the 206 | * object. 207 | * @arg position: The position of the ray. 208 | */ 209 | float performDistanceModification( 210 | const int modifications, 211 | const float edgeRadius, 212 | const float wallThickness, 213 | const float distance) 214 | { 215 | float result = distance; 216 | if (modifications & HOLLOW) 217 | { 218 | result = hollow(result, wallThickness); 219 | } 220 | return roundEdges(result, edgeRadius); 221 | } 222 | 223 | 224 | /** 225 | * Transform a ray's location. 226 | * 227 | * @arg rayOrigin: The location the ray originates from. 228 | * @arg position: The amount to translate the ray. 229 | * @arg rotation: The amount to rotate the ray (radians). 230 | * @arg modifications: The modifications to perform. 231 | * Each bit will enable a modification: 232 | * bit 0: finite repetition 233 | * bit 1: infinite repetition 234 | * bit 2: elongation 235 | * bit 3: mirror x 236 | * bit 4: mirror y 237 | * bit 5: mirror z 238 | * @arg repetition: The values to use when repeating the ray. 239 | * @arg elongation: The values to use when elongating the ray. 240 | * 241 | * @returns: The transformed ray origin. 242 | */ 243 | float3 transformRay( 244 | const float3 &rayOrigin, 245 | const float3 &translation, 246 | const float3 &rotation, 247 | const int modifications, 248 | const float4 &repetition, 249 | const float4 &elongation) 250 | { 251 | float3x3 rotMatrix; 252 | rotationMatrix(rotation, rotMatrix); 253 | float3 transformedRay = matmul( 254 | rotMatrix.invert(), 255 | rayOrigin - translation 256 | ); 257 | performShapeModification( 258 | modifications, 259 | repetition, 260 | elongation, 261 | transformedRay 262 | ); 263 | 264 | return transformedRay; 265 | } 266 | 267 | 268 | /** 269 | * Perform the inverse transform on a ray. 270 | * 271 | * @arg rayOrigin: The location the ray originates from. 272 | * @arg position: The amount to translate the ray. 273 | * @arg rotation: The amount to rotate the ray (radians). 274 | * @arg modifications: The modifications to perform. 275 | * Each bit will enable a modification: 276 | * bit 0: finite repetition 277 | * bit 1: infinite repetition 278 | * bit 2: elongation 279 | * bit 3: mirror x 280 | * bit 4: mirror y 281 | * bit 5: mirror z 282 | * @arg repetition: The values to use when repeating the ray. 283 | * @arg elongation: The values to use when elongating the ray. 284 | * 285 | * @returns: The transformed ray origin. 286 | */ 287 | float3 inverseTransformRay( 288 | const float3 &rayOrigin, 289 | const float3 &translation, 290 | const float3 &rotation, 291 | const int modifications, 292 | const float4 &repetition, 293 | const float4 &elongation) 294 | { 295 | float3 transformedRay = rayOrigin; 296 | performShapeModification( 297 | modifications, 298 | repetition, 299 | elongation, 300 | transformedRay 301 | ); 302 | 303 | float3x3 rotMatrix; 304 | reverseRotationMatrix(-rotation, rotMatrix); 305 | transformedRay = matmul( 306 | rotMatrix.invert(), 307 | transformedRay 308 | ) + translation; 309 | 310 | return transformedRay; 311 | } 312 | -------------------------------------------------------------------------------- /src/gizmos/sdf_primitive.gizmo: -------------------------------------------------------------------------------- 1 | Gizmo { 2 | inputs 3 3 | onCreate "__import__('sdf.primitive', fromlist='SDFPrimitive').SDFPrimitive().handle_node_created()" 4 | knobChanged "__import__('sdf.primitive', fromlist='SDFPrimitive').SDFPrimitive().handle_knob_changed()" 5 | tile_color 0xffffffff 6 | label sphere 7 | addUserKnob {20 User l "SDF Primitive"} 8 | addUserKnob {4 shape t "The shape of the object." M {sphere ellipsoid "cut sphere" "hollow sphere" "death star" "solid angle" "rectangular prism" "rectangular prism frame" rhombus "triangular prism" cylinder "infinite cylinder" plane capsule cone "infinite cone" "capped cone" "rounded cone" torus "capped torus" link "hexagonal prism" octahedron mandelbulb mandelbox ""}} 9 | addUserKnob {7 dimension_x l radius t "The radius of the sphere." R 0 10} 10 | dimension_x 0.5 11 | addUserKnob {7 dimension_y l "y radius" t "The radius along the y-axis of the ellipsoid." +HIDDEN R 0 10} 12 | dimension_y 0.25 13 | addUserKnob {7 dimension_z l "z radius" t "The radius along the z-axis of the ellipsoid." +HIDDEN R 0 10} 14 | dimension_z 0.25 15 | addUserKnob {7 dimension_w l "folding limit" t "Clamp the position between +/- this value when performing the box fold. Higher values will result in a denser fractal." +HIDDEN R 0.01 2} 16 | dimension_w 0.8 17 | addUserKnob {7 wall_thickness l "wall thickness" t "The thickness of the walls of the shape, if the shape is hollow." +DISABLED} 18 | wall_thickness 0.01 19 | addUserKnob {6 hollow t "If enabled, the object will be hollow, with a thickness of 'wall thickness'." -STARTLINE} 20 | addUserKnob {7 edge_radius l "edge radius" t "This parameter increases the hit tolerance by the specified amount for this specific object, having the effect of rounding off the edges."} 21 | addUserKnob {6 mirror_x l "mirror x" t "Mirror the shape in the yz-plane." +STARTLINE} 22 | addUserKnob {6 mirror_y l "mirror y" t "Mirror the shape in the xz-plane." -STARTLINE} 23 | addUserKnob {6 mirror_z l "mirror z" t "Mirror the shape in the xy-plane." -STARTLINE} 24 | addUserKnob {26 ""} 25 | addUserKnob {13 translate t "The translation of the object in its local coordinate space."} 26 | addUserKnob {13 rotate t "The rotation of the object in its local coordinate space."} 27 | addUserKnob {13 elongation t "The elongation of the object along the respective axes." +DISABLED} 28 | elongation {0.1 0 0} 29 | addUserKnob {6 elongate t "Enable the elongation of the object." -STARTLINE} 30 | addUserKnob {7 uniform_scale l "uniform scale" t "The scale of the object in its local coordinate space, along all axes." R 0 10} 31 | uniform_scale 1 32 | addUserKnob {26 div1 l "" +STARTLINE} 33 | addUserKnob {4 repetition t "Repeat objects in the scene with no extra memory consumption. Note that if the repeated objects overlap some strange things can occur." -STARTLINE M {none finite infinite "" ""}} 34 | addUserKnob {13 repetition_params l spacing t "The spacing along each positive axis to repeat the objects." +HIDDEN} 35 | repetition_params {1.1 1.1 100} 36 | addUserKnob {7 repetition_spacing l spacing t "The spacing between the objects." +HIDDEN R 0 10} 37 | repetition_spacing 1.1 38 | addUserKnob {26 ""} 39 | addUserKnob {4 blend_type l "blend type" t "The type of interaction this object will have with its children.\n Union: All objects will appear as normal.\n Subtraction: This object will be subtracted from all of its\n children, leaving holes.\n Intersection: Only the region where this object and its\n children overlap will remain.\n Smooth Union: All children will smoothly blend together\n with this object according to the 'blend strength'.\n Smooth Subtraction:This object will be subtracted from all\n of its children, leaving holes that are smoothed\n according to the 'blend strength'.\n Smooth Intersection: Only the region where this object\n and its children overlap will remain, and the remaining\n regions will be smoothed according to the 'blend\n strength'." +DISABLED M {union subtraction intersection "smooth union" "smooth subtraction" "smooth intersection" "" "" ""}} 40 | addUserKnob {7 blend_strength l "blend strength" t "The amount to blend between this and its child objects if one of the 'Smooth' blend types are selected." +DISABLED} 41 | blend_strength 0.1 42 | addUserKnob {6 is_bound l "is bound" t "If enabled, this object will act as a bounding volume for all its children. This means that until a ray hits the bounding volume, none of the child object's signed distance fields will be computed. This can vastly improve performance, especially when many complex objects are far from the camera. This option does not always play well with lighting effects that depend on the number of iterations in the computation such as 'ambient occlusion' and 'softened shadows' due to the variation near the surface of the bounding object." +DISABLED +STARTLINE} 43 | addUserKnob {26 ""} 44 | addUserKnob {26 info l "" +STARTLINE T "v2.1.0 - (c) Owen Bulka - 2022"} 45 | } 46 | Input { 47 | inputs 0 48 | name siblings 49 | xpos 1484 50 | ypos 476 51 | } 52 | Dot { 53 | name Dot2 54 | xpos 1518 55 | ypos 904 56 | } 57 | Input { 58 | inputs 0 59 | name children 60 | xpos 1357 61 | ypos 475 62 | number 1 63 | } 64 | Dot { 65 | name Dot1 66 | xpos 1391 67 | ypos 826 68 | } 69 | Input { 70 | inputs 0 71 | name material 72 | xpos 1183 73 | ypos 479 74 | number 2 75 | } 76 | add_layer {sdf_position_scale sdf_position_scale.position_x sdf_position_scale.position_y sdf_position_scale.position_z sdf_position_scale.uniform_scale} 77 | Constant { 78 | inputs 0 79 | channels sdf_position_scale 80 | color {{parent.translate.x} {parent.translate.y} {parent.translate.z} {parent.uniform_scale}} 81 | format "1 1 0 0 1 1 1 1x1" 82 | name position_scale 83 | xpos 387 84 | ypos 444 85 | } 86 | Dot { 87 | name Dot17 88 | xpos 421 89 | ypos 636 90 | } 91 | add_layer {sdf_rotation_wall_thickness sdf_rotation_wall_thickness.rotation_x sdf_rotation_wall_thickness.rotation_y sdf_rotation_wall_thickness.rotation_z sdf_rotation_wall_thickness.wall_thickness} 92 | Constant { 93 | inputs 0 94 | channels sdf_rotation_wall_thickness 95 | color {{"parent.rotate.x * pi / 180"} {"parent.rotate.y * pi / 180"} {"parent.rotate.z * pi / 180"} {parent.wall_thickness}} 96 | format "1 1 0 0 1 1 1 1x1" 97 | name rotation 98 | xpos 510 99 | ypos 446 100 | } 101 | Copy { 102 | inputs 2 103 | channels all 104 | name Copy1 105 | xpos 510 106 | ypos 715 107 | } 108 | add_layer {sdf_dimensions sdf_dimensions.dimension_x sdf_dimensions.dimension_y sdf_dimensions.dimension_z sdf_dimensions.dimension_w} 109 | Constant { 110 | inputs 0 111 | channels sdf_dimensions 112 | color {{parent.dimension_x} {parent.dimension_y} {parent.dimension_z} {parent.dimension_w}} 113 | format "1 1 0 0 1 1 1 1x1" 114 | name dimensions 115 | xpos 647 116 | ypos 449 117 | } 118 | Copy { 119 | inputs 2 120 | channels all 121 | name Copy2 122 | xpos 647 123 | ypos 715 124 | } 125 | add_layer {sdf_shape sdf_shape.shape_type sdf_shape.shape_operations sdf_shape.num_children sdf_shape.blend_strength} 126 | Constant { 127 | inputs 0 128 | channels sdf_shape 129 | color {{parent.shape} {"parent.repetition | (parent.elongate ? 4 : 0) | (parent.mirror_x ? 8 : 0) | (parent.mirror_y ? 16 : 0) | (parent.mirror_z ? 32 : 0) | (parent.hollow ? 64 : 0) | (parent.blend_type > 0 && !parent.is_bound ? (1 << (parent.blend_type + 6)) : 0) | (parent.is_bound ? 4096 : 0) "} {"parent.sdf_merge.disable ? 0 : parent.sdf_merge.input1.width"} {parent.blend_strength}} 130 | format "1 1 0 0 1 1 1 1x1" 131 | name shape 132 | xpos 789 133 | ypos 450 134 | } 135 | Copy { 136 | inputs 2 137 | channels all 138 | name Copy4 139 | xpos 789 140 | ypos 715 141 | } 142 | add_layer {sdf_shape_mods_0 sdf_shape_mods_0.repetion_x sdf_shape_mods_0.repetion_y sdf_shape_mods_0.repetion_z sdf_shape_mods_0.repetion_w} 143 | Constant { 144 | inputs 0 145 | channels sdf_shape_mods_0 146 | color {{parent.repetition_params.x} {parent.repetition_params.y} {parent.repetition_params.z} {parent.repetition_spacing}} 147 | format "1 1 0 0 1 1 1 1x1" 148 | name shape_mods 149 | xpos 910 150 | ypos 452 151 | } 152 | Copy { 153 | inputs 2 154 | channels all 155 | name Copy6 156 | xpos 910 157 | ypos 715 158 | } 159 | add_layer {sdf_shape_mods_1 sdf_shape_mods_1.elongation_x sdf_shape_mods_1.elongation_y sdf_shape_mods_1.elongation_z sdf_shape_mods_1.edge_radius} 160 | Constant { 161 | inputs 0 162 | channels sdf_shape_mods_1 163 | color {{parent.elongation.x} {parent.elongation.y} {parent.elongation.z} {parent.edge_radius}} 164 | format "1 1 0 0 1 1 1 1x1" 165 | name shape_mods1 166 | xpos 1037 167 | ypos 454 168 | } 169 | Copy { 170 | inputs 2 171 | channels all 172 | name Copy7 173 | xpos 1037 174 | ypos 715 175 | } 176 | Copy { 177 | inputs 2 178 | channels all 179 | name Copy3 180 | xpos 1183 181 | ypos 715 182 | } 183 | Group { 184 | inputs 2 185 | name sdf_merge 186 | xpos 1183 187 | ypos 822 188 | disable {{"!(\[exists parent.input1] && !input1.parent.input1.disable)"}} 189 | addUserKnob {20 User} 190 | addUserKnob {26 warning l Warning: T "Only merge lights with lights, and primitives with primitives"} 191 | } 192 | Reformat { 193 | inputs 0 194 | type "to box" 195 | box_width 1 196 | box_height 1 197 | box_fixed true 198 | name Reformat1 199 | xpos -119 200 | ypos 440 201 | } 202 | Input { 203 | inputs 0 204 | name Input2 205 | xpos 158 206 | ypos -28 207 | number 1 208 | } 209 | Dot { 210 | name Dot2 211 | xpos 192 212 | ypos -2 213 | } 214 | set N1379a720 [stack 0] 215 | Dot { 216 | name Dot3 217 | xpos 329 218 | ypos -2 219 | } 220 | Dot { 221 | name Dot4 222 | xpos 329 223 | ypos 391 224 | } 225 | Input { 226 | inputs 0 227 | name Input1 228 | xpos 0 229 | ypos -32 230 | } 231 | Dot { 232 | name Dot1 233 | xpos 34 234 | ypos -6 235 | } 236 | set N149e6650 [stack 0] 237 | Dot { 238 | name Dot6 239 | xpos -98 240 | ypos -6 241 | } 242 | Dot { 243 | name Dot5 244 | xpos -98 245 | ypos 336 246 | } 247 | push $N1379a720 248 | Crop { 249 | box {0 0 {Dot1.width+Dot2.width} 1} 250 | reformat true 251 | crop false 252 | name Crop3 253 | xpos 158 254 | ypos 69 255 | } 256 | Crop { 257 | box {0 0 {parent.Dot2.width} 1} 258 | name Crop4 259 | xpos 158 260 | ypos 95 261 | } 262 | Transform { 263 | translate {{parent.Dot1.width} 0} 264 | center {1 0.5} 265 | name Transform2 266 | xpos 158 267 | ypos 121 268 | } 269 | Crop { 270 | box {0 0 {width} {height}} 271 | crop false 272 | name Crop5 273 | xpos 158 274 | ypos 167 275 | } 276 | push $N149e6650 277 | Crop { 278 | box {0 0 {Dot1.width+Dot2.width} 1} 279 | reformat true 280 | crop false 281 | name Crop6 282 | xpos 0 283 | ypos 66 284 | } 285 | Crop { 286 | box {0 0 {parent.Dot1.width} 1} 287 | name Crop7 288 | xpos 0 289 | ypos 92 290 | } 291 | Crop { 292 | box {0 0 {width} {height}} 293 | crop false 294 | name Crop8 295 | xpos 0 296 | ypos 164 297 | } 298 | Merge2 { 299 | inputs 2 300 | also_merge all 301 | name Merge1 302 | xpos 0 303 | ypos 262 304 | disable {{"\[exists parent.input1] ? 0:1"}} 305 | } 306 | Switch { 307 | inputs 2 308 | which {{"\[exists parent.input1] ? 0:1"}} 309 | name Switch2 310 | xpos 0 311 | ypos 332 312 | } 313 | Switch { 314 | inputs 2 315 | which {{"\[exists parent.input0] ? 0:1"}} 316 | name Switch1 317 | xpos 0 318 | ypos 387 319 | } 320 | Switch { 321 | inputs 2 322 | which {{"\[exists parent.input0] ? 0:\[exists parent.input1] ? 0:1"}} 323 | name Switch3 324 | xpos 0 325 | ypos 456 326 | } 327 | Dot { 328 | name out_dot 329 | xpos 36 330 | ypos 518 331 | } 332 | Output { 333 | name Output1 334 | xpos 2 335 | ypos 661 336 | } 337 | end_group 338 | Group { 339 | inputs 2 340 | name sdf_merge1 341 | xpos 1183 342 | ypos 900 343 | disable {{"!(\[exists parent.input0] && !(parent.input0.disable && !\[exists parent.input0.input0]))"}} 344 | addUserKnob {20 User} 345 | addUserKnob {26 warning l Warning: T "Only merge lights with lights, and primitives with primitives"} 346 | } 347 | Reformat { 348 | inputs 0 349 | type "to box" 350 | box_width 1 351 | box_height 1 352 | box_fixed true 353 | name Reformat1 354 | xpos -119 355 | ypos 440 356 | } 357 | Input { 358 | inputs 0 359 | name Input2 360 | xpos 158 361 | ypos -28 362 | number 1 363 | } 364 | Dot { 365 | name Dot2 366 | xpos 192 367 | ypos -2 368 | } 369 | set N1e48f340 [stack 0] 370 | Dot { 371 | name Dot3 372 | xpos 329 373 | ypos -2 374 | } 375 | Dot { 376 | name Dot4 377 | xpos 329 378 | ypos 391 379 | } 380 | Input { 381 | inputs 0 382 | name Input1 383 | xpos 0 384 | ypos -32 385 | } 386 | Dot { 387 | name Dot1 388 | xpos 34 389 | ypos -6 390 | } 391 | set N405a0270 [stack 0] 392 | Dot { 393 | name Dot6 394 | xpos -98 395 | ypos -6 396 | } 397 | Dot { 398 | name Dot5 399 | xpos -98 400 | ypos 336 401 | } 402 | push $N1e48f340 403 | Crop { 404 | box {0 0 {Dot1.width+Dot2.width} 1} 405 | reformat true 406 | crop false 407 | name Crop3 408 | xpos 158 409 | ypos 69 410 | } 411 | Crop { 412 | box {0 0 {parent.Dot2.width} 1} 413 | name Crop4 414 | xpos 158 415 | ypos 95 416 | } 417 | Transform { 418 | translate {{parent.Dot1.width} 0} 419 | center {1 0.5} 420 | name Transform2 421 | xpos 158 422 | ypos 121 423 | } 424 | Crop { 425 | box {0 0 {width} {height}} 426 | crop false 427 | name Crop5 428 | xpos 158 429 | ypos 167 430 | } 431 | push $N405a0270 432 | Crop { 433 | box {0 0 {Dot1.width+Dot2.width} 1} 434 | reformat true 435 | crop false 436 | name Crop6 437 | xpos 0 438 | ypos 66 439 | } 440 | Crop { 441 | box {0 0 {parent.Dot1.width} 1} 442 | name Crop7 443 | xpos 0 444 | ypos 92 445 | } 446 | Crop { 447 | box {0 0 {width} {height}} 448 | crop false 449 | name Crop8 450 | xpos 0 451 | ypos 164 452 | } 453 | Merge2 { 454 | inputs 2 455 | also_merge all 456 | name Merge1 457 | xpos 0 458 | ypos 262 459 | disable {{"\[exists parent.input1] ? 0:1"}} 460 | } 461 | Switch { 462 | inputs 2 463 | which {{"\[exists parent.input1] ? 0:1"}} 464 | name Switch2 465 | xpos 0 466 | ypos 332 467 | } 468 | Switch { 469 | inputs 2 470 | which {{"\[exists parent.input0] ? 0:1"}} 471 | name Switch1 472 | xpos 0 473 | ypos 387 474 | } 475 | Switch { 476 | inputs 2 477 | which {{"\[exists parent.input0] ? 0:\[exists parent.input1] ? 0:1"}} 478 | name Switch3 479 | xpos 0 480 | ypos 456 481 | } 482 | Dot { 483 | name out_dot 484 | xpos 36 485 | ypos 518 486 | } 487 | Output { 488 | name Output1 489 | xpos 2 490 | ypos 661 491 | } 492 | end_group 493 | Output { 494 | name Output1 495 | xpos 1183 496 | ypos 1002 497 | } 498 | end_group 499 | -------------------------------------------------------------------------------- /src/blink/include/objectInteraction.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 by Owen Bulka. 2 | // All rights reserved. 3 | // This file is released under the "MIT License Agreement". 4 | // Please see the LICENSE.md file that should have been included as part 5 | // of this package. 6 | 7 | // 8 | // Signed Distance Transformations 9 | // 10 | // These operate on the signed values that have been computed 11 | // 12 | #define SUBTRACTION 128 13 | #define INTERSECTION 256 14 | #define SMOOTH_UNION 512 15 | #define SMOOTH_SUBTRACTION 1024 16 | #define SMOOTH_INTERSECTION 2048 17 | 18 | 19 | /** 20 | * The union of the two values. Ie. The minimum. The corresponding 21 | * colour will be placed in colour1, and the corresponding surface will 22 | * be placed in colour9. 23 | * 24 | * @arg value0: The first value. 25 | * @arg value1: The second value. 26 | * @arg colour0: The first colour. 27 | * @arg colour1: The second colour. 28 | * @arg colour8: The first surface. 29 | * @arg colour9: The second surface. 30 | * @arg value2: The third value. 31 | * @arg value3: The fourth value. 32 | * @arg value4: The fifth value. 33 | * @arg value5: The sixth value. 34 | * 35 | * @returns: The nearest, modified value. 36 | */ 37 | inline float union_( 38 | const float value0, 39 | const float value1, 40 | const float4 &colour0, 41 | float4 &colour1, 42 | const float4 &colour2, 43 | float4 &colour3, 44 | const float4 &colour4, 45 | float4 &colour5, 46 | const float4 &colour6, 47 | float4 &colour7, 48 | const float4 &colour8, 49 | float4 &colour9, 50 | const float value2, 51 | float &value3, 52 | const float value4, 53 | float &value5) 54 | { 55 | if (fabs(value0) < fabs(value1)) 56 | { 57 | colour1 = colour0; 58 | colour3 = colour2; 59 | colour5 = colour4; 60 | colour7 = colour6; 61 | colour9 = colour8; 62 | value3 = value2; 63 | value5 = value4; 64 | return value0; 65 | } 66 | return value1; 67 | } 68 | 69 | 70 | /** 71 | * The union of the two values. Ie. The minimum. The corresponding 72 | * colour will be placed in colour1, and the corresponding surface will 73 | * be placed in colour9. 74 | * 75 | * @arg value0: The first value. 76 | * @arg value1: The second value. 77 | * 78 | * @returns: The nearest, modified value. 79 | */ 80 | inline float union_(const float value0, const float value1) 81 | { 82 | if (fabs(value0) < fabs(value1)) 83 | { 84 | return value0; 85 | } 86 | return value1; 87 | } 88 | 89 | 90 | /** 91 | * Subtract the first object from the second. The corresponding 92 | * colour will be placed in colour1, and the corresponding surface will 93 | * be placed in colour9. 94 | * 95 | * @arg value0: The first value. 96 | * @arg value1: The second value. 97 | * @arg colour0: The first colour. 98 | * @arg colour1: The second colour. 99 | * @arg colour8: The first surface. 100 | * @arg colour9: The second surface. 101 | * @arg value2: The third value. 102 | * @arg value3: The fourth value. 103 | * @arg value4: The fifth value. 104 | * @arg value5: The sixth value. 105 | * 106 | * @returns: The nearest, modified value. 107 | */ 108 | inline float subtraction( 109 | const float value0, 110 | const float value1, 111 | const float4 &colour0, 112 | float4 &colour1, 113 | const float4 &colour2, 114 | float4 &colour3, 115 | const float4 &colour4, 116 | float4 &colour5, 117 | const float4 &colour6, 118 | float4 &colour7, 119 | const float4 &colour8, 120 | float4 &colour9, 121 | const float value2, 122 | float &value3, 123 | const float value4, 124 | float &value5) 125 | { 126 | if (-value0 > value1) 127 | { 128 | colour1 = colour0; 129 | colour3 = colour2; 130 | colour5 = colour4; 131 | colour7 = colour6; 132 | colour9 = colour8; 133 | value3 = value2; 134 | value5 = value4; 135 | return -value0; 136 | } 137 | return value1; 138 | } 139 | 140 | 141 | /** 142 | * Subtract the first object from the second. The corresponding 143 | * colour will be placed in colour1, and the corresponding surface will 144 | * be placed in colour9. 145 | * 146 | * @arg value0: The first value. 147 | * @arg value1: The second value. 148 | * 149 | * @returns: The nearest, modified value. 150 | */ 151 | inline float subtraction(const float value0, const float value1) 152 | { 153 | return max(-value0, value1); 154 | } 155 | 156 | 157 | /** 158 | * Render only the overlapping region of two objects. The corresponding 159 | * colour will be placed in colour1, and the corresponding surface will 160 | * be placed in colour9. 161 | * 162 | * @arg value0: The first value. 163 | * @arg value1: The second value. 164 | * @arg colour0: The first colour. 165 | * @arg colour1: The second colour. 166 | * @arg colour8: The first surface. 167 | * @arg colour9: The second surface. 168 | * @arg value2: The third value. 169 | * @arg value3: The fourth value. 170 | * @arg value4: The fifth value. 171 | * @arg value5: The sixth value. 172 | * 173 | * @returns: The nearest, modified value. 174 | */ 175 | inline float intersection( 176 | const float value0, 177 | const float value1, 178 | const float4 &colour0, 179 | float4 &colour1, 180 | const float4 &colour2, 181 | float4 &colour3, 182 | const float4 &colour4, 183 | float4 &colour5, 184 | const float4 &colour6, 185 | float4 &colour7, 186 | const float4 &colour8, 187 | float4 &colour9, 188 | const float value2, 189 | float &value3, 190 | const float value4, 191 | float &value5) 192 | { 193 | if (value0 > value1) 194 | { 195 | colour1 = colour0; 196 | colour3 = colour2; 197 | colour5 = colour4; 198 | colour7 = colour6; 199 | colour9 = colour8; 200 | value3 = value2; 201 | value5 = value4; 202 | return value0; 203 | } 204 | return value1; 205 | } 206 | 207 | 208 | /** 209 | * Render only the overlapping region of two objects. The corresponding 210 | * colour will be placed in colour1, and the corresponding surface will 211 | * be placed in colour9. 212 | * 213 | * @arg value0: The first value. 214 | * @arg value1: The second value. 215 | * 216 | * @returns: The nearest, modified value. 217 | */ 218 | inline float intersection(const float value0, const float value1) 219 | { 220 | return max(value0, value1); 221 | } 222 | 223 | 224 | /** 225 | * Smoothly blend between two objects. Ie. The minimum. The 226 | * corresponding colour will be placed in colour1, and the corresponding 227 | * surface will be placed in colour9. 228 | * 229 | * @arg value0: The first value. 230 | * @arg value1: The second value. 231 | * @arg colour0: The first colour. 232 | * @arg colour1: The second colour. 233 | * @arg colour8: The first surface. 234 | * @arg colour9: The second surface. 235 | * @arg value2: The third value. 236 | * @arg value3: The fourth value. 237 | * @arg value4: The fifth value. 238 | * @arg value5: The sixth value. 239 | * @arg blendSize: The amount to blend between the objects. 240 | * 241 | * @returns: The nearest, modified value. 242 | */ 243 | inline float smoothUnion( 244 | const float value0, 245 | const float value1, 246 | const float4 &colour0, 247 | float4 &colour1, 248 | const float4 &colour2, 249 | float4 &colour3, 250 | const float4 &colour4, 251 | float4 &colour5, 252 | const float4 &colour6, 253 | float4 &colour7, 254 | const float4 &colour8, 255 | float4 &colour9, 256 | const float value2, 257 | float &value3, 258 | const float value4, 259 | float &value5, 260 | const float blendSize) 261 | { 262 | float amount = saturate(0.5f + 0.5f * (fabs(value1) - fabs(value0)) / blendSize); 263 | 264 | colour1 = blend(colour0, colour1, amount); 265 | colour3 = blend(colour2, colour3, amount); 266 | colour5 = blend(colour4, colour5, amount); 267 | colour7 = blend(colour6, colour7, amount); 268 | colour9 = blend(colour8, colour9, amount); 269 | value3 = blend(value2, value3, amount); 270 | value5 = blend(value4, value5, amount); 271 | 272 | return blend(value0, value1, amount) - blendSize * amount * (1.0f - amount); 273 | } 274 | 275 | 276 | /** 277 | * Smoothly blend between two objects. Ie. The minimum. The 278 | * corresponding colour will be placed in colour1, and the corresponding 279 | * surface will be placed in colour9. 280 | * 281 | * @arg value0: The first value. 282 | * @arg value1: The second value. 283 | * @arg blendSize: The amount to blend between the objects. 284 | * 285 | * @returns: The nearest, modified value. 286 | */ 287 | inline float smoothUnion(const float value0, const float value1, const float blendSize) 288 | { 289 | float amount = saturate(0.5f + 0.5f * (fabs(value1) - fabs(value0)) / blendSize); 290 | return blend(value0, value1, amount) - blendSize * amount * (1.0f - amount); 291 | } 292 | 293 | 294 | /** 295 | * Smoothly blend the subtraction of the first object from the second. 296 | * The corresponding colour will be placed in colour1, and the 297 | * corresponding surface will be placed in colour9. 298 | * 299 | * @arg value0: The first value. 300 | * @arg value1: The second value. 301 | * @arg colour0: The first colour. 302 | * @arg colour1: The second colour. 303 | * @arg colour8: The first surface. 304 | * @arg colour9: The second surface. 305 | * @arg value2: The third value. 306 | * @arg value3: The fourth value. 307 | * @arg value4: The fifth value. 308 | * @arg value5: The sixth value. 309 | * @arg blendSize: The amount to blend between the objects. 310 | * 311 | * @returns: The nearest, modified value. 312 | */ 313 | inline float smoothSubtraction( 314 | const float value0, 315 | const float value1, 316 | const float4 &colour0, 317 | float4 &colour1, 318 | const float4 &colour2, 319 | float4 &colour3, 320 | const float4 &colour4, 321 | float4 &colour5, 322 | const float4 &colour6, 323 | float4 &colour7, 324 | const float4 &colour8, 325 | float4 &colour9, 326 | const float value2, 327 | float &value3, 328 | const float value4, 329 | float &value5, 330 | const float blendSize) 331 | { 332 | float amount = saturate(0.5f - 0.5f * (value1 + value0) / blendSize); 333 | 334 | colour1 = blend(colour0, colour1, amount); 335 | colour3 = blend(colour2, colour3, amount); 336 | colour5 = blend(colour4, colour5, amount); 337 | colour7 = blend(colour6, colour7, amount); 338 | colour9 = blend(colour8, colour9, amount); 339 | value3 = blend(value2, value3, amount); 340 | value5 = blend(value4, value5, amount); 341 | 342 | return blend(-value0, value1, amount) + blendSize * amount * (1.0f - amount); 343 | } 344 | 345 | 346 | /** 347 | * Smoothly blend the subtraction of the first object from the second. 348 | * The corresponding colour will be placed in colour1, and the 349 | * corresponding surface will be placed in colour9. 350 | * 351 | * @arg value0: The first value. 352 | * @arg value1: The second value. 353 | * @arg blendSize: The amount to blend between the objects. 354 | * 355 | * @returns: The nearest, modified value. 356 | */ 357 | inline float smoothSubtraction( 358 | const float value0, 359 | const float value1, 360 | const float blendSize) 361 | { 362 | float amount = saturate(0.5f - 0.5f * (value1 + value0) / blendSize); 363 | return blend(-value0, value1, amount) + blendSize * amount * (1.0f - amount); 364 | } 365 | 366 | 367 | /** 368 | * Smoothly blend the overlapping region of two objects. The 369 | * corresponding colour will be placed in colour1, and the corresponding 370 | * surface will be placed in colour9. 371 | * 372 | * @arg value0: The first value. 373 | * @arg value1: The second value. 374 | * @arg colour0: The first colour. 375 | * @arg colour1: The second colour. 376 | * @arg colour8: The first surface. 377 | * @arg colour9: The second surface. 378 | * @arg value2: The third value. 379 | * @arg value3: The fourth value. 380 | * @arg value4: The fifth value. 381 | * @arg value5: The sixth value. 382 | * @arg blendSize: The amount to blend between the objects. 383 | * 384 | * @returns: The nearest, modified value. 385 | */ 386 | inline float smoothIntersection( 387 | const float value0, 388 | const float value1, 389 | const float4 &colour0, 390 | float4 &colour1, 391 | const float4 &colour2, 392 | float4 &colour3, 393 | const float4 &colour4, 394 | float4 &colour5, 395 | const float4 &colour6, 396 | float4 &colour7, 397 | const float4 &colour8, 398 | float4 &colour9, 399 | const float value2, 400 | float &value3, 401 | const float value4, 402 | float &value5, 403 | const float blendSize) 404 | { 405 | float amount = saturate(0.5f - 0.5f * (value1 - value0) / blendSize); 406 | 407 | colour1 = blend(colour0, colour1, amount); 408 | colour3 = blend(colour2, colour3, amount); 409 | colour5 = blend(colour4, colour5, amount); 410 | colour7 = blend(colour6, colour7, amount); 411 | colour9 = blend(colour8, colour9, amount); 412 | value3 = blend(value2, value3, amount); 413 | value5 = blend(value4, value5, amount); 414 | 415 | return blend(value0, value1, amount) + blendSize * amount * (1.0f - amount); 416 | } 417 | 418 | 419 | /** 420 | * Smoothly blend the overlapping region of two objects. The 421 | * corresponding colour will be placed in colour1, and the corresponding 422 | * surface will be placed in colour9. 423 | * 424 | * @arg value0: The first value. 425 | * @arg value1: The second value. 426 | * @arg blendSize: The amount to blend between the objects. 427 | * 428 | * @returns: The nearest, modified value. 429 | */ 430 | inline float smoothIntersection( 431 | const float value0, 432 | const float value1, 433 | const float blendSize) 434 | { 435 | float amount = saturate(0.5f - 0.5f * (value1 - value0) / blendSize); 436 | return blend(value0, value1, amount) + blendSize * amount * (1.0f - amount); 437 | } 438 | 439 | 440 | /** 441 | * Compute the modified value resulting from the interaction between 442 | * two objects. The corresponding colour will be placed in colour1, and 443 | * the corresponding surface will be placed in colour9. 444 | * 445 | * @arg modifications: The modification to perform: 446 | * Each bit will enable a modification: 447 | * bit 7: subtraction 448 | * bit 8: intersection 449 | * bit 9: smooth union 450 | * bit 10: smooth subtraction 451 | * bit 11: smooth intersection 452 | * any other value will default to union. 453 | * @arg value0: The first value. 454 | * @arg value1: The second value. 455 | * @arg colour0: The first colour. 456 | * @arg colour1: The second colour. 457 | * @arg colour8: The first surface. 458 | * @arg colour9: The second surface. 459 | * @arg value2: The third value. 460 | * @arg value3: The fourth value. 461 | * @arg value4: The fifth value. 462 | * @arg value5: The sixth value. 463 | * @arg blendSize: The amount to blend between the objects. 464 | * 465 | * @returns: The nearest, modified value. 466 | */ 467 | float performChildInteraction( 468 | const int modifications, 469 | const float value0, 470 | const float value1, 471 | const float4 &colour0, 472 | float4 &colour1, 473 | const float4 &colour2, 474 | float4 &colour3, 475 | const float4 &colour4, 476 | float4 &colour5, 477 | const float4 &colour6, 478 | float4 &colour7, 479 | const float4 &colour8, 480 | float4 &colour9, 481 | const float value2, 482 | float &value3, 483 | const float value4, 484 | float &value5, 485 | const float blendSize) 486 | { 487 | if (modifications & SUBTRACTION) 488 | { 489 | return subtraction( 490 | value0, 491 | value1, 492 | colour0, 493 | colour1, 494 | colour2, 495 | colour3, 496 | colour4, 497 | colour5, 498 | colour6, 499 | colour7, 500 | colour8, 501 | colour9, 502 | value2, 503 | value3, 504 | value4, 505 | value5 506 | ); 507 | } 508 | if (modifications & INTERSECTION) 509 | { 510 | return intersection( 511 | value0, 512 | value1, 513 | colour0, 514 | colour1, 515 | colour2, 516 | colour3, 517 | colour4, 518 | colour5, 519 | colour6, 520 | colour7, 521 | colour8, 522 | colour9, 523 | value2, 524 | value3, 525 | value4, 526 | value5 527 | ); 528 | } 529 | if (modifications & SMOOTH_UNION) 530 | { 531 | return smoothUnion( 532 | value0, 533 | value1, 534 | colour0, 535 | colour1, 536 | colour2, 537 | colour3, 538 | colour4, 539 | colour5, 540 | colour6, 541 | colour7, 542 | colour8, 543 | colour9, 544 | value2, 545 | value3, 546 | value4, 547 | value5, 548 | blendSize 549 | ); 550 | } 551 | if (modifications & SMOOTH_SUBTRACTION) 552 | { 553 | return smoothSubtraction( 554 | value0, 555 | value1, 556 | colour0, 557 | colour1, 558 | colour2, 559 | colour3, 560 | colour4, 561 | colour5, 562 | colour6, 563 | colour7, 564 | colour8, 565 | colour9, 566 | value2, 567 | value3, 568 | value4, 569 | value5, 570 | blendSize 571 | ); 572 | } 573 | if (modifications & SMOOTH_INTERSECTION) 574 | { 575 | return smoothIntersection( 576 | value0, 577 | value1, 578 | colour0, 579 | colour1, 580 | colour2, 581 | colour3, 582 | colour4, 583 | colour5, 584 | colour6, 585 | colour7, 586 | colour8, 587 | colour9, 588 | value2, 589 | value3, 590 | value4, 591 | value5, 592 | blendSize 593 | ); 594 | } 595 | return union_( 596 | value0, 597 | value1, 598 | colour0, 599 | colour1, 600 | colour2, 601 | colour3, 602 | colour4, 603 | colour5, 604 | colour6, 605 | colour7, 606 | colour8, 607 | colour9, 608 | value2, 609 | value3, 610 | value4, 611 | value5 612 | ); 613 | } 614 | 615 | 616 | /** 617 | * Compute the modified value resulting from the interaction between 618 | * two objects. The corresponding colour will be placed in colour1, and 619 | * the corresponding surface will be placed in colour9. 620 | * 621 | * @arg modifications: The modification to perform: 622 | * Each bit will enable a modification: 623 | * bit 7: subtraction 624 | * bit 8: intersection 625 | * bit 9: smooth union 626 | * bit 10: smooth subtraction 627 | * bit 11: smooth intersection 628 | * any other value will default to union. 629 | * @arg value0: The first value. 630 | * @arg value1: The second value. 631 | * @arg blendSize: The amount to blend between the objects. 632 | * 633 | * @returns: The nearest, modified value. 634 | */ 635 | float performChildInteraction( 636 | const int modifications, 637 | const float value0, 638 | const float value1, 639 | const float blendSize) 640 | { 641 | if (modifications & SUBTRACTION) 642 | { 643 | return subtraction(value0, value1); 644 | } 645 | if (modifications & INTERSECTION) 646 | { 647 | return intersection(value0, value1); 648 | } 649 | if (modifications & SMOOTH_UNION) 650 | { 651 | return smoothUnion(value0, value1, blendSize); 652 | } 653 | if (modifications & SMOOTH_SUBTRACTION) 654 | { 655 | return smoothSubtraction(value0, value1, blendSize); 656 | } 657 | if (modifications & SMOOTH_INTERSECTION) 658 | { 659 | return smoothIntersection(value0, value1, blendSize); 660 | } 661 | return union_(value0, value1); 662 | } 663 | -------------------------------------------------------------------------------- /src/python/sdf/primitive.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 by Owen Bulka. 2 | # All rights reserved. 3 | # This file is released under the "MIT License Agreement". 4 | # Please see the LICENSE.md file that should have been included as part 5 | # of this package. 6 | """Knob management for sdf primitives. 7 | 8 | # Add on knob changed callback to sdf_primitive group: 9 | nuke.toNode("sdf_primitive").knob("knobChanged").setValue( 10 | "__import__('sdf.primitive', fromlist='SDFPrimitive').SDFPrimitive().handle_knob_changed()" 11 | ) 12 | 13 | # Add on node create callback to sdf_primitive group: 14 | nuke.toNode("sdf_primitive").knob("onCreate").setValue( 15 | "__import__('sdf.primitive', fromlist='SDFPrimitive').SDFPrimitive().handle_node_created()" 16 | ) 17 | """ 18 | from collections import OrderedDict 19 | 20 | from .knob_manager import KnobChangedCallbacks, SDFGeoKnobManager 21 | 22 | 23 | class SDFPrimitive(SDFGeoKnobManager): 24 | """Knob manager for primitive shapes in signed distance fields.""" 25 | 26 | shape_knob_name = "shape" 27 | hollow_knob_name = "hollow" 28 | wall_thickness_knob_name = "wall_thickness" 29 | elongate_knob_name = "elongate" 30 | elongation_knob_name = "elongation" 31 | is_bound_knob_name = "is_bound" 32 | blend_strength_knob_name = "blend_strength" 33 | blend_type_knob_name = "blend_type" 34 | repetition_knob_name = "repetition" 35 | repetition_params_knob_name = "repetition_params" 36 | repetition_spacing_knob_name = "repetition_spacing" 37 | 38 | mandelbox_shape_label = "mandelbox" 39 | 40 | _dimensional_axes = SDFGeoKnobManager._dimensional_axes + ("w",) 41 | 42 | _knob_changed_callbacks = KnobChangedCallbacks(SDFGeoKnobManager._knob_changed_callbacks) 43 | 44 | dimensional_knob_defaults = { 45 | "sphere": OrderedDict([ 46 | ( 47 | "radius", 48 | { 49 | "default": .5, 50 | "range": (0., 10.), 51 | "tooltip": "The radius of the sphere.", 52 | }, 53 | ), 54 | ]), 55 | "ellipsoid": OrderedDict([ 56 | ( 57 | "x radius", 58 | { 59 | "default": .5, 60 | "range": (0., 10.), 61 | "tooltip": "The radius along the x-axis of the ellipsoid.", 62 | }, 63 | ), 64 | ( 65 | "y radius", 66 | { 67 | "default": .25, 68 | "range": (0., 10.), 69 | "tooltip": "The radius along the y-axis of the ellipsoid.", 70 | }, 71 | ), 72 | ( 73 | "z radius", 74 | { 75 | "default": .25, 76 | "range": (0., 10.), 77 | "tooltip": "The radius along the z-axis of the ellipsoid.", 78 | }, 79 | ), 80 | ]), 81 | "cut sphere": OrderedDict([ 82 | ( 83 | "radius", 84 | { 85 | "default": .5, 86 | "range": (0., 10.), 87 | "tooltip": "The radius of the sphere.", 88 | }, 89 | ), 90 | ( 91 | "height", 92 | { 93 | "default": .25, 94 | "range": (0., 10.), 95 | "tooltip": "The height (y-axis) below which the sphere is culled.", 96 | }, 97 | ), 98 | ]), 99 | "hollow sphere": OrderedDict([ 100 | ( 101 | "radius", 102 | { 103 | "default": .5, 104 | "range": (0., 10.), 105 | "tooltip": "The radius of the sphere.", 106 | }, 107 | ), 108 | ( 109 | "height", 110 | { 111 | "default": .25, 112 | "range": (0., 10.), 113 | "tooltip": "The height (y-axis) at which an opening is created.", 114 | }, 115 | ), 116 | ( 117 | "thickness", 118 | { 119 | "default": .05, 120 | "range": (0., 1.), 121 | "tooltip": "The thickness of the walls of the hollow sphere.", 122 | }, 123 | ), 124 | ]), 125 | "death star": OrderedDict([ 126 | ( 127 | "solid radius", 128 | { 129 | "default": .5, 130 | "range": (0., 10.), 131 | "tooltip": "The radius of the sphere that remains solid.", 132 | }, 133 | ), 134 | ( 135 | "hollow radius", 136 | { 137 | "default": .5, 138 | "range": (0., 10.), 139 | "tooltip": "The radius of the sphere that is cut from the solid.", 140 | }, 141 | ), 142 | ( 143 | "hollow height", 144 | { 145 | "default": .75, 146 | "range": (0., 10.), 147 | "tooltip": ( 148 | "The height (y-axis) of the center of the sphere that is cut " 149 | "from the solid, above solidRadius + hollowRadius, the result " 150 | "will be a standard sphere of radius solidRadius." 151 | ), 152 | }, 153 | ), 154 | ]), 155 | "solid angle": OrderedDict([ 156 | ( 157 | "radius", 158 | { 159 | "default": .5, 160 | "range": (0., 10.), 161 | "tooltip": "The radius of the sphere to cut the angle out of.", 162 | }, 163 | ), 164 | ( 165 | "angle", 166 | { 167 | "default": 30., 168 | "range": (0., 180.), 169 | "tooltip": ( 170 | "The angle between the edge of the solid angle and the " 171 | "y-axis on [0-180] measured between the y-axis and wall of the " 172 | "solid angle." 173 | ), 174 | }, 175 | ), 176 | ]), 177 | "rectangular prism": OrderedDict([ 178 | ( 179 | "width", 180 | { 181 | "default": .5, 182 | "range": (0., 10.), 183 | "tooltip": "The width (x) of the prism.", 184 | }, 185 | ), 186 | ( 187 | "height", 188 | { 189 | "default": .75, 190 | "range": (0., 10.), 191 | "tooltip": "The height (y) of the prism.", 192 | }, 193 | ), 194 | ( 195 | "depth", 196 | { 197 | "default": .25, 198 | "range": (0., 10.), 199 | "tooltip": "The depth (z) of the prism.", 200 | }, 201 | ), 202 | ]), 203 | "rectangular prism frame": OrderedDict([ 204 | ( 205 | "width", 206 | { 207 | "default": .5, 208 | "range": (0., 10.), 209 | "tooltip": "The width (x) of the frame.", 210 | }, 211 | ), 212 | ( 213 | "height", 214 | { 215 | "default": .75, 216 | "range": (0., 10.), 217 | "tooltip": "The height (y) of the frame.", 218 | }, 219 | ), 220 | ( 221 | "depth", 222 | { 223 | "default": .25, 224 | "range": (0., 10.), 225 | "tooltip": "The depth (z) of the frame.", 226 | }, 227 | ), 228 | ( 229 | "thickness", 230 | { 231 | "default": .05, 232 | "range": (0., 1.), 233 | "tooltip": "The thickness of the frame.", 234 | }, 235 | ), 236 | ]), 237 | "rhombus": OrderedDict([ 238 | ( 239 | "width", 240 | { 241 | "default": .5, 242 | "range": (0., 10.), 243 | "tooltip": "The width (x) of the rhombus.", 244 | }, 245 | ), 246 | ( 247 | "height", 248 | { 249 | "default": .75, 250 | "range": (0., 10.), 251 | "tooltip": "The height (y) of the rhombus.", 252 | }, 253 | ), 254 | ( 255 | "depth", 256 | { 257 | "default": .25, 258 | "range": (0., 10.), 259 | "tooltip": ( 260 | "The depth (z) of the rhombus, this the extruded dimension, " 261 | "or thickness." 262 | ), 263 | }, 264 | ), 265 | ( 266 | "corner radius", 267 | { 268 | "default": .05, 269 | "range": (0., 1.), 270 | "tooltip": ( 271 | "The radius of the corners of the rhombus' xy-plane parallel " 272 | " face." 273 | ), 274 | }, 275 | ), 276 | ]), 277 | "triangular prism": OrderedDict([ 278 | ( 279 | "base", 280 | { 281 | "default": .5, 282 | "range": (0., 10.), 283 | "tooltip": "The equalateral triangles edge length (xy-plane).", 284 | }, 285 | ), 286 | ( 287 | "depth", 288 | { 289 | "default": .75, 290 | "range": (0., 10.), 291 | "tooltip": "The depth (z-axis) of the prism.", 292 | }, 293 | ), 294 | ]), 295 | "cylinder": OrderedDict([ 296 | ( 297 | "radius", 298 | { 299 | "default": .5, 300 | "range": (0., 10.), 301 | "tooltip": "The radius (xz-plane) of the cylinder.", 302 | }, 303 | ), 304 | ( 305 | "height", 306 | { 307 | "default": .75, 308 | "range": (0., 10.), 309 | "tooltip": "The height (y-axis) of the cylinder.", 310 | }, 311 | ), 312 | ]), 313 | "infinite cylinder": OrderedDict([ 314 | ( 315 | "radius", 316 | { 317 | "default": .5, 318 | "range": (0., 10.), 319 | "tooltip": "The radius (xz-plane) of the cylinder.", 320 | }, 321 | ), 322 | ]), 323 | "plane": OrderedDict([ 324 | ( 325 | "normal x", 326 | { 327 | "default": 0., 328 | "range": (0., 1.), 329 | "tooltip": "The x component of the normal direction of the plane.", 330 | }, 331 | ), 332 | ( 333 | "normal y", 334 | { 335 | "default": 0., 336 | "range": (0., 1.), 337 | "tooltip": "The y component of the normal direction of the plane.", 338 | }, 339 | ), 340 | ( 341 | "normal z", 342 | { 343 | "default": 1., 344 | "range": (0., 1.), 345 | "tooltip": "The z component of the normal direction of the plane.", 346 | }, 347 | ), 348 | ]), 349 | "capsule": OrderedDict([ 350 | ( 351 | "radius", 352 | { 353 | "default": .25, 354 | "range": (0., 10.), 355 | "tooltip": "The radius of the capsule.", 356 | }, 357 | ), 358 | ( 359 | "negative height", 360 | { 361 | "default": .25, 362 | "range": (0., 10.), 363 | "tooltip": "The distance along the negative y-axis before entering the dome.", 364 | }, 365 | ), 366 | ( 367 | "positive height", 368 | { 369 | "default": .25, 370 | "range": (0., 10.), 371 | "tooltip": "The distance along the positive y-axis before entering the dome.", 372 | }, 373 | ), 374 | ]), 375 | "cone": OrderedDict([ 376 | ( 377 | "angle", 378 | { 379 | "default": 30., 380 | "range": (0., 90.), 381 | "tooltip": ( 382 | "The angle between the tip and base of the cone [0-PI/2) " 383 | "measured between the y-axis and wall of the cone." 384 | ), 385 | }, 386 | ), 387 | ( 388 | "height", 389 | { 390 | "default": .33, 391 | "range": (0., 10.), 392 | "tooltip": "The height (y-axis) of the cone. Cannot be 0.", 393 | }, 394 | ), 395 | ]), 396 | "infinite cone": OrderedDict([ 397 | ( 398 | "angle", 399 | { 400 | "default": 30., 401 | "range": (0., 90.), 402 | "tooltip": ( 403 | "The angle between the tip and base of the cone [0-90) " 404 | "measured between the y-axis and wall of the cone." 405 | ), 406 | }, 407 | ), 408 | ]), 409 | "capped cone": OrderedDict([ 410 | ( 411 | "height", 412 | { 413 | "default": .5, 414 | "range": (0., 10.), 415 | "tooltip": ( 416 | "The height (y-axis) of the cone, centered at the origin. " 417 | "Cannot be 0." 418 | ), 419 | }, 420 | ), 421 | ( 422 | "lower radius", 423 | { 424 | "default": .5, 425 | "range": (0., 10.), 426 | "tooltip": "The radius of the cone at y = -height/2.", 427 | }, 428 | ), 429 | ( 430 | "upper radius", 431 | { 432 | "default": .25, 433 | "range": (0., 10.), 434 | "tooltip": "The radius of the cone at y = height/2.", 435 | }, 436 | ), 437 | ]), 438 | "rounded cone": OrderedDict([ 439 | ( 440 | "height", 441 | { 442 | "default": .3, 443 | "range": (0., 10.), 444 | "tooltip": "The height (y-axis) of the rounded cone.", 445 | }, 446 | ), 447 | ( 448 | "lower radius", 449 | { 450 | "default": .2, 451 | "range": (0., 10.), 452 | "tooltip": "The radius of the cone at y = 0.", 453 | }, 454 | ), 455 | ( 456 | "upper radius", 457 | { 458 | "default": .1, 459 | "range": (0., 10.), 460 | "tooltip": "The radius of the cone at y = height.", 461 | }, 462 | ), 463 | ]), 464 | "torus": OrderedDict([ 465 | ( 466 | "ring radius", 467 | { 468 | "default": .3, 469 | "range": (0., 10.), 470 | "tooltip": "The radius (xy-plane) of the ring of the torus.", 471 | }, 472 | ), 473 | ( 474 | "tube radius", 475 | { 476 | "default": .2, 477 | "range": (0., 5.), 478 | "tooltip": "The radius of the tube of the torus.", 479 | }, 480 | ), 481 | ]), 482 | "capped torus": OrderedDict([ 483 | ( 484 | "ring radius", 485 | { 486 | "default": .3, 487 | "range": (0., 10.), 488 | "tooltip": "The radius (xy-plane) of the ring of the torus.", 489 | }, 490 | ), 491 | ( 492 | "tube radius", 493 | { 494 | "default": .2, 495 | "range": (0., 5.), 496 | "tooltip": "The radius of the tube of the torus.", 497 | }, 498 | ), 499 | ( 500 | "cap angle", 501 | { 502 | "default": 30., 503 | "range": (0., 180.), 504 | "tooltip": ( 505 | "The angle (xy-plane, symmetric about y-axis) to cap at, in " 506 | "the range (0-180.)." 507 | ), 508 | }, 509 | ), 510 | ]), 511 | "link": OrderedDict([ 512 | ( 513 | "ring radius", 514 | { 515 | "default": .3, 516 | "range": (0., 10.), 517 | "tooltip": ( 518 | "The radius (xy-plane) of the ring of the torus that will be " 519 | "stretched to create the link." 520 | ), 521 | }, 522 | ), 523 | ( 524 | "tube radius", 525 | { 526 | "default": .2, 527 | "range": (0., 5.), 528 | "tooltip": "The radius of the tube that makes the link.", 529 | }, 530 | ), 531 | ( 532 | "height", 533 | { 534 | "default": .1, 535 | "range": (0., 10.), 536 | "tooltip": "The height (y-axis) to elongate the torus.", 537 | }, 538 | ), 539 | ]), 540 | "hexagonal prism": OrderedDict([ 541 | ( 542 | "height", 543 | { 544 | "default": .5, 545 | "range": (0., 10.), 546 | "tooltip": "The height (y) of the prism.", 547 | }, 548 | ), 549 | ( 550 | "depth", 551 | { 552 | "default": .5, 553 | "range": (0., 10.), 554 | "tooltip": "The depth (z) of the prism.", 555 | }, 556 | ), 557 | ]), 558 | "octahedron": OrderedDict([ 559 | ( 560 | "radial extent", 561 | { 562 | "default": .5, 563 | "range": (0., 10.), 564 | "tooltip": ( 565 | "The maximum distance along the x, y, and z axes. " 566 | "ie. The vertices are at +/-radial_extent on the x, y, and z axes." 567 | ), 568 | }, 569 | ), 570 | ]), 571 | "mandelbulb": OrderedDict([ 572 | ( 573 | "power", 574 | { 575 | "default": 8., 576 | "range": (2., 30.), 577 | "tooltip": "One greater than the axes of symmetry in the xy-plane.", 578 | }, 579 | ), 580 | ( 581 | "iterations", 582 | { 583 | "default": 10., 584 | "range": (1., 30.), 585 | "tooltip": ( 586 | "The number of iterations to compute, the higher this " 587 | "is the slower it will be to compute, but the deeper the " 588 | "fractal will have detail." 589 | ), 590 | }, 591 | ), 592 | ( 593 | "max square radius", 594 | { 595 | "default": 4., 596 | "range": (1., 9.), 597 | "tooltip": ( 598 | "When the square radius has reached this length, stop " 599 | "iterating." 600 | ), 601 | }, 602 | ), 603 | ]), 604 | mandelbox_shape_label: OrderedDict([ 605 | ( 606 | "scale", 607 | { 608 | "default": -1.75, 609 | "range": (-5., 5.), 610 | "tooltip": ( 611 | "The amount to scale the position between folds. " 612 | "Can be negative or positive." 613 | ), 614 | }, 615 | ), 616 | ( 617 | "iterations", 618 | { 619 | "default": 12., 620 | "range": (1., 30.), 621 | "tooltip": ( 622 | "The number of iterations to compute, the higher this " 623 | "is the slower it will be to compute, but the more detail " 624 | "the fractal will have." 625 | ), 626 | }, 627 | ), 628 | ( 629 | "min square radius", 630 | { 631 | "default": 0.001, 632 | "range": (0.001, 1.), 633 | "tooltip": "The minimum square radius to use when spherically folding.", 634 | }, 635 | ), 636 | ( 637 | "folding limit", 638 | { 639 | "default": 0.8, 640 | "range": (0.01, 2.), 641 | "tooltip": ( 642 | "Clamp the position between +/- this value when performing " 643 | "the box fold. Higher values will result in a denser fractal." 644 | ), 645 | }, 646 | ), 647 | ]), 648 | } 649 | 650 | repetition_knob_defaults = { 651 | "none": OrderedDict(), 652 | "finite": OrderedDict([ 653 | ( 654 | "limits", 655 | { 656 | "default": [3., 3., 0.], 657 | "tooltip": "The distance along each positive axis to repeat the objects.", 658 | }, 659 | ), 660 | ( 661 | "spacing", 662 | { 663 | "default": 1.1, 664 | "range": (0., 10.), 665 | "tooltip": "The spacing between the objects.", 666 | }, 667 | ), 668 | ]), 669 | "infinite": OrderedDict([ 670 | ( 671 | "spacing", 672 | { 673 | "default": [1.1, 1.1, 100.], 674 | "tooltip": "The spacing along each positive axis to repeat the objects.", 675 | }, 676 | ), 677 | ]), 678 | } 679 | 680 | def __init__(self): 681 | """Initialize the manager""" 682 | super(SDFPrimitive, self).__init__() 683 | 684 | self._knob_names_only_enabled_if_parent = { 685 | SDFPrimitive.is_bound_knob_name, 686 | SDFPrimitive.blend_strength_knob_name, 687 | SDFPrimitive.blend_type_knob_name, 688 | } 689 | 690 | @property 691 | def repetition_context_knobs(self): 692 | """list(nuke.Knob): The context knobs for an sdf node's 693 | dimensional parameters. 694 | """ 695 | return [ 696 | self._node.knob(repetition_knob) 697 | for repetition_knob in ( 698 | self.repetition_params_knob_name, 699 | self.repetition_spacing_knob_name, 700 | ) 701 | ] 702 | 703 | @_knob_changed_callbacks.register(shape_knob_name) 704 | def _shape_changed(self): 705 | """Dynamically enable/disable and change the labels/tooltips/values 706 | of the dimensional knobs when the selected shape has changed. 707 | """ 708 | self._dropdown_context_changed( 709 | self.dimensional_knob_defaults, 710 | self.dimensional_context_knobs, 711 | set_node_label=True, 712 | ) 713 | 714 | @_knob_changed_callbacks.register(repetition_knob_name) 715 | def _repetition_changed(self): 716 | """Change the repetition knobs based on which type of repetition 717 | has been selected. 718 | """ 719 | self._dropdown_context_changed( 720 | self.repetition_knob_defaults, 721 | self.repetition_context_knobs, 722 | ) 723 | 724 | @_knob_changed_callbacks.register(hollow_knob_name) 725 | def _hollow_changed(self): 726 | """Dynamically enable/disable the wall thickness knob depending 727 | on whether or not hollowing has been enabled. 728 | """ 729 | self._node.knob(self.wall_thickness_knob_name).setEnabled(self._knob.value()) 730 | 731 | @_knob_changed_callbacks.register(elongate_knob_name) 732 | def _elongate_changed(self): 733 | """Dynamically enable/disable the elongation knob depending 734 | on whether or not elongate has been enabled. 735 | """ 736 | self._node.knob(self.elongation_knob_name).setEnabled(self._knob.value()) 737 | 738 | @_knob_changed_callbacks.register(blend_type_knob_name) 739 | def _blend_type_changed(self): 740 | """Enable/disable the blend strength knob depending on the blend 741 | type. 742 | """ 743 | self._node.knob(self.blend_strength_knob_name).setEnabled( 744 | self._knob.value().startswith("smooth"), 745 | ) 746 | 747 | @_knob_changed_callbacks.register("inputChange") 748 | def _input_changed(self): 749 | """Enable/disable the knobs that only apply if this object has 750 | children. 751 | """ 752 | super(SDFPrimitive, self)._input_changed() 753 | 754 | self._knob = self._node.knob(self.blend_type_knob_name) 755 | if self._knob.enabled(): 756 | self._blend_type_changed() 757 | 758 | @_knob_changed_callbacks.register(is_bound_knob_name) 759 | def _is_bound_changed(self): 760 | """Enable/disable the blend strength knob and blend type.""" 761 | not_bound = not self._knob.value() 762 | blend_type_knob = self._node.knob(self.blend_type_knob_name) 763 | blend_type_knob.setEnabled(not_bound) 764 | self._node.knob(self.blend_strength_knob_name).setEnabled(not_bound) 765 | 766 | if not_bound: 767 | self._knob = blend_type_knob 768 | self._blend_type_changed() 769 | --------------------------------------------------------------------------------