Related
In the following example I would like to manually create points (x, y, angle) from SFML then fill a circle around each point. The angle will be used later, for now I use it for debugging.
SFML draws 2 points
Vertex shader convert points to -1..1 range
Geometry shader creates squares at each point position and pass the center to fragment shader
Fragment shader would pain a circle within each square.
From my understanding, in the geometry shader I emit center which is the center coordinates of each primitive. From computing the distance from this center I would be able to paint a circle in each primitive from the fragment shader.
In the picture below I notice the center is only set once and I don't understand why.
SFML App
#include <iostream>
#include <SFML/Graphics.hpp>
#include <vector>
#include <GL/glew.h>
#include <random>
#define WIDTH 800
int main() {
sf::RenderWindow window(sf::VideoMode(WIDTH, WIDTH), "Test");
sf::Shader shader;
shader.loadFromFile("shader.vert", "shader.geom", "shader.frag");
sf::Transform matrix = sf::Transform::Identity;
matrix.scale(1.0 / WIDTH, 1.0 / WIDTH);
sf::Glsl::Mat4 projectionViewMatrix = matrix;
shader.setUniform("projectionViewMatrix", projectionViewMatrix);
std::vector<GLfloat> vertices;
vertices.push_back(400.0); vertices.push_back(400.0); vertices.push_back(0.0);
vertices.push_back(400.0); vertices.push_back(-400.0); vertices.push_back(0.25);
vertices.push_back(-400.0); vertices.push_back(-400.0); vertices.push_back(0.5);
vertices.push_back(-400.0); vertices.push_back(400.0); vertices.push_back(0.75);
while (window.isOpen()) {
sf::Event currEvent;
while (window.pollEvent(currEvent)) {
switch (currEvent.type) {
case(sf::Event::Closed):
window.close(); break;
}
}
window.clear(sf::Color::Black);
glVertexPointer(3, GL_FLOAT, 0, vertices.data());
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_POINTS, 0, vertices.size() / 3);
glDisableClientState(GL_VERTEX_ARRAY);
sf::Shader::bind(&shader);
window.display();
}
}
Vertex Shader
#version 150
in vec3 position;
out vec3 pass_colour;
out float angle;
uniform mat4 projectionViewMatrix;
void main(void) {
gl_Position = projectionViewMatrix * vec4(position.xy, 0.0 ,1.0);
angle = position.z;
pass_colour = vec3(1.0);
}
Geometry shader
#version 150
layout (points) in;
layout (triangle_strip, max_vertices = 6) out;
in vec3 pass_colour[];
in float angle[];
out vec3 finalColour;
out vec4 centerPosition;
uniform mat4 projectionViewMatrix;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
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);
}
void createVertex(vec3 offset, vec3 colour, float z = 0.0) {
vec4 actualOffset = vec4(offset, z);
vec4 worldPosition = gl_in[0].gl_Position + actualOffset;
gl_Position = worldPosition;
finalColour = colour;
vec4 pointPosition = gl_in[0].gl_Position;
centerPosition = pointPosition;
EmitVertex();
}
void main(void) {
float corner = 0.3;
vec3 colour = hsv2rgb(vec3(angle[0], 1.0, 1.0));
createVertex(vec3(-corner, -corner, 0.0), colour, 0.0);
createVertex(vec3(corner, -corner, 0.0), colour, 0.0);
createVertex(vec3(-corner, corner, 0.0), colour, 0.0);
createVertex(vec3(corner, corner, 0.0), colour, 0.0);
createVertex(vec3(corner, -corner, 0.0), colour, 0.0);
createVertex(vec3(-corner, corner, 0.0), colour, 0.0);
EndPrimitive();
}
Fragment shader
#version 150
in vec3 finalColour;
in vec4 centerPosition;
out vec4 out_Colour;
void main(void){
vec2 resolution = vec2(800.0/2.0, 800.0/2.0);
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec2 uvc = (centerPosition.xy + vec2(1.0)) / 2.0;
float dist = length(uv - uvc);
out_Colour = vec4(finalColour * dist, 0.8);
}
I still don't explain everything, but it works with this fragment:
#version 150
in vec4 finalColour;
in vec4 centerPosition;
out vec4 out_Colour;
void main(void){
vec2 resolution = vec2(800.0/2.0, 800.0/2.0);
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec2 p = vec2(1.0, 1.0) + centerPosition.xy;
vec2 uvc = p;
float dist = length(uv - uvc);
float col = 1.0 - smoothstep(0.0, 0.1, dist);
out_Colour = vec4(finalColour.rgb * col, 1.0);
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I am trying to make a grid with fragment shader and i get problems with uv coords.
On this screenshot you can see first result:
float roundRect(vec2 p, vec2 size, float radius) {
vec2 d = abs(p) - size;
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius;
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec2 f_uv = fract(uv * 22.680);
float rect = smoothstep(0.040, 0.0, roundRect(f_uv - vec2(0.5), vec2(0.44), 0.040));
gl_FragColor = vec4(vec3(rect), 1.0);
}
Second:
float roundRect(vec2 p, vec2 size, float radius) {
vec2 d = abs(p) - size;
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius;
}
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec2 f_uv = fract(uv * 20.680);
float rect = smoothstep(0.040, 0.0, roundRect(f_uv - vec2(0.5), vec2(0.44), 0.040));
gl_FragColor = vec4(vec3(rect), 1.0);
}
These both screenshots have a difference in line
vec2 f_uv = fract(uv * x);
How can i fix it?
What you see is aliasing caused by gridlines
thinner than the pixels themselves, and
spaced at non-integer pixel intervals.
To fix that you need to band-limit your function. One way of doing this is as follows:
void main() {
float scale = 22.680;
vec2 uv = gl_FragCoord.xy / u_resolution.xy * scale;
float fw = max(fwidth(uv.x), fwidth(uv.y));
float rect = smoothstep(fw, -fw, roundRect(fract(uv) - vec2(0.5), vec2(0.44), 0.040));
gl_FragColor = vec4(vec3(rect), 1.0);
}
The results look as follows:
Note that some lines are still blurrier than others -- but the only way around it is to ensure that your scale factor is an integer amount of pixels.
I need to calculate projection of point on specific line segment in shader (OpenGL ES 2).
Here is how I test the algorithm:
I draw simple triangle with points A(0, 0.5), B(1, -0.5), C(-1, -0.5).
I calculate projection of every point on line segment AC.
I draw points with a projection in the middle of a line segment AC in blue. And the remaining points in green.
I expect to get a green triangle with a blue line perpendicular to the side AC. But blue line is not perpendicular to AC.
I check projection formula in code with drawing on canvas and got expected result.
What's my mistake?
Result of shader:
Vertex shader:
uniform mat4 matrix;
attribute vec4 position;
varying vec4 vPosition;
void main()
{
vPosition = matrix * position;
gl_Position = matrix * position;
}
Fragment shader:
precision mediump float;
varying vec4 vPosition;
void main()
{
vec2 P = vPosition.xy;
vec2 A = vec2(0.0, 0.5);
vec2 B = vec2(-1.0, -0.5);
vec2 AP = P - A;
vec2 AB = B - A;
vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB;
if(projection.x > -0.51 && projection.x < -0.49 && projection.y > -0.01 && projection.y < 0.01) {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
} else {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
}
You didn't consider the rectangular aspect of the of the window. When the normalized device coordinates in the range [-1, 1] are mapped to the viewport rectangle (see glViewport) then the triangle gets stretched. This causes that angles of 90 degree are not maintained.
Add a uniform variable to the fragment shader which contains the width and height of the viewport:
uniform vec2 u_resolution;
Calculate the aspect ratio:
float aspect = u_resolution.x / u_resolution.y;
Of course you can initialize the variable float aspect, by a constant value, too.
e.g. float aspect = 16.0/9.0;
Correct the coordinates of the points A, B and P according to the aspect ratio:
vec2 P = vPosition.xy;
vec2 A = vec2(0.0, 0.5);
vec2 B = vec2(-1.0, -0.5);
A.x *= aspect;
B.x *= aspect;
P.x *= aspect;
And consider the aspect ration when evaluating the result projection:
vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB;
projection.x /= aspect;
The final fragment shader may look like this:
precision mediump float;
varying vec4 vPosition;
uniform vec2 u_resolution;
void main()
{
float aspect = u_resolution.x / u_resolution.y;
vec2 as = vec2(aspect, 1.0);
vec2 P = as * vPosition.xy;
vec2 A = as * vec2(0.0, 0.5);
vec2 B = as * vec2(-1.0, -0.5);
vec2 AP = P - A;
vec2 AB = B - A;
vec2 projection = A + dot(AP, AB) / dot(AB, AB) * AB / as;
if(projection.x > -0.51 && projection.x < -0.49 && projection.y > -0.01 && projection.y < 0.01) {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
} else {
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
}
I'm trying to replicate a web design trick known as "gooey effect" (see it live here).
It's a technique applying SVG filters on moving ellipses in order to get a blob-like motion. The process is rather simple:
apply a gaussian blur
increase the contrast of the alpha channel only
The combination of the two creates a blob effect
The last step (increasing the alpha channel contrast) is usually done through a "color matrix filter".
A color matrix is composed of 5 columns (RGBA + offset) and 4 rows.
The values in the first four columns are multiplied with the source red, green, blue, and alpha values respectively. The fifth column value is added (offset).
In CSS, increasing the alpha channel contrast is as simple as calling a SVG filter and specifying the contrast value (here 18):
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" result="goo" />
In Processing though, it seems to be a bit more complicated. I believe (I may be wrong) the only way to apply a color matrix filter is to create one in a shader. After a few tries I came up with these (very basic) vertex and fragment shaders for color rendering:
colorvert.glsl
uniform mat4 transform;
attribute vec4 position;
attribute vec4 color;
varying vec4 vertColor;
uniform vec4 o=vec4(0, 0, 0, -9);
uniform lowp mat4 colorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 60.0);
void main() {
gl_Position = transform * position;
vertColor = (color * colorMatrix) + o ;
}
colorfrag.glsl
#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
varying vec4 vertColor;
void main() {
gl_FragColor = vertColor;
}
PROBLEM:
The color matrix is partially working: changing the RGB values do affect the colors but changing the alpha values (last row) don't !
When trying to combine the shader with a Gaussian filter, the drawn ellipse stays blurry even after I set the alpha channel contrast to 60 (like in the codepen example):
PShader colmat;
void setup() {
size(200, 200, P2D);
colmat = loadShader("colorfrag.glsl", "colorvert.glsl");
}
void draw() {
background(100);
shader(colmat);
noStroke();
fill(255, 30, 30);
ellipse(width/2, height/2, 40, 40);
filter(BLUR,6);
}
The same thing happens when I implement the color matrix within #cansik 's Gaussian blur shader (from the PostFX library). I can see the colors changing but not the alpha contrast:
blurFrag.glsl
/ Adapted from:
// http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
#define PROCESSING_TEXTURE_SHADER
uniform sampler2D texture;
uniform vec4 o=vec4(0, 0, 0, 0);
uniform lowp mat4 colorMatrix = mat4(1, 0.0, 0.0, 0.0,
0.0, 1, 0.0, 0.0,
0.0, 0.0, 1, 0.0,
0, 0.0, 0.0, 60.0); //Alpha contrast set to 60
varying vec2 center;
// The inverse of the texture dimensions along X and Y
uniform vec2 texOffset;
varying vec4 vertColor;
varying vec4 vertTexCoord;
uniform int blurSize;
uniform int horizontalPass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma; // The sigma value for the gaussian function: higher value means more blur
// A good value for 9x9 is around 3 to 5
// A good value for 7x7 is around 2.5 to 4
// A good value for 5x5 is around 2 to 3.5
// ... play around with this based on what you need <span class="Emoticon Emoticon1"><span>:)</span></span>
const float pi = 3.14159265;
void main() {
float numBlurPixelsPerSide = float(blurSize / 2);
vec2 blurMultiplyVec = 0 < horizontalPass ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
// Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
vec3 incrementalGaussian;
incrementalGaussian.x = 1.0 / (sqrt(2.0 * pi) * sigma);
incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;
vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
float coefficientSum = 0.0;
// Take the central sample first...
avgValue += texture2D(texture, vertTexCoord.st) * incrementalGaussian.x;
coefficientSum += incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= numBlurPixelsPerSide; i++) {
avgValue += texture2D(texture, vertTexCoord.st - i * texOffset *
blurMultiplyVec) * incrementalGaussian.x;
avgValue += texture2D(texture, vertTexCoord.st + i * texOffset *
blurMultiplyVec) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
gl_FragColor = (avgValue / coefficientSum ) * colorMatrix;
}
Setting glBlendFunc and enabling glEnable(GL_BLEND) in the main .pde file didn't fix the issue either.
sketch.pde
import ch.bildspur.postfx.builder.*;
import ch.bildspur.postfx.pass.*;
import ch.bildspur.postfx.*;
import processing.opengl.*;
import com.jogamp.opengl.*;
PostFX fx;
void setup() {
size(200, 200, P2D);
fx = new PostFX(this);
}
void draw() {
background(100);
GL gl = ((PJOGL)beginPGL()).gl.getGL();
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
gl.glDisable(GL.GL_DEPTH_TEST);
noStroke();
fill(255, 30, 30);
ellipse(width/2, height/2, 40, 40);
fx.render().blur(80, 14).compose();
}
Questions:
Why does the alpha channel contrast not work ? How can I make it work ?
Is there something wrong with the way I implemented the color matrix ?
Do you know a better way to implement that gooey effect ?
Any help would be much appreciated !
Thank you
#noahbuddy from the Processing Forum could find a solution to the problem so I'm posting it here.
To preserve transparency, with or without shaders, use an offscreen
buffer (PGraphics). For example, saving a PNG image with transparent
background.
I removed the contrast matrix from #cansik 's blur shader and instead
put it into a separate filter.
blurfrag.glsl
// Adapted from:
// http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif
#define PROCESSING_TEXTURE_SHADER
uniform sampler2D texture;
// The inverse of the texture dimensions along X and Y
uniform vec2 texOffset;
varying vec4 vertColor;
varying vec4 vertTexCoord;
uniform int blurSize;
uniform int horizontalPass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma; // The sigma value for the gaussian function: higher value means more blur
// A good value for 9x9 is around 3 to 5
// A good value for 7x7 is around 2.5 to 4
// A good value for 5x5 is around 2 to 3.5
// ... play around with this based on what you need <span class="Emoticon Emoticon1"><span>:)</span></span>
const float pi = 3.14159265;
void main() {
float numBlurPixelsPerSide = float(blurSize / 2);
vec2 blurMultiplyVec = 0 < horizontalPass ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
// Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
vec3 incrementalGaussian;
incrementalGaussian.x = 1.0 / (sqrt(2.0 * pi) * sigma);
incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;
vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
float coefficientSum = 0.0;
// Take the central sample first...
avgValue += texture2D(texture, vertTexCoord.st) * incrementalGaussian.x;
coefficientSum += incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
// Go through the remaining 8 vertical samples (4 on each side of the center)
for (float i = 1.0; i <= numBlurPixelsPerSide; i++) {
avgValue += texture2D(texture, vertTexCoord.st - i * texOffset *
blurMultiplyVec) * incrementalGaussian.x;
avgValue += texture2D(texture, vertTexCoord.st + i * texOffset *
blurMultiplyVec) * incrementalGaussian.x;
coefficientSum += 2.0 * incrementalGaussian.x;
incrementalGaussian.xy *= incrementalGaussian.yz;
}
gl_FragColor = avgValue / coefficientSum;
}
colfrag.glsl
#define PROCESSING_TEXTURE_SHADER
uniform sampler2D texture;
varying vec4 vertTexCoord;
uniform vec4 o = vec4(0, 0, 0, -7.0);
uniform lowp mat4 colorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 18.0);
void main() {
vec4 pix = texture2D(texture, vertTexCoord.st);
vec4 color = (pix * colorMatrix) + o;
gl_FragColor = color;
}
sketch.pde
PShader contrast, blurry;
PGraphics buf;
void setup() {
size(200, 200, P2D);
buf = createGraphics(width, height, P2D);
contrast = loadShader("colfrag.glsl");
blurry = loadShader("blurFrag.glsl");
// Don't forget to set these
blurry.set("sigma", 4.5);
blurry.set("blurSize", 9);
}
void draw() {
background(100);
buf.beginDraw();
// Reset transparency
// Note, the color used here will affect your edges
// even with zero for alpha
buf.background(100, 0); // set to match main background
buf.noStroke();
buf.fill(255, 30, 30);
buf.ellipse(width/2, height/2, 40, 40);
buf.ellipse(mouseX, mouseY, 40, 40);
blurry.set("horizontalPass", 1);
buf.filter(blurry);
blurry.set("horizontalPass", 0);
buf.filter(blurry);
buf.endDraw();
shader(contrast);
image(buf, 0,0, width,height);
}
Personally I think the sweet spot lies somewhere:
between 8 and 11 for the alpha contrast
between -7 and -9 for the alpha offset
uniform vec4 o = vec4(0, 0, 0, -9.0);
uniform lowp mat4 colorMatrix = mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
1.0, 1.0, 1.0, 11.0);
bewteen 10 and 15 for "sigma"
bewteen 30 and 40 for "blurSize"
blurry.set("sigma", 14.5)
blurry.set("blurSize", 35)
I've coded 2d metaballs before using signed distance functions and marching square algorithms but I find this solution to be the most efficient one. Performance wise I can display up to 4500 balls at 60 fps on a 800x600 canvas (tested on an entry-level 2012 imac desktop with Python Mode).
Unfortunately I'm not able to debug the exact issue, but I have a couple of ideas that hopefully might help you make some progress:
For a simpler/cheaper effect you can use the dilate filter
You can find other metaballs shaders on shadertoy and tweak the code a bit so you can run it in Processing
For example https://www.shadertoy.com/view/MlcGWn becomes:
// https://www.shadertoy.com/view/MlcGWn
uniform float iTime;
uniform vec2 iResolution;
vec3 Sphere(vec2 uv, vec2 position, float radius)
{
float dist = radius / distance(uv, position);
return vec3(dist * dist);
}
void main()
{
vec2 uv = 2.0 * vec2(gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
vec3 pixel = vec3(0.0, 0.0, 0.0);
vec2 positions[4];
positions[0] = vec2(sin(iTime * 1.4) * 1.3, cos(iTime * 2.3) * 0.4);
positions[1] = vec2(sin(iTime * 3.0) * 0.5, cos(iTime * 1.3) * 0.6);
positions[2] = vec2(sin(iTime * 2.1) * 0.1, cos(iTime * 1.9) * 0.8);
positions[3] = vec2(sin(iTime * 1.1) * 1.1, cos(iTime * 2.6) * 0.7);
for (int i = 0; i < 4; i++)
pixel += Sphere(uv, positions[i], 0.22);
pixel = step(1.0, pixel) * pixel;
gl_FragColor = vec4(pixel, 1.0);
}
and in Processing:
PShader shader;
void setup(){
size(900,900,P2D);
shader = loadShader("metaballs.glsl");
shader.set("iResolution",(float)width/2,(float)height/2);
}
void draw(){
shader.set("iTime", millis() * 0.001);
shader(shader);
rect(0,0,width,height);
}
or https://www.shadertoy.com/view/ldtSRX
// https://www.shadertoy.com/view/ldtSRX
uniform vec2 iResolution;
uniform vec2 iMouse;
uniform float iTime;
struct Metaball{
vec2 pos;
float r;
vec3 col;
};
vec4 calcball( Metaball ball, vec2 uv)
{
float dst = ball.r / (pow(abs(uv.x - ball.pos.x), 2.) + pow(abs(uv.y - ball.pos.y), 2.));
return vec4(ball.col * dst, dst);
}
vec3 doballs( vec2 uv )
{
Metaball mouse;
mouse.pos = iMouse.xy / iResolution.yy;
mouse.r = .015;
mouse.col = vec3(.5);
Metaball mb1, mb2, mb3, mb4;
mb1.pos = vec2(1.3, .55+.2*sin(iTime*.5)); mb1.r = .05; mb1.col = vec3(0., 1., 0.);
mb2.pos = vec2(.6, .45); mb2.r = .02; mb2.col = vec3(0., .5, 1.);
mb3.pos = vec2(.85, .65); mb3.r = .035; mb3.col = vec3(1., .2, 0.);
mb4.pos = vec2(1.+.5*sin(iTime), .2); mb4.r = .02; mb4.col = vec3(1., 1., 0.);
vec4 ball1 = calcball(mb1, uv);
vec4 ball2 = calcball(mb2, uv);
vec4 ball3 = calcball(mb3, uv);
vec4 ball4 = calcball(mb4, uv);
vec4 subball1 = calcball(mouse, uv);
float res = ball1.a + ball2.a + ball3.a + ball4.a;
res -= subball1.a;
float threshold = res >= 1.5 ? 1. : 0.;
vec3 color = (ball1.rgb + ball2.rgb + ball3.rgb + ball4.rgb - subball1.rgb) / res;
color *= threshold;
color = clamp(color, 0., 1.);
return color;
}
#define ANTIALIAS 1
void main()
{
vec2 uv = gl_FragCoord.xy / iResolution.yy;
vec3 color = doballs(uv);
#ifdef ANTIALIAS
float uvs = .75 / iResolution.y;
color *= .5;
color += doballs(vec2(uv.x + uvs, uv.y))*.125;
color += doballs(vec2(uv.x - uvs, uv.y))*.125;
color += doballs(vec2(uv.x, uv.y + uvs))*.125;
color += doballs(vec2(uv.x, uv.y - uvs))*.125;
#if ANTIALIAS == 2
color *= .5;
color += doballs(vec2(uv.x + uvs*.85, uv.y + uvs*.85))*.125;
color += doballs(vec2(uv.x - uvs*.85, uv.y + uvs*.85))*.125;
color += doballs(vec2(uv.x - uvs*.85, uv.y - uvs*.85))*.125;
color += doballs(vec2(uv.x + uvs*.85, uv.y - uvs*.85))*.125;
#endif
#endif
gl_FragColor = vec4(color, 1.);
}
and in Processing:
PShader shader;
PVector mouse = new PVector();
void setup(){
size(900,900,P2D);
shader = loadShader("metaballs.glsl");
shader.set("iResolution",(float)width/2,(float)height/2);
}
void draw(){
mouse.set(mouseX,mouseY);
shader.set("iMouse", mouse);
shader.set("iTime", millis() * 0.001);
shader(shader);
rect(0,0,width,height);
}
I'm trying to implement Oren-Nayar lighting in the fragment shader as shown here.
However, I'm getting some strange lighting effects on the terrain as shown below.
I am currently sending the shader the 'view direction' uniform as the camera's 'front' vector. I am not sure if this is correct, as moving the camera around changes the artifacts.
Multiplying the 'front' vector by the MVP matrix gives a better result, but the artifacts are still very noticable when viewing the terrain from some angles. It is particularly noticable in dark areas and around the edges of the screen.
What could be causing this effect?
Artifact example
How the scene should look
Vertex Shader
#version 450
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
out VS_OUT {
vec3 normal;
} vert_out;
void main() {
vert_out.normal = normal;
gl_Position = vec4(position, 1.0);
}
Tesselation Control Shader
#version 450
layout(vertices = 3) out;
in VS_OUT {
vec3 normal;
} tesc_in[];
out TESC_OUT {
vec3 normal;
} tesc_out[];
void main() {
if(gl_InvocationID == 0) {
gl_TessLevelInner[0] = 1.0;
gl_TessLevelInner[1] = 1.0;
gl_TessLevelOuter[0] = 1.0;
gl_TessLevelOuter[1] = 1.0;
gl_TessLevelOuter[2] = 1.0;
gl_TessLevelOuter[3] = 1.0;
}
tesc_out[gl_InvocationID].normal = tesc_in[gl_InvocationID].normal;
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}
Tesselation Evaluation Shader
#version 450
layout(triangles, equal_spacing) in;
in TESC_OUT {
vec3 normal;
} tesc_in[];
out TESE_OUT {
vec3 normal;
float height;
vec4 shadow_position;
} tesc_out;
uniform mat4 model_view;
uniform mat4 model_view_perspective;
uniform mat3 normal_matrix;
uniform mat4 depth_matrix;
vec3 lerp(vec3 v0, vec3 v1, vec3 v2) {
return (
(vec3(gl_TessCoord.x) * v0) +
(vec3(gl_TessCoord.y) * v1) +
(vec3(gl_TessCoord.z) * v2)
);
}
vec4 lerp(vec4 v0, vec4 v1, vec4 v2) {
return (
(vec4(gl_TessCoord.x) * v0) +
(vec4(gl_TessCoord.y) * v1) +
(vec4(gl_TessCoord.z) * v2)
);
}
void main() {
gl_Position = lerp(
gl_in[0].gl_Position,
gl_in[1].gl_Position,
gl_in[2].gl_Position
);
tesc_out.normal = normal_matrix * lerp(
tesc_in[0].normal,
tesc_in[1].normal,
tesc_in[2].normal
);
tesc_out.height = gl_Position.y;
tesc_out.shadow_position = depth_matrix * gl_Position;
gl_Position = model_view_perspective * gl_Position;
}
Fragment Shader
#version 450
in TESE_OUT {
vec3 normal;
float height;
vec4 shadow_position;
} frag_in;
out vec4 colour;
uniform vec3 view_direction;
uniform vec3 light_position;
#define PI 3.141592653589793
void main() {
const vec3 ambient = vec3(0.1, 0.1, 0.1);
const float roughness = 0.8;
const vec4 water = vec4(0.0, 0.0, 0.8, 1.0);
const vec4 sand = vec4(0.93, 0.87, 0.51, 1.0);
const vec4 grass = vec4(0.0, 0.8, 0.0, 1.0);
const vec4 ground = vec4(0.49, 0.27, 0.08, 1.0);
const vec4 snow = vec4(0.9, 0.9, 0.9, 1.0);
if(frag_in.height == 0.0) {
colour = water;
} else if(frag_in.height < 0.2) {
colour = sand;
} else if(frag_in.height < 0.575) {
colour = grass;
} else if(frag_in.height < 0.8) {
colour = ground;
} else {
colour = snow;
}
vec3 normal = normalize(frag_in.normal);
vec3 view_dir = normalize(view_direction);
vec3 light_dir = normalize(light_position);
float NdotL = dot(normal, light_dir);
float NdotV = dot(normal, view_dir);
float angleVN = acos(NdotV);
float angleLN = acos(NdotL);
float alpha = max(angleVN, angleLN);
float beta = min(angleVN, angleLN);
float gamma = dot(view_dir - normal * dot(view_dir, normal), light_dir - normal * dot(light_dir, normal));
float roughnessSquared = roughness * roughness;
float roughnessSquared9 = (roughnessSquared / (roughnessSquared + 0.09));
// calculate C1, C2 and C3
float C1 = 1.0 - 0.5 * (roughnessSquared / (roughnessSquared + 0.33));
float C2 = 0.45 * roughnessSquared9;
if(gamma >= 0.0) {
C2 *= sin(alpha);
} else {
C2 *= (sin(alpha) - pow((2.0 * beta) / PI, 3.0));
}
float powValue = (4.0 * alpha * beta) / (PI * PI);
float C3 = 0.125 * roughnessSquared9 * powValue * powValue;
// now calculate both main parts of the formula
float A = gamma * C2 * tan(beta);
float B = (1.0 - abs(gamma)) * C3 * tan((alpha + beta) / 2.0);
// put it all together
float L1 = max(0.0, NdotL) * (C1 + A + B);
// also calculate interreflection
float twoBetaPi = 2.0 * beta / PI;
float L2 = 0.17 * max(0.0, NdotL) * (roughnessSquared / (roughnessSquared + 0.13)) * (1.0 - gamma * twoBetaPi * twoBetaPi);
colour = vec4(colour.xyz * (L1 + L2), 1.0);
}
First I've plugged your fragment shader into my renderer with my view/normal/light vectors and it works perfectly. So the problem has to be in the way you calculate those vectors.
Next, you say that you set view_dir to your camera's front vector. I assume that you meant "camera's front vector in the world space" which would be incorrect. Since you calculate the dot products with vectors in the camera space, the view_dir must be in the camera space too. That is vec3(0,0,1) would be an easy way to check that. If it works -- we found your problem.
However, using (0,0,1) for the view direction is not strictly correct when you do perspective projection, because the direction from the fragment to the camera then depends on the location of the fragment on the screen. The correct formula then would be view_dir = normalize(-pos) where pos is the fragment's position in camera space (that is with model-view matrix applied without the projection). Further, this quantity now depends only on the fragment location on the screen, so you can calculate it as:
view_dir = normalize(vec3(-(gl_FragCoord.xy - frame_size/2) / (frame_width/2), flen))
flen is the focal length of your camera, which you can calculate as flen = cot(fovx/2).
I know this is a long dead thread, but I've been having the same problem (for several years), and finally found the solution...
It can be partially solved by fixing the orientation of the surface normals to match the polygon winding direction, but you can also get rid of the artifacts in the shader, by changing the following two lines...
float angleVN = acos(cos_nv);
float angleLN = acos(cos_nl);
to this...
float angleVN = acos(clamp(cos_nv, -1.0, 1.0));
float angleLN = acos(clamp(cos_nl, -1.0, 1.0));
Tada!