Skip to content

Instantly share code, notes, and snippets.

@amirrajan
Last active August 10, 2026 21:31
Show Gist options
  • Select an option

  • Save amirrajan/ce0823b016b002f5a3d29852cb89104d to your computer and use it in GitHub Desktop.

Select an option

Save amirrajan/ce0823b016b002f5a3d29852cb89104d to your computer and use it in GitHub Desktop.
hodor
Texture2D scene : register(t0, space2);
SamplerState sampler0 : register(s0, space2);
// Scalar uniforms passed from Ruby via `uniforms: [{ name:, value:, type: }, ...]`.
// Members must match the array order. Fragment uniform buffers live in space3
// (b registers) for SDL_shadercross HLSL.
cbuffer Uniforms : register(b0, space3) {
int tick_count;
float time;
};
// Pseudo-random hash: turns a 2D coordinate into a repeatable "random" value in
// [0, 1). The huge multiplier makes sin() wrap wildly so nearby inputs produce
// very different outputs, giving cheap noise without any lookup texture.
float rand(float2 n) {
return frac(sin(cos(dot(n, float2(12.9898, 12.1414)))) * 83758.5453);
}
// Value noise: smooth, continuous noise made by interpolating the random hash
// values at the four surrounding integer grid corners.
float noise(float2 n) {
const float2 d = float2(0.0, 1.0); // offsets: d.xy=(0,0) d.yx=(1,0) d.yy=(1,1)
float2 b = floor(n); // bottom-left integer cell corner
float2 f = smoothstep(float2(0.0, 0.0), float2(1.0, 1.0), frac(n)); // eased position within cell
// bilinear blend of the four corner random values using the eased weights
return lerp(lerp(rand(b), rand(b + d.yx), f.x),
lerp(rand(b + d.xy), rand(b + d.yy), f.x), f.y);
}
// Fractal Brownian motion: sums several octaves of value noise, each at higher
// frequency and lower amplitude, to build the turbulent, wispy look of flame.
float fbm(float2 n) {
float total = 0.0;
float amplitude = (1280.0 / 720.0) * 0.5; // starting octave weight (scaled by aspect ratio)
float2 vn = n;
for (int i = 0; i < 5; i++) {
total += noise(vn) * amplitude; // accumulate this octave
vn += vn * 1.7; // raise frequency (finer detail) next octave
amplitude *= 0.47; // shrink influence of each finer octave
}
return total;
}
struct Input {
float4 tex_color : COLOR0;
float2 tex_coord : TEXCOORD0;
};
struct Output {
float4 frag_color : SV_Target;
};
Output main(Input input) {
Output output;
// tick_count now comes straight from the `Uniforms` cbuffer above.
// Fire palette: warm/cool color keys later blended together by the noise.
const float3 c1 = float3(0.5, 0.0, 0.1); // deep red
const float3 c2 = float3(0.9, 0.1, 0.0); // orange-red
const float3 c3 = float3(0.2, 0.1, 0.7); // blue (cool tone added in)
const float3 c4 = float3(1.0, 0.9, 0.1); // yellow (hot core)
const float3 c5 = float3(0.1, 0.1, 0.1); // dark end of the shadow term
const float3 c6 = float3(0.9, 0.9, 0.9); // light end of the shadow term
// Animation time in seconds (provided directly as a float uniform).
float i_time = tick_count / 60.0;
float2 i_resolution = float2(1280.0, 720.0);
// Pixel coordinate with the Y axis flipped so the fire rises upward.
float2 frag_coord = float2(input.tex_coord.x * i_resolution.x,
i_resolution.y - input.tex_coord.y * i_resolution.y);
const float2 speed = float2(0.1, 0.9); // horizontal/vertical flow speed of the flames
float shift = 1.327 + sin(i_time * 2.0) / 2.4; // slow flicker/brightness pulse over time
const float alpha = 1.0; // fully opaque output
float dist = 3.5 - sin(i_time * 0.4) / 1.89; // gently "breathes" the zoom of the noise field
float2 uv = frag_coord.xy / i_resolution.xy; // normalized 0..1 coords (kept for reference)
// Sampling position into the noise field; dividing by resolution.xx keeps the
// aspect square so the flames aren't stretched horizontally.
float2 p = frag_coord.xy * dist / i_resolution.xx;
// Two layers of sine warping bend the coordinates so flames curl and sway.
p += sin(p.yx * 4.0 + float2(0.2, -0.3) * i_time) * 0.04;
p += sin(p.yx * 8.0 + float2(0.6, 0.1) * i_time) * 0.01;
p.x -= i_time / 4.0; // scroll sideways so the fire drifts (larger divisor = slower drift)
// Several fbm samples at different speeds/offsets/scales; each is a moving
// layer of turbulence. The added constants (-6, -4, +2) bias the layers so
// they combine into a plausible flame density field.
float q = fbm(p - i_time * 0.3 + 1.0 * sin(i_time + 0.5) / 2.0);
float qb = fbm(p - i_time * 0.4 + 0.1 * cos(i_time) / 2.0);
float q2 = fbm(p - i_time * 0.44 - 5.0 * cos(i_time) / 2.0) - 6.0;
float q3 = fbm(p - i_time * 0.9 - 10.0 * cos(i_time) / 15.0) - 4.0;
float q4 = fbm(p - i_time * 1.4 - 20.0 * sin(i_time) / 14.0) + 2.0;
// Weighted blend of the layers into one turbulence value.
q = (q + qb - 0.4 * q2 - 2.0 * q3 + 0.6 * q4) / 3.8;
// Feed the turbulence back in to distort the field again (domain warping),
// producing the characteristic licking, flowing flame shapes.
float2 r = float2(fbm(p + q / 2.0 + i_time * speed.x - p.x - p.y),
fbm(p + q - i_time * speed.y));
// Final flame color: a cool blue whose brightness falls off sharply (pow 4)
// away from the core. r.y drives the intensity and max(0, p.y) concentrates
// the glow toward the base of the fire.
float3 color = float3(0.05, 0.2, 1.0) / (pow((r.y + r.y) * max(0.0, p.y) + 0.1, 4.0));
// Reinhard-style tone map: compress bright values into [0,1] so hot spots
// don't simply clip to solid white.
color = color / (1.0 + max(float3(0.0, 0.0, 0.0), color));
output.frag_color = float4(color.x, color.y, color.z, alpha);
return output;
}
def tick args
args.outputs.shader = {
path: "shaders/effect.frag.hlsl",
uniforms: {
tick_count: { value: Kernel.tick_count, type: :int },
time: { value: Kernel.tick_count.fdiv(60), type: :float },
}
}
end
- [Game] * INFO - executing ./.dragonruby/shadercross/windows-amd64/bin/shadercross.exe C:\dr-shaders\dragonruby-windows-amd64\/mygame/shaders/effect.frag.hlsl -d DXIL -o C:\dr-shaders\dragonruby-windows-amd64\/mygame/shaders/effect.frag.dxil to transpile shader (57)
- [Game]
- [Render] shader compile: dxil='shaders/effect.frag.dxil' msl='shaders/effect.frag.msl' spirv='shaders/effect.frag.spv' num_samplers=2 num_uniform_buffers=0
- [Render] shader blobs loaded: dxil=3956 bytes, msl=730 bytes, spirv=1320 bytes
- [Render] shader compile: chose format DXIL (device supports DXBC DXIL )
- [Render] shader compile succeeded (format=DXIL )
- [Render] created top level gpu render state (num_sampler_bindings=1, shader num_samplers=2)
! [SDL] Could not create graphics pipeline state! Error Code: The parameter is incorrect. (0x80070057)
! [SDL] Could not create graphics pipeline state! Error Code: The parameter is incorrect. (0x80070057)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment