Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
421 views
in Technique[技术] by (71.8m points)

objective c - Repeating OpenGL-es texture bound to hills in cocos2d 2.0

ORIGINAL ARTICLE

I am in the process of trying to implement raywenderlich's tutorial on generating hills with repeating striped coordinates using cocos2d, This article was written for Cocos2D 1.0, and as I am trying to port it to Cocos2D 2.0 This means updating it for openGl-es 2. So far everything has worked perfectly, However I am having problems with getting the texture of the hill to repeat properly...

Here is my code:

Sending the hills the texture:

CCSprite *stripes = [self stripedSpriteWithColor1:color3 color2:color4 textureSize:512 stripes:nStripes];
    stripes.position = ccp(winSize.width/2,winSize.height/2);
    ccTexParams tp2 = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_CLAMP_TO_EDGE};
    [stripes.texture setTexParameters:&tp2];
    _terrain.stripes = stripes;
    _backgroundTerrain.stripes = stripes;

Generating texture:

-(CCSprite *)stripedSpriteWithColor1:(ccColor4F)c1 color2:(ccColor4F)c2 textureSize:(float)textureSize stripes:(int) nStripes {
    // 1: Create new CCRenderTexture
    CCRenderTexture *rt = [CCRenderTexture renderTextureWithWidth:textureSize height:textureSize];

    // 2: Call CCRenderTexture:begin

    [rt beginWithClear:c1.r g:c1.g b:c1.b a:c1.a];

    // 3: Draw into texture
    //OpenGL gradient

    NSLog(@"Strip color is: %f : %f : %f", c2.r,c2.g,c2.b);

    CGPoint vertices[nStripes*6];
    ccColor4F colors[nStripes*6];
    int nVertices = 0;
    float x1 = -textureSize;
    float x2;
    float y1 = textureSize;
    float y2 = 0;
    float dx = textureSize / nStripes * 2;
    float stripeWidth = dx/2;
    ccColor4F stripColor = (ccColor4F){c2.r,c2.g,c2.b,c2.a};
    for (int i=0; i<nStripes; i++) {
        x2 = x1 + textureSize;
        colors[nVertices] = stripColor;
        vertices[nVertices++] = ccpMult(CGPointMake(x1, y1), CC_CONTENT_SCALE_FACTOR());
        colors[nVertices] = stripColor;
        vertices[nVertices++] = ccpMult(CGPointMake(x1+stripeWidth, y1), CC_CONTENT_SCALE_FACTOR());
        colors[nVertices] = stripColor;
        vertices[nVertices++] = ccpMult(CGPointMake(x2, y2), CC_CONTENT_SCALE_FACTOR());
        colors[nVertices] = stripColor;
        vertices[nVertices++] = vertices[nVertices-3];
        colors[nVertices] = stripColor;
        vertices[nVertices++] = vertices[nVertices-3];
        colors[nVertices] = stripColor;
        vertices[nVertices++] = ccpMult(CGPointMake(x2+stripeWidth, y2), CC_CONTENT_SCALE_FACTOR());
        x1 += dx;
    }


    [self.shaderProgram use];


    ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position  | kCCVertexAttribFlag_Color);

    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, colors);

    glDrawArrays(GL_TRIANGLES, 0, (GLsizei)nVertices);

    //Gradient

    float gradientAlpha = 0.2;
    nVertices = 0;

    vertices[nVertices] = CGPointMake(0, 0);
    colors[nVertices++] = (ccColor4F){0,0,0,0};
    vertices[nVertices] = CGPointMake(textureSize, 0);
    colors[nVertices++] = (ccColor4F){0,0,0,0};
    vertices[nVertices] = CGPointMake(0, textureSize);
    colors[nVertices++] = (ccColor4F){0,0,0,gradientAlpha};
    vertices[nVertices] = CGPointMake(textureSize, textureSize);
    colors[nVertices++] = (ccColor4F){0,0,0,gradientAlpha};





    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, colors);

    glDrawArrays(GL_TRIANGLE_STRIP,0, (GLsizei)nVertices);



    // Highlighting

    float borderWidth = textureSize/8;
    float borderAlpha = 0.1f;
    nVertices = 0;

    vertices[nVertices] = CGPointMake(0, 0);
    colors [nVertices++] = (ccColor4F){1,1,1,borderAlpha};
    vertices[nVertices] = CGPointMake(textureSize*CC_CONTENT_SCALE_FACTOR(),0);
    colors [nVertices++] = (ccColor4F){1,1,1,borderAlpha};

    vertices[nVertices] = CGPointMake(0, borderWidth*CC_CONTENT_SCALE_FACTOR());
    colors [nVertices++] = (ccColor4F){0,0,0,0};
    vertices[nVertices] = CGPointMake(textureSize*CC_CONTENT_SCALE_FACTOR(),borderWidth*CC_CONTENT_SCALE_FACTOR());
    colors [nVertices++] = (ccColor4F){0,0,0,0};

    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_FLOAT, GL_FALSE, 0, colors);
    glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)nVertices);



    //Noise 
    CCSprite *noise = [CCSprite spriteWithFile:@"noise.png"];
    [noise setBlendFunc:(ccBlendFunc){GL_DST_COLOR, GL_ZERO}];
    noise.position = ccp(textureSize/2, textureSize/2);
    [noise visit];


    [rt end];
    // Return texture sprite
    return [CCSprite spriteWithTexture:rt.sprite.texture];

}

Getting TexCoords for bounding the stripes to the hill:

- (void)resetHillVertices {

    CGSize winSize = [CCDirector sharedDirector].winSize;

    static int prevFromKeyPointI = -1;
    static int prevToKeyPointI = -1;

    // key points interval for drawing
    while (_hillKeyPoints[_fromKeyPointI+1].x < _offsetX-winSize.width/self.scale) {
        _fromKeyPointI++;
    }
    while (_hillKeyPoints[_toKeyPointI].x < _offsetX+winSize.width*3/2/self.scale) {
        _toKeyPointI++;
    }

    if (prevFromKeyPointI != _fromKeyPointI || prevToKeyPointI != _toKeyPointI) {
        _nHillVertices = 0;
        _nBorderVertices = 0;
        CGPoint p0, p1, pt0, pt1;
        p0 = _hillKeyPoints[_fromKeyPointI];
        for (int i=_fromKeyPointI+1; i<_toKeyPointI+1; i++) {
            p1 = _hillKeyPoints[i];

            // triangle strip between p0 and p1
            int hSegments = floorf((p1.x-p0.x)/kHillSegmentWidth);
            float dx = (p1.x - p0.x) / hSegments;
            float da = M_PI / hSegments;
            float ymid = (p0.y + p1.y) / 2;
            float ampl = (p0.y - p1.y) / 2;
            pt0 = p0;
            _borderVertices[_nBorderVertices++] = pt0;
            for (int j=1; j<hSegments+1; j++) {
                pt1.x = p0.x + j*dx;
                pt1.y = ymid + ampl * cosf(da*j);
                _borderVertices[_nBorderVertices++] = pt1;

                _hillVertices[_nHillVertices] = CGPointMake(pt0.x, 0);
                _hillTexCoords[_nHillVertices++] = CGPointMake(pt0.x/512, 1.0f);
                _hillVertices[_nHillVertices] = CGPointMake(pt1.x, 0);
                _hillTexCoords[_nHillVertices++] = CGPointMake(pt1.x/512, 1.0f);

                _hillVertices[_nHillVertices] = CGPointMake(pt0.x, pt0.y);
                _hillTexCoords[_nHillVertices++] = CGPointMake(pt0.x/512, 0);
                _hillVertices[_nHillVertices] = CGPointMake(pt1.x, pt1.y);
                _hillTexCoords[_nHillVertices++] = CGPointMake(pt1.x/512, 0);

                pt0 = pt1;
            }

            p0 = p1;
        }

        prevFromKeyPointI = _fromKeyPointI;
        prevToKeyPointI = _toKeyPointI;
        [self resetBox2DBody];
    }

}

Drawing the texture:

- (void) draw {


    self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:kCCShader_PositionTexture];
    CC_NODE_DRAW_SETUP();
    ccGLBlendFunc( CC_BLEND_SRC, CC_BLEND_DST ); //TB 25-08-12: Allows change of blend function

    ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position  | kCCVertexAttribFlag_TexCoords);

    ccGLBindTexture2D(_stripes.texture.name);
    // Assign the vertices array to the 'position' attribute
    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, _hillVertices);

    // Assign the texCoords array to the 'TexCoords' attribute
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, _hillTexCoords);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)_nHillVertices);
}

The problem I'm having is this: after a certain number of repeats the texture starts to degrade in quality, like so:

Degradation

Is there any way to get the texture to repeat without degradation?

EDIT 1:

I've veen doing more analysis into how the texture degrades, it turns out it doesn't do it continuously, but degrades with power of 2 repetitions so it degrades for the first time on the first repeat then after 2 repeats, then 4, 8, 16, 32 and so on... It also seems that the vertical bands that start to appear that can be seen in the image double in width each time the image degrades in quality. Also on each degradation the frame rate of the game decreases substantially so I'm starting to think this is probably a memory issue.

EDIT 2:

My best guess at why this is happening so far is because the -draw method for the terrain is continually making GL_TRAINGLE_STRIP, and not deleting them once they are off-screen causing a build up in the memory usage of the terrain, causing the degradation and frame rate drop.

Degradation Doubling

UPDATE 1

I have solved two of the problems that were occurring with my texture generation...

Solving Misalignment

IN the sprite generation method this:

float x1 = -textureSize;
float x2;
float y1 = textureSize;
float y2 = 0;
float dx = textureSize / nStripes * 2;

to this:

float x1 = -winSize.width;
float x2;
float y1 = winSize.height;
float y2 = 0;
float dx = winSize.width / nStripes * 2;

I realised that this was totally unrelated to the main error, rather it was due to my stripes for some reason not appearing at a 45 degree angle, which causes them to misalign on repeat. I tried to think of reasons for this, and finally fixed it by assuming that the textures coordinate origin was at the top left corner of the screen as opposed to the top left corner of the texture.

Solving Degradation (Kind of)

I had an inkling that the image degradation was occurring due to the large amounts of repetitions of the texture, due to a similar reason as this Although I may be wrong on that front!

To solve this in the resetHillVertices I set it up so the texCoords are always between 0 and 1 meaning that the texture bound to the hills is always the first repetition of the texture. I implemented this like so:

for (int j=1; j<hSegments+1; j++) {
            pt1.x = p0.x + j*dx;
            pt1.y = ymid + ampl * cosf(da*j);
            _borderVertices[_nBorderVertices++] = pt1;
            float xTex0 = pt0.x/512;
            float xTex1 = pt1.x/512;
            while (xTex0 > 1) { // makes sure texture coordinates are always within the first repetition of texture
                xTex0 -= 1;
            }
            while (xTex1 > 1) {
                xTex1 -= 1;
            }
            _hillVertices[_nHillVertices] = CGPointMake(pt0.x, 0);
            _hillTexCoords[_nHillVertices++] = CGPointMake(xTex0, 1.0);
            _hillVertices[_nHillVertices] = CGPointMake(pt1.x, 0);
            _hillTexCoords[_nHillVertices++] = CGPointMake(xTex1, 1.0);

            _hillVertices[_nHillVertices] = CGPointMake(pt0.x, pt0.y);
            _hillTexCoords[_nHillVertices++] = CGPointMake(xTex0, 

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The triangle strip you generate for the visible part of the terrain will have texture coordinates which increase from left to right. When these get very large, you will have precision issues.

For your UV coordinates, you need to subtract the same value from all the coordinates in your triangle strip. Generally, take the integer part of the lowest coordinate (probably the far-left or first coordinate in your case), and subtract that from all the UVs you generate. Or use it as a baseline to generate the others, whichever you prefer.

So if the left hand side of the screen has a U value of 100.7 and the right hand side has a U value of 129.5, you actually want to output values ranging from 0.7 to 29.5. (if you still have precision issues, you might squeak out a bit more by centring the range on zero, using negative co-ords).

The alternative, if you need to have a discontinuity in the texture coordinates within a single strip, and to get maximum precision, is to introduce degenerate triangles, which are zero-area, and thus don't get rendered, while you change the tex-coords. You do that by repeating vertices, but with the adjusted tex-coords, before continuing.

Based on your code above, I'd suggest something like this:

// This line should happen only once per-strip. 
float U_Off = floor(pt0.x / 512);

// The inner loop then looks like this:
for (int j=1; j<hSegments+1; j++) {
    pt1.x = p0.x + j*dx;
    pt1.y = ymid + ampl * cosf(da*j);
    _borderVertices[_nBorderVertices++] = pt1;
    float xTex0 = pt0.x/512 - U_Off;
    float xTex1 = pt1.x/512 - U_Off;

    _hillVertices[_nHillVertices] = CGPointMake(pt0.x, 0);
    _hillTexCoords[_nHillVertices++] = CGPointMake(xTex0, 1.0);
    _hillVertices[_nHillVertices] = CGPointMake(pt1.x, 0);
    _hillTexCoords[_nHillVertices++] = CGPointMake(xTex1, 1.0);

    _hillVertices[_nHillVertices] = CGPointMake(pt0.x, pt0.y);
    _hillTexCoords[_nHillVertices++] = CGPointMake(xTex0, 0.0);
    _hillVertices[_nHillVertices] = CGPointMake(pt1.x, pt1.y);
    _hillTexCoords[_nHillVertices++] = CGPointMake(xTex1, 0.0);

    pt0 = pt1;
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...