Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace gas sprite animations with shader-based animations. #1554

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
78 changes: 72 additions & 6 deletions Content.Client/Atmos/Overlays/GasTileOverlay.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using System.Numerics;
using Content.Client.Atmos.Components;
using Content.Client.Atmos.EntitySystems;
@@ -7,6 +8,7 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Enums;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Map;
@@ -25,6 +27,9 @@ public sealed class GasTileOverlay : Overlay
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
private readonly ShaderInstance _shader;

private readonly ShaderInstance?[] _gasShaders; //imp edit - the list of shaders to use for each gas
private readonly Color[] _gasColours; //imp edit - the list of colours to use for each gas, as hex codes

// Gas overlays
private readonly float[] _timer;
private readonly float[][] _frameDelays;
@@ -59,19 +64,41 @@ public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IR
_frameCounter = new int[_gasCount];
_frames = new Texture[_gasCount][];

_gasShaders = new ShaderInstance?[_gasCount]; //imp addition
_gasColours = new Color[_gasCount]; //imp addition

for (var i = 0; i < _gasCount; i++)
{
var gasPrototype = protoMan.Index<GasPrototype>(system.VisibleGasId[i].ToString());

SpriteSpecifier overlay;

//imp edit start - assign shaders & gas colours
_gasColours[i] = gasPrototype.Color == string.Empty ? Color.White : Color.FromHex("#" + gasPrototype.Color);
if (!string.IsNullOrEmpty(gasPrototype.Shader))
{
var shader = protoMan.Index<ShaderPrototype>(gasPrototype.Shader).InstanceUnique();
for (var n = 0; n < gasPrototype.NoiseLayers; n++)
{
var layerName = $"noise_{n}"; //slightly hacky way of assigning noise texture samplers but I'll probably be the only person who ever touches this.
shader.SetParameter(layerName, spriteSys.Frame0(new SpriteSpecifier.Texture(new ResPath(gasPrototype.NoiseTexture + "/" + layerName + ".png"))));
}
_gasShaders[i] = shader;
}
else
{
_gasShaders[i] = null;
}
//imp edit end

if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
overlay = new SpriteSpecifier.Rsi(new (gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
overlay = new SpriteSpecifier.Texture(new (gasPrototype.GasOverlayTexture));
else
continue;


switch (overlay)
{
case SpriteSpecifier.Rsi animated:
@@ -110,6 +137,10 @@ protected override void FrameUpdate(FrameEventArgs args)

for (var i = 0; i < _gasCount; i++)
{

if (_gasShaders[i] != null)
continue; //imp edit - if this gas uses a shader, don't bother trying to find the next frame

var delays = _frameDelays[i];
if (delays.Length == 0)
continue;
@@ -156,9 +187,12 @@ protected override void Draw(in OverlayDrawArgs args)
_frameCounter,
_fireFrames,
_fireFrameCounter,
_shader,
_shader, //imp note - this is the fire shader specifically.
overlayQuery,
xformQuery);
xformQuery,
_gasShaders, //imp edit - the list of gas shaders
_gasColours //imp edit - the list of gas colours
);

var mapUid = _mapManager.GetMapEntityId(args.MapId);

@@ -178,9 +212,12 @@ protected override void Draw(in OverlayDrawArgs args)
int[] frameCounter,
Texture[][] fireFrames,
int[] fireFrameCounter,
ShaderInstance shader,
ShaderInstance shader, //imp note - this is the shader for fire specifically.
EntityQuery<GasTileOverlayComponent> overlayQuery,
EntityQuery<TransformComponent> xformQuery) state) =>
EntityQuery<TransformComponent> xformQuery,
ShaderInstance?[] gasShaders, //imp edit - the list of shaders used for each gas. I should be shot for making this a 12-item tuple.
Color[] colours //imp edit - the list of colours for each gas, as hex codes
) state) =>
{
if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
!state.xformQuery.TryGetComponent(uid, out var gridXform))
@@ -219,7 +256,23 @@ protected override void Draw(in OverlayDrawArgs args)
{
var opacity = gas.Opacity[i];
if (opacity > 0)
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
{
//imp edit start
//if no shader, continue as usual
if (state.gasShaders[i] == null)
{
state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
else
{
var colour = state.colours[i].WithAlpha(opacity); //get the colour
state.gasShaders[i]!.SetParameter("colour", colour);
state.drawHandle.UseShader(state.gasShaders[i]); //actually activate the shader
state.drawHandle.DrawRect(new Box2(tilePosition, new Vector2(tilePosition.X + 1f, tilePosition.Y + 1f)), Color.White); //draw the rect
state.drawHandle.UseShader(null); //reset the shader after drawing the rect so that non-shader gases don't get overwritten
}
//imp edit end
}
}
}
}
@@ -281,8 +334,21 @@ private void DrawMapOverlay(
{
var opacity = atmos.OverlayData.Opacity[i];

if (opacity > 0)
//imp edit start
//if no shader, continue as usual
if (_gasShaders[i] == null)
{
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
}
else
{
var colour = _gasColours[i].WithAlpha(opacity); //get the colour
_gasShaders[i]!.SetParameter("colour", colour);
handle.UseShader(_gasShaders[i]); //actually activate the shader
handle.DrawRect(new Box2(tilePosition, new Vector2(tilePosition.X + 1f, tilePosition.Y + 1f)), Color.White); //draw the rect
handle.UseShader(null); //reset the shader after drawing the rect so that non-shader gases don't get overwritten
}
//imp edit end
}
}
}
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ public override void Initialize()
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
var gasPrototype = ProtoMan.Index<GasPrototype>(i.ToString());
if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture) || !string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture) || !string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState) || !string.IsNullOrEmpty(gasPrototype.Shader)) //imp edit - added shader check
visibleGases.Add(i);
}

18 changes: 18 additions & 0 deletions Content.Shared/Atmos/Prototypes/GasPrototype.cs
Original file line number Diff line number Diff line change
@@ -83,5 +83,23 @@ public sealed partial class GasPrototype : IPrototype

[DataField("pricePerMole")]
public float PricePerMole { get; set; } = 0;

/// <summary>
/// imp edit - the id of the shader that this gas should use for rendering.
/// </summary>
[DataField]
public string Shader = string.Empty;

/// <summary>
/// imp edit - the path to the rsi that the noise texture(s) this gases uses are in. must be filled out if shader != empty. the actual textures must be named "noise_0" through "noise_NoiseLayers".
/// </summary>
[DataField]
public string NoiseTexture = string.Empty;

/// <summary>
/// imp edit - the number of layers of noise. only used if shader != null. the gas overlay automatically searches for the various noise texture RSI states using this.
/// </summary>
[DataField]
public int NoiseLayers = 0;
}
}
45 changes: 30 additions & 15 deletions Resources/Prototypes/Atmospherics/gases.yml
Original file line number Diff line number Diff line change
@@ -34,49 +34,61 @@
specificHeat: 200
heatCapacityRatio: 1.7
molarMass: 120
gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: plasma_atmos
color: FF3300
#gasOverlaySprite: /Textures/Effects/atmospherics.rsi # imp edit - commented out
#gasOverlayState: plasma_atmos # imp edit - commented out
color: FF6BE3 # imp edit - made it actually pink so it shows up good in-world. old was FF3300
reagent: Plasma
pricePerMole: 0
shader: PlasmaGasShader # imp edit - use the gas shader
noiseTexture: /Textures/_Impstation/Noise/perlin_noise # imp edit - set the noise tex
noiseLayers: 3 # imp edit - specify how many noise layers there are

- type: gas
id: 4
name: gases-tritium
specificHeat: 10
heatCapacityRatio: 1.3
molarMass: 6
gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: tritium_atmos
color: 13FF4B
#gasOverlaySprite: /Textures/Effects/atmospherics.rsi # imp edit - commented out
#gasOverlayState: tritium_atmos # imp edit - commented out
color: 75E536 # imp edit - he's geen. old was 13FF4B
reagent: Tritium
pricePerMole: 2
shader: TritiumGasShader # imp edit - use the gas shader
noiseTexture: /Textures/_Impstation/Noise/perlin_noise #/Textures/_Impstation/Noise/voronoi_noise_closest.rsi # imp edit - set the noise tex
noiseLayers: 3 # imp edit - specify how many noise layers there are

- type: gas
id: 5
name: gases-water-vapor
specificHeat: 40
heatCapacityRatio: 1.33
molarMass: 18
gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: water_vapor_atmos
color: bffffd
#gasOverlaySprite: /Textures/Effects/atmospherics.rsi # imp edit - commented out
#gasOverlayState: water_vapor_atmos # imp edit - commented out
color: ddfffd # imp note maybe change this away from a light blue? yeah, make it closer to white. was bffffd.
reagent: Water
pricePerMole: 0
shader: BasicGasShader # imp edit - use the gas shader
noiseTexture: /Textures/_Impstation/Noise/perlin_noise # imp edit - set the noise tex
noiseLayers: 3 # imp edit - specify how many noise layers there are

- type: gas
id: 6
name: gases-ammonia
specificHeat: 20
heatCapacityRatio: 1.4
molarMass: 44
gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: miasma_atmos
#gasOverlaySprite: /Textures/Effects/atmospherics.rsi # imp edit - commented out
#gasOverlayState: miasma_atmos # imp edit - commented out
gasMolesVisible: 2
gasVisbilityFactor: 3.5
color: 56941E
color: 662A88 #imp note - make this ourple. was 56941E.
reagent: Ammonia
pricePerMole: 0.15
shader: AmmoniaGasShader # imp edit - use the gas shader
noiseTexture: /Textures/_Impstation/Noise/perlin_noise # imp edit - set the noise tex
noiseLayers: 3 # imp edit - specify how many noise layers there are

- type: gas
id: 7
@@ -94,9 +106,12 @@
specificHeat: 600 # Strongest by far
heatCapacityRatio: 1.33
molarMass: 50
gasOverlaySprite: /Textures/Effects/atmospherics.rsi
gasOverlayState: frezon_atmos
#gasOverlaySprite: /Textures/Effects/atmospherics.rsi # imp edit - commented out
#gasOverlayState: frezon_atmos # imp edit - commented out
gasMolesVisible: 0.6
color: 3a758c
color: 2BCED4 #2BB6AF #imp edit - made it brighter show it shows up better in-world. old value was 3a758c.
reagent: Frezon
pricePerMole: 3
shader: FrezonGasShader # imp edit - use the gas shader
noiseTexture: /Textures/_Impstation/Noise/perlin_noise #imp edit - set the noise tex # todo make this use voronoi noise
noiseLayers: 3 #imp edit - specify how many noise layers there are
26 changes: 26 additions & 0 deletions Resources/Prototypes/_Impstation/Shaders/shaders.yml
Original file line number Diff line number Diff line change
@@ -13,3 +13,29 @@
func: NotEqual
params:
displacementSize: 127

- type: shader
id: BasicGasShader
kind: source
path: "/Textures/_Impstation/Shaders/basic_gas_shader.swsl"

- type: shader
id: PlasmaGasShader
kind: source
path: "/Textures/_Impstation/Shaders/plasma_gas_shader.swsl"

- type: shader
id: TritiumGasShader
kind: source
path: "/Textures/_Impstation/Shaders/tritium_gas_shader.swsl"
#todo give this two colours to fade between

- type: shader
id: FrezonGasShader
kind: source
path: "/Textures/_Impstation/Shaders/frezon_gas_shader.swsl"

- type: shader
id: AmmoniaGasShader
kind: source
path: "/Textures/_Impstation/Shaders/ammonia_gas_shader.swsl"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions Resources/Textures/_Impstation/Shaders/ammonia_gas_shader.swsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
uniform highp vec4 colour;

uniform sampler2D noise_0;
uniform sampler2D noise_1;
uniform sampler2D noise_2;

const highp float tileSize = 32.0;

void fragment() {

highp vec2 rounded = quantiseVecDown(UV, tileSize); //downsample to a 32 * 32 grid for stylistic consistency

COLOR = 0.3 * texture(noise_0, loopVec(rounded + TIME * 0.1));
COLOR += 0.3 * texture(noise_0, loopVec(vec2(rounded.x - TIME * 0.1, rounded.y + 0.2 + TIME * 0.1)));
COLOR += 0.3 * texture(noise_2, loopVec(rounded + vec2(0.2, 0.7)));
COLOR += 0.1 * texture(noise_1, loopVec(vec2(rounded.x + sin(rounded.y + TIME * 0.1), rounded.y + cos(rounded.x + TIME * 0.1))));
COLOR *= colour;
}

highp vec2 quantiseVecDown(highp vec2 v, highp float divisions) {
highp float multiple = 1.0 / divisions;
highp vec2 rem = mod(v, multiple);
return v - rem;
}

//hate that I have to do this because we can't just
highp vec2 loopVec(highp vec2 toLoop) {
return abs(mod(toLoop, 1));;
}
28 changes: 28 additions & 0 deletions Resources/Textures/_Impstation/Shaders/basic_gas_shader.swsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
uniform highp vec4 colour;

uniform sampler2D noise_0;
uniform sampler2D noise_1;
uniform sampler2D noise_2;

const highp float tileSize = 32.0;

void fragment() {

highp vec2 rounded = quantiseVecDown(UV, tileSize); //downsample to a 32 * 32 grid for stylistic consistency

COLOR = 0.4 * texture(noise_0, loopVec(rounded + TIME * 0.1));
COLOR += 0.3 * texture(noise_1, loopVec(rounded - TIME * 0.1));
COLOR += 0.3 * texture(noise_2, loopVec(rounded));
COLOR *= colour;
}

highp vec2 quantiseVecDown(highp vec2 v, highp float divisions) {
highp float multiple = 1.0 / divisions;
highp vec2 rem = mod(v, multiple);
return v - rem;
}

//hate that I have to do this because we can't just
highp vec2 loopVec(highp vec2 toLoop) {
return abs(mod(toLoop, 1));;
}
48 changes: 48 additions & 0 deletions Resources/Textures/_Impstation/Shaders/frezon_gas_shader.swsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
uniform highp vec4 colour;

uniform sampler2D noise_0;
uniform sampler2D noise_1;
uniform sampler2D noise_2;

const highp float tileSize = 32.0;

void fragment() {

highp vec2 rounded = quantiseVecDown(UV, tileSize); //downsample to a 32 * 32 grid for stylistic consistency

//note to self if I want to change things back - was .4, .2, .2, .2
COLOR = 0.25 * texture(noise_0, loopVec(vec2(rounded.x + sin(rounded.y * 4.0 + TIME * 0.1), rounded.y + sin(rounded.x * 4.0 + TIME * 0.1))));
COLOR += 0.25 * texture(noise_0, loopVec(vec2(rounded.x + sin(rounded.y * 4.0 + TIME * 0.1), rounded.y)));
COLOR += 0.25 * texture(noise_0, loopVec(vec2(rounded.x, rounded.y + sin(rounded.x * 4.0 + TIME * 0.1))));
COLOR += 0.25 * texture(noise_2, loopVec(vec2(rounded.x, rounded.y + TIME * 0.2)));
COLOR *= colour;
highp vec3 asHSV = rgb2hsv(COLOR.rgb);
COLOR.rgb = hsv2rgb(asHSV + vec3(0.0, 0.0, asHSV.z * asHSV.z));
}

highp vec2 quantiseVecDown(highp vec2 v, highp float divisions) {
highp float multiple = 1.0 / divisions;
highp vec2 rem = mod(v, multiple);
return v - rem;
}

//hate that I have to do this because we can't just set the loop behaviour on the texture
highp vec2 loopVec(highp vec2 toLoop) {
return abs(mod(toLoop, 1));
}

highp vec3 rgb2hsv(highp vec3 c) {
highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

highp float d = q.x - min(q.w, q.y);
highp float e = 0.00000000010; //because this doesn't support doing 1.0e-10
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

highp vec3 hsv2rgb(highp vec3 c) {
highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
28 changes: 28 additions & 0 deletions Resources/Textures/_Impstation/Shaders/plasma_gas_shader.swsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
uniform highp vec4 colour;

uniform sampler2D noise_0;
uniform sampler2D noise_1;
uniform sampler2D noise_2;

const highp float tileSize = 32.0;

void fragment() {

highp vec2 rounded = quantiseVecDown(UV, tileSize); //downsample to a 32 * 32 grid for stylistic consistency

COLOR = 0.4 * texture(noise_0, loopVec(vec2(rounded.x + sin(rounded.y * 4.0 + TIME * 0.1), rounded.y)));
COLOR += 0.3 * texture(noise_1, loopVec(rounded - TIME * 0.1));
COLOR += 0.3 * texture(noise_2, loopVec(rounded + 0.3)); //tiny offset so it's not identical to other gases
COLOR *= colour;
}

highp vec2 quantiseVecDown(highp vec2 v, highp float divisions) {
highp float multiple = 1.0 / divisions;
highp vec2 rem = mod(v, multiple);
return v - rem;
}

//hate that I have to do this because we can't just set the loop behaviour on the texture
highp vec2 loopVec(highp vec2 toLoop) {
return abs(mod(toLoop, 1));
}
56 changes: 56 additions & 0 deletions Resources/Textures/_Impstation/Shaders/tritium_gas_shader.swsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
uniform highp vec4 colour;

uniform sampler2D noise_0;
uniform sampler2D noise_1;
uniform sampler2D noise_2;

const highp float tileSize = 32.0;

void fragment() {

highp vec2 rounded = quantiseVecDown(UV, tileSize); //downsample to a 32 * 32 grid for stylistic consistency

COLOR = 0.25 * texture(noise_0, loopVec(vec2(rounded.x + 0.2 + TIME * 0.3, rounded.y)));
COLOR += 0.25 * texture(noise_0, loopVec(vec2(rounded.x - 0.5 - TIME * 0.3, rounded.y)));
COLOR += 0.25 * texture(noise_0, loopVec(vec2(rounded.x, rounded.y + 0.1 - TIME * 0.3)));
COLOR += 0.25 * texture(noise_0, loopVec(vec2(rounded.x, rounded.y + 0.7 + TIME * 0.3)));

highp vec3 asHSV = rgb2hsv(COLOR.rgb);
asHSV.y -= sin(TIME * 0.5) * 0.25;
asHSV.z *= 1.0 + (COLOR.a * COLOR.a * COLOR.a);//todo need to figure out a way to make brighter spots much brighter while keeping darker spots dark?
COLOR = vec4(hsv2rgb(asHSV), COLOR.a);
COLOR *= colour;
}

highp vec2 quantiseVecDown(highp vec2 v, highp float divisions) {
highp float multiple = 1.0 / divisions;
highp vec2 rem = mod(v, multiple);
return v - rem;
}

highp float quantiseFloatDown(highp float f, highp float divisions) {
highp float multiple = 1.0 / divisions;
highp float rem = mod(f, multiple);
return f - rem;
}

highp vec3 rgb2hsv(highp vec3 c) {
highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));

highp float d = q.x - min(q.w, q.y);
highp float e = 0.00000000010; //because this doesn't support doing 1.0e-10
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}

highp vec3 hsv2rgb(highp vec3 c) {
highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

//hate that I have to do this because we can't just set the loop behaviour on the texture
highp vec2 loopVec(highp vec2 toLoop) {
return abs(mod(toLoop, 1));
}