GLSL blending modes, creating additive transparency - glsl

I have a little shader that looks like this, and has a neat effect.
uniform float beatScale;
varying vec3 vPos;
void main() {
vec3 normPos = (normalize(vPos) + vec3(1, 1, 1)) / vec3(2, 2, 2);
float alpha;
if (normPos.y < beatScale - 0.1) {
alpha = 0.0;
} else if (normPos.y < beatScale) {
alpha = 1.0 - (beatScale - normPos.y) / 0.1;
} else {
alpha = 1.0;
}
gl_FragColor = vec4(normPos, alpha);
}
But the transparent parts of my objects in front block the objects behind, knocking out pixels that should have been rendered.
My sorry attempt was:
gl_FragColor = gl_FragColor + vec4(normPos, alpha);
But that didn't seem to change it. I was hoping gl_FragColor had the previous pixel color, but no dice.
How can I add the computed color the color that was there before? I don't care about what's on top, this is all glowy stuff I can simply add together.

If you're doing additive blending, then you should turn off depth writes.

Related

Fade texture inner borders to transparent in LibGDX using openGL shaders (glsl)

I'm currently working on a tile game in LibGDX and I'm trying to get a "fog of war" effect by obscuring unexplored tiles. The result I get from this is a dynamically generated black texture of the size of the screen that only covers unexplored tiles leaving the rest of the background visible. This is an example of the fog texture rendered on top of a white background:
What I'm now trying to achieve is to dynamically fade the inner borders of this texture to make it look more like a fog that slowly thickens instead of just a bunch of black boxes put together on top of the background.
Googling the problem I found out I could use shaders to do this, so I tried to learn some glsl (I'm at the very start with shaders) and I came up with this shader:
VertexShader:
//attributes passed from openGL
attribute vec3 a_position;
attribute vec2 a_texCoord0;
//variables visible from java
uniform mat4 u_projTrans;
//variables shared between fragment and vertex shader
varying vec2 v_texCoord0;
void main() {
v_texCoord0 = a_texCoord0;
gl_Position = u_projTrans * vec4(a_position, 1f);
}
FragmentShader:
//variables shared between fragment and vertex shader
varying vec2 v_texCoord0;
//variables visible from java
uniform sampler2D u_texture;
uniform vec2 u_textureSize;
uniform int u_length;
void main() {
vec4 texColor = texture2D(u_texture, v_texCoord0);
vec2 step = 1.0 / u_textureSize;
if(texColor.a > 0) {
int maxNearPixels = (u_length * 2 + 1) * (u_length * 2 + 1) - 1;
for(int i = 0; i <= u_length; i++) {
for(float j = 0; j <= u_length; j++) {
if(i != 0 || j != 0) {
texColor.a -= (1 - texture2D(u_texture, v_texCoord0 + vec2(step.x * float(i), step.y * float(j))).a) / float(maxNearPixels);
texColor.a -= (1 - texture2D(u_texture, v_texCoord0 + vec2(-step.x * float(i), step.y * float(j))).a) / float(maxNearPixels);
texColor.a -= (1 - texture2D(u_texture, v_texCoord0 + vec2(step.x * float(i), -step.y * float(j))).a) / float(maxNearPixels);
texColor.a -= (1 - texture2D(u_texture, v_texCoord0 + vec2(-step.x * float(i), -step.y * float(j))).a) / float(maxNearPixels);
}
}
}
}
gl_FragColor = texColor;
}
This is the result I got setting a length of 20:
So the shader I wrote kinda works, but has terrible performance cause it's O(n^2) where n is the length of the fade in pixels (so it can be very high, like 60 or even 80). It also has some problems, like that the edges are still a bit too sharp (I'd like a smother transition) and some of the angles of the border are less faded than others (I'd like to have a fade uniform everywhere).
I'm a little bit lost at this point: is there anything I can do to make it better and faster? Like I said I'm new to shaders, so: is it even the right way to use shaders?
As others mentioned in the comments, instead of blurring in the screen-space, you should filter in the tile-space while potentially exploiting the GPU bilinear filtering. Let's go through it with images.
First define a texture such that each pixel corresponds to a single tile, black/white depending on the fog at that tile. Here's such a texture blown up:
After applying the screen-to-tiles coordinate transformation and sampling that texture with GL_NEAREST interpolation we get the blocky result similar to what you have:
float t = texture2D(u_tiles, M*uv).r;
gl_FragColor = vec4(t,t,t,1.0);
If instead of GL_NEAREST we switch to GL_LINEAR, we get a somewhat better result:
This still looks a little blocky. To improve on that we can apply a smoothstep:
float t = texture2D(u_tiles, M*uv).r;
t = smoothstep(0.0, 1.0, t);
gl_FragColor = vec4(t,t,t,1.0);
Or here is a version with a linear shade-mapping function:
float t = texture2D(u_tiles, M*uv).r;
t = clamp((t-0.5)*1.5 + 0.5, 0.0, 1.0);
gl_FragColor = vec4(t,t,t,1.0);
Note: these images were generated within a gamma-correct pipeline (i.e. sRGB framebuffer enabled). This is one of those few scenarios, however, where ignoring gamma may give better results, so you're welcome to experiment.

Optimizing pixel-swapping shader

I'm using a shader that swaps colors/palettes on a texture. The shader checks a pixel for transparency and then sets the pixel if not transparent. Is there an efficient way to ignore 0 alpha pixels other than a potential branch? In this case, where I set pixel = newPixel:
uniform bool alternate;
uniform sampler2D texture;
void main()
{
vec4 pixel = texture2D(bitmap, openfl_TextureCoordv);
if(alternate)
{
vec4 newPixel = texture2D(texture, vec2(pixel.r, pixel.b));
if(newPixel.a != 0.0)
pixel = newPixel;
}
gl_FragColor = pixel;
}
You can use mix and step:
void main()
{
vec4 pixel = texture2D(bitmap, openfl_TextureCoordv);
vec4 newPixel = texture2D(texture, vec2(pixel.r, pixel.b));
gl_FragColor = mix(pixel, newPixel,
float(alternate) * (1.0 - step(newPixel.a, 0.0)));
}
You may want to make a smooth transition depending on the alpha channel. In this case you only need mix:
gl_FragColor = mix(pixel, newPixel, float(alternate) * newPixel.a);
I would look into the step function. You can express the if statement as a product of two conditions.
For example:
if (a >= 0) {
b = c;
}
Is equivalent to
b = (step(a, 0)*c) + (1.0 - step(a, 0)*b);

Opengl texture flickering when used with mix()

I'm rendering a terrain with multiple textures that includes smooth transitions between the textures, based on the height of each fragment.
Here's my fragment shader:
#version 430
uniform sampler2D tex[3];
uniform float renderHeight;
in vec3 fsVertex;
in vec2 fsTexCoords;
out vec4 color;
void main()
{
float height = fsVertex.y / renderHeight;
const float range1 = 0.2;
const float range2 = 0.35;
const float range3 = 0.7;
const float range4 = 0.85;
if(height < range1)
color = texture(tex[0], fsTexCoords);
else if(height < range2) //smooth transition
color = mix( texture(tex[0], fsTexCoords), texture(tex[1], fsTexCoords), (height - range1) / (range2 - range1) );
else if(height < range3)
color = texture(tex[1], fsTexCoords);
else if(height < range4) //smooth transition
color = mix( texture(tex[1], fsTexCoords), texture(tex[2], fsTexCoords), (height - range3) / (range4 - range3) );
else
color = texture(tex[2], fsTexCoords);
}
'height' will always be in the range [0,1].
Here's the weird flickering I get. From what I can see they happen when 'height' equals one of the rangeN variables when using mix().
What may be the cause of this? I also tried playing around with adding and subtracting a 'bias' variable in some computations but had no luck.
Your problem is non-uniform flow control.
Basically, you can't call texture() inside an if.
Two solutions:
make all the calls to texture() first then blend the results with mix()
calculate the partial derivatives of the texture coordinates (with dFdx & co., there is an example in the link above) and use textureGrad() instead of texture()
In very simple cases, the first solution may be slightly faster. The second one is the way to go if you want to have many textures (normal maps etc.) But don't take my word for it, measure.

Changing colors in a shader

I have a Grey Scale Shader for the Shaders Mod for Minecraft. I have the final.fsh, and it works real nice with this resource pack:
"Craftboy Grey"
However, I'd like to modify the shader to show up in green, similar to the grey does.
"Craftboy Green"
The reason I need the shader, is to modify all the colors the resource pack cannot, and also change other player's skins to the same scale, without them needing to manually do it.
Here's the code for the shader:
// Grayscale shader by daxnitro.
// Small edit by Edrem
// It makes the green brighter =D
uniform sampler2D sampler0;
uniform sampler2D sampler1;
uniform sampler2D sampler2;
uniform float near;
uniform float far;
float getBrightness(vec4 color);
float getDepth(vec2 coord);
void applyEffect() {
float brightness = getBrightness(gl_FragColor);
gl_FragColor = vec4(brightness, brightness, brightness, gl_FragColor[3]);
}
void main() {
vec4 baseColor = texture2D(sampler0, gl_TexCoord[0].st);
gl_FragColor = baseColor;
float depth = getDepth(gl_TexCoord[0].st);
if (gl_FragColor[3] == 0.0) {
gl_FragColor = gl_Fog.color;
}
applyEffect();
}
float getBrightness(vec4 color) {
return color[0] * 0.299f + color[1] * 0.587f + color[2] * 0.114f;
}
float getDepth(vec2 coord) {
float depth = texture2D(sampler1, coord).x;
float depth2 = texture2D(sampler2, coord).x;
if (depth2 < 1.0) {
depth = depth2;
}
depth = 2.0 * near * far / (far + near - (2.0 * depth - 1.0) * (far - near));
return depth;
}
To make it "green scale" instead of grey scale, write the brightness only to the green component of the output:
void applyEffect() {
float brightness = getBrightness(gl_FragColor);
gl_FragColor = vec4(0.0, brightness, 0.0, gl_FragColor[3]);
}
If you want more overall brightness while still having the whole thing tinted green, you can add some brightness back in the red and blue components. For example:
gl_FragColor = vec4(brightness * vec3(0.5, 1.0, 0.5), gl_FragColor[3]);

OpenGL GLSL blend two textures by arbitrary shape

I have a full screen quad with two textures.
I want to blend two textures in arbitrary shape according to user selection.
For example, the quad at first is 100% texture0 while texture1 is transparent.
If the user selects a region, for example a circle, by dragging the mouse on the quad, then
circle region should display both texture0 and texture1 as translucent.
The region not enclosed by the circle should still be texture0.
Please see example image, textures are simplified as colors.
For now, I have achieved blending two textures on the quad, but the blending region can only be vertical slices because I use the step() function.
My frag shader:
uniform sampler2D Texture0;
uniform sampler2D Texture1;
uniform float alpha;
uniform float leftBlend;
uniform float rightBlend;
varying vec4 oColor;
varying vec2 oTexCoord;
void main()
{
vec4 first_sample = texture2D(Texture0, oTexCoord);
vec4 second_sample = texture2D(Texture1, oTexCoord);
float stepLeft = step(leftBlend, oTexCoord.x);
float stepRight = step(rightBlend, 1.0 - oTexCoord.x);
if(stepLeft == 1.0 && stepRight == 1.0)
gl_FragColor = oColor * first_sample;
else
gl_FragColor = oColor * (first_sample * alpha + second_sample * (1.0-alpha));
if (gl_FragColor.a < 0.4)
discard;
}
To achieve arbitrary shape, I assume I need to create a alpha mask texture which is the same size as texture0 and texture 1?
Then I pass that texture to frag shader to check values, if value is 0 then texture0, if value is 1 then blend texture0 and texture1.
Is my approach correct? Can you point me to any samples?
I want effect such as OpenGL - mask with multiple textures
but I want to create mask texture in my program dynamically, and I want to implement blending in GLSL
I have got blending working with mask texture of black and white
uniform sampler2D TextureMask;
vec4 mask_sample = texture2D(TextureMask, oTexCoord);
if(mask_sample.r == 0)
gl_FragColor = first_sample;
else
gl_FragColor = (first_sample * alpha + second_sample * (1.0-alpha));
now mask texture is loaded statically from a image on disk, now I just need to create mask texture dynamically in opengl
Here's one approach and sample.
Create a boolean test for whether you want to blend.
In my sample, I use an equation for a circle centered on the screen.
Then blend (i blended by weighted addition of the 2 colors).
(NOTE: i didn't have texture coords to work with in this sample, so i used the screen resolution to determine the circle position).
uniform vec2 resolution;
void main( void ) {
vec2 position = gl_FragCoord.xy / resolution;
// test if we're "in" or "out" of the blended region
// lets use a circle of radius 0.5, but you can make this mroe complex and/or pass this value in from the user
bool isBlended = (position.x - 0.5) * (position.x - 0.5) +
(position.y - 0.5) * (position.y - 0.5) > 0.25;
vec4 color1 = vec4(1,0,0,1); // this could come from texture 1
vec4 color2 = vec4(0,1,0,1); // this could come from texture 2
vec4 finalColor;
if (isBlended)
{
// blend
finalColor = color1 * 0.5 + color2 * 0.5;
}
else
{
// don't blend
finalColor = color1;
}
gl_FragColor = finalColor;
}
See the sample running here: http://glsl.heroku.com/e#18231.0
(tried to post my sample image but i don't have enough rep) sorry :/
Update:
Here's another sample using mouse position to determine the position of the blended area.
To run, paste the code in this sandbox site: https://www.shadertoy.com/new
This one should work on objects of any shape, as long as you have the mouse data setup correct.
void main(void)
{
vec2 position = gl_FragCoord.xy;
// test if we're "in" or "out" of the blended region
// lets use a circle of radius 10px, but you can make this mroe complex and/or pass this value in from the user
float diffX = position.x - iMouse.x;
float diffY = position.y - iMouse.y;
bool isBlended = (diffX * diffX) + (diffY * diffY) < 100.0;
vec4 color1 = vec4(1,0,0,1); // this could come from texture 1
vec4 color2 = vec4(0,1,0,1); // this could come from texture 2
vec4 finalColor;
if (isBlended)
{
// blend
finalColor = color1 * 0.5 + color2 * 0.5;
}
else
{
// don't blend
finalColor = color1;
}
gl_FragColor = finalColor;
}