From 5c9cd56884016213cb5ef92d384f2487f74720a7 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Thu, 31 Aug 2023 17:02:03 +0900 Subject: [PATCH] Impl. shader for SparkEffect The shader gives the "sparks" a white center. Drawing sparks now generates a flat "circle" (facing the camera) instead of drawing a 3D sphere. --- src/3d_helpers.cc | 36 +++++++++++ src/3d_helpers.h | 8 +++ src/electricity_effect.h | 4 +- src/screen_trunner.cc | 6 +- src/spark_effect.cc | 134 ++++++++++++++++++++++++++++++++++++++- src/spark_effect.h | 13 +++- 6 files changed, 194 insertions(+), 7 deletions(-) diff --git a/src/3d_helpers.cc b/src/3d_helpers.cc index 14d0b56..0108209 100644 --- a/src/3d_helpers.cc +++ b/src/3d_helpers.cc @@ -196,6 +196,42 @@ std::array get_quad_from_start_end(Vector3 start, Vector3 end, return quad; } +std::array get_circle_facing_viewer(Vector3 pos, Vector3 normal, + float radius) { + std::array vertices; + + vertices[0] = pos; + + // Normalize just in case the normal isn't a unit vector. + normal = Vector3Normalize(normal); + + vertices[1] = Vector3Perpendicular(normal) * radius; + vertices[2] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 1.0F / 8.0F) + + pos; + vertices[3] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 2.0F / 8.0F) + + pos; + vertices[4] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 3.0F / 8.0F) + + pos; + vertices[5] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 4.0F / 8.0F) + + pos; + vertices[6] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 5.0F / 8.0F) + + pos; + vertices[7] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 6.0F / 8.0F) + + pos; + vertices[8] = + Vector3RotateByAxisAngle(vertices[1], normal, PI * 2.0F * 7.0F / 8.0F) + + pos; + vertices[1] = vertices[1] + pos; + + return vertices; +} + Vector3 operator+(Vector3 a, Vector3 b) { return Vector3{a.x + b.x, a.y + b.y, a.z + b.z}; } diff --git a/src/3d_helpers.h b/src/3d_helpers.h index 1a6be94..89bcbc5 100644 --- a/src/3d_helpers.h +++ b/src/3d_helpers.h @@ -46,6 +46,14 @@ extern std::array get_quad_from_start_end(Vector3 start, Vector3 normal, float width); +/* + * First vertex is the center of the circle. + * Every consecutive vertex starting at index 1 is a circle edge c-clockwise. + */ +extern std::array get_circle_facing_viewer(Vector3 pos, + Vector3 normal, + float radius); + // Unimplemented as this function isn't really needed and it exposes some // weirdness regarding column-major matrices. // extern Vector4 operator*(const Matrix &m, const Vector4 &v); diff --git a/src/electricity_effect.h b/src/electricity_effect.h index aecc322..4bd816a 100644 --- a/src/electricity_effect.h +++ b/src/electricity_effect.h @@ -27,8 +27,6 @@ class ElectricityEffect { static Shader get_shader(); static void cleanup_shader(); static void update_shader_height(); - static void update_shader_sides(Vector2 a, Vector2 adir, Vector2 b, - Vector2 bdir, float width); private: struct EndPoint { @@ -44,6 +42,8 @@ class ElectricityEffect { float lifetime; float timer; + static void update_shader_sides(Vector2 a, Vector2 adir, Vector2 b, + Vector2 bdir, float width); static void init_shader(); }; diff --git a/src/screen_trunner.cc b/src/screen_trunner.cc index 397ec8c..8767ba6 100644 --- a/src/screen_trunner.cc +++ b/src/screen_trunner.cc @@ -78,6 +78,8 @@ TRunnerScreen::TRunnerScreen(std::weak_ptr stack) // Initialize ElectricityEffect shader. ElectricityEffect::update_shader_height(); + // Initialize SparkEffect shader. + SparkEffect::update_shader_height(); #ifndef NDEBUG std::cout << "Screen finished init.\n"; @@ -85,6 +87,7 @@ TRunnerScreen::TRunnerScreen(std::weak_ptr stack) } TRunnerScreen::~TRunnerScreen() { + SparkEffect::cleanup_shader(); ElectricityEffect::cleanup_shader(); UnloadRenderTexture(fgRenderTexture); @@ -103,6 +106,7 @@ bool TRunnerScreen::update(float dt, bool is_resized) { fgRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); ElectricityEffect::update_shader_height(); + SparkEffect::update_shader_height(); } if (flags.test(1)) { @@ -378,7 +382,7 @@ bool TRunnerScreen::draw(RenderTexture *render_texture) { } for (auto &se : sparkEffects) { - se.draw(); + se.draw(&camera); } // TODO DEBUG diff --git a/src/spark_effect.cc b/src/spark_effect.cc index d60133d..18d1ea3 100644 --- a/src/spark_effect.cc +++ b/src/spark_effect.cc @@ -1,9 +1,34 @@ #include "spark_effect.h" +#include +#include + +#ifndef NDEBUG +#define DEBUG_PRINT_VEC2(v2) \ + do { \ + std::cout << "Loading " << #v2 << " with value " << (v2).x << ", " \ + << (v2).y << std::endl; \ + } while (false); +#define DEBUG_PRINT_FLOAT(f) \ + do { \ + std::cout << "Loading " << #f << " with value " << (f) << std::endl; \ + } while (false); +#else +#define DEBUG_PRINT_VEC2(v2) +#define DEBUG_PRINT_FLOAT(f) +#endif + +// standard library includes +#ifndef NDEBUG +#include +#endif + // local includes #include "3d_helpers.h" #include "ems.h" +std::optional SparkEffect::shader = std::nullopt; + SparkEffect::SparkEffect(int count, float lifetime, Vector3 pos, float pos_xz_variance, float radius, Color color) : sparks(), color(color), lifetime(lifetime), timer(0.0F) { @@ -36,10 +61,115 @@ bool SparkEffect::update(float dt) { return timer > lifetime; } -void SparkEffect::draw() { +void SparkEffect::draw(Camera *camera) { float ratio = timer < lifetime ? (1.0F - timer / lifetime) : 0.0F; + float radius = SPARK_RADIUS * ratio; for (const auto &spark : sparks) { - DrawSphere(spark.pos, SPARK_RADIUS * ratio, color); + auto circle = get_circle_facing_viewer( + spark.pos, camera->position - spark.pos, radius); + + Vector2 screen_pos = GetWorldToScreen(spark.pos, *camera); + Vector2 circle_edge_pos = GetWorldToScreen(circle[1], *camera); + update_shader_uniforms(Vector2Distance(screen_pos, circle_edge_pos), + screen_pos); + BeginShaderMode(get_shader()); + for (decltype(circle.size()) idx = 1; idx < circle.size() - 1; ++idx) { + DrawTriangle3D(circle[0], circle[idx], circle[idx + 1], color); + } + DrawTriangle3D(circle[0], circle[circle.size() - 1], circle[1], color); + EndShaderMode(); } } + +Shader SparkEffect::get_shader() { + if (!shader.has_value()) { + init_shader(); + } + + return shader.value(); +} + +void SparkEffect::cleanup_shader() { + if (shader.has_value()) { + UnloadShader(shader.value()); + shader.reset(); + } +} + +void SparkEffect::update_shader_height() { + if (!shader.has_value()) { + init_shader(); + } + int uniform_loc = GetShaderLocation(get_shader(), "screen_height"); + float height = GetScreenHeight(); + DEBUG_PRINT_FLOAT(height); + SetShaderValue(get_shader(), uniform_loc, &height, SHADER_UNIFORM_FLOAT); +} + +void SparkEffect::update_shader_uniforms(float radius, Vector2 pos) { + if (!shader.has_value()) { + init_shader(); + } + + // radius *= 100.0F; + + int uniform_loc = GetShaderLocation(get_shader(), "spark_radius"); + DEBUG_PRINT_FLOAT(radius); + SetShaderValue(get_shader(), uniform_loc, &radius, SHADER_UNIFORM_FLOAT); + uniform_loc = GetShaderLocation(get_shader(), "spark_pos"); + DEBUG_PRINT_VEC2(pos); + SetShaderValue(get_shader(), uniform_loc, &pos, SHADER_UNIFORM_VEC2); +} + +void SparkEffect::init_shader() { + // Set up spark shader. + // Vertex shader is exactly the same as Raylib's default vertex shader. + shader = LoadShaderFromMemory( + // vertex + "#version 100 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n", + + // fragment + "#version 100 \n" + "precision mediump float; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "uniform float screen_height; \n" + "uniform float spark_radius; \n" + "uniform vec2 spark_pos; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " vec4 color = texelColor*colDiffuse*fragColor; \n" + " if (spark_radius < 0.00001) { \n" + " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); \n" + " return; \n" + " } \n" + " vec2 pos = gl_FragCoord.xy; \n" + " pos.y = screen_height - pos.y; \n" + " float dist = distance(pos, spark_pos); \n" + " float redge = spark_radius / 3.0; \n" + " float inv_redge = spark_radius * 2.0 / 3.0; \n" + " if (dist < redge) { \n" + " gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); \n" + " } else { \n" + " float lerpVal = min(1.0, (dist - redge) / inv_redge); \n" + " gl_FragColor = color * lerpVal + vec4(1.0, 1.0, 1.0, 1.0) * " + "(1.0 - lerpVal); \n" + " } \n" + "} \n"); +} diff --git a/src/spark_effect.h b/src/spark_effect.h index 430cf7a..0614f3f 100644 --- a/src/spark_effect.h +++ b/src/spark_effect.h @@ -2,12 +2,13 @@ #define JUMPARTIFACT_DOT_COM_DEMO_0_SPARK_EFFECT_H_ // standard library includes +#include #include // third party includes #include -constexpr float SPARK_RADIUS = 0.03F; +constexpr float SPARK_RADIUS = 0.04F; constexpr float SPARK_VEL_RATE = 5.0F; constexpr float SPARK_VEL_VARIANCE = 1.0F; constexpr float SPARK_ACC_RATE = 8.0F; @@ -21,17 +22,25 @@ class SparkEffect { bool update(float dt); /// Assumes draw mode is active when called. - void draw(); + void draw(Camera *camera); + + static Shader get_shader(); + static void cleanup_shader(); + static void update_shader_height(); private: struct Spark { Vector3 pos, vel; }; + static std::optional shader; std::vector sparks; Color color; float lifetime; float timer; + + static void update_shader_uniforms(float radius, Vector2 pos); + static void init_shader(); }; #endif