LEARN BY MAKING

Uniforms

Pass CPU-side data into Kraken fragment shaders.

Uniforms are small packets of data you send from Python to a shader. Use them for values that change per frame or per draw call: time, tint, strength, resolution, offsets, thresholds, and similar effect controls.

Shader.set_uniform(binding, data) accepts a buffer-like object containing the exact bytes your shader expects. That can be bytes, bytearray, memoryview, or a small class that exposes bytes through a method or property you control.

Match the Shader Layout

The Python bytes and the shader uniform block must have the same field order, sizes, and padding. For portable fragment uniforms, prefer packing values into 16-byte groups, such as float4 in HLSL or vec4 in GLSL.

This shader expects one uniform buffer at binding 0. The buffer is a single float4: time, strength, scale, and an unused padding value.

cbuffer WindUniform : register(b0, space3) {
    float4 wind; // x = time, y = strength, z = scale, w = unused
};
layout(set = 3, binding = 0) uniform WindUniform {
    vec4 wind; // x = time, y = strength, z = scale, w = unused
};

The matching Python data can be packed with struct:

import struct

def pack_wind_uniform(time: float, strength: float, scale: float) -> bytes:
    return struct.pack("<4f", time, strength, scale, 0.0)
#include <array>

std::array<float, 4> packWindUniform(float time, float strength, float scale) {
    return {time, strength, scale, 0.0f};
}

"<4f" means four little-endian 32-bit floats. That produces 16 bytes, matching the shader’s float4 or vec4.

A Small Data Structure

For anything you update every frame, a tiny data structure keeps the code pleasant without hiding the byte layout.

from dataclasses import dataclass
import struct

@dataclass
class WindUniform:
    time: float = 0.0
    strength: float = 1.0
    scale: float = 0.1

    def to_bytes(self) -> bytes:
        return struct.pack("<4f", self.time, self.strength, self.scale, 0.0)
struct alignas(16) WindUniform {
    float time = 0.0f;
    float strength = 1.0f;
    float scale = 0.1f;
    float padding = 0.0f;
};
static_assert(sizeof(WindUniform) == 16);

Then create the shader with one uniform buffer:

wind_shader = kn.shaders.Shader(
    "assets/shaders/wind.frag",
    uniform_buffer_count=1,
    sampler_count=2,
)
kn::shaders::Shader windShader("assets/shaders/wind.frag", 1, 2);

Update and send the uniform before drawing with the shader:

wind = WindUniform(strength=0.8, scale=0.1)

wind.time = kn.time.get_elapsed()
wind_shader.set_uniform(0, wind.to_bytes())
WindUniform wind = {
    .time = static_cast<float>(kn::time::getElapsed()),
    .strength = 0.8f,
    .scale = 0.1f,
};
windShader.setUniform(0, wind);

The 0 in set_uniform(0, ...) matches register(b0, space3) in HLSL and layout(set = 3, binding = 0) in GLSL.

Multiple Uniform Buffers

If you use more than one uniform buffer, keep the bindings explicit and sequential:

cbuffer WindUniform : register(b0, space3) {
    float4 wind;
};

cbuffer ColorUniform : register(b1, space3) {
    float4 tint;
};
shader = kn.shaders.Shader(
    "assets/shaders/effect.frag",
    uniform_buffer_count=2,
    sampler_count=1,
)

shader.set_uniform(0, wind_uniform.to_bytes())
shader.set_uniform(1, tint_uniform.to_bytes())
kn::shaders::Shader shader("assets/shaders/effect.frag", 2, 1);
shader.setUniform(0, windUniform);
shader.setUniform(1, tintUniform);

The constructor count is not optional bookkeeping. It tells Kraken how many uniform bindings the shader will use, so set it to the highest binding you use plus one.

KEEP EXPLORING Texture Samplers

Find your next step

Start typing to search the manual.