2013年5月27日 星期一

OpenGL基本實作(六)

這是一個從iphone 3D Programm書上改過來的實作例,改成使用xcode4.6.2,並加入切換的Button,讓ES1及ES2兩種模式可以切換。為了區別兩者的不同,顏色的顯示改成不同。原始程式碼在iphone上可以轉動較順暢,轉動後並會自動轉為向上,但不知為何在ipad上並不順暢。因此在ES2上自己修改了一個版本,轉動起來就非常順暢了,而原始版本就保留在ES1中。後來知道是因為在我修改的過程中將元件設成靜態的緣故,但因為已經做完了,就懶得再改了。

1. 先開一個專案



2. 加入所需 framework以及所新增的檔名,如下圖



3. storyboard下增加一個Button,用來切換ES1及ES2兩種模式的顯示方式。





4.. 先新增GLSL的檔案,每個檔案的第一行是使用Define的方式,來作為c string的取代,此處與前幾個實作方式不同,前面數例使用objectC的檔案讀入。

frag.glsl

const char* SimpleFragmentShader = STRINGIFY(
varying lowp vec4 DestinationColor;

void main(void)
{
    gl_FragColor = DestinationColor;
}
);


vertex.glsl

const char* SimpleVertexShader = STRINGIFY(

attribute vec4 Position;
attribute vec4 SourceColor;
varying vec4 DestinationColor;
uniform mat4 Projection;
uniform mat4 Modelview;

void main(void)
{
    DestinationColor = SourceColor;
    gl_Position = Projection * Modelview * Position;
}

);


5. 加入IRenderingEngine.hpp的內容
// 此部分改由c++寫成,為因應RenderingEngine1/2.cpp下方向的計算,而不用原本Xcode下所帶的方向定義,但兩者之間的定義值是一樣的,這是因為objectC不能為C++呼叫及聯結,反之可以,
enum DeviceOrientation2 {
    DeviceOrientationUnknown,
    DeviceOrientationPortrait,
    DeviceOrientationPortraitUpsideDown,
    DeviceOrientationLandscapeLeft,
    DeviceOrientationLandscapeRight,
    DeviceOrientationFaceUp,
    DeviceOrientationFaceDown,
};


// Create an instance of the renderer and set up various OpenGL state.
struct IRenderingEngine* CreateRenderer1();
struct IRenderingEngine* CreateRenderer2();

// Interface to the OpenGL ES renderer; consumed by GLView.
struct IRenderingEngine { // 對應   
    virtual void Initialize(int width, int height) = 0;
    virtual void Render() const = 0;
    virtual void UpdateAnimation(float timeStep) = 0;
    virtual void OnRotate(DeviceOrientation2 newOrientation) = 0;
    virtual ~IRenderingEngine() {}
};


6. RenderingEngine1.cpp
#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#include "IRenderingEngine.hpp"

static const float RevolutionsPerSecond = 1;

class RenderingEngine1 : public IRenderingEngine {
public:
    RenderingEngine1();
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep);
    void OnRotate(DeviceOrientation2 newOrientation); // DeviceOrientation
private:
    float RotationDirection() const;
    float m_desiredAngle;
    float m_currentAngle;
    GLuint m_framebuffer;
    GLuint m_renderbuffer;
};

IRenderingEngine* CreateRenderer1()
{
    return new RenderingEngine1();
}

struct Vertex {
    float Position[2];
    float Color[4];
};

// Define the positions and colors of two triangles.
const Vertex Vertices[] = {
    {{-0.5, -0.866}, {1, 1, 1.0f, 1}},
    {{0.5, -0.866},  {1, 1, 1.0f, 1}},
    {{0, 1},         {1, 1, 1.0f, 1}},

    {{-0.5, -0.866}, {0.5f, 1.5f, 0.5f}},
    {{0.5, -0.866},  {0.5f, 1.5f, 0.5f}},
    {{0, -0.4f},     {0.5f, 1.5f, 0.5f}},

};

RenderingEngine1::RenderingEngine1()
{
    // Create & bind the color buffer so that the caller can allocate its space.
    glGenRenderbuffersOES(1, &m_renderbuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffer);
}

void RenderingEngine1::Initialize(int width, int height)
{
    // Create the framebuffer object and attach the color buffer.
    glGenFramebuffersOES(1, &m_framebuffer);
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer);
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
                                 GL_COLOR_ATTACHMENT0_OES,
                                 GL_RENDERBUFFER_OES,
                                 m_renderbuffer);
   
    glViewport(0, 0, width, height);
   
    glMatrixMode(GL_PROJECTION);
   
    // Initialize the projection matrix.
    const float maxX = 2;
    const float maxY = 3;
    glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1);
   
    glMatrixMode(GL_MODELVIEW);
   
    // Initialize the rotation animation state.
    OnRotate(DeviceOrientationPortrait);
    m_currentAngle = m_desiredAngle;
}

void RenderingEngine1::Render() const
{
    glClearColor(0.5f, 0.5f, 1.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glPushMatrix();
    glRotatef(m_currentAngle, 0, 0, 1);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
   
    glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &Vertices[0].Position[0]);
    glColorPointer(4, GL_FLOAT, sizeof(Vertex), &Vertices[0].Color[0]);
   
    GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);
   
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glPopMatrix();
}

float RenderingEngine1::RotationDirection() const
{
    float delta = m_desiredAngle - m_currentAngle;
    if (delta == 0)
        return 0;
   
    bool counterclockwise = ((delta > 0 && delta <= 180) || (delta < -180));
    return counterclockwise ? +1 : -1;
}

void RenderingEngine1::UpdateAnimation(float timeStep)
{
    float direction = RotationDirection();
    if (direction == 0)
        return;
   
    float degrees = timeStep * 360 * RevolutionsPerSecond;
    m_currentAngle += degrees * direction;
   
    // Normalize the angle to [0, 360)
    if (m_currentAngle >= 360)
        m_currentAngle -= 360;
    else if (m_currentAngle < 0)
        m_currentAngle += 360;
   
    // If the rotation direction changed, then we overshot the desired angle.
    if (RotationDirection() != direction)
        m_currentAngle = m_desiredAngle;
}


void RenderingEngine1::OnRotate(DeviceOrientation2 orientation) // DeviceOrientation
{
    float angle = 0;
   
    switch (orientation) {
        case DeviceOrientationLandscapeLeft:
            angle = 90; //
            break;
           
        case DeviceOrientationPortraitUpsideDown:
            angle = 180;
            break;
           
        case DeviceOrientationLandscapeRight:
            angle = 270;
            break;
  
           
        default:
            angle = 0;
            break;

    }
   
    m_desiredAngle = angle;
}


7. RenderingEngine2.cpp
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include <cmath>
#include <iostream>
#include "IRenderingEngine.hpp"

#define STRINGIFY(A)  #A
#include "./frag.glsl"
#include "./vertex.glsl"

static const float RevolutionsPerSecond = 0.1;

class RenderingEngine2 : public IRenderingEngine {
public:
    RenderingEngine2();
    void Initialize(int width, int height);
    void Render() const;
    void UpdateAnimation(float timeStep);
    void OnRotate(DeviceOrientation2 newOrientation); // DeviceOrientation
private:
    float RotationDirection() const;
    GLuint BuildShader(const char* source, GLenum shaderType) const;
    GLuint BuildProgram(const char* vShader, const char* fShader) const;
    void ApplyOrtho(float maxX, float maxY) const;
    void ApplyRotation(float degrees) const;
    double m_desiredAngle;
    double m_currentAngle;
    GLuint m_simpleProgram;
    GLuint m_framebuffer;
    GLuint m_renderbuffer;
};

IRenderingEngine* CreateRenderer2()
{
    return new RenderingEngine2();
}

struct Vertex {
    float Position[2];
    float Color[4];
};

// Define the positions and colors of two triangles.
const Vertex Vertices[] = {
    {{-0.5, -0.866}, {1, 1, 0.5f, 1}},  // (x,y) + (r,g,b,a)
    {{0.5, -0.866},  {1, 1, 0.5f, 1}},
    {{0, 1},         {1, 1, 0.5f, 1}},

    {{-0.5, -0.866}, {0.5f, 0.5f, 0.5f}},
    {{0.5, -0.866},  {0.5f, 0.5f, 0.5f}},
    {{0, -0.4f},     {0.5f, 0.5f, 0.5f}},
};

RenderingEngine2::RenderingEngine2()
{
    // Create & bind the color buffer so that the caller can allocate its space.
    glGenRenderbuffers(1, &m_renderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer);
}

void RenderingEngine2::Initialize(int width, int height)
{
    // Create the framebuffer object and attach the color buffer.
    glGenFramebuffers(1, &m_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                              GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER,
                              m_renderbuffer);
   
    glViewport(0, 0, width, height);
   
    m_simpleProgram = BuildProgram(SimpleVertexShader, SimpleFragmentShader);
   
    glUseProgram(m_simpleProgram);
   
    // Initialize the projection matrix.
    ApplyOrtho(2, 3);
   
    // Initialize rotation animation state.
    OnRotate(DeviceOrientationPortrait);
   
    m_desiredAngle = 0;
   
    m_currentAngle = m_desiredAngle;
}

// 參考 Page 60 of iphone 3D program
void RenderingEngine2::ApplyOrtho(float maxX, float maxY) const
{
    float a = 1.0f / maxX;
    float b = 1.0f / maxY;
    float ortho[16] = {
        a, 0,  0, 0,
        0, b,  0, 0,
        0, 0, -1, 0,
        0, 0,  0, 1
    };
   
    GLint projectionUniform = glGetUniformLocation(m_simpleProgram, "Projection");
    glUniformMatrix4fv(projectionUniform, 1, 0, &ortho[0]);
}

void RenderingEngine2::ApplyRotation(float degrees) const
{
    float radians = degrees * 3.14159f / 180.0f;
    float s = std::sin(radians);
    float c = std::cos(radians);
    float zRotation[16] = {
        c, s, 0, 0,
        -s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    };
   
    GLint modelviewUniform = glGetUniformLocation(m_simpleProgram, "Modelview");
    glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]);
}

void RenderingEngine2::Render() const
{
    glClearColor(0.5f, 0.5f, 0.5f, 1);
    glClear(GL_COLOR_BUFFER_BIT);
   
    ApplyRotation(m_currentAngle);
   
    GLuint positionSlot = glGetAttribLocation(m_simpleProgram, "Position");
    GLuint colorSlot = glGetAttribLocation(m_simpleProgram, "SourceColor");
   
    glEnableVertexAttribArray(positionSlot);
    glEnableVertexAttribArray(colorSlot);
   
    GLsizei stride = sizeof(Vertex);
    const GLvoid* pCoords = &Vertices[0].Position[0];
    const GLvoid* pColors = &Vertices[0].Color[0];
   
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords);
    glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors);
   
    GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex);
    glDrawArrays(GL_TRIANGLES, 0, vertexCount);
   
    glDisableVertexAttribArray(positionSlot);
    glDisableVertexAttribArray(colorSlot);
}

float RenderingEngine2::RotationDirection() const
{
    float delta = 360 - m_currentAngle;
   
    if (delta == 0 || delta == 360)
        return 0;
   
    bool counterclockwise = ((delta > 0 && delta <= 180) || (delta < 360 && delta > 180));
   
    return counterclockwise ? +1 : -1;

}


void RenderingEngine2::UpdateAnimation(float timeStep)
{
   
    float direction = RotationDirection();
    if (direction == 0 ) {
        //m_currentAngle =0;
        return;
    }
   
    printf("direction = %f \n", direction);
   
    printf("print old m_currentAngle = %f \n", m_currentAngle);
   
    printf("print m_currentAngle = %f \n", m_currentAngle);
   
    // Normalize the angle to [0, 360)
    if (m_currentAngle >= 360)
        m_currentAngle -= 360;
    else if (m_currentAngle < 0)
        m_currentAngle += 360;
   
    if (m_currentAngle >= 1 && m_currentAngle <= 180)
        m_currentAngle  =  m_currentAngle - (m_currentAngle/20);
    else if (m_currentAngle > 180 && m_currentAngle < 359)
        m_currentAngle  = m_currentAngle + ((360- m_currentAngle )/20);
    else {
        m_currentAngle = 0;
        m_desiredAngle =0;
    }
   
    float new_direction = RotationDirection();
   
   
    if ((m_desiredAngle > 1) && (m_desiredAngle < 180.1))
        m_desiredAngle = m_desiredAngle -  0.0005;
    else if ((m_desiredAngle < 359) && (m_desiredAngle > 180))
        m_desiredAngle = m_desiredAngle +  0.0005;
    else {
            m_desiredAngle = 0;
    }
    printf("print m_desiredAngle = %f \n", m_desiredAngle);
   

}



void RenderingEngine2::OnRotate(DeviceOrientation2 orientation) // DeviceOrientation
{
    float angle = 0;
   
    switch (orientation) {
        case DeviceOrientationLandscapeLeft:
            angle = 90;
            break;
           
        case DeviceOrientationPortraitUpsideDown:
            angle = 180;
            break;
           
        case DeviceOrientationLandscapeRight:
            angle = 270;
            break;
          
        default:
            angle = 0;
            break;
    }
   
    //m_desiredAngle = angle;
    m_currentAngle = angle;

}


GLuint RenderingEngine2::BuildShader(const char* source, GLenum shaderType) const
{
    GLuint shaderHandle = glCreateShader(shaderType);
    glShaderSource(shaderHandle, 1, &source, 0);
    glCompileShader(shaderHandle);
   
    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
   
    if (compileSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
        exit(1);
    }
   
    return shaderHandle;
}

GLuint RenderingEngine2::BuildProgram(const char* vertexShaderSource,
                                      const char* fragmentShaderSource) const
{
    GLuint vertexShader = BuildShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = BuildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
   
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    glLinkProgram(programHandle);
   
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar messages[256];
        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
        std::cout << messages;
        exit(1);
    }
   
    return programHandle;
}


8. GLView.h 作為OPENGLES載體的物件定義
#import <UIKit/UIKit.h>

#import "IRenderingEngine.hpp"
#import <QuartzCore/QuartzCore.h>

@interface GLView : UIView {

EAGLContext* m_context;
IRenderingEngine* m_renderingEngine;
float m_timestamp;
     
@public
BOOL ForceES1 ;

   
}

- (void) drawView: (CADisplayLink*) displayLink;
- (void) didRotate: (NSNotification*) notification;

- (void) setRender;

@end


9. GLView.mm 作為OPENGLES載體的設定主程式
#import "GLView.h"
#import <OpenGLES/ES2/gl.h> // <-- for GL_RENDERBUFFER only

@implementation GLView

+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

- (id) initWithFrame:(CGRect)frame
{
    ForceES1 = FALSE;
}


- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
       
         [self setRender];
       

        m_timestamp = CACurrentMediaTime();
       
        CADisplayLink* displayLink;
        displayLink = [CADisplayLink displayLinkWithTarget:self
                                                  selector:@selector(drawView:)];
       
        [displayLink addToRunLoop:[NSRunLoop currentRunLoop]
                          forMode:NSDefaultRunLoopMode];
       
        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
       
        [[NSNotificationCenter defaultCenter]
         addObserver:self
         selector:@selector(didRotate:)
         name:UIDeviceOrientationDidChangeNotification
         object:nil];
       
    }
    return self;
}

- (void) didRotate: (NSNotification*) notification
{
    // 手機旋轉時,讓箭頭也一起旋轉
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
    m_renderingEngine->OnRotate((DeviceOrientation2) orientation); // DeviceOrientation
   
    [self drawView: nil];
}

- (void) drawView: (CADisplayLink*) displayLink
{
   
    if (displayLink != nil) {
        float elapsedSeconds = displayLink.timestamp - m_timestamp;
        m_timestamp = displayLink.timestamp;
        m_renderingEngine->UpdateAnimation(elapsedSeconds);
        //NSLog(@"elapsedSeconds = %f", elapsedSeconds);
    }
   
    m_renderingEngine->Render();
    [m_context presentRenderbuffer:GL_RENDERBUFFER];
}

- (void) setRender
{
    CAEAGLLayer* eaglLayer = (CAEAGLLayer*) super.layer;
    eaglLayer.opaque = YES;
   
    EAGLRenderingAPI api;
   
    if (ForceES1 == NO){
        api = kEAGLRenderingAPIOpenGLES2;
        m_context = [[EAGLContext alloc] initWithAPI:api];
    }
    else{
        api = kEAGLRenderingAPIOpenGLES1;
        m_context = [[EAGLContext alloc] initWithAPI:api];
    }
   
    if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
        //[self release];
        return ;
    }
   
    if (api == kEAGLRenderingAPIOpenGLES1) {
        NSLog(@"Using OpenGL ES 1.1");
        m_renderingEngine = CreateRenderer1();
    } else {
        NSLog(@"Using OpenGL ES 2.0");
        m_renderingEngine = CreateRenderer2();
    }
   
    [m_context
     renderbufferStorage:GL_RENDERBUFFER
     fromDrawable: eaglLayer];
   
    m_renderingEngine->Initialize(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
   
    [self drawView: nil];

}

@end

 10. mainViewController.h

#import <UIKit/UIKit.h>
#import "GLView.h"

@interface mainViewController : UIViewController

@property (strong, nonatomic) IBOutlet GLView  *controllerView;

@property (strong, nonatomic) IBOutlet UIButton *ESBtn;


@end

11. mainViewController.mm ,此處副檔名要改成mm,因為連結的GLView.h含有C++的程式碼。
#import "mainViewController.h"

@interface mainViewController ()

@end

@implementation mainViewController

@synthesize controllerView;

BOOL oldForceES1;

@synthesize ESBtn;


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    oldForceES1 = false;
   
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


- (IBAction)ChangeEngine:(id)sender {
   
    oldForceES1 = ~oldForceES1;
  
    controllerView->ForceES1 = oldForceES1;
  
    [controllerView setRender];
   
    if (oldForceES1)
        [self.ESBtn setTitle:@"ES1 MODE" forState:UIControlStateNormal];
    else
        [self.ESBtn setTitle:@"ES2 MODE" forState:UIControlStateNormal];
   
}


@end



12. 結果圖







沒有留言: