Skip to content

Particles, post effects and the camera

Lesson 9.6

Your mod can paint on the world: sparks, scorch marks on the floor, a red haze over the screen, thick fog, a tilted camera. This lesson shows each tool once. The guides have every option: Particles and Decals, Post-processing, Fog, Sky and Camera, Menus and Themes.

Particles

Every option struct has an _INIT macro with the defaults, so you only set what you need:

#include <clight_easy.h>

static void on_register(void* payload, void* user) {
    (void)payload, (void)user;
    cl_particle_desc d = CL_PARTICLE_DESC_INIT;
    d.texture = CL_STR("my_mod:textures/particle/spark.png");
    d.frames = 4;
    d.columns = 2;
    d.size = 0.12f;
    d.size_end = 0.02f;
    d.gravity = 0.02f;
    d.color = 0xFFFFD080u;          /* ARGB */
    d.emissive = true;
    d.additive = true;
    d.fallback = CL_STR("minecraft:flame");
    cl_render_particle_type(CL_STR("spark"), &d);
}

static cl_result mod_main(void) { return CL_ON(CL_EV_LIFECYCLE_REGISTER, on_register); }

CLIGHT_MOD_ENTRY(mod_main)

Spawning takes a level, a type and a cl_particle_spawn:

cl_particle_spawn s = CL_PARTICLE_SPAWN_INIT;
s.pos = pos;
s.vel = (cl_vec3){0, 0.1, 0};
s.count = 20;
s.spread = (cl_vec3){0.3, 0.3, 0.3};
cl_level_particle(level, CL_STR("my_mod:spark"), &s);

On the server everyone nearby sees them (players without your mod get the fallback); on the client only that player.

Decals

cl_decal_desc d = CL_DECAL_DESC_INIT;
d.texture = CL_STR("my_mod:textures/decal/scorch.png");
d.pos = hit_pos;
d.normal = hit_normal;
d.size = 2.0f;
cl_level_decal(level, &d);

A post effect

The shader is a GLSL file in your assets with one function:

vec4 cl_post(vec2 uv) {
    vec4 c = cl_scene(uv);
    return vec4(mix(c.rgb, vec3(c.r, 0.0, 0.0), amount), 1.0);
}

Register it on the client, then set its uniform whenever you like, for example every frame in CL_EV_RENDER_FRAME:

static cl_post_pass red;
static float amount = 0.0f;

static void on_frame(void* payload, void* user) {
    (void)payload, (void)user;
    cl_post_pass_set_uniform(red, CL_STR("amount"), amount);
}

static void on_client_setup(void* payload, void* user) {
    (void)payload, (void)user;
    cl_post_pass_desc d = CL_POST_PASS_DESC_INIT;
    d.id = CL_STR("red");
    d.shader = CL_STR("my_mod:shaders/post/red.glsl");
    d.uniforms = CL_STR("amount");
    if (cl_render_post_pass(&d, &red) == CL_OK) CL_ON(CL_EV_RENDER_FRAME, on_frame);
}

A shader that does not compile returns CL_E_PARSE; cl_last_error_message has the compiler’s message.

Fog and the camera

static void on_fog(void* payload, void* user) {
    cl_ev_render_fog* e = CL_EVENT(cl_ev_render_fog, payload);
    (void)user;
    e->render_distance_end = 24.0f;
    e->red = 0.2f, e->green = 0.2f, e->blue = 0.25f;
}
/* CL_ON(CL_EV_RENDER_FOG, on_fog); */

cl_camera_effects fx = CL_CAMERA_EFFECTS_INIT;
fx.fov_mult = 0.9f;
fx.roll = 5.0f;
fx.vignette = 0.5f;
cl_camera_set_effects(&fx);

The camera effects follow the player’s accessibility settings; shake needs the player’s Mod Camera Shake option.

Try it

Make water murkier: under water (fog_type == 1), halve environmental_end and tint the fog green.

Show a solution
static void on_fog(void* payload, void* user) {
    cl_ev_render_fog* e = CL_EVENT(cl_ev_render_fog, payload);
    (void)user;
    if (e->fog_type != 1) return;
    e->environmental_end *= 0.5f;
    e->red = 0.1f, e->green = 0.35f, e->blue = 0.15f;
}

Recap: cl_render_particle_type and cl_level_particle for particles, cl_level_decal for marks, cl_render_post_pass plus cl_post_pass_set_uniform for screen effects, CL_EV_RENDER_FOG for fog, cl_camera_set_effects for the camera.