Harvest the power of the GPU

for awesome special effects

PyCon 2022

Paul Vincent Craven

https://tinyurl.com/pythongpu

Outline

  • What can you do with the GPU?
  • How does it work?
  • How can you get started writing your own code?

https://tinyurl.com/pythongpu

What can you do?

Crazy-fast sprite drawing

What can you do?

Add flashes, explosions, glow

How do we offload our calculations to the GPU?

https://tinyurl.com/pythongpu

We can't just issue drawing commands

draw_rect()

draw_rect()

https://tinyurl.com/pythongpu

We load info to the GPU

send_data()

https://tinyurl.com/pythongpu

Then trigger GPU to draw

draw_all()

https://tinyurl.com/pythongpu

Still not perfect

  • This only off-loads drawing
  • What happens if something moves?
  • The CPU still does the movement
  • Any updated data has to get re-sent to the GPU
  • We need to minimize data sent to the GPU each frame

https://tinyurl.com/pythongpu

We load info, and a script to GPU

send_data()

send_script()

How do I get started?

https://tinyurl.com/pythongpu

Write a shader

  • Create a Python program to interface with a shader
  • Write our own shader in OpenGL Shading Language (GLSL)
  • Send data to the shader (position, color)
  • Shader runs a mini-GLSL program for each pixel on the screen
  • Python Arcade makes interfacing and using these shaders easy

https://tinyurl.com/pythongpu

Shadertoy.com

  • Website that provides a framework for writing shaders
  • The Python Arcade library has support to help run these shaders

https://tinyurl.com/pythongpu

Tutorial Goal:

Draw a Glowing Ball

  • Move the glow to where the mouse is
  • Allow the color to be configured
  •  

Step 1

Open a window

https://tinyurl.com/pythongpu

import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

    def on_draw(self):
        # Clear the screen
        self.clear()

if __name__ == "__main__":
    MyGame()
    arcade.run()

Step 2

Load and render a shader

https://tinyurl.com/pythongpu

import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Load a file and create a shader from it
        shader_path = "circle_1.glsl"
        size = self.get_size()
        self.shadertoy = Shadertoy.create_from_file(size, shader_path)

    def on_draw(self):
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Load a file and create a shader from it
        shader_path = "circle_1.glsl"
        size = self.get_size()
        self.shadertoy = Shadertoy.create_from_file(size, shader_path)

    def on_draw(self):
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Load a file and create a shader from it
        shader_path = "circle_1.glsl"
        size = self.get_size()
        self.shadertoy = Shadertoy.create_from_file(size, shader_path)

    def on_draw(self):
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()

Step 3

Write a shader

If we are close to the origin (0, 0):

  draw white

else

  draw black

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(uv);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

Step 4

Center circle

Adjust for aspect ratio

https://tinyurl.com/pythongpu

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);

    // Default our color to white
    vec3 color = vec3(1.0, 1.0, 1.0);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);

    // Default our color to white
    vec3 color = vec3(1.0, 1.0, 1.0);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);

    // Default our color to white
    vec3 color = vec3(1.0, 1.0, 1.0);

    // Are we are 20% of the screen away from the origin?
    if (distance > 0.2) {
        // Black
        fragColor = vec4(0.0, 0.0, 0.0, 1.0);
    } else {
        // White
        fragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

Step 5

Add a fade effect

https://tinyurl.com/pythongpu

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float strength = 1.0 / distance * scale;

    // Fade our white color
    vec3 color = strength * vec3(1.0, 1.0, 1.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float strength = 1.0 / distance * scale;

    // Fade our white color
    vec3 color = strength * vec3(1.0, 1.0, 1.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float strength = 1.0 / distance * scale;

    // Fade our white color
    vec3 color = strength * vec3(1.0, 1.0, 1.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float strength = 1.0 / distance * scale;

    // Fade our white color
    vec3 color = strength * vec3(1.0, 1.0, 1.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}

Steps 6 and 7

  • Change rate of fade
  • Change color
  • Add tone mapping
  • Full details on-line:
  • https://tinyurl.com/pythongpu

https://tinyurl.com/pythongpu

Step 6

  • Change rate of fade
  • Change color
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
  
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
  
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.5;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * vec3(1.0, 0.5, 0.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    // Position of fragment relative to center of screen
    vec2 rpos = uv - 0.5;
  
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
  
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.5;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * vec3(1.0, 0.5, 0.0);

    // Output to the screen
    fragColor = vec4(color, 1.0);
}

1.5

1.0

0.5

Step 7

  • Add Tonal Mapping
uniform vec2 pos;
uniform vec3 color;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    vec2 npos = pos/iResolution.xy;

    // Position of fragment relative to specified position
    vec2 rpos = npos - uv;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.5;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * color;

    // Tone mapping
    color = 1.0 - exp( -color );

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
1.0 - e^{color}

Step 8: Position the glow

  • Move the glow to where the mouse is
  • Allow the color to be configured
  • This demonstrates sending data from Python to the shader
import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Load a file and create a shader from it
        shader_path = "circle_6.glsl"
        size = self.get_size()
        self.shadertoy = Shadertoy.create_from_file(size, shader_path)

    def on_draw(self):
        # Set uniform data to send to the GLSL shader
        position = self.mouse["x"], self.mouse["y"]
        color = arcade.get_three_float_color(arcade.color.LIGHT_BLUE)
        self.shadertoy.program['pos'] = position
        self.shadertoy.program['color'] = color
        
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Load a file and create a shader from it
        shader_path = "circle_6.glsl"
        size = self.get_size()
        self.shadertoy = Shadertoy.create_from_file(size, shader_path)

    def on_draw(self):
        # Set uniform data to send to the GLSL shader
        position = self.mouse["x"], self.mouse["y"]
        color = arcade.get_three_float_color(arcade.color.LIGHT_BLUE)
        self.shadertoy.program['pos'] = position
        self.shadertoy.program['color'] = color
        
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()
import arcade
from arcade.experimental import Shadertoy

# Derive an application window from Arcade's parent Window class
class MyGame(arcade.Window):

    def __init__(self):
        # Call the parent constructor
        super().__init__(width=1920, height=1080)

        # Load a file and create a shader from it
        shader_path = "circle_6.glsl"
        size = self.get_size()
        self.shadertoy = Shadertoy.create_from_file(size, shader_path)

    def on_draw(self):
        # Set uniform data to send to the GLSL shader
        position = self.mouse["x"], self.mouse["y"]
        color = arcade.get_three_float_color(arcade.color.LIGHT_BLUE)
        self.shadertoy.program['pos'] = position
        self.shadertoy.program['color'] = color
        
        # Run the GLSL code
        self.shadertoy.render()

if __name__ == "__main__":
    MyGame()
    arcade.run()
uniform vec2 pos;
uniform vec3 color;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    vec2 npos = pos/iResolution.xy;

    // Position of fragment relative to specified position
    vec2 rpos = npos - uv;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.1;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * color;

    // Tone mapping
    color = 1.0 - exp( -color );

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
uniform vec2 pos;
uniform vec3 color;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    vec2 npos = pos/iResolution.xy;

    // Position of fragment relative to specified position
    vec2 rpos = npos - uv;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.1;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * color;

    // Tone mapping
    color = 1.0 - exp( -color );

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
uniform vec2 pos;
uniform vec3 color;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    vec2 npos = pos/iResolution.xy;

    // Position of fragment relative to specified position
    vec2 rpos = npos - uv;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.1;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * color;

    // Tone mapping
    color = 1.0 - exp( -color );

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
uniform vec2 pos;
uniform vec3 color;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    vec2 npos = pos/iResolution.xy;

    // Position of fragment relative to specified position
    vec2 rpos = npos - uv;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.1;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * color;

    // Tone mapping
    color = 1.0 - exp( -color );

    // Output to the screen
    fragColor = vec4(color, 1.0);
}
uniform vec2 pos;
uniform vec3 color;

void mainImage(out vec4 fragColor, in vec2 fragCoord) {

    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    vec2 npos = pos/iResolution.xy;

    // Position of mouse relative to fragment position
    vec2 rpos = npos - uv;
    
    // Adjust y by aspect ratio
    rpos.y /= iResolution.x/iResolution.y;

    // How far is the current pixel from the origin (0, 0)
    float distance = length(rpos);
    
    // Use an inverse 1/distance to set the fade
    float scale = 0.02;
    float fade = 1.1;
    float strength = pow(1.0 / distance * scale, fade);

    // Fade our orange color
    vec3 color = strength * color;

    // Tone mapping
    color = 1.0 - exp( -color );

    // Output to the screen
    fragColor = vec4(color, 1.0);
}

Summary

  • In less than 50 lines of Python + GLSL code we've written a Python program that interfaces with a shader.
  • If you programming by experimenting with small incremental changes and quickly seeing visual feedback, this could be for you!

https://tinyurl.com/pythongpu

Additional learning

https://tinyurl.com/pythongpu