{ "revision": 0, "last_node_id": 22, "last_link_id": 0, "nodes": [ { "id": 22, "type": "3324cf54-bcff-405f-a4bf-c5122c72fe56", "pos": [ 4800, -1180 ], "size": [ 250, 154 ], "flags": {}, "order": 4, "mode": 0, "inputs": [ { "label": "image", "localized_name": "images.image0", "name": "images.image0", "type": "IMAGE", "link": null } ], "outputs": [ { "label": "IMAGE", "localized_name": "IMAGE0", "name": "IMAGE0", "type": "IMAGE", "links": [] } ], "title": "Film Grain", "properties": { "proxyWidgets": [ [ "17", "value" ], [ "18", "value" ], [ "19", "value" ], [ "20", "value" ], [ "21", "choice" ] ] }, "widgets_values": [] } ], "links": [], "version": 0.4, "definitions": { "subgraphs": [ { "id": "3324cf54-bcff-405f-a4bf-c5122c72fe56", "version": 1, "state": { "lastGroupId": 0, "lastNodeId": 21, "lastLinkId": 30, "lastRerouteId": 0 }, "revision": 0, "config": {}, "name": "Film Grain", "inputNode": { "id": -10, "bounding": [ 4096.671470760602, -948.2184031393472, 120, 60 ] }, "outputNode": { "id": -20, "bounding": [ 4900, -948.2184031393472, 120, 60 ] }, "inputs": [ { "id": "062968ea-da25-47e7-a180-d913c267f148", "name": "images.image0", "type": "IMAGE", "linkIds": [ 22 ], "localized_name": "images.image0", "label": "image", "pos": [ 4196.671470760602, -928.2184031393472 ] } ], "outputs": [ { "id": "43247d06-a39f-4733-9828-c39400fe02a4", "name": "IMAGE0", "type": "IMAGE", "linkIds": [ 23 ], "localized_name": "IMAGE0", "label": "IMAGE", "pos": [ 4920, -928.2184031393472 ] } ], "widgets": [], "nodes": [ { "id": 15, "type": "GLSLShader", "pos": [ 4510, -1180 ], "size": [ 330, 272 ], "flags": {}, "order": 5, "mode": 0, "inputs": [ { "label": "image0", "localized_name": "images.image0", "name": "images.image0", "type": "IMAGE", "link": 22 }, { "label": "image1", "localized_name": "images.image1", "name": "images.image1", "shape": 7, "type": "IMAGE", "link": null }, { "label": "u_float0", "localized_name": "floats.u_float0", "name": "floats.u_float0", "shape": 7, "type": "FLOAT", "link": 26 }, { "label": "u_float1", "localized_name": "floats.u_float1", "name": "floats.u_float1", "shape": 7, "type": "FLOAT", "link": 27 }, { "label": "u_float2", "localized_name": "floats.u_float2", "name": "floats.u_float2", "shape": 7, "type": "FLOAT", "link": 28 }, { "label": "u_float3", "localized_name": "floats.u_float3", "name": "floats.u_float3", "shape": 7, "type": "FLOAT", "link": 29 }, { "label": "u_float4", "localized_name": "floats.u_float4", "name": "floats.u_float4", "shape": 7, "type": "FLOAT", "link": null }, { "label": "u_int0", "localized_name": "ints.u_int0", "name": "ints.u_int0", "shape": 7, "type": "INT", "link": 30 }, { "label": "u_int1", "localized_name": "ints.u_int1", "name": "ints.u_int1", "shape": 7, "type": "INT", "link": null }, { "localized_name": "fragment_shader", "name": "fragment_shader", "type": "STRING", "widget": { "name": "fragment_shader" }, "link": null }, { "localized_name": "size_mode", "name": "size_mode", "type": "COMFY_DYNAMICCOMBO_V3", "widget": { "name": "size_mode" }, "link": null } ], "outputs": [ { "localized_name": "IMAGE0", "name": "IMAGE0", "type": "IMAGE", "links": [ 23 ] }, { "localized_name": "IMAGE1", "name": "IMAGE1", "type": "IMAGE", "links": null }, { "localized_name": "IMAGE2", "name": "IMAGE2", "type": "IMAGE", "links": null }, { "localized_name": "IMAGE3", "name": "IMAGE3", "type": "IMAGE", "links": null } ], "properties": { "Node name for S&R": "GLSLShader" }, "widgets_values": [ "#version 300 es\nprecision highp float;\n\nuniform sampler2D u_image0;\nuniform vec2 u_resolution;\nuniform float u_float0; // grain amount [0.0 – 1.0] typical: 0.2–0.8\nuniform float u_float1; // grain size [0.3 – 3.0] lower = finer grain\nuniform float u_float2; // color amount [0.0 – 1.0] 0 = monochrome, 1 = RGB grain\nuniform float u_float3; // luminance bias [0.0 – 1.0] 0 = uniform, 1 = shadows only\nuniform int u_int0; // noise mode [0 or 1] 0 = smooth, 1 = grainy\n\nin vec2 v_texCoord;\nlayout(location = 0) out vec4 fragColor0;\n\n// High-quality integer hash (pcg-like)\nuint pcg(uint v) {\n uint state = v * 747796405u + 2891336453u;\n uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n return (word >> 22u) ^ word;\n}\n\n// 2D -> 1D hash input\nuint hash2d(uvec2 p) {\n return pcg(p.x + pcg(p.y));\n}\n\n// Hash to float [0, 1]\nfloat hashf(uvec2 p) {\n return float(hash2d(p)) / float(0xffffffffu);\n}\n\n// Hash to float with offset (for RGB channels)\nfloat hashf(uvec2 p, uint offset) {\n return float(pcg(hash2d(p) + offset)) / float(0xffffffffu);\n}\n\n// Convert uniform [0,1] to roughly Gaussian distribution\n// Using simple approximation: average of multiple samples\nfloat toGaussian(uvec2 p) {\n float sum = hashf(p, 0u) + hashf(p, 1u) + hashf(p, 2u) + hashf(p, 3u);\n return (sum - 2.0) * 0.7; // Centered, scaled\n}\n\nfloat toGaussian(uvec2 p, uint offset) {\n float sum = hashf(p, offset) + hashf(p, offset + 1u) \n + hashf(p, offset + 2u) + hashf(p, offset + 3u);\n return (sum - 2.0) * 0.7;\n}\n\n// Smooth noise with better interpolation\nfloat smoothNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n \n // Quintic interpolation (less banding than cubic)\n f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n \n uvec2 ui = uvec2(i);\n float a = toGaussian(ui);\n float b = toGaussian(ui + uvec2(1u, 0u));\n float c = toGaussian(ui + uvec2(0u, 1u));\n float d = toGaussian(ui + uvec2(1u, 1u));\n \n return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\nfloat smoothNoise(vec2 p, uint offset) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n \n f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n \n uvec2 ui = uvec2(i);\n float a = toGaussian(ui, offset);\n float b = toGaussian(ui + uvec2(1u, 0u), offset);\n float c = toGaussian(ui + uvec2(0u, 1u), offset);\n float d = toGaussian(ui + uvec2(1u, 1u), offset);\n \n return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n}\n\nvoid main() {\n vec4 color = texture(u_image0, v_texCoord);\n \n // Luminance (Rec.709)\n float luma = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));\n \n // Grain UV (resolution-independent)\n vec2 grainUV = v_texCoord * u_resolution / max(u_float1, 0.01);\n uvec2 grainPixel = uvec2(grainUV);\n \n float g;\n vec3 grainRGB;\n \n if (u_int0 == 1) {\n // Grainy mode: pure hash noise (no interpolation = no banding)\n g = toGaussian(grainPixel);\n grainRGB = vec3(\n toGaussian(grainPixel, 100u),\n toGaussian(grainPixel, 200u),\n toGaussian(grainPixel, 300u)\n );\n } else {\n // Smooth mode: interpolated with quintic curve\n g = smoothNoise(grainUV);\n grainRGB = vec3(\n smoothNoise(grainUV, 100u),\n smoothNoise(grainUV, 200u),\n smoothNoise(grainUV, 300u)\n );\n }\n \n // Luminance weighting (less grain in highlights)\n float lumWeight = mix(1.0, 1.0 - luma, clamp(u_float3, 0.0, 1.0));\n \n // Strength\n float strength = u_float0 * 0.15;\n \n // Color vs monochrome grain\n vec3 grainColor = mix(vec3(g), grainRGB, clamp(u_float2, 0.0, 1.0));\n \n color.rgb += grainColor * strength * lumWeight;\n fragColor0 = vec4(clamp(color.rgb, 0.0, 1.0), color.a);\n}\n", "from_input" ] }, { "id": 21, "type": "CustomCombo", "pos": [ 4280, -780 ], "size": [ 210, 153.8888931274414 ], "flags": {}, "order": 0, "mode": 0, "inputs": [ { "label": "grain_mode", "localized_name": "choice", "name": "choice", "type": "COMBO", "widget": { "name": "choice" }, "link": null } ], "outputs": [ { "localized_name": "STRING", "name": "STRING", "type": "STRING", "links": null }, { "localized_name": "INDEX", "name": "INDEX", "type": "INT", "links": [ 30 ] } ], "properties": { "Node name for S&R": "CustomCombo" }, "widgets_values": [ "Smooth", 0, "Smooth", "Grainy", "" ] }, { "id": 17, "type": "PrimitiveFloat", "pos": [ 4276.671470760602, -1180.3256994061358 ], "size": [ 210, 58 ], "flags": {}, "order": 1, "mode": 0, "inputs": [ { "label": "grain_amount", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": { "name": "value" }, "link": null } ], "outputs": [ { "localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [ 26 ] } ], "title": "Grain amount", "properties": { "Node name for S&R": "PrimitiveFloat", "min": 0, "max": 1, "step": 0.05, "precision": 2 }, "widgets_values": [ 0.25 ] }, { "id": 18, "type": "PrimitiveFloat", "pos": [ 4280, -1080 ], "size": [ 210, 58 ], "flags": {}, "order": 2, "mode": 0, "inputs": [ { "label": "grain_size", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": { "name": "value" }, "link": null } ], "outputs": [ { "localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [ 27 ] } ], "title": "Grain size", "properties": { "Node name for S&R": "PrimitiveFloat", "min": 0.05, "max": 3, "precision": 2, "step": 0.05 }, "widgets_values": [ 0.1 ] }, { "id": 19, "type": "PrimitiveFloat", "pos": [ 4280, -980 ], "size": [ 210, 58 ], "flags": {}, "order": 3, "mode": 0, "inputs": [ { "label": "color_amount", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": { "name": "value" }, "link": null } ], "outputs": [ { "localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [ 28 ] } ], "title": "Color amount", "properties": { "Node name for S&R": "PrimitiveFloat", "min": 0, "max": 1, "precision": 2, "step": 0.05 }, "widgets_values": [ 0 ] }, { "id": 20, "type": "PrimitiveFloat", "pos": [ 4280, -880 ], "size": [ 210, 58 ], "flags": {}, "order": 4, "mode": 0, "inputs": [ { "label": "shadow_focus", "localized_name": "value", "name": "value", "type": "FLOAT", "widget": { "name": "value" }, "link": null } ], "outputs": [ { "localized_name": "FLOAT", "name": "FLOAT", "type": "FLOAT", "links": [ 29 ] } ], "title": "Luminance bias", "properties": { "Node name for S&R": "PrimitiveFloat", "min": 0, "max": 1, "precision": 2, "step": 0.05 }, "widgets_values": [ 0 ] } ], "groups": [], "links": [ { "id": 26, "origin_id": 17, "origin_slot": 0, "target_id": 15, "target_slot": 2, "type": "FLOAT" }, { "id": 27, "origin_id": 18, "origin_slot": 0, "target_id": 15, "target_slot": 3, "type": "FLOAT" }, { "id": 28, "origin_id": 19, "origin_slot": 0, "target_id": 15, "target_slot": 4, "type": "FLOAT" }, { "id": 29, "origin_id": 20, "origin_slot": 0, "target_id": 15, "target_slot": 5, "type": "FLOAT" }, { "id": 30, "origin_id": 21, "origin_slot": 1, "target_id": 15, "target_slot": 7, "type": "INT" }, { "id": 22, "origin_id": -10, "origin_slot": 0, "target_id": 15, "target_slot": 0, "type": "IMAGE" }, { "id": 23, "origin_id": 15, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "IMAGE" } ], "extra": { "workflowRendererVersion": "LG" }, "category": "Image Tools/Color adjust" } ] } }