This guide explains how to create custom 2D post-processing filters for the Range Engine using Python and GLSL shaders.
Range Engine (a Blender Game Engine fork) allows you to create custom visual effects using 2D filters. These filters are post-processing effects applied to the rendered image, allowing you to create various visual effects like blur, chromatic aberration, color grading, and more.
This project includes the following folders and purpose-built examples to help you create and test 2D post-processing filters:
-
Template_ShaderFilterscripts/shader.py— example Python component that demonstrates a two-pass 2D filter system (buffer + image). This is the recommended starting point.shadersFilter.range—.rangescene (Range/Blender) with the example already configured. Open this file to see the example running.- Usage: Start here when creating an offscreen shader — you usually don't need to implement everything from scratch: copy and adapt the shader code and the component script.
-
Scripts- Contains
.glsl/.fragshader files and helper scripts (effect examples). These are ready GLSL codes you can attach or paste into the Python component. - Usage: Copy the GLSL you want into the template or into a new component.
- Contains
-
Filters and Shaders Materials- Contains
.rangescene files where each shader is already applied and working. - Usage: Open a
.rangescene to see the shader applied in context — useful for learning the parameters/layer setup and for testing.
- Contains
The Template_ShaderFilter directory contains a complete example showing how to implement a 2D filter system with two render passes.
Template_ShaderFilter/
├── scripts/
│ └── shader.py # Python component for filter management
└── shadersFilter.range # Blender/Range scene file
The Python script (shader.py) demonstrates a two-pass rendering system. Let's break down each component:
from Range import types, logic, render
from collections import OrderedDict- Range.types: Provides base classes for game components
- Range.logic: Game logic utilities
- Range.render: Rendering functions to access viewport dimensions
- OrderedDict: Maintains argument order for the component
The script defines two GLSL fragment shaders as string variables:
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
void main() {
vec2 texcoord = gl_TexCoord[0].st;
vec2 pixelSize = vec2(1.0 / bgl_RenderedTextureWidth, 1.0 / bgl_RenderedTextureHeight);
vec4 color = texture2D(bgl_RenderedTexture, texcoord);
gl_FragColor = color;
}Purpose: This is the first pass shader that:
- Receives the rendered scene as a texture (
bgl_RenderedTexture) - Calculates pixel dimensions for precise sampling
- Outputs the color to an offscreen buffer
This shader currently just passes through the color, but you can modify it to apply effects before storing to the buffer.
uniform sampler2D bgl_RenderedBuffe;
uniform sampler2D bgl_RenderedTexture;
vec2 texcoord = gl_TexCoord[0].st;
void main() {
vec4 bufferTex = texture2D(bgl_RenderedBuffe, gl_TexCoord[0].st);
vec4 renderedTex = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);
gl_FragColor = renderedTex + bufferTex;
}Purpose: This is the second pass shader that:
- Receives both the buffered texture from the first pass and the current rendered frame
- Combines both textures (in this case, by adding them together)
- Outputs the final result to the screen
class filterShader(types.KX_PythonComponent):
args = OrderedDict([
("Layer", 0)
])Component Definition:
- Inherits from
KX_PythonComponentto integrate with Range's component system - Defines arguments that can be configured in the Range/Blender editor
Layerparameter: Specifies which rendering layer to apply the filter to
def start(self, args):
w = render.getWindowWidth()
h = render.getWindowHeight()
getFilter = self.object.scene.filterManager.addFilter
getCustom = logic.RAS_2DFILTER_CUSTOMFILTER
self.buffe = getFilter(self.args["Layer"], getCustom, buffe)
self.image = getFilter(self.args["Layer"] + 1, getCustom, image)
self.buffe.addOffScreen(1,
width = int(w/2), height = int(h/2),
hdr = 0, mipmap = False
)
self.image.setTexture(0, self.buffe.offScreen.colorBindCodes[0], "bgl_RenderedBuffe")Initialization Process:
-
Get viewport dimensions:
render.getWindowWidth()andrender.getWindowHeight() -
Register filters:
- Creates two filter passes using
addFilter(layer, type, shader_code) - First filter on specified layer
- Second filter on layer + 1
- Creates two filter passes using
-
Create offscreen buffer:
addOffScreen()creates a render target for the first pass- Width/height set to half resolution (optimization technique)
hdr=0: Standard dynamic rangemipmap=False: No mipmap generation
-
Link textures:
- Connects the offscreen buffer output to the second shader's input
- The buffer becomes available as
bgl_RenderedBuffein the image shader
def update(self):
passCurrently empty but can be used for per-frame updates to shader uniforms.
Create a fragment shader as a Python string. Example for a simple grayscale effect:
grayscale_shader = """
uniform sampler2D bgl_RenderedTexture;
void main() {
vec2 texcoord = gl_TexCoord[0].st;
vec4 color = texture2D(bgl_RenderedTexture, texcoord);
// Convert to grayscale using luminance formula
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
gl_FragColor = vec4(vec3(gray), color.a);
}
"""from Range import types, logic, render
from collections import OrderedDict
class MyCustomFilter(types.KX_PythonComponent):
args = OrderedDict([
("Layer", 0),
("Intensity", 1.0) # Custom parameter
])
def start(self, args):
getFilter = self.object.scene.filterManager.addFilter
getCustom = logic.RAS_2DFILTER_CUSTOMFILTER
self.filter = getFilter(self.args["Layer"], getCustom, grayscale_shader)
def update(self):
# You can update shader uniforms here if needed
pass- Open your Range/Blender scene
- Select any game object (usually an Empty object)
- Add your Python script as a component
- Configure the Layer parameter to match your scene setup
The template demonstrates a two-pass system:
- First Pass: Renders to an offscreen buffer at lower resolution
- Second Pass: Combines the buffer with the current frame
This is useful for:
- Bloom effects (blur bright areas)
- Motion blur (accumulate previous frames)
- Depth of field
- Temporal effects
You can pass custom values to your shaders:
def update(self):
# Update uniform values each frame
self.filter.setUniform("myCustomValue", some_value)Range provides several built-in uniforms automatically:
bgl_RenderedTexture: The rendered scenebgl_RenderedTextureWidth: Viewport widthbgl_RenderedTextureHeight: Viewport height
Sample multiple texture coordinates around the current pixel.
Sample RGB channels at slightly offset positions.
Use Sobel or other edge detection kernels with pixel neighbors.
Modify color channels using curves or lookup tables.
- Resolution scaling: Use lower resolution buffers for expensive effects
- Minimize texture samples: Each
texture2D()call has a cost - Avoid complex math: Keep shader calculations simple when possible
- Use proper precision:
lowp,mediump,highpfor mobile/performance
- Black screen: Check that your shader has a valid
gl_FragColoroutput - No effect visible: Verify the Layer parameter matches your scene setup
- Performance issues: Reduce offscreen buffer resolution or simplify shader code
- GLSL Tutorial: Learn more about OpenGL Shading Language
- Range Engine Documentation: Official documentation for Range-specific features
- Shader Examples: Check the
Scriptsfolder for more shader examples
The repository includes several example filters:
AnalogTV.glsl: CRT TV effectChromaticAberration.glsl: RGB channel separationSimpleToon.glsl: Cel-shading effectFractalTexturing.frag: Procedural fractal textures
#Some shaders were an adaptation of ShaderToy shaders:
- Analog TV simulation - https://www.shadertoy.com/view/WsSSDc
- Fractal Texturing - https://www.shadertoy.com/view/mds3R4
- Simple Toon - https://www.shadertoy.com/view/ldsfzH
- Lens/Film Chromatic Aberration - https://www.shadertoy.com/view/4dsGDn
These can serve as templates for your own custom effects!