GPUImage create a custom Filter that change selected colors - ios

Using the amazing GPU image framework, I'm trying to create a custom filter using a custom fragment shader that passed some color vectors as uniforms, elaborate each fragment substituting a choosen color with one in the uniform. I made that using Quartz and it works, but since I'm moving my first step in OpenGL world using this framework, I'd like to give a try to the GPU processing.
The fragment shader I made seems to work fine, but there is a problem in the output. I post just a sample for debugging porpoise
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
bool compareVectors (lowp vec3 x,lowp vec3 y){
bool result;
if (x.r != y.r) {
return result = false;
}
if (x.b != y.b) {
return result = false;
}
if (x.g != y.g ) {
return result = false;
}
return result = true;
}
void main()
{
lowp vec3 tc = vec3(0.0, 0.0, 0.0);
lowp vec4 pixcol = texture2D(inputImageTexture, textureCoordinate).rgba;
lowp vec3 sampleColors[3];
lowp vec3 newColors[3];
sampleColors[0] = vec3(0.2, 0.2, 0.2);
sampleColors[1] = vec3(0.0, 0.0, 0.0);
sampleColors[2] = vec3(1.0, 1.0, 1.0);
newColors[0] = vec3(0.4, 0.4, 1.0);
newColors[1] = vec3(0.3, 0.4, 1.0);
newColors[2] = vec3(0.6, 0.7, 0.5);
if (pixcol.a >= 0.2) {
if (compareVectors (sampleColors[0],pixcol.rgb))
tc = newColors[0];
else if (compareVectors (sampleColors[1],pixcol.rgb))
tc = newColors[1];
else if (compareVectors (sampleColors[2],pixcol.rgb))
tc = newColors[2];
else
tc = pixcol.rgb;
}
else
tc = pixcol.rgb;
gl_FragColor = vec4(tc.rgb, pixcol.a);
}
The resulting image has a lot of artifacts. It seems pixellate on the screen and not well created if written to disk. Here are some screen.
The first image is the starting image, the second is a screenshot of the filtered mage on iphone screen, the third is the filtered image written to disk.
Digging into that I remembered that texel and pixel aren't the same thing, so probably I'm not mapping them correctly. I'd like to have a 1:1 position ratio and probably is not happening. How can I achieve that? Thanks, Andrea

As stated in the comment by Brad, the solution was to simply confront the texel color in a range of values. This is due to the float precision (I'm feeling stupid writing it right now, it was pretty obvious). The starting image was with fixed controlled color, but since the original image is sampled probably the information is not equal from the starting image.
Here is the correct fragment shader:
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
bool compareVectors (lowp vec3 sample,lowp vec3 texel){
bool result;
if ( abs(texel.r-sample.r) > 0.1 ) {
return result = false;
}
if ( abs(texel.g-sample.g) > 0.1 ) {
return result = false;
}
if ( abs(texel.b-sample.b) > 0.1 ) {
return result = false;
}
return result = true;
}
void main()
{
lowp vec3 tc = vec3(0.0, 0.0, 0.0);
lowp vec4 pixcol = texture2D(inputImageTexture, textureCoordinate).rgba;
lowp vec3 sampleColors[3];
lowp vec3 newColors[3];
sampleColors[0] = vec3(0.5, 0.5, 0.5);
sampleColors[1] = vec3(0.0, 0.0, 0.0);
sampleColors[2] = vec3(1.0, 1.0, 1.0);
newColors[0] = vec3(0.4, 0.4, 1.0);
newColors[1] = vec3(0.3, 0.4, 1.0);
newColors[2] = vec3(0.6, 0.7, 0.5);
if (pixcol.a >= 0.2) {
if (compareVectors (sampleColors[0],pixcol.rgb))
tc = newColors[0];
else if (compareVectors (sampleColors[1],pixcol.rgb))
tc = newColors[1];
else if (compareVectors (sampleColors[2],pixcol.rgb))
tc = newColors[2];
else
tc = pixcol.rgb;
}
else
tc = pixcol.rgb;
gl_FragColor = vec4(tc.rgb, pixcol.a);
}
I'd like to thanks Brad that found the answer. Hope this helps.

Related

Framebuffer Attachments In WebGL Producing Unexpected Results

I have a problem with framebuffer attachments. Basically, my framebuffers always worked fine, but for my last project I needed to initialize them with some color values. So I created an attachment with a texture containing the color values I wanted. This leads to some really unexplainable (to me) behaviour, you can see in the code below that I create a framebuffer with an attachment, then have one shader which renders a very simple shape to the framebuffer and another shader which reads out the values with some noise added to the readout position.
The weird thing is that
The readouts seem to be weirdly all over the place, if you delete the framebuffer attachment in the source (just comment out the lines so that an empty object remains in the array) you will see how it is meant to look like (notice the noisy edges, so writing to and reading from framebuffer works as expected).
Instead it looks like this:
Also changing the values of the attachment texture changes the result, which is weird as I never read from the framebuffer before writing to it. It seems as if the readouts return the initial color value most of the time (in this case gray).
If you remove the noise term or attach a constant noise term (independent of position), the readouts seem to work just fine.
(function main() {
const dim = [512, 512];
twgl.setDefaults({ attribPrefix: "a_" });
const gl = twgl.getContext(document.querySelector("canvas"));
gl.canvas.width = dim[0];
gl.canvas.height = dim[1];
const bfi = twgl.primitives.createXYQuadBufferInfo(gl);
const pgi = {
cross: twgl.createProgramInfo(gl, ["vs", "fs_cross"]),
noise: twgl.createProgramInfo(gl, ["vs", "fs_noise"])
};
const fbi = twgl.createFramebufferInfo(
gl,
[
{
attachment: twgl.createTexture(gl, {
src: Array(dim[0] * dim[1])
.fill([128, 128, 0, 0])
.flat()
})
}
],
...dim
);
(function frame() {
twgl.bindFramebufferInfo(gl, fbi);
gl.useProgram(pgi.cross.program);
twgl.setUniforms(pgi.cross, {
u_resolution: dim
});
twgl.setBuffersAndAttributes(gl, pgi.cross, bfi);
twgl.drawBufferInfo(gl, bfi);
twgl.bindFramebufferInfo(gl, null);
gl.useProgram(pgi.noise.program);
twgl.setUniforms(pgi.noise, {
u_framebuffer: fbi.attachments[0],
u_pi: Math.PI,
u_resolution: dim,
u_seed: Array(24).fill().map(Math.random)
});
twgl.setBuffersAndAttributes(gl, pgi.noise, bfi);
twgl.drawBufferInfo(gl, bfi);
window.requestAnimationFrame(frame);
})();
})();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_position;
attribute vec2 a_texcoord;
varying vec2 v_texcoord;
void main() {
v_texcoord = a_texcoord;
gl_Position = a_position;
}
</script>
<script id="fs_cross" type="x-shader/x-fragment">
precision highp float;
varying vec2 v_texcoord;
uniform vec2 u_resolution;
void main() {
if(sign(v_texcoord.x - 0.5) * sign(v_texcoord.y - 0.5) < 0.0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
} else {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
</script>
<script id="fs_noise" type="x-shader/x-fragment">
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_framebuffer;
uniform float u_pi;
uniform vec2 u_resolution;
uniform vec2 u_seed[12];
vec2 normal(vec2 uv) {
float scl = sqrt(-2.0 * log(uv.x));
float ang = 2.0 * u_pi * uv.y;
return vec2(scl * cos(ang), scl * sin(ang));
}
vec2 noisySample(sampler2D tex, vec2 coord) {
vec2 sum = vec2(0.0, 0.0);
vec2 uni = vec2(0.0, 0.0);
for(int i = 0; i < 6; i++) {
uni = fract(uni + vec2(
dot(gl_FragCoord.xy + sin(gl_FragCoord.xy), u_seed[i]),
dot(gl_FragCoord.xy + sin(gl_FragCoord.xy), u_seed[i + 6])
));
vec2 nc = coord + normal(uni) / u_resolution;
sum += texture2D(u_framebuffer, nc).rg;
}
return sum / 6.0;
}
void main() {
vec2 tmp = noisySample(u_framebuffer, v_texcoord);
gl_FragColor = vec4(tmp, 0.5, 1.0);
}
</script>
<canvas></canvas>

OpenGL-ES: selective blending

I'm using GPUImage's corner detector for extracting corners from camera captured frames. I'd like to render sparkles at the corners coordinates. When I've got the corners coordinates I pass it to my sparkles generator (pretty similar to GPUImage's crosshair generator):
public func renderSparkles(_ positions:[Position]) {
imageFramebuffer.activateFramebufferForRendering()
imageFramebuffer.timingStyle = .stillImage
glEnable(GLenum(GL_POINT_SPRITE_OES))
sparklesShader.use()
uniformSettings.restoreShaderSettings(sparklesShader)
clearFramebufferWithColor(Color.transparent)
guard let positionAttribute = sparklesShader.attributeIndex("position") else {
fatalError("A position attribute was missing from the shader program during rendering.")
}
let convertedPositions = positions.flatMap{$0.toGLArray()}
glVertexAttribPointer(positionAttribute, 2, GLenum(GL_FLOAT), 0, 0, convertedPositions)
glDrawArrays(GLenum(GL_POINTS), 0, GLsizei(positions.count))
notifyTargets()
}
In the fragment shader of generator I draw sparkle at the corner coordinate:
uniform lowp vec3 crosshairColor;
varying highp vec2 centerLocation;
void main()
{
lowp vec2 distanceFromCenter = abs(centerLocation - gl_PointCoord.xy);
lowp float r = length(distanceFromCenter)*0.042;
lowp float a = atan(distanceFromCenter.y,distanceFromCenter.x);
lowp float f = abs(cos(a*2.0))*0.100*-0.322/0.224;
lowp float b = abs(cos(a*2.0))*0.078*-0.882/17.088;
lowp float c = abs(cos(a*2.0))*0.030*-0.178*0.688;
lowp vec4 color = vec4(1.0-smoothstep(f,b+0.04,r)) + vec4(1.0-smoothstep(b,c+0.02,r/0.644)) + vec4(1.0-smoothstep(b,c+0.011,r/0.764));
gl_FragColor = color;
}
Here is an example of output I've got
How could I blend the sparkles?
In case if someone else will find it helpful, here is the code of rendering sparkles method based on Rabbid76 suggestion:
public func renderSparkles(_ positions:[Position]) {
imageFramebuffer.activateFramebufferForRendering()
imageFramebuffer.timingStyle = .stillImage
glEnable(GLenum(GL_POINT_SPRITE_OES))
glEnable(GLenum(GL_BLEND))
glBlendFunc(GLenum(GL_SRC_ALPHA),GLenum(GL_ONE_MINUS_CONSTANT_ALPHA))
sparklesShader.use()
uniformSettings.restoreShaderSettings(sparklesShader)
clearFramebufferWithColor(Color.transparent)
guard let positionAttribute = sparklesShader.attributeIndex("position") else {
fatalError("A position attribute was missing from the shader program during rendering.")
}
let convertedPositions = positions.flatMap{$0.toGLArray()}
glVertexAttribPointer(positionAttribute, 2, GLenum(GL_FLOAT), 0, 0, convertedPositions)
glDrawArrays(GLenum(GL_POINTS), 0, GLsizei(positions.count))
notifyTargets()
}

GPUImage add hue/color adjustments per-RGB channel (adjust reds to be more pink or orange)

Stumped trying to adjust the hue of a specific channel (or perhaps, more specifically, a specific range of colors - in this case, reds). Looking at the hue filter, I thought maybe I might get somewhere by commenting out the green and blue modifiers, impacting the changes on only the red channel:
precision highp float;
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform mediump float hueAdjust;
const highp vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
const highp vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);
const highp vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);
const highp vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);
const highp vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);
const highp vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);
void main ()
{
// Sample the input pixel
highp vec4 color = texture2D(inputImageTexture, textureCoordinate);
// Convert to YIQ
highp float YPrime = dot (color, kRGBToYPrime);
highp float I = dot (color, kRGBToI);
highp float Q = dot (color, kRGBToQ);
// Calculate the hue and chroma
highp float hue = atan (Q, I);
highp float chroma = sqrt (I * I + Q * Q);
// Make the user's adjustments
hue += (-hueAdjust); //why negative rotation?
// Convert back to YIQ
Q = chroma * sin (hue);
I = chroma * cos (hue);
// Convert back to RGB
highp vec4 yIQ = vec4 (YPrime, I, Q, 0.0);
color.r = dot (yIQ, kYIQToR);
// --> color.g = dot (yIQ, kYIQToG);
// --> color.b = dot (yIQ, kYIQToB);
// Save the result
gl_FragColor = color;
}
);
But that just leaves the photo either grey/blue and washed-out or purplish green. Am I on the right track? If not, how can I modify this filter to affect individual channels while leaving the others intact?
Some examples:
Original, and the effect I'm trying to achieve:
(The second image is almost unnoticeably different, however the red channel's hue has been made slightly more pinker. I need to be able to adjust it between pink<->orange).
But here's what I get with B and G commented out:
(Left side: <0º, right side: >0º)
It looks to me like it's not affecting the hue of the reds in the way I'd like it to; possibly I'm approaching this incorrectly, or if I'm on the right track, this code isn't correctly adjusting the red channel hue?
(I also tried to achieve this effect using the GPUImageColorMatrixFilter, but I didn't get very far with it).
Edit: here's my current iteration of the shader using #VB_overflow's code + GPUImage wrapper, which is functionally affecting the input image in a way similar to what I'm aiming for:
#import "GPUImageSkinToneFilter.h"
#implementation GPUImageSkinToneFilter
NSString *const kGPUImageSkinToneFragmentShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
// [-1;1] <=> [pink;orange]
uniform highp float skinToneAdjust; // will make reds more pink
// Other parameters
uniform mediump float skinHue;
uniform mediump float skinHueThreshold;
uniform mediump float maxHueShift;
uniform mediump float maxSaturationShift;
// RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
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 = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
// HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
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);
}
// Main
void main ()
{
// Sample the input pixel
highp vec4 colorRGB = texture2D(inputImageTexture, textureCoordinate);
// Convert color to HSV, extract hue
highp vec3 colorHSV = rgb2hsv(colorRGB.rgb);
highp float hue = colorHSV.x;
// check how far from skin hue
highp float dist = hue - skinHue;
if (dist > 0.5)
dist -= 1.0;
if (dist < -0.5)
dist += 1.0;
dist = abs(dist)/0.5; // normalized to [0,1]
// Apply Gaussian like filter
highp float weight = exp(-dist*dist*skinHueThreshold);
weight = clamp(weight, 0.0, 1.0);
// We want more orange, so increase saturation
if (skinToneAdjust > 0.0)
colorHSV.y += skinToneAdjust * weight * maxSaturationShift;
// we want more pinks, so decrease hue
else
colorHSV.x += skinToneAdjust * weight * maxHueShift;
// final color
highp vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);
// display
gl_FragColor = vec4(finalColorRGB, 1.0);
}
);
#pragma mark -
#pragma mark Initialization and teardown
#synthesize skinToneAdjust;
#synthesize skinHue;
#synthesize skinHueThreshold;
#synthesize maxHueShift;
#synthesize maxSaturationShift;
- (id)init
{
if(! (self = [super initWithFragmentShaderFromString:kGPUImageSkinToneFragmentShaderString]) )
{
return nil;
}
skinToneAdjustUniform = [filterProgram uniformIndex:#"skinToneAdjust"];
skinHueUniform = [filterProgram uniformIndex:#"skinHue"];
skinHueThresholdUniform = [filterProgram uniformIndex:#"skinHueThreshold"];
maxHueShiftUniform = [filterProgram uniformIndex:#"maxHueShift"];
maxSaturationShiftUniform = [filterProgram uniformIndex:#"maxSaturationShift"];
self.skinHue = 0.05;
self.skinHueThreshold = 50.0;
self.maxHueShift = 0.14;
self.maxSaturationShift = 0.25;
return self;
}
#pragma mark -
#pragma mark Accessors
- (void)setSkinToneAdjust:(CGFloat)newValue
{
skinToneAdjust = newValue;
[self setFloat:newValue forUniform:skinToneAdjustUniform program:filterProgram];
}
- (void)setSkinHue:(CGFloat)newValue
{
skinHue = newValue;
[self setFloat:newValue forUniform:skinHueUniform program:filterProgram];
}
- (void)setSkinHueThreshold:(CGFloat)newValue
{
skinHueThreshold = newValue;
[self setFloat:newValue forUniform:skinHueThresholdUniform program:filterProgram];
}
- (void)setMaxHueShift:(CGFloat)newValue
{
maxHueShift = newValue;
[self setFloat:newValue forUniform:maxHueShiftUniform program:filterProgram];
}
- (void)setMaxSaturationShift:(CGFloat)newValue
{
maxSaturationShift = newValue;
[self setFloat:newValue forUniform:maxSaturationShiftUniform program:filterProgram];
}
#end
I made an example on ShaderToy. Use latest Chrome to see it, on my side it does not work on Firefox or IE because it uses a video as input.
After some experiments it seems to me that for red hues to be more "pink" you need to decrease the hue, but to get more "orange" you need to increase saturation.
In the code I convert to HSV instead of YIQ because this is faster, makes tweaking saturation possible and still allow to tweak hue. Also HSV components are in a [0-1] interval, so no need to handle radians.
So here is how this is done :
You choose a reference hue or color (in your case a red hue)
Shader compute the "distance" from current pixel hue to ref hue
Based on this distance, decrease hue if you want pink, increase saturation if you want orange
It is important to note that hue behaves differently than saturation and value: it should be treated as an angle (more info here).
The reference hue should be hardcoded, chosen by user (by color picking image), or found by analysing image content.
There are many different possible ways the compute the distance, in the example I chose to use the angular distance between hues.
You also need to apply some kind of filtering after computing the distance to "select" only closest colors, like this gaussian like function.
Here is the code, without the ShaderToy stuff:
precision highp float;
// [-1;1] <=> [pink;orange]
const float EFFECT_AMOUNT = -0.25; // will make reds more pink
// Other parameters
const float SKIN_HUE = 0.05;
const float SKIN_HUE_TOLERANCE = 50.0;
const float MAX_HUE_SHIFT = 0.04;
const float MAX_SATURATION_SHIFT = 0.25;
// RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
// HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
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);
}
// Main
void main ()
{
// Sample the input pixel
vec4 colorRGB = texture2D(inputImageTexture, textureCoordinate);
// get effect amount to apply
float skin_tone_shift = EFFECT_AMOUNT;
// Convert color to HSV, extract hue
vec3 colorHSV = rgb2hsv(colorRGB.rgb);
float hue = colorHSV.x;
// check how far from skin hue
float dist = hue - SKIN_HUE;
if (dist > 0.5)
dist -= 1.0;
if (dist < -0.5)
dist += 1.0;
dist = abs(dist)/0.5; // normalized to [0,1]
// Apply Gaussian like filter
float weight = exp(-dist*dist*SKIN_HUE_TOLERANCE);
weight = clamp(weight, 0.0, 1.0);
// We want more orange, so increase saturation
if (skin_tone_shift > 0.0)
colorHSV.y += skin_tone_shift * weight * MAX_SATURATION_SHIFT;
// we want more pinks, so decrease hue
else
colorHSV.x += skin_tone_shift * weight * MAX_HUE_SHIFT;
// final color
vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);
// display
gl_FragColor = vec4(finalColorRGB, 1.0);
}
More Orange:
More Pink:
--EDIT--
It seems to me that you are not setting the uniform values in your ObjectiveC code. If you forget this shader will get zero for all those.
Code should look like this :
- (id)init
{
if(! (self = [super initWithFragmentShaderFromString:kGPUImageSkinToneFragmentShaderString]) )
{
return nil;
}
skinToneAdjustUniform = [filterProgram uniformIndex:#"skinToneAdjust"];
[self setFloat:0.5 forUniform:skinToneAdjustUniform program:filterProgram]; // here 0.5 so should increase saturation
skinHueUniform = [filterProgram uniformIndex:#"skinHue"];
self.skinHue = 0.05;
[self setFloat:self.skinHue forUniform:skinHueUniform program:filterProgram];
skinHueToleranceUniform = [filterProgram uniformIndex:#"skinHueTolerance"];
self.skinHueTolerance = 50.0;
[self setFloat:self.skinHueTolerance forUniform:skinHueToleranceUniform program:filterProgram];
maxHueShiftUniform = [filterProgram uniformIndex:#"maxHueShift"];
self.maxHueShift = 0.04;
[self setFloat:self.maxHueShift forUniform:maxHueShiftUniform program:filterProgram];
maxSaturationShiftUniform = [filterProgram uniformIndex:#"maxSaturationShift"];
self.maxSaturationShift = 0.25;
[self setFloat:self.maxSaturationShift forUniform:maxSaturationShiftUniform program:filterProgram];
return self;
}
#end

Drawing a grid in a WebGL fragment shader

I'm working on porting a ZUI from SVG over to WebGL for a few reasons, and I'd like to render a grid using a fragment shader.
Here's the basic effect I'm going for https://dl.dropboxusercontent.com/u/412963/steel/restel_2.mp4
I'd like to have a triangle that has thin, 1px lines every 10 units, and a thicker 2px line every 100 units (the units here being arbitrary but consistent with world-space, not screen-space).
Here's what I have so far, without the secondary thicker lines like in the video (note that this is literally a copy from my open buffer, and obviously isn't right):
Vertex Shader:
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying float vX;
varying float vY;
void main(void) {
vX = aVertexPosition.x;
vY = aVertexPosition.y;
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
Fragment Shader:
precision mediump float;
uniform vec2 resolution;
uniform float uZoomFactor;
varying float vX;
varying float vY;
void main(void) {
float distance = gl_FragCoord.z / gl_FragCoord.w;
float fuzz = 1.0 / distance;
float minorLineFreq;
if (distance > 10.0) {
minorLineFreq = 1.0;
} else if (distance > 5.0) {
minorLineFreq = 1.0;
} else {
minorLineFreq = 0.10;
}
float xd = mod(vX, minorLineFreq) * 88.1;
float yd = mod(vY, minorLineFreq) * 88.1;
if (xd < fuzz) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
} else if (yd < fuzz) {
gl_FragColor = vec4(0.0,0.0,0.0,1.0);
} else {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
It produces approximately the right image at a certain distance (but notice the banding effect where there's 2px lines instead of 1px):
Grid with banding
Zoomed in grid with unwanted thicker lines
So, how can I get a consistent grid, with 1px thick lines at every distance, all inside of a WebGL fragment shader?
I believe I've found an acceptable solution.
Using the following vertices (drawn in a triangle strip):
[ 1.0 1.0 0.0
-1.0 1.0 0.0
1.0 -1.0 0.0
-1.0 -1.0 0.0]
Vertex shader:
attribute vec4 aVertexPosition;
void main(void) {
gl_Position = aVertexPosition;
}
Fragment Shader:
precision mediump float;
uniform float vpw; // Width, in pixels
uniform float vph; // Height, in pixels
uniform vec2 offset; // e.g. [-0.023500000000000434 0.9794000000000017], currently the same as the x/y offset in the mvMatrix
uniform vec2 pitch; // e.g. [50 50]
void main() {
float lX = gl_FragCoord.x / vpw;
float lY = gl_FragCoord.y / vph;
float scaleFactor = 10000.0;
float offX = (scaleFactor * offset[0]) + gl_FragCoord.x;
float offY = (scaleFactor * offset[1]) + (1.0 - gl_FragCoord.y);
if (int(mod(offX, pitch[0])) == 0 ||
int(mod(offY, pitch[1])) == 0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
} else {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
Gives results (depending on the pitch and offset) like:
gl_FragCoord is already scaled to the render target resolution. So you can simply:
precision mediump float;
vec4 color = vec4(1.);
vec2 pitch = vec2(50., 50.);
void main() {
if (mod(gl_FragCoord.x, pitch[0]) < 1. ||
mod(gl_FragCoord.y, pitch[1]) < 1.) {
gl_FragColor = color;
} else {
gl_FragColor = vec4(0.);
}
}
https://glslsandbox.com/e#74754.0

How do I draw a filled circle with OpenGL ES on iPhone?

How do I draw a filled circle with openGl on iPhone ?
I've found many solutions but none of them work. Probably because there are many ways to do it. But what's the method with shortest code ?
For a truly smooth circle, you're going to want a custom fragment shader. For example, the following vertex shader:
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
varying vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
}
and fragment shader:
varying highp vec2 textureCoordinate;
const highp vec2 center = vec2(0.5, 0.5);
const highp float radius = 0.5;
void main()
{
highp float distanceFromCenter = distance(center, textureCoordinate);
lowp float checkForPresenceWithinCircle = step(distanceFromCenter, radius);
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * checkForPresenceWithinCircle;
}
will draw a smooth red circle within a square that you draw to the screen. You'll need to supply vertices for your square to the position attribute and coordinates that range from 0.0 to 1.0 in X and Y to the inputTextureCoordinate attribute, but this will draw a circle that's as sharp as your viewport's resolution allows and do so very quickly.
One way would be to use GL_POINTS:
glPointSize(radius);
glBegin(GL_POINTS);
glVertex2f(x,y);
glEnd();
Another alternative would be to use GL_TRIANGLE_FAN:
radius = 1.0;
glBegin(GL_TRIANGLE_FAN);
glVertex2f(x, y);
for(int angle = 1; angle <= 360; angle = angle + 1)
glVertex2f(x + sind(angle) * radius, y + cosd(angle) * radius);
glEnd();
To get the verices of a circle:
float[] verts=MakeCircle2d(1,100,0,0)
public static float[] MakeCircle2d(float rad,int points,float x,float y)//x,y ofsets
{
float[] verts=new float[points*2+2];
boolean first=true;
float fx=0;
float fy=0;
int c=0;
for (int i = 0; i < points; i++)
{
float fi = 2*Trig.PI*i/points;
float xa = rad*Trig.sin(fi + Trig.PI)+x ;
float ya = rad*Trig.cos(fi + Trig.PI)+y ;
if(first)
{
first=false;
fx=xa;
fy=ya;
}
verts[c]=xa;
verts[c+1]=ya;
c+=2;
}
verts[c]=fx;
verts[c+1]=fy;
return verts;
}
Draw it as GL10.GL_LINES if you want a empty circle
gl.glDrawArrays(GL10.GL_LINES, 0, verts.length / 2);
Or draw it as GL10.GL_TRIANGLE_FAN if you want a filled one
gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, verts.length / 2);
Its java but it is really easy to convert to c++/objc
Here is a super fast way using shaders... Just make a Quad with a vertex buffer and set the UV's from -1 to 1 for each corner of the quad.
The vertex buffer in floats should look like:
NOTE: this needs a index buffer too.
var verts = new float[20]
{
-1, -1, 0, -1, -1,
-1, 1, 0, -1, 1,
1, 1, 0, 1, 1,
1, -1, 0, 1, -1,
};
#VS
attribute vec3 Position0;
attribute vec2 Texcoord0;
varying vec4 Position_VSPS;
varying vec2 Texcoord_VSPS;
uniform vec2 Location;
void main()
{
vec3 loc = vec3(Position0.xy + Location, Position0.z);
gl_Position = Position_VSPS = vec4(loc, 1);
Texcoord_VSPS = loc.xy;
}
#END
#PS
varying vec4 Position_VSPS;
varying vec2 Texcoord_VSPS;
uniform vec2 Location;
void main()
{
float dis = distance(Location, Texcoord_VSPS);
if (.1 - dis < 0.0) discard;
gl_FragData[0] = vec4(0, 0, 1, 1);
}
#END

Resources