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. 編譯並執行:
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。