Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 245 additions & 17 deletions glsl/f_oit_particles.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,263 @@

uniform sampler2D sMainTex;

const int PARTICLE_RECONSTRUCTION_LEGACY = 0;
const int PARTICLE_RECONSTRUCTION_CUBIC = 1;
const int PARTICLE_ALPHA_LEGACY = 0;
const int PARTICLE_ALPHA_STORED = 1;
const int PARTICLE_ALPHA_LUMINANCE = 2;
const int PARTICLE_ALPHA_AND_LUMINANCE = 3;
const int PARTICLE_TRAIL_LEGACY = 0;
const int PARTICLE_TRAIL_ANALYTIC_CORE = 1;
const int PARTICLE_DIAGNOSTIC_COMPOSITE = 0;
const int PARTICLE_DIAGNOSTIC_TEXTURE = 1;
const int PARTICLE_DIAGNOSTIC_ALPHA = 2;
const int PARTICLE_DIAGNOSTIC_VERTEX = 3;

in vec2 fragUV1;
flat in int fragInstanceID;

layout(location = 0) out vec4 fragColor1;
layout(location = 1) out vec4 fragColor2;

vec4 cubicWeights(float fraction) {
float t = clamp(fraction, 0.0, 1.0);
float t2 = t * t;
float t3 = t2 * t;
return vec4(
-0.5 * t + t2 - 0.5 * t3,
1.0 - 2.5 * t2 + 1.5 * t3,
0.5 * t + 2.0 * t2 - 1.5 * t3,
-0.5 * t2 + 0.5 * t3);
}

void enhancedAtlasUV(
out vec2 uv,
out vec2 frameMinUV,
out vec2 frameMaxUV,
out ivec2 textureDimensions) {

textureDimensions = max(textureSize(sMainTex, 0), ivec2(1));
ivec2 gridSize = clamp(uGridSize, ivec2(1), textureDimensions);
int frameCount = gridSize.x * gridSize.y;
int frame = clamp(int(uParticles[fragInstanceID].positionFrame.w), 0, frameCount - 1);
ivec2 frameCoord = ivec2(frame % gridSize.x, frame / gridSize.x);

vec2 cellMin = vec2(frameCoord) / vec2(gridSize);
vec2 cellMax = vec2(frameCoord + ivec2(1)) / vec2(gridSize);
vec2 cellCenter = 0.5 * (cellMin + cellMax);
vec2 halfTexel = 0.5 / vec2(textureDimensions);
frameMinUV = min(cellMin + halfTexel, cellCenter);
frameMaxUV = max(cellMax - halfTexel, cellCenter);

vec2 localUV = clamp(fragUV1, vec2(0.0), vec2(1.0));
uv = (vec2(frameCoord) + localUV) / vec2(gridSize);
uv = clamp(uv, frameMinUV, frameMaxUV);
}

vec4 sampleCubicAtlas(
vec2 uv,
vec2 frameMinUV,
vec2 frameMaxUV,
ivec2 textureDimensions) {

vec2 pixel = uv * vec2(textureDimensions) - 0.5;
vec2 basePixel = floor(pixel);
vec2 fraction = fract(pixel);
vec4 weightsX = cubicWeights(fraction.x);
vec4 weightsY = cubicWeights(fraction.y);

vec4 cubicSample = vec4(0.0);
float weightSum = 0.0;
vec4 sample00 = vec4(0.0);
vec4 sample10 = vec4(0.0);
vec4 sample01 = vec4(0.0);
vec4 sample11 = vec4(0.0);
vec4 neighborhoodMin = vec4(1.0);
vec4 neighborhoodMax = vec4(0.0);

for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
vec2 samplePixel = basePixel + vec2(x - 1, y - 1);
vec2 sampleUV = (samplePixel + 0.5) / vec2(textureDimensions);
sampleUV = clamp(sampleUV, frameMinUV, frameMaxUV);
vec4 value = texture(sMainTex, sampleUV);
float weight = weightsX[x] * weightsY[y];
cubicSample += value * weight;
weightSum += weight;
neighborhoodMin = min(neighborhoodMin, value);
neighborhoodMax = max(neighborhoodMax, value);

if (x == 1 && y == 1) {
sample00 = value;
} else if (x == 2 && y == 1) {
sample10 = value;
} else if (x == 1 && y == 2) {
sample01 = value;
} else if (x == 2 && y == 2) {
sample11 = value;
}
}
}

vec4 bilinearSample = mix(
mix(sample00, sample10, fraction.x),
mix(sample01, sample11, fraction.x),
fraction.y);
vec4 reconstructed = cubicSample / max(abs(weightSum), 0.0001);
reconstructed = clamp(reconstructed, neighborhoodMin, neighborhoodMax);
return mix(
bilinearSample,
reconstructed,
clamp(uParticleReconstructionStrength, 0.0, 1.0));
}

void decodeParticleSample(
vec4 sampleValue,
out vec3 sampleColor,
out float sampleAlpha) {

sampleColor = sampleValue.rgb;
float storedAlpha = clamp(sampleValue.a, 0.0, 1.0);
float luminance = clamp(rgbToLuma(sampleColor), 0.0, 1.0);

if (!isFeatureEnabled(FEATURE_PREMULALPHA)) {
sampleAlpha = storedAlpha;
return;
} else if (uParticleAlphaMode == PARTICLE_ALPHA_STORED) {
sampleAlpha = storedAlpha;
} else if (uParticleAlphaMode == PARTICLE_ALPHA_LUMINANCE) {
sampleAlpha = luminance;
sampleColor *= 1.0 / max(0.0001, luminance);
} else if (uParticleAlphaMode == PARTICLE_ALPHA_AND_LUMINANCE) {
sampleAlpha = min(storedAlpha, luminance);
if (luminance <= storedAlpha) {
sampleColor *= 1.0 / max(0.0001, luminance);
}
} else {
sampleAlpha = luminance;
sampleColor *= 1.0 / max(0.0001, luminance);
}

sampleAlpha = pow(
clamp(sampleAlpha, 0.0, 1.0),
max(uParticleAlphaExponent, 0.0001));
}

float analyticParticleCoreEnvelope(vec2 localUV) {
if (uParticleTrailMode != PARTICLE_TRAIL_ANALYTIC_CORE ||
uParticleTrailCoreIntensity <= 0.0) {
return 0.0;
}

vec2 centered = 2.0 * localUV - 1.0;
if (uMotionBlur == 0) {
float radialCore =
1.0 - smoothstep(0.0, 0.65, length(centered));
return clamp(uParticleTrailCoreIntensity, 0.0, 1.0) *
radialCore * radialCore;
}

vec2 inside = max(vec2(1.0) - abs(centered), vec2(0.0));
float crossSection = inside.x * inside.x;
crossSection *= crossSection;
float endTaper = inside.y * inside.y;
return clamp(uParticleTrailCoreIntensity, 0.0, 1.0) * crossSection * endTaper;
}

void main() {
float oneOverGridX = 1.0 / uGridSize.x;
float oneOverGridY = 1.0 / uGridSize.y;
bool enhancedPolicy =
uParticleReconstructionMode != PARTICLE_RECONSTRUCTION_LEGACY ||
uParticleAlphaMode != PARTICLE_ALPHA_LEGACY ||
uParticleTrailMode != PARTICLE_TRAIL_LEGACY ||
uParticleDiagnosticMode != PARTICLE_DIAGNOSTIC_COMPOSITE ||
uParticleCoverageContrast > 0.0001 ||
abs(uParticleAlphaExponent - 1.0) > 0.0001;
if (!enhancedPolicy) {
float oneOverGridX = 1.0 / uGridSize.x;
float oneOverGridY = 1.0 / uGridSize.y;

vec2 uv = fragUV1;
uv.x *= oneOverGridX;
uv.y *= oneOverGridY;

int frame = int(uParticles[fragInstanceID].positionFrame.w);
if (frame > 0) {
uv.y += oneOverGridY * (frame / uGridSize.x);
uv.x += oneOverGridX * (frame % uGridSize.x);
}

vec2 uv = fragUV1;
uv.x *= oneOverGridX;
uv.y *= oneOverGridY;
vec4 mainTexSample = texture(sMainTex, uv);
vec3 mainTexColor = mainTexSample.rgb;
float mainTexAlpha = mainTexSample.a;
if (isFeatureEnabled(FEATURE_PREMULALPHA)) {
mainTexAlpha = rgbToLuma(mainTexSample.rgb);
mainTexColor *= 1.0 / max(0.0001, mainTexAlpha);
}
vec3 objectColor = uParticles[fragInstanceID].color.rgb * mainTexColor;
float objectAlpha = uParticles[fragInstanceID].color.a * mainTexAlpha;
if (objectAlpha == 0.0) {
discard;
}

float w = OIT_weight(gl_FragCoord.z, objectAlpha);
fragColor1 = vec4(objectColor * w, objectAlpha);
fragColor2 = vec4(w);
return;
}

vec2 enhancedUV;
vec2 frameMinUV;
vec2 frameMaxUV;
ivec2 textureDimensions;
enhancedAtlasUV(enhancedUV, frameMinUV, frameMaxUV, textureDimensions);

vec4 mainTexSample = uParticleReconstructionMode == PARTICLE_RECONSTRUCTION_CUBIC
? sampleCubicAtlas(
enhancedUV,
frameMinUV,
frameMaxUV,
textureDimensions)
: texture(sMainTex, enhancedUV);
vec3 mainTexColor;
float mainTexAlpha;
decodeParticleSample(mainTexSample, mainTexColor, mainTexAlpha);

if (uParticleCoverageContrast > 0.0) {
float contrast = clamp(uParticleCoverageContrast, 0.0, 1.0);
float detail = max(
mainTexAlpha,
smoothstep(0.02, 0.28, mainTexAlpha));
mainTexAlpha = mix(mainTexAlpha, detail, contrast);
float luminance = max(rgbToLuma(mainTexColor), 0.0001);
vec3 normalizedColor = clamp(mainTexColor / luminance, vec3(0.0), vec3(4.0));
mainTexColor = mix(
mainTexColor,
normalizedColor,
0.4 * contrast * detail);
}

int frame = int(uParticles[fragInstanceID].positionFrame.w);
if (frame > 0) {
uv.y += oneOverGridY * (frame / uGridSize.x);
uv.x += oneOverGridX * (frame % uGridSize.x);
float trailCore = analyticParticleCoreEnvelope(fragUV1);
if (trailCore > 0.0) {
mainTexAlpha = max(mainTexAlpha, trailCore);
mainTexColor = mix(mainTexColor, vec3(1.0), trailCore);
}

vec4 mainTexSample = texture(sMainTex, uv);
vec3 mainTexColor = mainTexSample.rgb;
float mainTexAlpha = mainTexSample.a;
if (isFeatureEnabled(FEATURE_PREMULALPHA)) {
mainTexAlpha = rgbToLuma(mainTexSample.rgb);
mainTexColor *= 1.0 / max(0.0001, mainTexAlpha);
vec3 objectColor;
float objectAlpha;
if (uParticleDiagnosticMode == PARTICLE_DIAGNOSTIC_TEXTURE) {
objectColor = mainTexColor;
objectAlpha = mainTexAlpha;
} else if (uParticleDiagnosticMode == PARTICLE_DIAGNOSTIC_ALPHA) {
objectColor = vec3(mainTexAlpha);
objectAlpha = 1.0;
} else if (uParticleDiagnosticMode == PARTICLE_DIAGNOSTIC_VERTEX) {
objectColor = uParticles[fragInstanceID].color.rgb;
objectAlpha = uParticles[fragInstanceID].color.a;
} else {
objectColor = uParticles[fragInstanceID].color.rgb * mainTexColor;
objectAlpha = uParticles[fragInstanceID].color.a * mainTexAlpha;
}
vec3 objectColor = uParticles[fragInstanceID].color.rgb * mainTexColor;
float objectAlpha = uParticles[fragInstanceID].color.a * mainTexAlpha;
if (objectAlpha == 0.0) {
discard;
}
Expand Down
9 changes: 9 additions & 0 deletions glsl/u_particles.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,14 @@ struct Particle {

layout(std140) uniform Particles {
ivec2 uGridSize;
int uParticleReconstructionMode;
int uParticleAlphaMode;
int uParticleTrailMode;
int uMotionBlur;
float uParticleReconstructionStrength;
float uParticleAlphaExponent;
float uParticleTrailCoreIntensity;
float uParticleCoverageContrast;
int uParticleDiagnosticMode;
Particle uParticles[MAX_PARTICLES];
};
16 changes: 15 additions & 1 deletion include/reone/game/effect/visual.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,35 @@

#pragma once

#include "reone/resource/types.h"

#include "../effect.h"

namespace reone {

namespace scene {
class ModelSceneNode;
struct ParticleRenderProfile;
}

namespace game {

class ServicesView;
struct VisualEffectDesc;

bool particleRenderProfileForVisualEffect(
resource::GameID gameId,
uint32_t visualEffectId,
const VisualEffectDesc &desc,
scene::ParticleRenderProfile &profile);

class VisualEffect : public Effect {
public:
VisualEffect(int visualEffectId, bool missEffect, ServicesView &services);
VisualEffect(
int visualEffectId,
bool missEffect,
resource::GameID gameId,
ServicesView &services);
~VisualEffect();

void applyTo(Object &object) override;
Expand All @@ -42,6 +55,7 @@ class VisualEffect : public Effect {
private:
int _visualEffectId;
bool _missEffect;
resource::GameID _gameId;
const VisualEffectDesc *_desc {nullptr};
std::optional<glm::vec3> _location;
ServicesView &_services;
Expand Down
14 changes: 14 additions & 0 deletions include/reone/game/visualeffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ struct VisualEffectDesc {
std::shared_ptr<audio::AudioClip> soundImpact;
};

namespace VisualEffectIds {

constexpr uint32_t grenadeFragmentation = 3003;
constexpr uint32_t grenadeStun = 3004;
constexpr uint32_t thermalDetonator = 3005;
constexpr uint32_t grenadePoison = 3006;
constexpr uint32_t grenadeSonic = 3007;
constexpr uint32_t grenadeAdhesive = 3008;
constexpr uint32_t grenadeCryoban = 3009;
constexpr uint32_t grenadePlasma = 3010;
constexpr uint32_t grenadeIon = 3011;

} // namespace VisualEffectIds

class IVisualEffects {
public:
virtual std::optional<const VisualEffectDesc *> get(uint32_t id) const = 0;
Expand Down
1 change: 1 addition & 0 deletions include/reone/graphics/modelnode.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ class ModelNode : boost::noncopyable {
bool quaternionValueAt(ControllerType type, float time, glm::quat &value) const;

KeyframeTrackMap<float> &floatTracks() { return _floatTracks; }
const KeyframeTrackMap<float> &floatTracks() const { return _floatTracks; }
KeyframeTrackMap<glm::vec3> &vectorTracks() { return _vectorTracks; }
KeyframeTrackMap<glm::quat> &quaternionTracks() { return _quaternionTracks; }

Expand Down
9 changes: 9 additions & 0 deletions include/reone/graphics/uniforms.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ struct alignas(16) ParticleUniformsParticle {

struct ParticleUniforms {
glm::ivec2 gridSize {0};
int reconstructionMode {0};
int alphaMode {0};
int trailMode {0};
int motionBlur {0};
float reconstructionStrength {0.0f};
float alphaExponent {1.0f};
float trailCoreIntensity {0.0f};
float coverageContrast {0.0f};
int diagnosticMode {0};
ParticleUniformsParticle particles[kMaxParticles];
};

Expand Down
Loading
Loading