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);
}
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
-
Read more about using OpenGL in Arcade with OpenGL Notes
-
Learn to do a compute shader in Compute Shader Tutorial
https://tinyurl.com/pythongpu