shader: optimize and enhance water effects

This commit is contained in:
Andrei Alexeyev 2023-03-17 02:50:16 +01:00
parent b0e0041015
commit 50e571e39a
No known key found for this signature in database
GPG key ID: 72D26128040B9690
14 changed files with 185 additions and 168 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

View file

@ -3,11 +3,17 @@
#include "pbr.glslh"
VARYING(6) vec2 waterTexCoord;
UNIFORM(60) float time;
UNIFORM(61) float water_depth;
UNIFORM(62) float wave_height;
UNIFORM(63) vec2 wave_offset;
UNIFORM(64) float wave_scale;
UNIFORM(65) mat4 inverse_modelview;
UNIFORM(66) sampler2D water_noisetex;
UNIFORM(67) vec3 water_color;
UNIFORM(68) vec3 wave_highlight_color;
UNIFORM(69) int water_has_bottom_layer;
#endif

View file

@ -1,37 +1,15 @@
// smooth noise that is tweaked to look like water
// Based on https://www.shadertoy.com/view/Xl2XWz
#ifndef I_WATER_H
#define I_WATER_H
float smoothNoise(vec2 p) {
vec2 f = fract(p);
p -= f;
f *= f * (3 - f - f);
// Intel's sin() function breaks down with values over 1e4 (yes, really)
// get rid of some bits here w/o sacrificing the nice effect
vec4 a = vec4(0, 1, 27, 28) + p.x + p.y * 27;
vec4 b = mod(a, tau);
// WARNING: Some versions of the Windows AMD driver choke on temp_mat = mat2(temp_vec)
vec4 temp_vec = fract(sin(b) * 1e5);
mat2 temp_mat = mat2(temp_vec.x, temp_vec.y, temp_vec.z, temp_vec.w);
return dot(temp_mat * vec2(1 - f.y, f.y), vec2(1 - f.x, f.x));
float water_sampleRawNoise(vec2 p) {
return texture(water_noisetex, p * (1.0 / 16.0)).x;
}
float fractalNoise(vec2 p) {
return
smoothNoise(p) * 0.5333 +
smoothNoise(p * 2) * 0.2667 +
smoothNoise(p * 4) * 0.1333 +
smoothNoise(p * 8) * 0.0667;
}
float warpedNoise(vec2 p, float time) {
vec2 m = vec2(0.0, -time);
float x = fractalNoise(p + m);
float y = fractalNoise(p + m.yx + x);
float z = fractalNoise(p - m - x);
return fractalNoise(p + vec2(x, y) + vec2(y, z) + vec2(z, x) + length(vec3(x, y, z)) * 0.1);
float water_sampleWarpedNoise(vec2 p, mat2 s, vec2 m) {
float x = water_sampleRawNoise(p + 2.0 * m);
float y = water_sampleRawNoise(p - 0.5 * m - x);
return water_sampleRawNoise(p - s * vec2(x, y));
}
#endif

View file

@ -25,39 +25,45 @@ const float IOR_WATER = 1.333;
const float AIR_TO_WATER = IOR_AIR / IOR_WATER;
const float F0_WATER = IOR_TO_F0(IOR_WATER);
vec3 topLayer(vec3 normal, vec3 pos, vec3 bottom);
vec3 topLayer(vec3 normal, vec3 pos, vec3 bottom, float highlight);
vec3 bottomLayer(mat3 tbn, vec2 uv, vec3 pos);
void main(void) {
vec2 uv = rot(pi/2) * (texCoord + vec2(-0*time, 0));
vec2 uv = waterTexCoord;
float height = warpedNoise(uv, time);
vec2 dheightduv = wave_height * dFduv(height, uv.xy);
float height = water_sampleWarpedNoise(uv, mat2(2), vec2(-time, 0));
vec2 dheightduv = dFduv(height, uv).yx;
vec2 dheightduv2 = dheightduv * dheightduv;
float wave_highlight = max(dheightduv2.y - 5 * dheightduv2.x, 0);
mat3 tbn = mat3(normalize(tangent), normalize(bitangent), normalize(normal));
vec3 surface_normal = normalize(vec3(-dheightduv, 1));
vec3 surface_normal = normalize(vec3(-wave_height * dheightduv, 1));
vec3 penetration_vec = refract(normalize(pos), tbn * surface_normal, AIR_TO_WATER);
penetration_vec = transpose(tbn) * penetration_vec;
penetration_vec /= abs(penetration_vec.z);
penetration_vec = tbn * penetration_vec;
vec3 bottom;
vec3 bottom_pos = pos + water_depth * penetration_vec;
vec2 bottom_uv = wave_offset +(inverse_modelview * vec4(bottom_pos, 1.0)).xy;
if(water_has_bottom_layer == 0) {
bottom = water_color * 0.05;
} else {
vec3 penetration_vec = refract(normalize(pos), tbn * surface_normal, AIR_TO_WATER);
penetration_vec = transpose(tbn) * penetration_vec;
penetration_vec /= abs(penetration_vec.z);
penetration_vec = tbn * penetration_vec;
vec3 bottom = bottomLayer(tbn, bottom_uv, bottom_pos);
vec3 combined = topLayer(tbn * surface_normal, pos, bottom);
vec3 bottom_pos = pos + water_depth * penetration_vec;
vec2 bottom_uv = wave_offset + (inverse_modelview * vec4(bottom_pos, 1.0)).xy;
bottom = bottomLayer(tbn, bottom_uv, bottom_pos);
}
vec3 combined = topLayer(tbn * surface_normal, pos, bottom, wave_highlight);
PBR_Generic_MaybeTonemap(combined, features_mask);
fragColor = vec4(combined, 1.0);
}
vec3 topLayer(vec3 normal, vec3 pos, vec3 bottom) {
vec3 topLayer(vec3 normal, vec3 pos, vec3 bottom, float h) {
PBRParams p;
p.fragPos = pos;
p.inv_camera_transform = inv_camera_transform;
p.mat.albedo = mix(water_color, water_color + wave_highlight_color, h);
p.mat.roughness = 0.1;
p.mat.metallic = 0;
p.mat.normal = normal;

View file

@ -13,6 +13,6 @@ void main(void) {
tangent = normalize(mv3 * tangentIn.xyz);
bitangent = normalize(mv3 * cross(normalIn.xyz, tangentIn.xyz) * tangentIn.w);
mat2 trans = wave_scale * rot(-3*pi/4);
texCoord = trans * flip_native_to_bottomleft(texCoordRawIn - wave_offset);
texCoord = wave_scale * flip_native_to_bottomleft(texCoordRawIn - wave_offset);
waterTexCoord = texCoord + vec2(-time, 0);
}

View file

@ -3,10 +3,12 @@
#include "lib/render_context.glslh"
#include "interface/ssr.glslh"
#include "lib/util.glslh"
#include "lib/water.glslh"
#include "lib/frag_util.glslh"
UNIFORM(5) float wave_height;
UNIFORM(6) sampler2D water_noisetex;
#include "lib/water.glslh"
const int steps = 10;
@ -53,7 +55,7 @@ vec3 trace_screenspace_reflection(vec3 pos, vec3 n, sampler2D screen_depth, samp
void main(void) {
vec2 uv = flip_native_to_bottomleft(texCoord - wave_offset);
float height = wave_height * warpedNoise(uv * 4, time);
float height = wave_height * water_sampleWarpedNoise(uv * 4, mat2(2), vec2(time, 0));
vec2 dheightduv = dFduv(height, uv);
mat3 tbn = mat3(normalize(tangent), normalize(bitangent), normalize(normal));

View file

@ -1,35 +1,65 @@
#version 330 core
#include "lib/util.glslh"
#include "lib/water.glslh"
#include "lib/frag_util.glslh"
#include "interface/standard.glslh"
UNIFORM(1) float time;
UNIFORM(2) vec4 water_color;
UNIFORM(3) vec2 wave_offset;
UNIFORM(4) sampler2D water_noisetex;
#include "lib/water.glslh"
const vec4 reflection_color = vec4(0.5, 0.8, 0.8, 1.0);
const vec4 wave_color = vec4(0.8, 0.8, 1.0, 1.0);
float vignette(vec2 uv, float threshold, float intensity) {
uv *= 1.0 - uv.yx;
return smoothstep(0, threshold, uv.x * uv.y * intensity);
}
// don't try to understand this, it's all fake and horrible
void main(void) {
vec2 uv_raw = texCoord;
float v = vignette(uv_raw, 0.75, 20);
vec2 uv = flip_native_to_bottomleft(texCoord - wave_offset);
float n = warpedNoise(uv * 4, time);
mat2 m = mat2(0, -1,
1, 0);
float n = water_sampleWarpedNoise(uv * 4, m, vec2(0, -0.25 * time));
vec2 dnduv = dFduv(n, uv)*0.15;
vec2 dnduv = v * dFduv(n, uv) * 0.2;
vec2 dnduv2 = dnduv * dnduv * 0.5;
uv = flip_native_to_bottomleft(texCoord);
uv += 0.25 * dnduv2;
uv = flip_bottomleft_to_native(uv);
vec2 reflect_uv = flip_native_to_bottomleft(texCoord);
reflect_uv.y += 0.03;
reflect_uv -= 0.5;
reflect_uv.x *= 3 / (1 + smoothstep(0.5, 1, gl_FragCoord.z)); // yikes
reflect_uv += 0.5;
reflect_uv.y *= reflect_uv.y;
reflect_uv += 0.03 * sign(dnduv) * sqrt(abs(dnduv)) * vec2(0.1, 1); // oof
reflect_uv = flip_bottomleft_to_native(reflect_uv);
vec4 reflection = texture(tex, uv);
reflection.rgb = mix(reflection.rgb, water_color.rgb, reflection.a * 0.5);
reflection = reflection * reflection_color * 0.5;
vec4 surface = alphaCompose(water_color, reflection);
float reflection_vignette = vignette(clamp(reflect_uv - vec2(0, 0.35), 0, 1), 0.5, 40);
reflection_vignette *= vignette(reflect_uv, 0.2, 20);
fragColor = mix(surface, wave_color, max((dnduv2.y-dnduv2.x*0.25),0) * 0.10);
vec4 surface;
vec2 dUVdx = dFdx(reflect_uv);
vec2 dUVdy = dFdy(reflect_uv);
if(reflection_vignette < 1.0/127.0) {
surface = water_color;
} else {
vec4 reflection = textureGrad(tex, reflect_uv, dUVdx, dUVdy);
reflection.rgb = mix(reflection.rgb, water_color.rgb, reflection.a * 0.5);
reflection = reflection * reflection_color * (0.5 * reflection_vignette);
surface = alphaCompose(water_color, reflection);
}
float w = max((dnduv2.y - dnduv2.x * 0.25), 0) * 0.10;
fragColor = mix(surface, wave_color, w);
}

View file

@ -33,6 +33,7 @@ void stage1_drawsys_init(void) {
cfg.tex_params.wrap.s = TEX_WRAP_CLAMP;
cfg.tex_params.wrap.t = TEX_WRAP_CLAMP;
stage1_draw_data->water_shader = res_shader("stage1_water");
stage1_draw_data->water_fbpair.front = stage_add_background_framebuffer(
"Stage 1 water FB 1", 0.5, 0.5, 1, &cfg);
stage1_draw_data->water_fbpair.back = stage_add_background_framebuffer(
@ -53,6 +54,7 @@ static bool reflect_draw_predicate(EntityInterface *ent) {
switch(ent->draw_layer & ~LAYER_LOW_MASK) {
case LAYER_PLAYER_SLAVE:
case LAYER_PLAYER_FOCUS:
return true;
case LAYER_PLAYER_SHOT:
case LAYER_PLAYER_SHOT_HIGH:
return false;
@ -60,6 +62,7 @@ static bool reflect_draw_predicate(EntityInterface *ent) {
}
switch(ent->type) {
case ENT_TYPE_ID(Player):
case ENT_TYPE_ID(Boss):
case ENT_TYPE_ID(Enemy):
return true;
@ -80,132 +83,98 @@ static bool reflect_draw_predicate(EntityInterface *ent) {
UNREACHABLE;
}
static void stage1_water_draw(vec3 pos) {
Stage1DrawData *draw_data = stage1_get_draw_data();
int pp_quality = config_get_int(CONFIG_POSTPROCESS);
// don't even ask
static void stage1_horizon_draw(vec3 pos) {
r_state_push();
r_mat_mv_push();
r_mat_mv_translate(0, pos[1] + 500, 0);
r_mat_mv_rotate(M_PI, 1, 0, 0);
static const Color water_color = { 0, 0.08, 0.08, 1 };
r_clear(CLEAR_COLOR, &water_color, 1);
r_mat_mv_push();
r_mat_mv_translate(0, -9000, 0);
r_mat_mv_rotate(M_PI/2, 1, 0, 0);
r_mat_mv_scale(3640*1.4, 1456*1.4, 1);
r_mat_mv_translate_v(pos);
r_mat_mv_rotate(1.5 * M_PI, 1, 0, 0);
r_mat_mv_scale(3640 * 1.4, 1456 * 1.4, 1);
r_mat_mv_translate(0, -0.5, 0);
r_shader_standard();
r_uniform_sampler("tex", "stage1/horizon");
r_draw_quad();
r_mat_mv_pop();
r_state_pop();
}
static uint stage1_horizon_pos(Stage3D *s3d, vec3 cam, float maxrange) {
vec3 o;
glm_vec3_copy(cam, o);
o[2] = 0;
o[1] += 9700;
return stage3d_pos_single(s3d, cam, o, maxrange);
}
static const Color water_color = { 0, 0.08, 0.08, 1 };
static void stage1_water_render_reflections(void) {
r_disable(RCAP_DEPTH_TEST);
r_shader("sprite_default");
r_blend(BLEND_PREMUL_ALPHA);
r_mat_proj_push_ortho(VIEWPORT_W, VIEWPORT_H);
ent_draw(reflect_draw_predicate);
r_mat_proj_pop();
}
static void stage1_water_render_waves(float pos) {
r_shader_ptr(stage1_draw_data->water_shader);
r_uniform_float("time", 0.5f * global.frames / (float)FPS);
r_uniform_vec4_rgba("water_color", &water_color);
r_uniform_vec2("wave_offset", 0, pos / 2400.0f);
r_uniform_sampler("water_noisetex", "fractal_noise");
r_mat_mv_push();
r_mat_mv_scale(VIEWPORT_W, VIEWPORT_H, 1);
r_mat_mv_scale(0.01, 0.01, 1);
r_mat_mv_translate(-VIEWPORT_W/2, -VIEWPORT_H/2, -0.1);
draw_framebuffer_tex(stage1_draw_data->water_fbpair.front, VIEWPORT_W, VIEWPORT_H);
r_mat_mv_pop();
}
static void stage1_water_draw(vec3 pos) {
r_state_push();
r_disable(RCAP_CULL_FACE);
r_mat_mv_push();
r_mat_mv_translate(pos[0], pos[1], 0);
r_state_push();
r_enable(RCAP_DEPTH_TEST);
r_depth_func(DEPTH_ALWAYS);
r_shader_standard_notex();
r_mat_mv_push();
r_mat_mv_scale(80000, 80000, 1);
r_mat_mv_scale(10000, 900000, 1);
r_color(&water_color);
r_draw_quad();
r_color4(1, 1, 1, 1);
r_mat_mv_pop();
r_state_pop();
if(pp_quality < 1) {
r_state_pop();
r_mat_mv_pop();
return;
}
r_disable(RCAP_DEPTH_TEST);
r_disable(RCAP_CULL_FACE);
Framebuffer *bg_fb = r_framebuffer_current();
FBPair *fbpair = &draw_data->water_fbpair;
r_framebuffer(fbpair->back);
r_mat_proj_push();
set_ortho(VIEWPORT_W, VIEWPORT_H);
r_mat_mv_push_identity();
float hack = (stage_3d_context.cam.rot.v[0] - 60) / 15.0;
float z = glm_lerp(0.75, 0.8, hack);
float zo = glm_lerp(-0.05, -0.3, hack);
r_mat_mv_translate(VIEWPORT_W * 0.5 * (1 - z), VIEWPORT_H * 0.5 * (1 - z), 0);
r_mat_mv_scale(z, z, 1);
r_clear(CLEAR_ALL, RGBA(0, 0, 0, 0), 1);
r_shader("sprite_default");
ent_draw(reflect_draw_predicate);
r_mat_mv_pop();
fbpair_swap(fbpair);
r_framebuffer(fbpair->back);
ShaderProgram *water_shader = res_shader("stage1_water");
r_uniform_float(r_shader_uniform(water_shader, "time"), 0.5 * global.frames / (float)FPS);
r_uniform_vec4_rgba(r_shader_uniform(water_shader, "water_color"), &water_color);
r_uniform_vec2(r_shader_uniform(water_shader, "wave_offset"), 0, pos[1] / 2400.0);
if(pp_quality > 1) {
r_shader("blur5");
r_uniform_vec2("blur_resolution", VIEWPORT_W, VIEWPORT_H);
r_uniform_vec2("blur_direction", 1, 0);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
fbpair_swap(fbpair);
r_framebuffer(fbpair->back);
r_uniform_vec2("blur_direction", 0, 1);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
fbpair_swap(fbpair);
r_framebuffer(fbpair->back);
switch(config_get_int(CONFIG_POSTPROCESS)) {
case 0: break;
case 1:
// NOTE: we used to render water into a half-res framebuffer here,
// but it's no longer beneficial. In fact it's slightly slower.
default: {
r_mat_mv_translate(0, 0, pos[2]);
stage1_water_render_waves(pos[1]);
break;
}
}
if(pp_quality == 1) {
r_mat_mv_push_identity();
r_shader_ptr(water_shader);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
fbpair_swap(fbpair);
r_mat_mv_pop();
}
r_mat_proj_pop();
r_enable(RCAP_DEPTH_TEST);
r_disable(RCAP_DEPTH_WRITE);
r_framebuffer(bg_fb);
if(pp_quality > 1) {
r_shader_ptr(water_shader);
} else {
r_shader_standard();
}
r_mat_mv_push();
r_mat_mv_translate(0, 70 - 140 * hack, 0);
r_mat_mv_rotate(10 * DEG2RAD, 1, 0, 0);
r_mat_mv_scale(0.85 / (z + zo), -0.85 / (z + zo), 0.85);
r_mat_mv_translate(-VIEWPORT_W/2, VIEWPORT_H/4 * hack, 0);
r_color4(1, 1, 1, 1);
draw_framebuffer_tex(fbpair->front, VIEWPORT_W, VIEWPORT_H);
r_mat_mv_pop();
r_mat_mv_pop();
r_state_pop();
}
static uint stage1_water_pos(Stage3D *s3d, vec3 p, float maxrange) {
return stage3d_pos_single(s3d, p, p, maxrange);
static uint stage1_water_pos(Stage3D *s3d, vec3 cam, float maxrange) {
cmplx sp = VIEWPORT_W*0.5 + VIEWPORT_H*0.6*I;
vec3 ray;
camera3d_unprojected_ray(&s3d->cam, sp, ray);
vec3 o = { 0, 0, 0 };
glm_vec3_scale(ray, 3000, ray);
glm_vec3_add(cam, ray, o);
return stage3d_pos_single(s3d, cam, o, maxrange);
}
static void stage1_smoke_draw(vec3 pos) {
@ -303,7 +272,7 @@ static void stage1_waterplants_draw(vec3 pos) {
}
static uint stage1_waterplants_pos(Stage3D *s3d, vec3 cam, float maxrange) {
vec3 origin = {0,0,-300};
vec3 origin = {0,0,0};
vec3 step = {0,150,0};
return stage3d_pos_ray_farfirst(s3d, cam, origin, step, maxrange * 0.5f, 0.0f);
}
@ -362,7 +331,19 @@ static uint stage1_snow_pos(Stage3D *s3d, vec3 cam, float maxrange) {
}
void stage1_draw(void) {
int ppq = config_get_int(CONFIG_POSTPROCESS);
if(ppq > 0) {
r_state_push();
r_framebuffer(stage1_draw_data->water_fbpair.back);
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
stage1_water_render_reflections();
r_state_pop();
fbpair_swap(&stage1_draw_data->water_fbpair);
}
Stage3DSegment segs[] = {
{ stage1_horizon_draw, stage1_horizon_pos },
{ stage1_water_draw, stage1_water_pos },
{ stage1_waterplants_draw, stage1_waterplants_pos },
{ stage1_snow_draw, stage1_snow_pos },

View file

@ -14,6 +14,7 @@
typedef struct Stage1DrawData {
FBPair water_fbpair;
ShaderProgram *water_shader;
struct {
float near, near_target;

View file

@ -100,6 +100,7 @@ static void stage1_preload(void) {
"stage1/waterplants",
NULL);
preload_resources(RES_TEXTURE, RESF_DEFAULT,
"fractal_noise",
"stage1/horizon",
NULL);
preload_resources(RES_SHADER_PROGRAM, RESF_DEFAULT,

View file

@ -267,6 +267,9 @@ static void stage2_bg_water_draw(vec3 pos) {
r_uniform_float("wave_scale", 16);
r_uniform_vec2("wave_offset", pos[0]/WATER_SIZE, pos[1]/WATER_SIZE);
r_uniform_float("water_depth", 5.0);
r_uniform_sampler("water_noisetex", "fractal_noise");
r_uniform_vec3("water_color", 0.1, 0.2, 0.3);
r_uniform_vec3("wave_highlight_color", 0.1, 0.1, 0.1);
PBREnvironment env = { 0 };
stage2_bg_setup_pbr_env(&stage_3d_context.cam, STAGE2_MAX_LIGHTS, &env);
@ -275,9 +278,13 @@ static void stage2_bg_water_draw(vec3 pos) {
r_mat_mv_translate(pos[0], pos[1], pos[2]);
r_mat_mv_scale(-WATER_SIZE, WATER_SIZE, 1);
mat4 imv;
glm_mat4_inv_fast(*r_mat_mv_current_ptr(), imv);
r_uniform_mat4("inverse_modelview", imv);
bool have_bottom = config_get_int(CONFIG_POSTPROCESS) > 1;
r_uniform_int("water_has_bottom_layer", have_bottom);
if(have_bottom) {
mat4 imv;
glm_mat4_inv_fast(*r_mat_mv_current_ptr(), imv);
r_uniform_mat4("inverse_modelview", imv);
}
r_mat_tex_push();
r_mat_tex_translate(pos[0], pos[1], 0);

View file

@ -78,6 +78,7 @@ static void stage2_preload(void) {
"part/blast_huge_rays",
NULL);
preload_resources(RES_TEXTURE, RESF_DEFAULT,
"fractal_noise",
"ibl_brdf_lut",
"stage2/envmap",
"stage2/spellbg1",

View file

@ -57,7 +57,7 @@ static bool stage4_tonemap(Framebuffer *fb) {
}
static bool should_draw_water(void) {
return stage_3d_context.cam.pos[1] < 0 && config_get_int(CONFIG_POSTPROCESS) > 1;
return stage_3d_context.cam.pos[1] < 0;
}
static bool stage4_water(Framebuffer *fb) {
@ -65,6 +65,8 @@ static bool stage4_water(Framebuffer *fb) {
return false;
}
// TODO: SSR-less version for postprocess < 2
r_clear(CLEAR_COLOR, RGBA(0, 0, 0, 0), 1);
r_mat_proj_push_perspective(stage_3d_context.cam.fovy, stage_3d_context.cam.aspect, stage_3d_context.cam.near, stage_3d_context.cam.far);
r_state_push();
@ -80,6 +82,7 @@ static bool stage4_water(Framebuffer *fb) {
r_uniform_float("time", global.frames * 0.002);
r_uniform_vec2("wave_offset", -global.frames * 0.0005, 0);
r_uniform_float("wave_height", 0.005);
r_uniform_sampler("water_noisetex", "fractal_noise");
r_color4(0.8, 0.9, 1.0, 1);
r_mat_tex_push();
r_mat_tex_scale(3, 3, 3);
@ -109,7 +112,7 @@ static bool stage4_water_composite(Framebuffer *reflections) {
// exempt from the fog effect, which is not 100% correct, but the error is not
// noticeable with our camera and fog settings.
// Perhaps a more delcarative post-processing system would be nice, so that we
// Perhaps a more declarative post-processing system would be nice, so that we
// could specify which passes consume and/or update depth, and have the depth
// buffer copied automatically when needed.

View file

@ -97,6 +97,7 @@ static void stage4_preload(void) {
portrait_preload_face_sprite("kurumi", "normal", RESF_DEFAULT);
preload_resources(RES_BGM, RESF_OPTIONAL, "stage4", "stage4boss", NULL);
preload_resources(RES_TEXTURE, RESF_DEFAULT,
"fractal_noise",
"stage4/kurumibg1",
"stage4/kurumibg2",
NULL);