Impl. shader for SparkEffect
All checks were successful
Build and Publish WASM version of demo / Build-And-Deploy (push) Successful in 25s

The shader gives the "sparks" a white center.
Drawing sparks now generates a flat "circle" (facing the camera) instead
of drawing a 3D sphere.
This commit is contained in:
Stephen Seo 2023-08-31 17:02:03 +09:00
parent c14123b3d0
commit 5c9cd56884
6 changed files with 194 additions and 7 deletions

View file

@ -196,6 +196,42 @@ std::array<Vector3, 4> get_quad_from_start_end(Vector3 start, Vector3 end,
return quad; 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) { Vector3 operator+(Vector3 a, Vector3 b) {
return Vector3{a.x + b.x, a.y + b.y, a.z + b.z}; return Vector3{a.x + b.x, a.y + b.y, a.z + b.z};
} }

View file

@ -46,6 +46,14 @@ extern std::array<Vector3, 4> get_quad_from_start_end(Vector3 start,
Vector3 normal, Vector3 normal,
float width); 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 // Unimplemented as this function isn't really needed and it exposes some
// weirdness regarding column-major matrices. // weirdness regarding column-major matrices.
// extern Vector4 operator*(const Matrix &m, const Vector4 &v); // extern Vector4 operator*(const Matrix &m, const Vector4 &v);

View file

@ -27,8 +27,6 @@ class ElectricityEffect {
static Shader get_shader(); static Shader get_shader();
static void cleanup_shader(); static void cleanup_shader();
static void update_shader_height(); static void update_shader_height();
static void update_shader_sides(Vector2 a, Vector2 adir, Vector2 b,
Vector2 bdir, float width);
private: private:
struct EndPoint { struct EndPoint {
@ -44,6 +42,8 @@ class ElectricityEffect {
float lifetime; float lifetime;
float timer; float timer;
static void update_shader_sides(Vector2 a, Vector2 adir, Vector2 b,
Vector2 bdir, float width);
static void init_shader(); static void init_shader();
}; };

View file

@ -78,6 +78,8 @@ TRunnerScreen::TRunnerScreen(std::weak_ptr<ScreenStack> stack)
// Initialize ElectricityEffect shader. // Initialize ElectricityEffect shader.
ElectricityEffect::update_shader_height(); ElectricityEffect::update_shader_height();
// Initialize SparkEffect shader.
SparkEffect::update_shader_height();
#ifndef NDEBUG #ifndef NDEBUG
std::cout << "Screen finished init.\n"; std::cout << "Screen finished init.\n";
@ -85,6 +87,7 @@ TRunnerScreen::TRunnerScreen(std::weak_ptr<ScreenStack> stack)
} }
TRunnerScreen::~TRunnerScreen() { TRunnerScreen::~TRunnerScreen() {
SparkEffect::cleanup_shader();
ElectricityEffect::cleanup_shader(); ElectricityEffect::cleanup_shader();
UnloadRenderTexture(fgRenderTexture); UnloadRenderTexture(fgRenderTexture);
@ -103,6 +106,7 @@ bool TRunnerScreen::update(float dt, bool is_resized) {
fgRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); fgRenderTexture = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
ElectricityEffect::update_shader_height(); ElectricityEffect::update_shader_height();
SparkEffect::update_shader_height();
} }
if (flags.test(1)) { if (flags.test(1)) {
@ -378,7 +382,7 @@ bool TRunnerScreen::draw(RenderTexture *render_texture) {
} }
for (auto &se : sparkEffects) { for (auto &se : sparkEffects) {
se.draw(); se.draw(&camera);
} }
// TODO DEBUG // TODO DEBUG

View file

@ -1,9 +1,34 @@
#include "spark_effect.h" #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 // local includes
#include "3d_helpers.h" #include "3d_helpers.h"
#include "ems.h" #include "ems.h"
std::optional<Shader> SparkEffect::shader = std::nullopt;
SparkEffect::SparkEffect(int count, float lifetime, Vector3 pos, SparkEffect::SparkEffect(int count, float lifetime, Vector3 pos,
float pos_xz_variance, float radius, Color color) float pos_xz_variance, float radius, Color color)
: sparks(), color(color), lifetime(lifetime), timer(0.0F) { : sparks(), color(color), lifetime(lifetime), timer(0.0F) {
@ -36,10 +61,115 @@ bool SparkEffect::update(float dt) {
return timer > lifetime; return timer > lifetime;
} }
void SparkEffect::draw() { void SparkEffect::draw(Camera *camera) {
float ratio = timer < lifetime ? (1.0F - timer / lifetime) : 0.0F; float ratio = timer < lifetime ? (1.0F - timer / lifetime) : 0.0F;
float radius = SPARK_RADIUS * ratio;
for (const auto &spark : sparks) { 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");
}

View file

@ -2,12 +2,13 @@
#define JUMPARTIFACT_DOT_COM_DEMO_0_SPARK_EFFECT_H_ #define JUMPARTIFACT_DOT_COM_DEMO_0_SPARK_EFFECT_H_
// standard library includes // standard library includes
#include <optional>
#include <vector> #include <vector>
// third party includes // third party includes
#include <raylib.h> #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_RATE = 5.0F;
constexpr float SPARK_VEL_VARIANCE = 1.0F; constexpr float SPARK_VEL_VARIANCE = 1.0F;
constexpr float SPARK_ACC_RATE = 8.0F; constexpr float SPARK_ACC_RATE = 8.0F;
@ -21,17 +22,25 @@ class SparkEffect {
bool update(float dt); bool update(float dt);
/// Assumes draw mode is active when called. /// 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: private:
struct Spark { struct Spark {
Vector3 pos, vel; Vector3 pos, vel;
}; };
static std::optional<Shader> shader;
std::vector<Spark> sparks; std::vector<Spark> sparks;
Color color; Color color;
float lifetime; float lifetime;
float timer; float timer;
static void update_shader_uniforms(float radius, Vector2 pos);
static void init_shader();
}; };
#endif #endif