How to draw triangles on sub-pixels in OpenGL ES? - ios

I'm trying to animate slow movement of two triangles in OpenGL ES.
Although my phase offset is a float which is incremented by 0.003f in every iteration of the run loop, my triangles don't move for a while and then just jump by one pixel to the right.
GLfloat vertices[ ] = {
rect.origin.x, rect.origin.y + rect.size.height,
rect.origin.x + rect.size.width, rect.origin.y + rect.size.height,
rect.origin.x, rect.origin.y,
rect.origin.x + rect.size.width, rect.origin.y
};
glVertexPointer(2, GL_FLOAT, 0, vertices);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
I confirmed that the values are floats with fractional digits. For example:
0, 0.003, 0.006, ... 0.3, 0.303, 0.306, ... and so on.
So instead of slowly interpolating between pixels OpenGL ES just jumps down. When I set vertices to values like 0.5 then I get them positioned between two pixels. But setting to 0.6 has no effect, 0.7 either, and then it just snaps to the next pixel. As if it was rounding those values to either full or dividable by 0.5. I want a smooth interpolation between pixels so the animation looks nice when it moves very slow.
What must I do so it interpolates vertices between sub-pixels instead of just snapping to a pixel?

Related

Trails effect, clearing a frame buffer with a transparent quad

I want to get a trails effect. I am drawing particles to a frame buffer. which is never cleared (accumulates draw calls). Fading out is done by drawing a black quad with small alpha, for example 0.0, 0.0, 0.0, 0.1. A two step process, repeated per frame:
- drawing a black quad
- drawing particles at new positions
All works nice, the moving particles produce long trails EXCEPT the black quad does not clear the FBO down to perfect zero. Faint trails remain forever (e.g. buffer's RGBA = 4,4,4,255).
I assume the problem starts when a blending function multiplies small values of FBO's 8bit RGBA (destination color) by, for example (1.0-0.1)=0.9 and rounding prevents further reduction. For example 4 * 0.9 = 3.6 -> rounded back to 4, for ever.
Is my method (drawing a black quad) inherently useless for trails? I cannot find a blend function that could help, since all of them multiply the DST color by some value, which must be very small to produce long trails.
The trails are drawn using a code:
GLuint drawableFBO;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &drawableFBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO); /// has an attached texture glFramebufferTexture2D -> FBOTextureId
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(fboClearShader);
glUniform4f(fboClearShader.uniforms.color, 0.0, 0.0, 0.0, 0.1);
glUniformMatrix4fv(fboClearShader.uniforms.modelViewProjectionMatrix, 1, 0, mtx.m);
glBindVertexArray(fboClearShaderBuffer);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glUseProgram(particlesShader);
glUniformMatrix4fv(shader.uniforms.modelViewProjectionMatrix, 1, 0, mtx.m);
glUniform1f(shader.uniforms.globalAlpha, 0.9);
glBlendFunc(GL_ONE, GL_ONE);
glBindTexture(particleTextureId);
glBindVertexArray(particlesBuffer);
glDrawArrays(GL_TRIANGLES, 0, 1000*6);
/// back to drawable buffer
glBindFramebuffer(GL_FRAMEBUFFER, drawableFBO);
glUseProgram(fullScreenShader);
glBindVertexArray(screenQuad);
glBlendFuncGL_ONE dFactor:GL_ONE];
glBindTexture(FBOTextureId);
glDrawArrays(GL_TRIANGLES, 0, 6);
Blending is not only defined by the by the blend function glBlendFunc, it is also defined by the blend equation glBlendEquation.
By default the source value and the destination values are summed up, after they are processed by the blend function.
Use a blend function which subtracts a tiny value from the destination buffer, so the destination color will slightly decreased in each frame and finally becomes 0.0.
The the results of the blend equations is clamped to the range [0, 1].
e.g.
dest_color = dest_color - RGB(0.01)
The blend equation which subtracts the source color form the destination color is GL_FUNC_REVERSE_SUBTRACT:
float dec = 0.01f; // should be at least 1.0/256.0
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE);
glUseProgram(fboClearShader);
glUniform4f(fboClearShader.uniforms.color, dec, dec, dec, 0.0);

Elliptical radial gradient in CGContext?

As far as I can tell you can use two methods to draw gradients in a CGContext, that's drawLinearGradient and drawRadialGradient. What I'm looking for is a way to define an elliptical gradient where I can define x and y radii.
An example of this capability in another environment (SVG).
<RadialGradient id="gradient" cx="50" cy="50" rx="20" ry="40" fx="150" fy="75">
The existing declaration for drawRadialGradient is as follows.
func drawRadialGradient(_ gradient: CGGradient,
startCenter: CGPoint,
startRadius: CGFloat,
endCenter: CGPoint,
endRadius: CGFloat,
options: CGGradientDrawingOptions)
Both start and end radii are scalar values, so all you can do is circles. How can I draw elliptical gradients in a CGContext?
You should be able to scale the context and use CGContextDrawRadialGradient(). If you scale down, there should be no artifacts. Does the following work?
CGContextRef context;
CGGradientRef gradient;
CGGradientDrawingOptions options;
CGPoint center;
CGFloat radiusX;
CGFloat radiusY;
CGFloat radius = MAX(radiusX, radiusY);
CGContextSaveGState(context);
// scale down by the smaller dimension, and translate so the center stays in place
if (radiusX < radiusY) {
CGContextTranslateCTM(context, center.x - (center.x * (radiusX / radiusY)), 0);
CGContextScaleCTM(context, radiusX / radiusY, 1.0);
}
else {
CGContextTranslateCTM(context, 0, center.y - (center.y * (radiusY / radiusX)));
CGContextScaleCTM(context, 1.0, radiusY / radiusX);
}
CGContextDrawRadialGradient(context, gradient, center, 0, center, radius, options);
CGContextRestoreGState(context);
Incidentally, I think this is roughly the behavior if you set the type property of CAGradientLayer to the undocumented and private value of #"radial". It uses the startPoint as the center, and the difference of the startPoint and endPoint to determine the radiusX and radiusY values (i.e. the endPoint defines a corner of the bounding box of the gradient, and the startPoint is the center). It does have odd behavior when you make the start and end point nearly the same, so there is probably more going on there than I have figured out (and probably why Apple never bothered to make it public).
The only thing I can think of would be to apply a scale transform with unequal x and y scale factors to your context before drawing the gradient. That would stretch it out of round, and should make it oval.
There's no "royal road". This facility is not built-in so you'll have to draw every pixel yourself. (There are probably third-party libraries that will do that for you.)

In iOS, arcs are malformed for certain start angles

I use the following code to draw an arc
double radius = 358.40001058578491;
startAngle = 0.13541347644783652;
double center_x= 684;
double center_y = 440;
std::complex<double> start1( std::polar(radius,startAngle) );
CGPoint targetStart1 = CGPointMake(start1.real() + center_x, start1.imag() +center_y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, targetStart1.x, targetStart1.y);
CGPathAddArc(path, NULL, center_x, center_y, radius, startAngle, 0.785, 0 );
CGContextAddPath(context, path);
CGContextSetLineWidth( context, 30 );
CGContextSetStrokeColorWithColor( context, targetColor.CGColor);
CGContextStrokePath(context);
CGPathRelease(path);
If u check it in retina, it looks like this:
My arc is the green arc. I have shown the place that the start angle is with a orange line. As I have shown in the red rectangle, there is an extra thing drawn in the very beginning of the arc. This happens not for all start angles, but only for certain start angles.
Do you have any idea why it happens?
Thanks.
In your original question, you specified a literal starting point that was not quite right and, as a result, Core Graphics will draw a line from that point to the start of the arc. And because that starting point was just a few pixels away from the actual start of the arc, it results in that curious rendering you illustrate in your question.
In your revised question, you're calculating the starting point, but I might suggest calculating it programmatically like so:
CGFloat centerX = 684.0;
CGFloat centerY = 440.0;
CGFloat radius = 360.0;
CGFloat startAngle = 0.135;
CGFloat endAngle = 0.785;
CGFloat startingX = centerX + radius * cosf(startAngle);
CGFloat startingY = centerY + radius * sinf(startAngle);
CGContextMoveToPoint(context, startingX, startingY);
CGContextAddArc(context, centerX, centerY, radius, startAngle, endAngle, 0);
CGContextSetLineWidth(context, 30);
CGContextSetStrokeColorWithColor(context, targetColor.CGColor);
CGContextStrokePath(context);
When I calculated it this way, there was no rounding errors that resulted in the artifact illustrated in your original question.
Note, if you're not drawing anything before the arc, you can just omit the CGContextMoveToPoint call altogether. You only need that "move to point" call if you've drawn something before the arc and don't want the path connecting from that CGContextGetPathCurrentPoint to the start of the arc.

How do I draw thousands of squares with glkit, opengl es2?

I'm trying to draw up to 200,000 squares on the screen. Or a lot of squares basically. I believe I'm just calling way to many draw calls, and it's crippling the performance of the app. The squares only update when I press a button, so I don't necessarily have to update this every frame.
Here's the code i have now:
- (void)glkViewControllerUpdate:(GLKViewController *)controller
{
//static float transY = 0.0f;
//float y = sinf(transY)/2.0f;
//transY += 0.175f;
GLKMatrix4 modelview = GLKMatrix4MakeTranslation(0, 0, -5.f);
effect.transform.modelviewMatrix = modelview;
//GLfloat ratio = self.view.bounds.size.width/self.view.bounds.size.height;
GLKMatrix4 projection = GLKMatrix4MakeOrtho(0, 768, 1024, 0, 0.1f, 20.0f);
effect.transform.projectionMatrix = projection;
_isOpenGLViewReady = YES;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
if(_model.updateView && _isOpenGLViewReady)
{
glClear(GL_COLOR_BUFFER_BIT);
[effect prepareToDraw];
int pixelSize = _model.pixelSize;
if(!_model.isReady)
return;
//NSLog(#"UPDATING: %d, %d", _model.rows, _model.columns);
for(int i = 0; i < _model.rows; i++)
{
for(int ii = 0; ii < _model.columns; ii++)
{
ColorModel *color = [_model getColorAtRow:i andColumn:ii];
CGRect rect = CGRectMake(ii * pixelSize, i*pixelSize, pixelSize, pixelSize);
//[self drawRectWithRect:rect withColor:c];
GLubyte squareColors[] = {
color.red, color.green, color.blue, 255,
color.red, color.green, color.blue, 255,
color.red, color.green, color.blue, 255,
color.red, color.green, color.blue, 255
};
// NSLog(#"Drawing color with red: %d", color.red);
int xVal = rect.origin.x;
int yVal = rect.origin.y;
int width = rect.size.width;
int height = rect.size.height;
GLfloat squareVertices[] = {
xVal, yVal, 1,
xVal + width, yVal, 1,
xVal, yVal + height, 1,
xVal + width, yVal + height, 1
};
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, squareVertices);
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, squareColors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(GLKVertexAttribPosition);
glDisableVertexAttribArray(GLKVertexAttribColor);
}
}
_model.updateView = YES;
}
First, do you really need to draw 200,000 squares? Your viewport only has 786,000 pixels total. You might be able to reduce the number of drawn objects without significantly impacting the overall quality of your scene.
That said, if these are smaller squares, you could draw them as points with a pixel size large enough to cover your square's area. That would require setting gl_PointSize in your vertex shader to the appropriate pixel width. You could then generate your coordinates and send them all to be drawn at once as GL_POINTS. That should remove the overhead of the extra geometry of the triangles and the individual draw calls you are using here.
Even if you don't use points, it's still a good idea to calculate all of the triangle geometry you need first, then send all that in a single draw call. This will significantly reduce your OpenGL ES API call overhead.
One other thing you could look into would be to use vertex buffer objects to store this geometry. If the geometry is static, you can avoid sending it on each drawn frame, or only update a part of it that has changed. Even if you just change out the data each frame, I believe using a VBO for dynamic geometry has performance advantages on the modern iOS devices.
Can you not try to optimize it somehow? I'm not terribly familiar with graphics type stuff, but I'd imagine that if you are drawing 200,000 squares chances that all of them are actually visible seems to be unlikely. Could you not add some sort of isVisible tag for your mySquare class that determines whether or not the square you want to draw is actually visible? Then the obvious next step is to modify your draw function so that if the square isn't visible, you don't draw it.
Or are you asking for someone to try to improve the current code you have, because if your performance is as bad as you say, I don't think making small changes to the above code will solve your problem. You'll have to rethink how you're doing your drawing.
It looks like what your code is actually trying to do is take a _model.rows × _model.columns 2D image and draw it upscaled by _model.pixelSize. If -[ColorModel getColorAtRow:andColumn:] is retrieving 3 bytes at a time from an array of color values, then you may want to consider uploading that array of color values into an OpenGL texture as GL_RGB/GL_UNSIGNED_BYTE data and letting the GPU scale up all of your pixels at once.
Alternatively, if scaling up the contents of your ColorModel is the only reason that you’re using OpenGL ES and GLKit, you might be better off wrapping your color values into a CGImage and allowing UIKit and Core Animation do the drawing for you. How often do the color values in the ColorModel get updated?

How to use GLKit to rotate about the center of a sprite off an atlas

I have a texture that follows a user's finger in GLKit. I calculate the radian to draw the angle at using arctan between the two points.
Part of the trick here is to keep the object centered underfed the finger. So i have introduced the idea of an anchor point so that things can be drawn relative to their origin or center. My goal is to move the sprite into place and then rotate. I have the following code in my renderer.
// lets adjust for our location based on our anchor point.
GLKVector2 adjustment = GLKVector2Make(self.spriteSize.width * self.anchorPoint.x,
self.spriteSize.height * self.anchorPoint.y);
GLKVector2 adjustedPosition = GLKVector2Subtract(self.position, adjustment);
GLKMatrix4 modelMatrix = GLKMatrix4Multiply(GLKMatrix4MakeTranslation(adjustedPosition.x, adjustedPosition.y, 1.0), GLKMatrix4MakeScale(adjustedScale.x, adjustedScale.y, 1));
modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotation, 0, 0, 1);
effect.transform.modelviewMatrix = modelMatrix;
effect.transform.projectionMatrix = scene.projection;
One other note is that my sprite is on a texture alias. If i take out my rotation my sprite draws correctly centered under my finger. My project matrix is GLKMatrix4MakeOrtho(0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame), 0, 1, -1); so it matches the UIkit and the view its embedded in.
I ended up having to add a little more math to calculate additional offsets before i rotate.
// lets adjust for our location based on our anchor point.
GLKVector2 adjustment = GLKVector2Make(self.spriteSize.width * self.anchorPoint.x,
self.spriteSize.height * self.anchorPoint.y);
// we need to further adjust based on our so we can calucate the adjust based on our anchor point in our image.
GLKVector2 angleAdjustment;
angleAdjustment.x = adjustment.x * cos(self.rotation) - adjustment.y * sin(self.rotation);
angleAdjustment.y = adjustment.x * sin(self.rotation) + adjustment.y * cos(self.rotation);
// now create our real position.
GLKVector2 adjustedPosition = GLKVector2Subtract(self.position, angleAdjustment);
GLKMatrix4 modelMatrix = GLKMatrix4Multiply(GLKMatrix4MakeTranslation(adjustedPosition.x, adjustedPosition.y, 1.0), GLKMatrix4MakeScale(adjustedScale.x, adjustedScale.y, 1));
modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotation, 0, 0, 1);
This will create an additional adjustment based on where in the image we want to rotate and then transform based on that. This works like a charm..
There is a similar code I used to rotate a sprite around its center
First you move it to the position, then you rotate it, then you move it back halfsprite
- (GLKMatrix4) modelMatrix {
GLKMatrix4 modelMatrix = GLKMatrix4Identity;
float radians = GLKMathDegreesToRadians(self.rotation);
modelMatrix = GLKMatrix4Multiply(
GLKMatrix4Translate(modelMatrix, self.position.x , self.position.y , 0),
GLKMatrix4MakeRotation(radians, 0, 0, 1));
modelMatrix = GLKMatrix4Translate(modelMatrix, -self.contentSize.height/2, -self.contentSize.width/2 , 0);
return modelMatrix;
}

Resources