So I'm having a bit of a problem with an OpenGL 1.1 skewed drawing.
Background:
Basically the app is a painting app (some code borrowed from glPaint) in which the user can draw with various colors and point widths. When they exit the drawing screen I use glReadPixels to persist the pixel color data in RGBA format. When they come back to continue drawing I take the color data from disk, put it into a colorPointer and I generate an array of vertices like so:
typedef struct _vertexStruct{ GLfloat position[2];} vertexStruct;
vertexStruct vertices[VERTEX_SIZE];
And the loop
GLfloat row = 0.0f;
GLfloat col = 768.0f;
for (int i = 0; i < (768 * 1024); i++) {
if (row == 1024.0f) {
col-- ;
row = 0.0f;
}
else {
row++;
}
vertices[i].position[0] = row;
vertices[i].position[1] = [self bounds].size.height - col;
}
And here are the actual drawing calls:
glVertexPointer(2, GL_FLOAT, sizeof(vertexStruct),&vertices[0].position);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(pixelData.data), pixelData.data);
glEnableClientState(GL_COLOR_ARRAY);
glDrawArrays(GL_POINTS, 0, VERTEX_SIZE);
glDisableClientState(GL_COLOR_ARRAY);
// Display the buffer
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
So, the drawing succeeds but it is skewed off to the left of where it should be. I thought that I was compensating for OpenGL(I'm using standard bottom=0,left=0 coord system) --> UIKit coordinate system differences with the
vertices[i].position[1] = [self bounds].size.height - col;
call in the loop but this may just be a naive assumption. Anyone have any clues as to what I'm doing wrong or perhaps what I need to be doing addition to have the drawing appear in the right place?? Thanks in advance!
UPDATE: Solved, I just drew the saved image to a texture (NPOT texture)! If anyone else has worries about drawing NPOT textures, it should work, worked for me at least, with the only caveat being that it's not supported on earlier devices...
Related
I need to create a sound wave animation like Siri (SiriAnim)
With OpenGL I'v got a shape of wave:
Here is my code:
#property (strong, nonatomic) EAGLContext *context;
#property (strong, nonatomic) GLKBaseEffect *effect;
// .....
- (void)setupGL {
[EAGLContext setCurrentContext:self.context];
glEnable(GL_CULL_FACE);
self.effect = [[GLKBaseEffect alloc] init];
self.effect.useConstantColor = GL_TRUE;
self.effect.constantColor = GLKVector4Make(0.0f, 1.0f, 0.0f, 1.0f);
}
// .....
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(_curRed, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
GLfloat line[1440];
for (int i = 0; i < 1440; i += 4) {
float x = 0.002*i - 0.75;
float K = 8.0f;
float radians = DEGREES_TO_RADIANS(i/2);
float func_x = 0.4 *
pow(K/(K + pow(radians-M_PI,4.0f)), K) *
cos(radians-M_PI);
line[i] = x;
line[i+1] = func_x;
line[i+2] = x;
line[i+3] = -func_x;
}
GLuint bufferObjectNameArray;
glGenBuffers(1, &bufferObjectNameArray);
glBindBuffer(GL_ARRAY_BUFFER, bufferObjectNameArray);
glBufferData(
GL_ARRAY_BUFFER,
sizeof(line),
line,
GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(
GLKVertexAttribPosition,
2,
GL_FLOAT,
GL_FALSE,
2*4,
NULL);
glLineWidth(15.0);
glDrawArrays(GL_LINES, 0, 360);
}
BUT! I'm confused because i can't find any solutions for gradient. After a lot of time of searching I even have a strong suspicion that such task is impossible (because GLKBaseEffect *effect . constantColor i think).
So! Could anyone help me with any solution for this task?
Can this problem be solved with shaders or textures (the worst solution)?
Bless you for your answers!
Although this could be done with a texture, I think the easiest way to accomplish this is by using OpenGL's default color interpolation. If you make the top vertex of the lines you're drawing a light blue, and the bottom vertex a darker blue, the GPU will automatically interpolate the colors between them to gradually change, and produce the gradient effect you're looking for.
The easiest way to implement this in your code is to make room in your buffer, the "lines" array, for the color of every single vertex of the line, and set up your shaders to output this value. That means you'll have to add inputs and outputs for this color to your vertex and pixel shaders. The idea is to pass it from the vertex to the pixel shader, and the pixel shader outputs the value unmodified. The hardware handles the interpolation between colors automatically for you(!).
Many modern OpenGL tutorials have examples of doing this. One free online one is from LearnOpenGL's Shader tutorial. If you have the money, though, my favorite explanation of buffers, shaders, and the pipeline itself is in Graham Sellers' OpenGL SuperBible. If you plan on using OpenGL often and really learning it, it's an invaluable desktop reference.
Basically what I'm doing is making a simple finger drawing application. I have a single class that takes the input touch points and does all the fun work of turning those touch points into bezier curves, calculating vertices from those, etc. That's all working fine.
The only interesting constraint I'm working with is that I need strokes to blend on on top of each other, but not with themselves. Imagine having a scribbly line that crosses itself and has 50% opacity. Where the line crosses itself, there should be no visible blending (it should all look like the same color). However, the line SHOULD blend with the rest of the drawing below it.
To accomplish this, I'm using two textures. A back texture and a scratch texture. While the line is actively being updated (during the course of the stroke), I disable blending, draw the vertices on the scratch texture, then enable blending, and draw the back texture and scratch texture into my frame buffer. When the stroke is finished, I draw the scratch texture into the back texture, and we're ready to start the next stroke.
This all works very smoothly on a newer device, but on older devices the frame rate takes a severe hit. From some testing, it seems that the biggest performance hit is in drawing the textures to the frame buffer, because they're relatively large textures (due to the iPhone's retina resolution).
Does anybody have any hints on some strategies to work around this? I'm happy to provide more specifics or code, I'm just not sure where to start.
I am using OpenGL ES 2.0, targeting iOS 7.0, but testing on an iPhone 4S
The following is code I'm using to draw into the framebuffers:
- (void)drawRect:(CGRect)rect
{
[self drawRect:rect
ofTexture:_backTex
withOpacity:1.0];
if (_activeSpriteStroke)
{
[self drawStroke:_activeSpriteStroke
intoFrameBuffer:0];
}
}
Those rely on the following few methods:
- (void)drawRect:(CGRect)rect
ofTexture:(GLuint)tex
withOpacity:(CGFloat)opacity
{
_texShader.color = GLKVector4Make(1.0, 1.0, 1.0, opacity);
[_texShader prepareToDraw];
glBindTexture(GL_TEXTURE_2D, tex);
glBindVertexArrayOES(_texVertexVAO);
glBindBuffer(GL_ARRAY_BUFFER, _texVertexVBO);
[self bufferTexCoordsForRect:rect];
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArrayOES(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, tex);
}
- (void)drawStroke:(AHSpriteStroke *)stroke
intoFrameBuffer:(GLuint)frameBuffer
{
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
[self renderStroke:stroke
ontoTexture:_scratchTex
inFrameBuffer:_scratchFrameBuffer];
if (frameBuffer == 0)
{
[self bindDrawable];
}
else
{
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
}
[self setScissorRect:_activeSpriteStroke.boundingRect];
glEnable(GL_SCISSOR_TEST);
[self drawRect:self.bounds
ofTexture:_scratchTex
withOpacity:stroke.lineOpacity];
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
}
- (void)renderStroke:(AHSpriteStroke *)stroke
ontoTexture:(GLuint)tex
inFrameBuffer:(GLuint)framebuffer
{
glBindFramebuffer(GL_FRAMEBUFFER, _msFrameBuffer);
glBindTexture(GL_TEXTURE_2D, tex);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
[stroke render];
glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, framebuffer);
glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, _msFrameBuffer);
glResolveMultisampleFramebufferAPPLE();
const GLenum discards[] = { GL_COLOR_ATTACHMENT0 };
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 1, discards);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
And a couple of the helper methods just for completeness so you can follow it:
- (void)bufferTexCoordsForRect:(CGRect)rect
{
AHTextureMap textureMaps[4] =
{
[self textureMapForPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))
inRect:self.bounds],
[self textureMapForPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect))
inRect:self.bounds],
[self textureMapForPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect))
inRect:self.bounds],
[self textureMapForPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect))
inRect:self.bounds]
};
glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(AHTextureMap), textureMaps, GL_DYNAMIC_DRAW);
}
- (AHTextureMap)textureMapForPoint:(CGPoint)point
inRect:(CGRect)outerRect
{
CGPoint pt = CGPointApplyAffineTransform(point, CGAffineTransformMakeScale(self.contentScaleFactor, self.contentScaleFactor));
return (AHTextureMap) { { pt.x, pt.y }, { point.x / outerRect.size.width, 1.0 - (point.y / outerRect.size.height) } };
}
From what I understand you are drawing each quad in a separate draw call.
If your stroke consist of a lot of quads(from sampling the bezier curve) your code will make many draw calls per frame.
Having many draw calls in OpenGL ES 2 on older iOS devices will probably generate a bottle neck on the CPU.
The reason is that draw calls in OpenGL ES 2 can have a lot of overhead in the driver.
The driver tries to organize the draw calls you make into something the GPU can digest and it does this organization using the CPU.
If you intend to draw many quads to simulate a brush stroke you should update a vertex buffer to contain many quads and then draw it with one draw call instead of making a draw call per quad.
You can verify that your bottle neck is in the CPU with the Time Profiler instrument.
You can then check if the CPU is spending most of his time on the OpenGL draw call methods or rather on your own functions.
If the CPU spends most of it's time on the OpenGL draw call methods it is likely because you are making too many draw calls per frame.
I am having trouble accomplishing something that I thought was going to be much easier. I am trying to run a method whenever a non transparent part of a picture inside a UIImage touches another non-transparent part of an image contained within a UIImage. I have included an example to help further explain my question.
As you can see in the image above, I have two triangles that are both inside a UIImage. The triangles are both PNG pictures. Only the triangle is visible because the background has been made transparent. Both of the UIImages are inside a UIImageView. I want to be able to run a method when the visible part of the triangle touches the visible part of the other triangle. Can someone please help me?
The brute force solution to this problem is to create a 2D array of bools for each image, where each array entry is true for an opaque pixel, and false for the transparent pixels. If CGRectIntersectsRect returns true (indicating a possible collision), then the code scans the two arrays (with appropriate offsets depending on relative positions) to check for an actual collision. That gets complicated, and is computationally intensive.
One alternative to the brute force method is to use OpenGLES to do all of the work. This is still a brute force solution, but it offloads the work to the GPU, which is much better at such things. I'm not an expert on OpenGLES, so I'll leave the details to someone else.
A second alternative is to place restrictions on the problem that allow it to be solved more easily. For example, given two triangles A and B, collisions can only occur if one of the vertices of A is contained within the area of B, or if one of the vertices of B is in A. This problem can be solved using the UIBezierPath class in objective-C. The UIBezierPath can be used to create a path in the shape of a triangle. Then the containsPoint: method of UIBezierPath can be used to check if the vertex of the opposing triangle is contained in the area of the target triangle.
In summary, the solution is to add a UIBezierPath property to each object. Initialize the UIBezierPath to approximate the object's shape. If CGRectIntersectsRect indicates a possible collision, then check if the vertices of one object are contained in the area of the other object using the containsPoint: method.
What I did is:
counted the amount of non alpha pixels in image A
did the same for image B
merged A + B image into one image: C
compared the resulting pixel count
If pixes amount was less after merging then we have a hit.
if (C.count < A.count + B.count) -> we have a hit
+ (int)countPoints:(UIImage *)img
{
CGImageRef cgImage = img.CGImage;
NSUInteger width = img.size.width;
NSUInteger height = img.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t bitsPerComponent = 8;
size_t bytesPerPixel = 1;
size_t bytesPerRow = width * bitsPerComponent * bytesPerPixel;
size_t dataSize = bytesPerRow * height;
unsigned char *bitmapData = malloc(dataSize);
memset(bitmapData, 0, dataSize);
CGContextRef bitmap = CGBitmapContextCreate(bitmapData, width, height, bitsPerComponent, width, NULL,(CGBitmapInfo)kCGImageAlphaOnly);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM(bitmap, 0, img.size.height);
CGContextScaleCTM(bitmap, 1.0, -1.0);
CGContextDrawImage(bitmap, CGRectMake(0, 0, width, height), cgImage);
int p = 0;
int i = 0;
while (i < width * height) {
if (bitmapData[i] > 0) {
p++;
}
i++;
}
free(bitmapData);
bitmapData = NULL;
CGContextRelease(bitmap);
bitmap = NULL;
//NSLog(#"points: %d",p);
return p;
}
+ (UIImage *)marge:(UIImage *)imageA withImage:(UIImage *)imageB {
CGSize itemSize = CGSizeMake(imageA.size.width, imageB.size.width);
UIGraphicsBeginImageContext(itemSize);
CGRect rect = CGRectMake(0,
0,
itemSize.width,
itemSize.height);
[imageA drawInRect:rect];
[imageB drawInRect:rect];
UIImage *overlappedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return overlappedImage;
}
I am using OpenGL 2.0 to draw a rectangle. Initially the viewport is such that i am looking from above and i can see my rectangle as i expected.
Then i start rotating this rectangle about the x-axis. When the angle of rotation equals -90deg (or +90 deg if rotating in the other direction), the rectangle disappears.
What i expect to see if the bottom surface of the rectangle when i rotate past 90deg/-90deg but instead the view disappears. It does re-appear with the total rotation angle is -270deg (or +270 deg) when the upper surface is just about ready to be shown.
How do i ensure that i can see the rectangle all along (both upper and lower surface has to be visible while rotating)?
Here' the relevant piece of code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
if ([touches count] == 1) {
CGPoint currLoc = [touch locationInView:self];
CGPoint lastLoc = [touch previousLocationInView:self];
CGPoint diff = CGPointMake(lastLoc.x - currLoc.x, lastLoc.y - currLoc.y);
rotX = -1 * GLKMathDegreesToRadians(diff.y / 2.0);
rotY = -1 * GLKMathDegreesToRadians(diff.x / 2.0);
totalRotationX += ((rotX * 180.0f)/3.141592f);
NSLog(#"rotX: %f, rotY: %f, totalRotationX: %f", rotX, rotY, totalRotationX);
//rotate around x axis
GLKVector3 xAxis = GLKMatrix4MultiplyVector3(GLKMatrix4Invert(_rotMatrix, &isInvertible), GLKVector3Make(1, 0, 0));
_rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotX, xAxis.v[0], 0, 0);
}
}
-(void)update{
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0, 0, -6.0f);
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, _rotMatrix);
self.effect.transform.modelviewMatrix = modelViewMatrix;
float aspect = fabsf(self.bounds.size.width / self.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0, 10.0f);
self.effect.transform.projectionMatrix = projectionMatrix;
}
- (void)setupGL {
NSLog(#"setupGL");
isInvertible = YES;
totalRotationX = 0;
[EAGLContext setCurrentContext:self.context];
glEnable(GL_CULL_FACE);
self.effect = [[GLKBaseEffect alloc] init];
// New lines
glGenVertexArraysOES(1, &_vertexArray);
glBindVertexArrayOES(_vertexArray);
// Old stuff
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
glGenBuffers(1, &_indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// New lines (were previously in draw)
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Position));
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Color));
_rotMatrix = GLKMatrix4Identity;
// New line
glBindVertexArrayOES(0);
initialized = 1;
}
I am a newbie to OpenGL and i am using the GLKit along with OpenGL 2.0
Thanks.
There are many causes for things not rendering in OpenGL. In this case, it was back face culling (see comments on the question). Back face culling is useful because it can ignore triangles facing away from the camera and save some rasterization/fragment processing time. Since many meshes/objects are watertight and you'd never want to see the inside anyway it's uncommon to actually want two-sided shading. This functionality starts with defining a front/back of a triangle. This is done with the order the vertices are given in (sometimes called winding direction). glFrontFace chooses the direction clockwise/counter-clockwise that defines forwards, glCullFace chooses to cull either front or back (I guess some could argue not much point in having both) and finally to enable/disable:
glEnable(GL_CULL_FACE); //discards triangles facing away from the camera
glDisable(GL_CULL_FACE); //default, two-sided rendering
Some other things I check for geometry not being visible include...
Is the geometry colour the same as the background. Choosing a non-black/white background can be handy here.
Is the geometry actually drawing within the viewing volume. Throw in a simple object (immediate mode helps) and maybe use identity projection/modelview to rule them out.
Is the viewing volume correct. Near/far planes too far apart (causing z-fighting) or 0.0f near planes are common issues. Also when switching to a projection matrix, drawing anything on the Z=0 plane won't be visible any more.
Is blending enabled and everything's transparent.
Is the depth buffer not being cleared and causing subsequent frames to be discarded.
In fixed pipeline rendering, are glTranslate/glRotate transforms being carried over from the previous frame causing objects to shoot off into the distance. Always keep a glLoadIdentity at the top of the display function.
Is the rendering loop structured correctly - clear/draw/swapbuffers
Of course there's heaps more - geometry shaders not outputting anything, vertex shaders transforming vertices to the same position (so they're all degenerate), fragment shaders calling discard when they shouldn't, VBO binding/indexing issues etc. Checking GL errors is a must but never catches all mistakes.
I want to draw simple square with size of full screen with glDrawArray method in cocos2d. When retina is disabled everything draws as expected but when enabled - everything is half as big as it should be. (it seems like coordinate system in glDrawArray is not in points but in pixels)
Other draw functions works as expected but since I am drawing complicated shapes we have to use glDrawArray since it is much faster.
Any ideas how to solve this?
-(void) draw
{
CGPoint box[4];
CGPoint boxTex[4];
CGSize winSize = [[CCDirector sharedDirector] winSize];
//float boxSize = winSize.width;
box[0] = ccp(0,winSize.height); // top left
box[1] = ccp(0,0); // bottom left
box[2] = ccp(winSize.width,winSize.height);
box[3] = ccp(winSize.width,0);
boxTex[0] = ccp(0,1);
boxTex[1] = ccp(0,0);
boxTex[2] = ccp(1,1);
boxTex[3] = ccp(1,0);
// texture backround
glBindTexture(GL_TEXTURE_2D, self.sprite.texture.name);
glVertexPointer(2, GL_FLOAT, 0, box);
glTexCoordPointer(2, GL_FLOAT, 0, boxTex);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
Yes, the drawing is done in pixels, so in order to handle proper rendering on the retina display as well, you need to multiply your vertices by CC_CONTENT_SCALE_FACTOR():
for (int i = 0; i < 3; i++)
box[i] = ccpMult(box[i], CC_CONTENT_SCALE_FACTOR());
CC_CONTENT_SCALE_FACTOR returns 2 on retina devices instead of 1, so using it should take care of the scaling.