]> git.seodisparate.com - jumpartifact.com_demo_0/commitdiff
Impl. shader for SparkEffect
authorStephen Seo <seo.disparate@gmail.com>
Thu, 31 Aug 2023 08:02:03 +0000 (17:02 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Thu, 31 Aug 2023 08:02:36 +0000 (17:02 +0900)
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
src/3d_helpers.h
src/electricity_effect.h
src/screen_trunner.cc
src/spark_effect.cc
src/spark_effect.h

index 14d0b5650f49be985e9accda4a336d25d5cf3c2f..0108209eaaf4d09346f3018bb4ed33a9d107ac6c 100644 (file)
@@ -196,6 +196,42 @@ std::array<Vector3, 4> get_quad_from_start_end(Vector3 start, Vector3 end,
   return quad;
 }
 
+std::array<Vector3, 9> get_circle_facing_viewer(Vector3 pos, Vector3 normal,
+                                                float radius) {
+  std::array<Vector3, 9> 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};
 }
index 1a6be9463c402cfddc8beaf64337cdeda7ea2c0e..89bcbc5218977b6eaa8aff0c7e4e154cd0405b64 100644 (file)
@@ -46,6 +46,14 @@ extern std::array<Vector3, 4> 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<Vector3, 9> 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);
index aecc3222e0661c82c4367e142e078380978fec13..4bd816ab794a49baa7ba8f424b3fbbc2188450f1 100644 (file)
@@ -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();
 };
 
index 397ec8cbbcf3cd03499c74023d76d7d92bdcd9d4..8767ba6cb0744f772a996ce6845b759b6bf002fb 100644 (file)
@@ -78,6 +78,8 @@ TRunnerScreen::TRunnerScreen(std::weak_ptr<ScreenStack> 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<ScreenStack> 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
index d60133dc494b8f7f99581134702417d1e1c200a8..18d1ea3b59a595dcb47d20ebdfdab0e2440ece2a 100644 (file)
@@ -1,9 +1,34 @@
 #include "spark_effect.h"
 
+#include <raylib.h>
+#include <raymath.h>
+
+#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 <iostream>
+#endif
+
 // local includes
 #include "3d_helpers.h"
 #include "ems.h"
 
+std::optional<Shader> 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");
+}
index 430cf7a7afab7d138d0cc68e9b36bb39e40e58fb..0614f3f424d9bdd1c1c076aee3a7f319891475a7 100644 (file)
@@ -2,12 +2,13 @@
 #define JUMPARTIFACT_DOT_COM_DEMO_0_SPARK_EFFECT_H_
 
 // standard library includes
+#include <optional>
 #include <vector>
 
 // third party includes
 #include <raylib.h>
 
-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> shader;
   std::vector<Spark> sparks;
   Color color;
   float lifetime;
   float timer;
+
+  static void update_shader_uniforms(float radius, Vector2 pos);
+  static void init_shader();
 };
 
 #endif