Skip to content

Commit

Permalink
Rework of Starfield alpha blending and testing (TODO: other games)
Browse files Browse the repository at this point in the history
  • Loading branch information
fo76utils committed Aug 18, 2024
1 parent 1bf38b7 commit dc737e6
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 114 deletions.
73 changes: 31 additions & 42 deletions res/shaders/stf_default.frag
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ struct EmissiveSettingsComponent {
float minOffsetEmittance;
};

struct TerrainTintSettingsComponent {
bool isEnabled;
float terrainBlendStrength;
float terrainBlendGradientFactor;
};

struct DecalSettingsComponent {
bool isDecal;
float materialOverallAlpha;
Expand Down Expand Up @@ -189,16 +183,6 @@ struct TranslucencySettingsComponent {
int transmittanceSourceLayer;
};

struct TerrainSettingsComponent {
bool isEnabled;
int textureMappingType;
float rotationAngle;
float blendSoftness;
float tilingDistance;
float maxDisplacement;
float displacementMidpoint;
};

struct DetailBlenderSettings {
bool detailBlendMaskSupported;
int maskTexture;
Expand All @@ -222,7 +206,6 @@ struct LayeredMaterial {
OpacityComponent opacity;
AlphaSettingsComponent alphaSettings;
TranslucencySettingsComponent translucencySettings;
TerrainSettingsComponent terrainSettings;
DetailBlenderSettings detailBlender;
};

Expand All @@ -239,6 +222,8 @@ uniform bool isWireframe;
uniform bool isSkinned;
uniform mat4 worldMatrix;
uniform vec4 parallaxOcclusionSettings; // min. steps, max. steps, height scale, height offset
// bit 0: alpha testing, bit 1: alpha blending
uniform int alphaFlags;

uniform LayeredMaterial lm;

Expand Down Expand Up @@ -592,23 +577,6 @@ void main()
}
}

normal = normalize( btnMatrix_norm * normal );
if ( !gl_FrontFacing )
normal *= -1.0;

vec3 L = normalize(LightDir);
vec3 V = ViewDir_norm;
vec3 R = reflect(-V, normal);

float NdotL = dot(normal, L);
float NdotL0 = max(NdotL, 0.0);
float LdotR = dot(L, R);
float NdotV = abs(dot(normal, V));
float LdotV = dot(L, V);

vec3 reflectedWS = vec3(reflMatrix * (gl_ModelViewMatrixInverse * vec4(R, 0.0)));
vec3 normalWS = vec3(reflMatrix * (gl_ModelViewMatrixInverse * vec4(normal, 0.0)));

if ( lm.isEffect ) {
if ( lm.effectSettings.vertexColorBlend )
baseMap *= C;
Expand All @@ -623,16 +591,33 @@ void main()
if ( lm.decalSettings.isDecal )
alpha = lm.decalSettings.materialOverallAlpha;

vec4 color;
vec3 albedo = baseMap.rgb;

// emissive intensity
if ( lm.emissiveSettings.isEnabled ) {
emissive *= emissiveIntensity( lm.emissiveSettings.adaptiveEmittance, lm.emissiveSettings.enableAdaptiveLimits, vec4(lm.emissiveSettings.luminousEmittance, lm.emissiveSettings.exposureOffset, lm.emissiveSettings.maxOffsetEmittance, lm.emissiveSettings.minOffsetEmittance) );
} else if ( lm.layeredEmissivity.isEnabled ) {
emissive *= emissiveIntensity( lm.layeredEmissivity.adaptiveEmittance, lm.layeredEmissivity.enableAdaptiveLimits, vec4(lm.layeredEmissivity.luminousEmittance, lm.layeredEmissivity.exposureOffset, lm.layeredEmissivity.maxOffsetEmittance, lm.layeredEmissivity.minOffsetEmittance) );
vec4 color = vec4(1.0);
if ( alphaFlags != 0 ) {
alpha = alpha * baseMap.a;
if ( ( alphaFlags & 1 ) != 0 && !( alpha > ( !lm.isEffect ? lm.alphaSettings.alphaTestThreshold : lm.effectSettings.alphaTestThreshold ) ) )
discard;
if ( ( alphaFlags & 2 ) != 0 )
color.a = alpha;
}

normal = normalize( btnMatrix_norm * normal );
if ( !gl_FrontFacing )
normal *= -1.0;

vec3 L = normalize(LightDir);
vec3 V = ViewDir_norm;
vec3 R = reflect(-V, normal);

float NdotL = dot(normal, L);
float NdotL0 = max(NdotL, 0.0);
float LdotR = dot(L, R);
float NdotV = abs(dot(normal, V));
float LdotV = dot(L, V);

vec3 reflectedWS = vec3(reflMatrix * (gl_ModelViewMatrixInverse * vec4(R, 0.0)));
vec3 normalWS = vec3(reflMatrix * (gl_ModelViewMatrixInverse * vec4(normal, 0.0)));

vec3 f0 = mix(vec3(0.04), albedo, pbrMap.g);
albedo = albedo * (1.0 - pbrMap.g);

Expand Down Expand Up @@ -685,6 +670,11 @@ void main()
color.rgb += refl;

// Emissive
if ( lm.emissiveSettings.isEnabled ) {
emissive *= emissiveIntensity( lm.emissiveSettings.adaptiveEmittance, lm.emissiveSettings.enableAdaptiveLimits, vec4(lm.emissiveSettings.luminousEmittance, lm.emissiveSettings.exposureOffset, lm.emissiveSettings.maxOffsetEmittance, lm.emissiveSettings.minOffsetEmittance) );
} else if ( lm.layeredEmissivity.isEnabled ) {
emissive *= emissiveIntensity( lm.layeredEmissivity.adaptiveEmittance, lm.layeredEmissivity.enableAdaptiveLimits, vec4(lm.layeredEmissivity.luminousEmittance, lm.layeredEmissivity.exposureOffset, lm.layeredEmissivity.maxOffsetEmittance, lm.layeredEmissivity.minOffsetEmittance) );
}
color.rgb += emissive;

// Transmissive
Expand All @@ -699,7 +689,6 @@ void main()
}

color.rgb = tonemap(color.rgb * D.a, A.a);
color.a = baseMap.a * alpha;

fragColor = color;
}
Expand Down
96 changes: 44 additions & 52 deletions src/gl/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,24 @@ static int setFlipbookParameters( const CE2Material::Material & m )
return materialFlags;
}

static inline void setupGLBlendModeSF( int blendMode, QOpenGLFunctions * fn )
{
// source RGB, destination RGB, source alpha, destination alpha
static const GLenum blendModeMap[32] = {
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, // AlphaBlend
GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, // Additive
GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, // SourceSoftAdditive (TODO: not implemented yet)
GL_DST_COLOR, GL_ZERO, GL_DST_ALPHA, GL_ZERO, // Multiply
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, // TODO: DestinationSoftAdditive
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, // TODO: DestinationInvertedSoftAdditive
GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA, // TODO: TakeSmaller
GL_ZERO, GL_ONE, GL_ZERO, GL_ONE // None
};
const GLenum * p = &( blendModeMap[blendMode << 2] );
glEnable( GL_BLEND );
fn->glBlendFuncSeparate( p[0], p[1], p[2], p[3] );
}

bool Renderer::setupProgramCE2( const NifModel * nif, Program * prog, Shape * mesh )
{
auto scene = mesh->scene;
Expand Down Expand Up @@ -1254,62 +1272,44 @@ bool Renderer::setupProgramCE2( const NifModel * nif, Program * prog, Shape * me

//glEnable( GL_LIGHTING );

// setup blending
// setup alpha blending and testing

int alphaFlags = 0;
if ( mat && scene->hasOption(Scene::DoBlending) ) {
static const GLenum blendMapS[8] = {
GL_SRC_ALPHA, // "AlphaBlend"
GL_SRC_ALPHA, // "Additive"
GL_SRC_ALPHA, // "SourceSoftAdditive" (TODO: not implemented yet)
GL_DST_COLOR, // "Multiply"
GL_SRC_ALPHA, // "DestinationSoftAdditive" (not implemented)
GL_SRC_ALPHA, // "DestinationInvertedSoftAdditive" (not implemented)
GL_SRC_ALPHA, // "TakeSmaller" (not implemented)
GL_ZERO // "None"
};
static const GLenum blendMapD[8] = {
GL_ONE_MINUS_SRC_ALPHA, // "AlphaBlend"
GL_ONE, // "Additive"
GL_ONE, // "SourceSoftAdditive"
GL_ZERO, // "Multiply"
GL_ONE_MINUS_SRC_ALPHA, // "DestinationSoftAdditive"
GL_ONE_MINUS_SRC_ALPHA, // "DestinationInvertedSoftAdditive"
GL_ONE_MINUS_SRC_ALPHA, // "TakeSmaller"
GL_ONE // "None"
};

if ( isEffect ) {
glEnable( GL_BLEND );
if ( mat->effectSettings->flags & (CE2Material::EffectFlag_EmissiveOnly | CE2Material::EffectFlag_EmissiveOnlyAuto) )
glBlendFunc( GL_SRC_ALPHA, GL_ONE );
else
glBlendFunc( blendMapS[mat->effectSettings->blendMode], blendMapD[mat->effectSettings->blendMode] );
} else if ( (mat->flags & CE2Material::Flag_IsDecal) && (mat->flags & CE2Material::Flag_AlphaBlending) ) {
glEnable( GL_BLEND );
glBlendFunc( blendMapS[mat->decalSettings->blendMode], blendMapD[mat->decalSettings->blendMode] );
} else {
glDisable( GL_BLEND );
if ( isEffect || !( ~(mat->flags) & ( CE2Material::Flag_IsDecal | CE2Material::Flag_AlphaBlending ) ) ) {
int blendMode;
if ( !isEffect ) {
blendMode = mat->decalSettings->blendMode;
} else if ( !( mat->effectSettings->flags & (CE2Material::EffectFlag_EmissiveOnly | CE2Material::EffectFlag_EmissiveOnlyAuto) ) ) {
blendMode = mat->effectSettings->blendMode;
} else {
blendMode = 1; // emissive only: additive blending
}
setupGLBlendModeSF( blendMode, prog->f );
alphaFlags = 2;
}

if ( isEffect && (mat->effectSettings->flags & CE2Material::EffectFlag_IsAlphaTested) ) {
glEnable( GL_ALPHA_TEST );
glAlphaFunc( GL_GREATER, mat->effectSettings->alphaThreshold );
} else if ( mat->flags & CE2Material::Flag_HasOpacity && mat->alphaThreshold > 0.0f ) {
glEnable( GL_ALPHA_TEST );
glAlphaFunc( GL_GREATER, mat->alphaThreshold );
} else {
glDisable( GL_ALPHA_TEST );
}
if ( isEffect )
alphaFlags |= int( bool(mat->effectSettings->flags & CE2Material::EffectFlag_IsAlphaTested) );
else
alphaFlags |= int( bool(mat->flags & CE2Material::Flag_HasOpacity) && mat->alphaThreshold > 0.0f );

if ( mat->flags & CE2Material::Flag_IsDecal ) {
glEnable( GL_POLYGON_OFFSET_FILL );
glPolygonOffset( -1.0f, -1.0f );
}
}
prog->uni1i_l( prog->uniLocation("alphaFlags"), alphaFlags );
if ( !( alphaFlags & 2 ) )
glDisable( GL_BLEND );
glDisable( GL_ALPHA_TEST );

glDisable( GL_COLOR_MATERIAL );
glEnable( GL_DEPTH_TEST );
glDepthMask( GL_TRUE );
if ( !mesh->depthTest ) [[unlikely]]
glDisable( GL_DEPTH_TEST );
else
glEnable( GL_DEPTH_TEST );
glDepthMask( !mesh->depthWrite || mesh->translucent ? GL_FALSE : GL_TRUE );
glDepthFunc( GL_LEQUAL );
if ( mat->flags & CE2Material::Flag_TwoSided ) {
glDisable( GL_CULL_FACE );
Expand All @@ -1319,14 +1319,6 @@ bool Renderer::setupProgramCE2( const NifModel * nif, Program * prog, Shape * me
}
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

if ( !mesh->depthTest ) {
glDisable( GL_DEPTH_TEST );
}

if ( !mesh->depthWrite || mesh->translucent ) {
glDepthMask( GL_FALSE );
}

return true;
}

Expand Down
31 changes: 11 additions & 20 deletions src/glview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1692,27 +1692,18 @@ void GLView::saveImage()
int imgWidth = rgbImg.bytesPerLine() >> 2;
int imgHeight = rgbImg.height();

for ( int y = 0; haveAlpha && y < imgHeight; y++ ) {
// Convert image data (FIXME: possible byte order issues)
for ( int y = 0; useSilhouette && y < imgHeight; y++ ) {
// Combine RGB image with alpha mask from silhouette (FIXME: possible byte order issues)
if ( !( alphaImg.width() >= rgbImg.width() && y < alphaImg.height() ) ) [[unlikely]]
continue;
std::uint32_t * rgbPtr = reinterpret_cast< std::uint32_t * >( rgbImg.scanLine( y ) );
const std::uint32_t * alphaPtr = rgbPtr;
if ( useSilhouette && alphaImg.width() >= rgbImg.width() && y < alphaImg.height() ) {
alphaPtr = reinterpret_cast< const std::uint32_t * >( alphaImg.constScanLine( y ) );
for ( int x = 0; x < imgWidth; x++ ) {
// combine RGB image with alpha mask from silhouette
FloatVector4 rgba( rgbPtr + x );
FloatVector4 a( alphaPtr + x );
rgba[3] = a.dotProduct3( FloatVector4( -1.0f / 3.0f ) ) + 255.0f;
rgbPtr[x] = std::uint32_t( rgba );
}
} else {
for ( int x = 0; x < imgWidth; x++ ) {
// work around the alpha channel being squared after blending
FloatVector4 rgba( rgbPtr + x );
FloatVector4 a( rgba );
rgba.blendValues( ( a * 255.0f ).squareRoot(), 0x08 );
rgbPtr[x] = std::uint32_t( rgba );
}
const std::uint32_t * alphaPtr =
reinterpret_cast< const std::uint32_t * >( alphaImg.constScanLine( y ) );
for ( int x = 0; x < imgWidth; x++ ) {
FloatVector4 rgba( rgbPtr + x );
FloatVector4 a( alphaPtr + x );
rgba[3] = a.dotProduct3( FloatVector4( -1.0f / 3.0f ) ) + 255.0f;
rgbPtr[x] = std::uint32_t( rgba );
}
}

Expand Down

0 comments on commit dc737e6

Please sign in to comment.