From ecec6d23de95423b675bc2960ccd4c3950bb4f5f Mon Sep 17 00:00:00 2001
From: Silas Bartha
Date: Sat, 3 May 2025 02:16:02 -0400
Subject: Ported blog post
---
content/blog/blacklight_shader/blacklight.png | Bin 846587 -> 0 bytes
content/blog/blacklight_shader/index.md | 171 -------------------
content/projects/nix_aurea/index.md | 11 --
public/blog/blacklight_shader/blacklight.png | Bin 0 -> 846587 bytes
public/blog/blacklight_shader/index.html | 227 ++++++++++++++++++++++++++
public/index.html | 5 +-
public/projects/games/NIX_AVREA/index.html | 26 +++
7 files changed, 255 insertions(+), 185 deletions(-)
delete mode 100644 content/blog/blacklight_shader/blacklight.png
delete mode 100644 content/blog/blacklight_shader/index.md
delete mode 100644 content/projects/nix_aurea/index.md
create mode 100644 public/blog/blacklight_shader/blacklight.png
create mode 100644 public/blog/blacklight_shader/index.html
create mode 100644 public/projects/games/NIX_AVREA/index.html
diff --git a/content/blog/blacklight_shader/blacklight.png b/content/blog/blacklight_shader/blacklight.png
deleted file mode 100644
index 2c5caf1..0000000
Binary files a/content/blog/blacklight_shader/blacklight.png and /dev/null differ
diff --git a/content/blog/blacklight_shader/index.md b/content/blog/blacklight_shader/index.md
deleted file mode 100644
index c5e4b27..0000000
--- a/content/blog/blacklight_shader/index.md
+++ /dev/null
@@ -1,171 +0,0 @@
-+++
-title = "creating a blacklight shader"
-date = 2024-11-29
-[taxonomies]
-projects = ["bevy_blacklight_material"]
-+++
-
-today i wanted to take a bit of time to write about a shader i implemented for my in-progress game project (more on that soon™)
-
-i wanted to create a "blacklight" effect, where specific lights could reveal part of the base texture. this shader works with **spot lights** only, but could be extended to work with point lights
-
-;
-
-i wrote this shader in wgsl for a [bevy engine](https://bevyengine.org) project, but it should translate easily to other shading languages
-
-the finished shader can be found as part of [this repo](https://github.com/soaosdev/bevy_blacklight_material)
-## shader inputs
-
-for this shader, i wanted the following features:
-- the number of lights should be dynamic
-- the revealed portion of the object should match the area illuminated by each light
- - the falloff of the light over distance should match the fading of the object
-
-for this to work i need the following information about each light:
-- position (world space)
-- direction (world space)
-- range
-- inner and outer angle
- - these will control the falloff of the light at its edges
- - outer angle should be less than pi/2 radians
- - inner angle should be less than the outer angle
-
-i also need some info from the vertex shader:
-- position (**world space!**)
-- uv
-
-bevy's default pbr vertex shader provides this information, but as long as you can get this info into your fragment shader you should be good to go
-
-lastly i'll take a base color texture and a sampler
-
-with all of that, i can start off the shader by setting up the inputs and fragment entry point:
-
-```wgsl
-#import bevy_pbr::forward_io::VertexOutput;
-
-struct BlackLight {
- position: vec3,
- direction: vec3,
- range: f32,
- inner_angle: f32,
- outer_angle: f32,
-}
-
-@group(2) @binding(0) var lights: array;
-@group(2) @binding(1) var base_texture: texture_2d;
-@group(2) @binding(2) var base_sampler: sampler;
-
-@fragment
-fn fragment(
- in: VertexOutput,
-) -> @location(0) vec4 {
-}
-```
-(bevy uses group 2 for custom shader bindings)
-
-since the number of lights is dynamic, i use a [storage buffer](https://google.github.io/tour-of-wgsl/types/arrays/runtime-sized-arrays/) to store that information
-
-## shader calculations
-
-the first thing we'll need to know is how close to looking at the fragment the light source is
-
-we can get this information using some interesting math:
-
-```wgsl
-let light = lights[0];
-let light_to_fragment_direction = normalize(in.world_position.xyz - light.position);
-let light_to_fragment_angle = acos(dot(light.direction, light_to_fragment_direction));
-```
-
-the first step of this is taking the dot product of light direction and the direction from the light to the fragment
-
-since both direction vectors are normalized, the dot product will be between -1.0 and 1.0
-
-the dot product of two unit vectors is the cosine of the angle between them ([proof here](https://math.libretexts.org/Bookshelves/Calculus/Calculus_(OpenStax)/12%3A_Vectors_in_Space/12.03%3A_The_Dot_Product#Evaluating_a_Dot_Product))
-
-therefore, we take the arccosine of that dot product to get the angle between the light and the fragment
-
-once we have this angle we can plug it in to a falloff based on the angle properties of the light:
-
-```wgsl
-let angle_inner_factor = light.inner_angle/light.outer_angle;
-let angle_factor = linear_falloff_radius(light_to_fragment_angle / light.outer_angle, angle_inner_factor)));
-```
-```wgsl
-fn linear_falloff_radius(factor: f32, radius: f32) -> f32 {
- if factor < radius {
- return 1.0;
- } else {
- return 1.0 - (factor - radius) / (1.0 - radius);
- }
-}
-```
-next, we need to make sure the effect falls off properly over distance
-
-we can do this by getting the distance from the light to the fragment and normalizing it with the range of the light before plugging that into an inverse square falloff
-
-we'll use squared distance to avoid expensive and unnecessary square root operations:
-
-```wgsl
-let light_distance_squared = distance_squared(in.world_position.xyz, light.position);
-let distance_factor = inverse_falloff_radius(saturate(light_distance_squared / (light.range * light.range)), 0.5);
-```
-```wgsl
-fn distance_squared(a: vec3f, b: vec3f) -> f32 {
- let vec = a - b;
- return dot(vec, vec);
-}
-
-fn inverse_falloff(factor: f32) -> f32 {
- return pow(1.0 - factor, 2.0);
-}
-
-fn inverse_falloff_radius(factor: f32, radius: f32) -> f32 {
- if factor < radius {
- return 1.0;
- } else {
- return inverse_falloff((factor - radius) / (1.0 - radius));
- }
-}
-```
-
-now we'll have a float multiplier between 0.0 and 1.0 for our angle and distance to the light
-
-we can get the resulting color by multiplying these with the base color texture:
-
-```wgsl
-let base_color = textureSample(base_texture, base_sampler, in.uv);
-let final_color = base_color * angle_factor * distance_factor;
-```
-
-this works for one light, but we need to refactor it to loop over all the provided blacklights:
-```wgsl
-@fragment
-fn fragment(
- in: VertexOutput,
-) -> @location(0) vec4 {
- let base_color = textureSample(base_texture, base_sampler, in.uv);
- var final_color = vec4f(0.0, 0.0, 0.0, 0.0);
- for (var i = u32(0); i < arrayLength(&lights); i = i+1) {
- let light = lights[i];
-
- let light_to_fragment_direction = normalize(in.world_position.xyz - light.position);
- let light_to_fragment_angle = acos(dot(light.direction, light_to_fragment_direction));
- let angle_inner_factor = light.inner_angle / light.outer_angle;
- let angle_factor = linear_falloff_radius(light_to_fragment_angle / light.outer_angle, angle_inner_factor);
-
- let light_distance_squared = distance_squared(in.world_position.xyz, light.position);
- let distance_factor = inverse_falloff_radius(saturate(light_distance_squared / (light.range * light.range)), 0.5);
-
- final_color = saturate(final_color + base_color * angle_factor * distance_factor);
- }
- return final_color;
-}
-```
-
-and with that, the shader is pretty much complete
-
-you can view the full completed shader code [here](https://github.com/soaosdev/bevy_blacklight_material/blob/master/assets/shaders/blacklight_material.wgsl)
-
-have fun!
-
diff --git a/content/projects/nix_aurea/index.md b/content/projects/nix_aurea/index.md
deleted file mode 100644
index 2c68a6d..0000000
--- a/content/projects/nix_aurea/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
-+++
-title = "nix_aurea"
-[taxonomies]
-categories = ["game"]
-languages = ["rust"]
-[extra]
-wip = true
-very_cool = true
-+++
-
-this page is under construction ;)
diff --git a/public/blog/blacklight_shader/blacklight.png b/public/blog/blacklight_shader/blacklight.png
new file mode 100644
index 0000000..2c5caf1
Binary files /dev/null and b/public/blog/blacklight_shader/blacklight.png differ
diff --git a/public/blog/blacklight_shader/index.html b/public/blog/blacklight_shader/index.html
new file mode 100644
index 0000000..db4b54e
--- /dev/null
+++ b/public/blog/blacklight_shader/index.html
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+ Creating a Blacklight Shader - soaos
+
+
+
+ Go Home
+ Go Back
+
Creating a Blacklight Shader
+
+ NOTE: THIS POST WAS TRANSFERRED FROM MARKDOWN BY HAND SO I MIGHT HAVE MISSED SOME STUFF SORRY
+
+
today i wanted to take a bit of time to write about a shader i implemented for my in-progress game project (more
+ on that soon™)
+
i wanted to create a "blacklight" effect, where specific lights could reveal part of the base texture. this
+ shader works with spot lights only, but could be extended to work with point lights
+
+
+ Example of shader running, showing hidden writing on a wall.
+
+
+
i wrote this shader in wgsl for a bevy engine project, but
+ it should translate easily to other shading languages
+
+
the finished shader can be found as part of this repo
+
+
shader inputs
+
+
+ for this shader, i wanted the following features:
+
+
+ the number of lights should be dynamic
+
+
+ the revealed portion of the object should match the area illuminated by each light
+
+
+ the falloff of the light over distance should match the fading of the object
+
+
+
+ for this to work i need the following information about each light:
+
+
+ position (world space)
+
+
+ direction (world space)
+
+
+ range
+
+
+ inner and outer angle
+
+
+ these will control the falloff of the light at its edges
+
+
+ outer angle should be less than pi/2 radians
+
+
+ inner angle should be less than the outer angle
+
+
+
+ i also need some info from the vertex shader:
+
+
+ position (world space!)
+
+
+ uv
+
+
+
+
bevy's default pbr vertex shader provides this information, but as long as you can get this info into your
+ fragment
+ shader you should be good to go
+
+
lastly i'll take a base color texture and a sampler
+
+
+ with all of that, i can start off the shader by setting up the inputs and fragment entry point:
+
+
+ next, we need to make sure the effect falls off properly over distance we can do this by getting the distance
+ from the light to
+ the fragment and normalizing it with the range of the light before plugging that into an inverse square falloff
+ we'll use squared distance to avoid expensive and unnecessary square root operations:
+
+ let light_distance_squared=distance_squared(in.world_position.xyz, light.position);
+ let distance_factor=inverse_falloff_radius(saturate(light_distance_squared / (light.range * light.range)), 0.5);
+
+ now we'll have a float multiplier between 0.0 and 1.0 for our angle and distance to the light we can get the
+ resulting color by multiplying these with the base color texture:
+
+ let base_color = textureSample(base_texture, base_sampler, in.uv);
+ let final_color=base_color * angle_factor * distance_factor;
+
+ this works for one light, but we need to refactor it to loop over all the provided blacklights:
+
+
+ @fragment fn fragment( in: VertexOutput ) -> @location(0) vec4<f32> {
+ let base_color = textureSample(base_texture, base_sampler, in.uv);
+ var final_color = vec4f(0.0, 0.0, 0.0, 0.0);
+ for (var i = u32(0); i < arrayLength(&lights); i = i+1) {
+ let light=lights[i];
+ let light_to_fragment_direction = normalize(in.world_position.xyz - light.position);
+ let light_to_fragment_angle = acos(dot(light.direction, light_to_fragment_direction));
+ let angle_inner_factor = light.inner_angle / light.outer_angle;
+ let angle_factor = linear_falloff_radius(light_to_fragment_angle / light.outer_angle, angle_inner_factor);
+ let light_distance_squared = distance_squared(in.world_position.xyz, light.position);
+ let distance_factor = inverse_falloff_radius(saturate(light_distance_squared / (light.range * light.range)), 0.5);
+ final_color = saturate(final_color + base_color * angle_factor * distance_factor);
+ }
+ return final_color;
+ }
+
+ and with that, the shader is pretty much complete you can view the full completed shader code here
+
+
have fun!
+
+
+
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index c4224ab..725c752 100644
--- a/public/index.html
+++ b/public/index.html
@@ -29,14 +29,13 @@
- Blog (I wanna start posting more to my mastodon, might bring this back anyway, POSSE right?)
+ Blog
Things I Like
diff --git a/public/projects/games/NIX_AVREA/index.html b/public/projects/games/NIX_AVREA/index.html
new file mode 100644
index 0000000..47c914b
--- /dev/null
+++ b/public/projects/games/NIX_AVREA/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ NIX AVREA
+
+
+
+ Go Home
+ Go Back
+
NIX AVREA
+
This is a project I've been working on since April 2024. It's probably the longest-running personal project I've ever done and has been a monumental undertaking so far.
+
I'm unsure how much I want to reveal about this project while I'm developing it, I want the experience to be as novel as possible once it's out. I think I'll probably stick to posting about it here on my site and the occasional YouTube video until it's closer to ready.
+
About the Project
+
NIX AVREA is the codename for my first game project. The game is highly experimental and features mechanics that (as far as I know) have never been attempted. The game is centered around dynamic content, using steganographic techniques to embed binary payloads inside of asset files in order to construct the game world from a directory on the player's filesystem.
+
There is a ton of stuff that's already implemented for this project and I'll gradually add more to the following directories explaining in-depth some of the components: