2012年4月10日 星期二

OpenGL ES 入門: 五. 材質之一

since: 2012/04/10
update: 2012/04/13

reference:
1. 原文:
iPhone Development: Procedural Spheres in OpenGL ES
iPhone Development: OpenGL ES From the Ground Up, Part 5: Living in a Material World

2. 翻譯:
從零開始學習OpenGL ES之五 – 材質

材質: 繪製球體

A. 說明
      1. 在真正進入 "材質" 之前, 由於先前使用的二十面體, 無法在光效材質的相互
          影響下, 明顯的顯示鏡射元素(specular component)效果; 而最理想用來展示
          鏡射光
效果的形狀是球體.

      2. 接下來的球體, 將允許你指定在 "slices"(切片, 垂直向) 和  "stacks"(堆疊, 水平向)
          方面的 "解析度". 基本上是在緯度上(latitudinally)經度上(longitudinally)定義
          頂點
的數量.

----------------------------------------------------------------------------------------------

B. 繪製球體的前置作業
      1. 開啓 ConstantsAndMacros.h 檔案, 修改如下:
....
// PI
#define PI 3.14159265358979323846f

      2. 開啓 OpenGLESCommon.h 檔案, 修改如下:
....
//@add: 2D 紋理
#pragma mark -
#pragma mark Texture2D
#pragma mark -

typedef struct {
    GLfloat s;
    GLfloat t;
} Texture2D;

static inline Texture2D Texture2DMake(GLfloat inS, GLfloat inT)
{
    Texture2D ret;
    ret.s = inS;
    ret.t = inT;
   
    return ret;
}

      3. 開啓 GLView.h 檔案, 修改如下:
....
//@add for protocol
@protocol GLViewDelegate

@required
- (void)setupView:(GLView *)view;

@optional
//@update for drawing
- (void)drawView:(GLView *)view;
- (void)drawTriangle3D; // 畫三角形
- (void)drawSquare; // 畫正方形
- (void)drawVertexColor; // 畫頂點顏色
- (void)drawIcosahedron; // 畫二十面體
- (void)drawPerspective; // 畫透視多面體
- (void)drawLight; // 畫多面體光效
-(void)drawSpheres; // 畫球體

@end

      4. 開啓 GLView.m 檔案, 修改如下:
....
- (void)drawView
{
....
    //@update for drawing
    //[self.delegate drawTriangle3D]; // 畫三角形
    //[self.delegate drawSquare]; // 畫正方形
    //[self.delegate drawVertexColor]; // 畫頂點顏色
    //[self.delegate drawIcosahedron]; // 畫二十面體
    //[self.delegate drawPerspective]; // 畫透視多面體
    //[self.delegate drawLight]; // 畫多面體光效
    [self.delegate drawSpheres]; // 畫球體
....
}
....

      5. 開啓 ViewController.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import "GLView.h"

//@interface ViewController : UIViewController
//@update
@interface ViewController : UIViewController <GLViewDelegate>
{
    //@add for draw Spheres
    Vertex3D    *sphereTriangleStripVertices;     // 構成球面的三角形區塊之頂點
    Vector3D    *sphereTriangleStripNormals;    // 構成球面的三角形區塊之法線
    GLuint      sphereTriangleStripVertexCount; // 構成球面的三角形區塊之頂點數量
   
    Vertex3D    *sphereTriangleFanVertices;       // 構成球面的三角形扇狀之頂點
    Vector3D    *sphereTriangleFanNormals;      // 構成球面的三角形扇狀之法線
    GLuint      sphereTriangleFanVertexCount;   // 構成球面的三角形扇狀之頂點數量
}

//@add: getSolidSphere
void getSolidSphere(Vertex3D **triangleStripVertexHandle,  
                    // Will hold vertices to be drawn as a triangle strip.
                    //      Calling code responsible for freeing if not NULL
                    //
                    // 會將持有的頂點繪製成三角形區塊的一部分;
                    // 當不再使用時必須要釋放掉(free)


                    Vector3D **triangleStripNormalHandle,  
                    // Will hold normals for vertices to be drawn as triangle
                    //      strip. Calling code is responsible for freeing if
                    //      not NULL
                    //
                    // 會將持有的頂點法線繪製成三角形區塊的一部分;
                    // 當不再使用時必須要釋放掉(free)

                   
                    GLuint *triangleStripVertexCount,      
                    // On return, will hold the number of vertices contained in
                    //      triangleStripVertices
                    //
                    // 這個回傳的指標, 會持有 "構成三角形區塊頂點" 的頂點數量.
                   
                    Vertex3D **triangleFanVertexHandle,    
                    // Will hold vertices to be drawn as a triangle fan. Calling
                    //      code responsible for freeing if not NULL
                    //
                    // 會將持有要繪製成三角形扇狀的頂點;
                    // 當不再使用時必須要釋放掉(free)

                   
                    Vector3D **triangleFanNormalHandle,    
                    // Will hold normals for vertices to be drawn as triangle
                    //      strip. Calling code is responsible for freeing if
                    //      not NULL
                    //
                    // 會將持有要繪製成三角形區塊的頂點法線;
                    // 當不再使用時必須要釋放掉(free)

                   
                    GLuint *triangleFanVertexCount,        
                    // On return, will hold the number of vertices contained in
                    //      the triangleFanVertices
                    //
                    // 這個回傳的指標, 會持有 "構成三角形扇狀頂點" 的頂點數量.
                   
                    GLfloat radius,                        
                    // The radius of the circle to be drawn
                    //
                    // 要繪製的圓之半徑
                   
                    GLuint slices,                         
                    // The number of slices, determines vertical "resolution"
                    //
                    // 切片的數量, 決定垂直 "解析度"
                   
                    GLuint stacks                         
                    // the number of stacks, determines horizontal "resolution"
                    //
                    // 堆疊的數量, 決定水平 "解析度"
);

@end

      6. 開啓 ViewController.m 檔案, 修改如下:
....
//@add: getSolidSphere
void getSolidSphere(Vertex3D **triangleStripVertexHandle,  
                    // 將會持有的頂點繪製成三角形區塊的一部分;
                    // 當不再使用時必須要釋放掉(free)
                   
                    Vector3D **triangleStripNormalHandle,  
                    // 將會持有的頂點法線繪製成三角形區塊的一部分;
                    // 當不再使用時必須要釋放掉(free)
                   
                    GLuint *triangleStripVertexCount,      
                    // 這個回傳的指標, 會持有 "構成三角形區塊頂點" 的頂點數量.
                   
                    Vertex3D **triangleFanVertexHandle,    
                    // 將會持有要繪製成三角形扇狀的頂點;
                    // 當不再使用時必須要釋放掉(free)
                   
                    Vector3D **triangleFanNormalHandle,    
                    // 將會持有要繪製成三角形區塊的頂點法線;
                    // 當不再使用時必須要釋放掉(free)
                   
                    GLuint *triangleFanVertexCount,        
                    // 這個回傳的指標, 會持有 "構成三角形扇狀頂點" 的頂點數量.
                   
                    GLfloat radius,                        
                    // 要繪製的圓之半徑
                   
                    GLuint slices,                         
                    // 切片的數量, 決定垂直 "解析度"
                   
                    GLuint stacks)
                    // 堆疊的數量, 決定水平 "解析度"
{
    NSLog(@"ViewController => getSolidSphere");
    // Draw code here
   
}
....
//@add for <GLViewDelegate> method
-(void)setupView:(GLView *)view
{
    NSLog(@"step 05. ViewController => setupView:");
   
    //@add: 計算二十面體的頂點法線
    computeVerticesNormal();
   
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0; // 設定為 45 度視野
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);   
   
    CGRect rect = view.bounds;

    // 設定透視 viewport (基於視野角度計算錐台)
    glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
   
    // 設定正交 viewport (基於視野角度計算錐台)   
    /*
    glOrthof(-1.0,                                // Left
             1.0,                                          // Right
             -1.0 / (rect.size.width / rect.size.height),   // Bottom
             1.0 / (rect.size.width / rect.size.height),   // Top
             0.01,                                         // Near
             10000.0);                                     // Far   
    */
   
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height); 
   
    glMatrixMode(GL_MODELVIEW);
   
    //@add
    glShadeModel(GL_SMOOTH);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
   
    //@add for setup light
    //
    // 定義第一個光源的環境光
    // Define the ambient component of the first light
    /*
    const GLfloat light0Ambient[] = {0.1, 0.1, 0.1, 1.0};
    glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
    */
    //@update
    /*
    static const Color3D light0Ambient[] = {{0.05, 0.05, 0.05, 1.0}};
    glLightfv(GL_LIGHT0, GL_AMBIENT, (const GLfloat *)light0Ambient);
     */
    //@update
    static const Color3D light0Ambient[] = {{0.2, 0.2, 0.2, 1.0}};
    glLightfv(GL_LIGHT0, GL_AMBIENT, (const GLfloat *)light0Ambient);
   
    // 定義第一個光源的散射光
    // Define the diffuse component of the first light
    /*
    const GLfloat light0Diffuse[] = {0.7, 0.7, 0.7, 1.0};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
    */
    //@update
    /*
    static const Color3D light0Diffuse[] = {{0.4, 0.4, 0.4, 1.0}};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (const GLfloat *)light0Diffuse);
    */
    //@update
    static const Color3D light0Diffuse[] = {{0.8, 0.8, 0.8, 1.0}};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (const GLfloat *)light0Diffuse);
   
    // 定義第一個光源的鏡射光與亮度
    // Define the specular component and shininess of the first light
    /*
    const GLfloat light0Specular[] = {0.7, 0.7, 0.7, 1.0};
    const GLfloat light0Shininess = 0.4;
    glLightfv(GL_LIGHT0, GL_SPECULAR, light0Specular);
    glLightfv(GL_LIGHT0, GL_SHININESS, &light0Shininess);
    */
    //@update
    /*
    static const Color3D light0Specular[] = {{0.7, 0.7, 0.7, 1.0}};
    glLightfv(GL_LIGHT0, GL_SPECULAR, (const GLfloat *)light0Specular);
    glLightf(GL_LIGHT0, GL_SHININESS, 0.4);
     */
    //@update
    static const Color3D light0Specular[] = {{0.6, 0.6, 0.6, 1.0}};
    glLightfv(GL_LIGHT0, GL_SPECULAR, (const GLfloat *)light0Specular);
   
    // 定義第一個光源的位置
    // Define the position of the first light
    /*
    const GLfloat light0Position[] = {0.0, 10.0, 10.0, 0.0};
    glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
    */
    //@update
    static const Vertex3D light0Position[] = {{10.0, 10.0, 10.0}};
    glLightfv(GL_LIGHT0, GL_POSITION, (const GLfloat *)light0Position);   
   
    // 定義第一個光源的方向向量: 沿 z 軸而下
    // Define a direction vector for the light, this one points right down the Z axis
    /*
    const GLfloat light0Direction[] = {0.0, 0.0, -1.0};
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction);
    */
    //@update
    // Calculate light vector so it points at the object
    static const Vertex3D objectPoint[] = {{0.0, 0.0, -3.0}};
    const Vertex3D lightVector = Vector3DMakeWithStartAndEndPoints(light0Position[0], objectPoint[0]);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, (GLfloat *)&lightVector);
   
    // 定義第一個光源的遮光角: 限制角度為 90 度(使用 45 度遮光角)
    // Define a cutoff angle. This defines a 90 度 field of vision, since the cutoff
    // is number of degrees to each side of an imaginary line drawn from the light's
    // position along the vector supplied in GL_SPOT_DIRECTION above
    //glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
    // @update
    // This defines a 50 度 field of vision
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 25.0);
   
    glLoadIdentity();
   
    // 清除緩存用的灰色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
....
// 畫球體
- (void)drawSpheres
{
    NSLog(@"ViewController => drawSpheres");
    static GLfloat rot = 0.0;
   
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-3.0f); 
    glRotatef(rot,1.0f,1.0f,1.0f);
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    // Draw code here ....

 
    glDisableClientState(GL_VERTEX_ARRAY);

    glDisableClientState(GL_NORMAL_ARRAY);
   
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;               
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}

....
//@add
- (void)dealloc
{
    if(sphereTriangleStripVertices)
        free(sphereTriangleStripVertices);

    if (sphereTriangleStripNormals)
        free(sphereTriangleStripNormals);
   
    if (sphereTriangleFanVertices)
        free(sphereTriangleFanVertices);

    if (sphereTriangleFanNormals)
        free(sphereTriangleFanNormals);
    //[super dealloc];
}
....

----------------------------------------------------------------------------------------------

C. 開始繪製球體
      1. 開啓 ViewController.m 檔案, 修改如下:
....
//@add: getSolidSphere
void getSolidSphere(Vertex3D **triangleStripVertexHandle,  
                    // 將會持有的頂點繪製成三角形區塊的一部分;
                    // 當不再使用時必須要釋放掉(free)

                   
                    Vector3D **triangleStripNormalHandle,  
                    // 將會持有的頂點法線繪製成三角形區塊的一部分;
                    // 當不再使用時必須要釋放掉(free)

                   
                    GLuint *triangleStripVertexCount,      
                    // 這個回傳的指標, 會持有 "構成三角形區塊頂點" 的頂點數量.
                   
                    Vertex3D **triangleFanVertexHandle,    
                    // 將會持有要繪製成三角形扇狀的頂點;
                    // 當不再使用時必須要釋放掉(free)

                   
                    Vector3D **triangleFanNormalHandle,    
                    // 將會持有要繪製成三角形區塊的頂點法線;
                    // 當不再使用時必須要釋放掉(free)

                   
                    GLuint *triangleFanVertexCount,        
                    // 這個回傳的指標, 會持有 "構成三角形扇狀頂點" 的頂點數量.
                   
                    GLfloat radius,                        
                    // 要繪製的圓之半徑
                   
                    GLuint slices,                         
                    // 切片的數量, 決定垂直 "解析度"
                   
                    GLuint stacks)
                    // 堆疊的數量, 決定水平 "解析度"
{
    NSLog(@"ViewController => getSolidSphere");
    // Draw code here
    GLfloat rho, drho, theta, dtheta;
    GLfloat x, y, z;
    GLfloat nsign=1.0;
    drho = PI / (GLfloat) stacks;
    dtheta = 2.0 * PI / (GLfloat) slices;
    Vertex3D *triangleStripVertices, *triangleFanVertices;
    Vector3D *triangleStripNormals, *triangleFanNormals;
   
    // Calculate the Triangle Fan for the endcaps
    *triangleFanVertexCount = slices+2;

    triangleFanVertices = (Vertex3D *) calloc(*triangleFanVertexCount, sizeof(Vertex3D));

    triangleFanVertices[0].x = 0.0;
    triangleFanVertices[0].y = 0.0;
    triangleFanVertices[0].z = nsign * radius;
    int counter = 1;
    for (int j = 0; j <= slices; j++)
    {
        theta = (j == slices) ? 0.0 : j * dtheta;
        x = -sin(theta) * sin(drho);
        y = cos(theta) * sin(drho);
        z = nsign * cos(drho);
        triangleFanVertices[counter].x = x * radius;
        triangleFanVertices[counter].y = y * radius;
        triangleFanVertices[counter++].z = z * radius;
    }
   
    // Normals for a sphere around the origin are darn easy
    // - just treat the vertex as a vector and normalize it.

    triangleFanNormals = (Vertex3D *) malloc(*triangleFanVertexCount * sizeof(Vertex3D));

    memcpy(triangleFanNormals, triangleFanVertices, *triangleFanVertexCount * sizeof(Vertex3D));

    for (int i = 0; i < *triangleFanVertexCount; i++)
        Vector3DNormalize(&triangleFanNormals[i]);
   
    // Calculate the triangle strip for the sphere body
    *triangleStripVertexCount = (slices + 1) * 2 * stacks;

    triangleStripVertices = (Vertex3D *) calloc(*triangleStripVertexCount, sizeof(Vertex3D));
    //
    counter = 0;

    for (int i = 0; i < stacks; i++) {
        rho = i * drho;
       
        for (int j = 0; j <= slices; j++)
        {
            /*
             0.0, 1.0,
             1.0, 1.0,
             0.0, 0.0,
             1.0, 0.0
             */
            theta = (j == slices) ? 0.0 : j * dtheta;
            x = -sin(theta) * sin(rho);
            y = cos(theta) * sin(rho);
            z = nsign * cos(rho);
            // TODO: Implement texture mapping if texture used
            //                TXTR_COORD(s, t);
            triangleStripVertices[counter].x = x * radius;
            triangleStripVertices[counter].y = y * radius;
            triangleStripVertices[counter++].z = z * radius;
            x = -sin(theta) * sin(rho + drho);
            y = cos(theta) * sin(rho + drho);
            z = nsign * cos(rho + drho);
            //                TXTR_COORD(s, t - dt);
            triangleStripVertices[counter].x = x * radius;
            triangleStripVertices[counter].y = y * radius;
            triangleStripVertices[counter++].z = z * radius;
        }
    }
   
    triangleStripNormals = (Vertex3D *) malloc(*triangleStripVertexCount * sizeof(Vertex3D));

    memcpy(triangleStripNormals, triangleStripVertices, *triangleStripVertexCount * sizeof(Vertex3D));

    for (int i = 0; i < *triangleStripVertexCount; i++)
        Vector3DNormalize(&triangleStripNormals[i]);
   
    *triangleStripVertexHandle = triangleStripVertices;
    *triangleStripNormalHandle = triangleStripNormals;
    *triangleFanVertexHandle = triangleFanVertices;
    *triangleFanNormalHandle = triangleFanNormals;
}
....
-(void)setupView:(GLView *)view
{
....
    //@add
    getSolidSphere(&sphereTriangleStripVertices, &sphereTriangleStripNormals, &sphereTriangleStripVertexCount, &sphereTriangleFanVertices, &sphereTriangleFanNormals, &sphereTriangleFanVertexCount, 1.0, 50, 50);
}
....
// 畫球體
- (void)drawSpheres
{
    NSLog(@"ViewController => drawSpheres");
    static GLfloat rot = 0.0;
   
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-3.0f); 
    glRotatef(rot,1.0f,1.0f,1.0f);
    glClearColor(0.7, 0.7, 0.7, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
   
    // Draw code here ....
    //@update
    glVertexPointer(3, GL_FLOAT, 0, sphereTriangleFanVertices);
    glNormalPointer(GL_FLOAT, 0, sphereTriangleFanNormals);
    glDrawArrays(GL_TRIANGLE_FAN, 0, sphereTriangleFanVertexCount);
   
    glVertexPointer(3, GL_FLOAT, 0, sphereTriangleStripVertices);
    glNormalPointer(GL_FLOAT, 0, sphereTriangleStripNormals);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, sphereTriangleStripVertexCount);
   
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
   
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot += 50 * timeSinceLastDraw;               
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
....

      2. 編譯並執行:

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。