web-dev-qa-db-ja.com

OpenGL ES 2.0で複数のオブジェクトをレンダリングする

IPhoneゲーム開発を行うためにOpenGL ES 2.0を学ぼうとしています。複数のチュートリアルとOpenGL ES 2.0仕様の一部を読みました。私が見たすべての例では、単一のメッシュを作成し、それを頂点バッファーにロードしてからレンダリングしました(予想される並進、回転、グラデーションなどを使用して)。

私の質問はこれです:メッシュが異なり、独立して移動しているシーン内の複数のオブジェクトをどのようにレンダリングしますか?たとえば、車とバイクがある場合、2つの頂点バッファーを作成し、レンダーコールごとに両方のメッシュデータを保持し、各オブジェクトのシェーダーに異なるマトリックスを送信することはできますか?または、メッシュを何らかの方法で変換してから1つのメッシュに結合して、1つのパスでレンダリングできるようにする必要がありますか?コード例ではなく、より高度な戦略/プログラム構造を探しています。私はこれがどのように機能するかについて間違ったメンタルモーダルを持っていると思います。

ありがとう!

31

はい、オブジェクトごとに別々の頂点/インデックスバッファーを維持します。たとえば、RenderedObjectクラスがあり、各インスタンスに独自の頂点バッファーがあるとします。 1つのRenderedObjectが頂点をハウスメッシュから取得したり、1つがキャラクターメッシュから取得したりする場合があります。

レンダリング中に、作業している頂点バッファーに適切な変換/回転/シェーディングを設定します。

void RenderedObject::render()
{
    ...
    //set textures/shaders/transformations

    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
    ...
}

他の回答で述べたように、bufferIDは単なるバッファの内容ではなく単なるGLuintです。頂点バッファーの作成とデータの入力に関する詳細が必要な場合は、それらも追加できます。

10
TaylorP

私がこれを行うために見つけた最良の方法は、VBOに加えてVAOを使用することです。

まず、VBOのみを使用して質問に回答します。

まず、2つのオブジェクトの2つのメッシュが次の配列に格納されていると仮定します。

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

どこ:

GLfloat gCubeVertexData1[36] = {...};
GLfloat gCubeVertexData2[36] = {...};

そして、あなたはまた、垂直バッファをしなければなりません:

GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;

ここで、これら2つのキューブ(VAOなし)を描画するには、次のようにする必要があります:(OpenGLESテンプレートから)描画関数で:

//Draw first object, bind VBO, adjust your attributes then call DrawArrays
glGenBuffers(1, &_vertexBufferCube1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData1), gCubeVertexData1, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));


glDrawArrays(GL_TRIANGLES, 0, 36);



//Repeat for second object:
glGenBuffers(1, &_vertexBufferCube2);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube2);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData2), gCubeVertexData2, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glUseProgram(_program);

glDrawArrays(GL_TRIANGLES, 0, 36);

これはあなたの質問に答えます。しかし、VAOを使用するために、描画関数のコードははるかに単純です(これは繰り返し関数であるため、これは良いことです)。

最初に、VAOを定義します。

GLuint _vertexArray1;
GLuint _vertexArray2;

次に、以前にdrawメソッドで実行したすべての手順を実行します。VAOにバインドした後、setupGL関数で実行します。次に、描画関数で、必要なVAOにバインドします。

VAOは、多くのプロパティを含むプロファイルのようなものです(スマートデバイスプロファイルを想像してください)。色、デスクトップ、フォントなどを変更するたびに変更するのではなく、一度変更してプロファイル名で保存します。次に、プロファイルを切り替えるだけです。

つまり、setupGL内で一度それを実行してから、描画でそれらを切り替えます。

もちろん、コードに(VAOなしで)コードを入れて関数を呼び出すこともできます。それは本当ですが、アップルによればVAOはより効率的です:

http://developer.Apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html#//Apple_ref/doc/uid/TP40008793-CH107-SW1

コードに移ります:

SetupGLでは:

glGenVertexArraysOES(1, &_vertexArray1); //Bind to first VAO
glBindVertexArrayOES(_vertexArray1);

glGenBuffers(1, &_vertexBufferCube1); //All steps from this one are done to first VAO only
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData1), gCubeVertexData1, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));

glGenVertexArraysOES(1, &_vertexArray2); // now bind to the second
glBindVertexArrayOES(_vertexArray2);

glGenBuffers(1, &_vertexBufferCube2); //repeat with the second mesh
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube2);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData2), gCubeVertexData2, GL_STATIC_DRAW);

glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));


glBindVertexArrayOES(0);

次に、最後にあなたのdrawメソッドで:

glBindVertexArrayOES(_vertexArray1);
glDrawArrays(GL_TRIANGLES, 0, 36);


glBindVertexArrayOES(_vertexArray2);    
glDrawArrays(GL_TRIANGLES, 0, 36);
12
Tony

これは古い投稿だと思いますが、OpenGL内に複数のオブジェクトをレンダリングする方法についての説明を探していました。複数のオブジェクトをレンダリングする方法を説明し、さまざまなタイプ(つまり、1つの立方体、1つのピラミッド)のオブジェクトをレンダリングするように簡単に拡張できる素晴らしいチュートリアルを見つけました。

私が投稿しているチュートリアルでは、GLKitを使用してオブジェクトをレンダリングする方法についても説明しています。私はそれが役に立ったと思ったので、ここに再投稿すると思いました。参考になれば幸いです。

http://games.ianterrell.com/opengl-basics-with-glkit-in-ios5-encapsulated-drawing-and-animation/

4
MikeyE

メッシュが異なる場合は、それらを異なる頂点バッファーに保持します。それらが類似している場合(アニメーション、色など)、引数をシェーダーに渡します。アプリケーション側でオブジェクトをアニメーション化する予定がない場合は、頂点データ自体ではなく、VBOへのハンドルを保持するだけで済みます。デバイス側のアニメーションis可能です。

3

私はこの問題を別の方法で解決することを約束したので、うまくいけばこの古い投稿に貢献しています。質問者のように、「単一オブジェクト」の例をたくさん見ました。すべての頂点を1つのVBOに配置することを約束し、バッファハンドルではなく、オブジェクトの(オブジェクトごとの)位置へのオフセットを保存しました。出来た。オフセットは、以下のようにパラメーターとしてglDrawElementsに指定できます。振り返ってみると明らかなようですが、それが機能するのを見るまでは確信が持てませんでした。現在の「頂点属性ポインター」ではなく、「頂点ポインター」で作業していることに注意してください。シェーダーを活用できるように、私は後者に向けて取り組んでいます。 「描画要素」を呼び出す前に、すべてのオブジェクトが同じ頂点バッファーに「バインド」されます。

        gl.glVertexPointer( 3, GLES20.GL_FLOAT, 0, vertexBufferOffset );

        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, indicesCount,
                GLES20.GL_UNSIGNED_BYTE, indexBufferOffset
        );

この相殺の目的が何であるかを詳しく説明したところはどこにも見つからなかったので、私はチャンスをつかみました。また、この問題:頂点や浮動小数点数ではなく、バイト単位でオフセットを指定する必要があります。つまり、正しい位置を取得するには、4を掛けます。

0
Javaneer

シェーダーを使用する場合、コンパイル、リンク、作成を行わなくても、すべてのオブジェクトに同じプログラムを使用できます。これを行うには、GLuint値をプログラムに格納し、次に各オブジェクトに対して「glUseProgram(programId);」を格納します。結果として、個人的な経験として、シングルトンを使用してGLProgram構造を管理します。(以下に含まれます:))

@interface TDShaderSet : NSObject {

    NSMutableDictionary     *_attributes;
    NSMutableDictionary     *_uniforms;
    GLuint                  _program;

}

    @property (nonatomic, readonly, getter=getUniforms) NSMutableDictionary *uniforms;
    @property (nonatomic, readonly, getter=getAttributes) NSMutableDictionary *attributes;

    @property (nonatomic, readonly, getter=getProgram) GLuint program;

    - (GLint) uniformLocation:(NSString*)name;
    - (GLint) attribLocation:(NSString*)name;

@end


@interface TDProgamManager : NSObject

    + (TDProgamManager *) sharedInstance;
    + (TDProgamManager *) sharedInstanceWithContext:(EAGLContext*)context;

    @property (nonatomic, readonly, getter=getAllPrograms) NSArray *allPrograms;

    - (BOOL) loadShader:(NSString*)shaderName referenceName:(NSString*)refName;

    - (TDShaderSet*) getProgramForRef:(NSString*)refName;

@end

@interface TDProgamManager () {

    NSMutableDictionary     *_glPrograms;
    EAGLContext             *_context;

}

@end


@implementation TDShaderSet

    - (GLuint) getProgram
    {
        return _program;
    }

    - (NSMutableDictionary*) getUniforms
    {
        return _uniforms;
    }

    - (NSMutableDictionary*) getAttributes
    {
        return _attributes;
    }

    - (GLint) uniformLocation:(NSString*)name
    {
        NSNumber *number = [_uniforms objectForKey:name];
        if (!number) {
            GLint location = glGetUniformLocation(_program, name.UTF8String);
            number = [NSNumber numberWithInt:location];
            [_uniforms setObject:number forKey:name];
        }
        return number.intValue;
    }

    - (GLint) attribLocation:(NSString*)name
    {
        NSNumber *number = [_attributes objectForKey:name];
        if (!number) {
            GLint location = glGetAttribLocation(_program, name.UTF8String);
            number = [NSNumber numberWithInt:location];
            [_attributes setObject:number forKey:name];
        }
        return number.intValue;
    }

    - (id) initWithProgramId:(GLuint)program
    {
        self = [super init];
        if (self) {
            _attributes = [[NSMutableDictionary alloc] init];
            _uniforms = [[NSMutableDictionary alloc] init];
            _program = program;
        }
        return self;
    }

@end


@implementation TDProgamManager {

@private

}

    static TDProgamManager *_sharedSingleton = nil;

    - (NSArray *) getAllPrograms
    {
        return _glPrograms.allValues;
    }

    - (TDShaderSet*) getProgramForRef:(NSString *)refName
    {
        return (TDShaderSet*)[_glPrograms objectForKey:refName];
    }

    - (BOOL) loadShader:(NSString*)shaderName referenceName:(NSString*)refName
    {

        NSAssert(_context, @"No Context available");

        if ([_glPrograms objectForKey:refName]) return YES;

        [EAGLContext setCurrentContext:_context];

        GLuint vertShader, fragShader;

        NSString *vertShaderPathname, *fragShaderPathname;

        // Create shader program.
        GLuint _program = glCreateProgram();

        // Create and compile vertex shader.
        vertShaderPathname = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"vsh"];

        if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
            NSLog(@"Failed to compile vertex shader");
            return NO;
        }

        // Create and compile fragment shader.
        fragShaderPathname = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"fsh"];

        if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
            NSLog(@"Failed to compile fragment shader");
            return NO;
        }

        // Attach vertex shader to program.
        glAttachShader(_program, vertShader);

        // Attach fragment shader to program.
        glAttachShader(_program, fragShader);

        // Bind attribute locations.
        // This needs to be done prior to linking.
        glBindAttribLocation(_program, GLKVertexAttribPosition, "a_position");
        glBindAttribLocation(_program, GLKVertexAttribNormal, "a_normal");
        glBindAttribLocation(_program, GLKVertexAttribTexCoord0, "a_texCoord");

        // Link program.
        if (![self linkProgram:_program]) {

            NSLog(@"Failed to link program: %d", _program);

            if (vertShader) {
                glDeleteShader(vertShader);
                vertShader = 0;
            }
            if (fragShader) {
                glDeleteShader(fragShader);
                fragShader = 0;
            }
            if (_program) {
                glDeleteProgram(_program);
                _program = 0;
            }

            return NO;

        }

        // Release vertex and fragment shaders.
        if (vertShader) {
            glDetachShader(_program, vertShader);
            glDeleteShader(vertShader);
        }

        if (fragShader) {
            glDetachShader(_program, fragShader);
            glDeleteShader(fragShader);
        }

        TDShaderSet *_newSet = [[TDShaderSet alloc] initWithProgramId:_program];

        [_glPrograms setValue:_newSet forKey:refName];

        return YES;
    }

    - (BOOL) compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
    {

        GLint status;
        const GLchar *source;

        source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
        if (!source) {
            NSLog(@"Failed to load vertex shader");
            return NO;
        }

        *shader = glCreateShader(type);
        glShaderSource(*shader, 1, &source, NULL);
        glCompileShader(*shader);

    #if defined(DEBUG)
        GLint logLength;
        glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar *)malloc(logLength);
            glGetShaderInfoLog(*shader, logLength, &logLength, log);
            NSLog(@"Shader compile log:\n%s", log);
            free(log);
        }
    #endif

        glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
        if (status == 0) {
            glDeleteShader(*shader);
            return NO;
        }

        return YES;
    }

    - (BOOL) linkProgram:(GLuint)prog
    {
        GLint status;
        glLinkProgram(prog);

    #if defined(DEBUG)
        GLint logLength;
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
        if (logLength > 0) {
            GLchar *log = (GLchar *)malloc(logLength);
            glGetProgramInfoLog(prog, logLength, &logLength, log);
            NSLog(@"Program link log:\n%s", log);
            free(log);
        }
    #endif

        glGetProgramiv(prog, GL_LINK_STATUS, &status);
        if (status == 0) {
            return NO;
        }

        return YES;
    }

    - (BOOL) validateProgram:(GLuint)prog
    {
        GLint logLength, status;

        glValidateProgram(prog);
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);

        if (logLength > 0) {
            GLchar *log = (GLchar *)malloc(logLength);
            glGetProgramInfoLog(prog, logLength, &logLength, log);
            NSLog(@"Program validate log:\n%s", log);
            free(log);
        }

        glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);

        if (status == 0) {
            return NO;
        }

        return YES;
    }

    #pragma mark - Singleton stuff... Don't mess with this other than proxyInit!

    - (void) proxyInit
    {

        _glPrograms = [[NSMutableDictionary alloc] init];

    }

    - (id) init
    {
        Class myClass = [self class];
        @synchronized(myClass) {
            if (!_sharedSingleton) {
                if (self = [super init]) {
                    _sharedSingleton = self;
                    [self proxyInit];
                }
            }
        }
        return _sharedSingleton;
    }

    + (TDProgamManager *) sharedInstance
    {
        @synchronized(self) {
            if (!_sharedSingleton) {
                _sharedSingleton = [[self alloc] init];
            }
        }
        return _sharedSingleton;
    }

    + (TDProgamManager *) sharedInstanceWithContext:(EAGLContext*)context
    {
        @synchronized(self) {
            if (!_sharedSingleton) {
                _sharedSingleton = [[self alloc] init];
            }
            _sharedSingleton->_context = context;
        }
        return _sharedSingleton;
    }

    + (id) allocWithZone:(NSZone *)zone
    {
        @synchronized(self) {
            if (!_sharedSingleton) {
                return [super allocWithZone:zone];
            }
        }
        return _sharedSingleton;
    }

    + (id) copyWithZone:(NSZone *)zone
    {
        return self;
    }

@end

データスペース(属性/ユニフォーム)が渡されると、各レンダリングサイクルでそれらを渡す必要はありませんが、無効化された場合のみであることに注意してください。これにより、GPUのパフォーマンスが大幅に向上します。

事柄のVBO側によれば、上記の答えはこれに対処する最善の方法を明記しています。方程式の方向の側面ごとに、tdobjectを相互にネストし(iOSのUIViewおよび子と同様)、親に対する相対回転などを評価するメカニズムが必要になります。

幸運を !

0
Samuel Colak