10.3、OpenGL ES摄像机
视平截体定义了在5.6节讲解过的,并在第9章详细说明的“视点”。 很多3D应用会使用一个比喻的‘摄像机”, 通过这个“ 摄像机”来查看场景。使用一个虚拟的摄像机在虚拟空间中移动,而不是直接使用一个平截体数据结构。当需要的时候,与摄像机的位置、方向和视野对应的平截体可以计算出来。 摄像机是用于定义视点的一个非常有用的比喻,使用这个比喻便于创建类似下面的在OpenGLES_Ch10_1中的UtilityCamera类的类。UtilityCamera 类有类似“-(void)setPosition:(GLK Vector3 )aPosition lookAtPosition:(GLKVector3)lookAtPosition”和 “ -(void)moveBy:(GLKVector3) aVector”的方法。可以使用比直接处理平截体数据结构更熟悉和直观的动作和术语在虚拟世界中移动摄像机,以此来活动视点。
//
// UtilityOpenGLCamera.h
//
//
#include <GLKit/GLKit.h>
#import "AGLKFrustum.h"
@class UtilityCamera;
@protocol UtilityOpenGLCameraDelegate <NSObject>
/////////////////////////////////////////////////////////////////
// Returning NO prevents changes.
@optional
- (BOOL)camera:(UtilityCamera *)aCamera
willChangeEyePosition:(GLKVector3 *)eyePositionPtr
lookAtPosition:(GLKVector3 *)lookAtPositionPtr;
@end
@interface UtilityCamera : NSObject
@property (nonatomic, assign, readwrite)
__unsafe_unretained IBOutlet id delegate;
@property(assign, nonatomic, readonly)
GLKMatrix4 projectionMatrix;
@property(assign, nonatomic, readonly)
GLKMatrix4 modelviewMatrix;
@property(assign, nonatomic, readonly)
GLKVector3 position;
@property(assign, nonatomic, readonly)
GLKVector3 lookAtPosition;
@property(assign, nonatomic, readonly)
GLKVector3 upUnitVector;
@property(nonatomic, readonly)
const AGLKFrustum *frustumForCulling;
- (void)configurePerspectiveFieldOfViewRad:(GLfloat)angle
aspectRatio:(GLfloat)anAspectRatio
near:(GLfloat)nearLimit
far:(GLfloat)farLimit;
- (void)rotateAngleRadiansAboutY:(GLfloat)anAngleRadians;
- (void)rotateAngleRadiansAboutX:(GLfloat)anAngleRadians;
- (void)moveBy:(GLKVector3)aVector;
- (void)moveTo:(GLKVector3)aVector;
- (void)setPosition:(GLKVector3)aPosition
lookAtPosition:(GLKVector3)lookAtPosition;
- (void)setOrientation:(GLKMatrix4)aMatrix;
@end
UtilityCamera类重用并封装了在第9章介绍的AGlKFrustum数据结构。Utility-Camera的projectionMatrix和modelviewMatrix属性是直接使用与摄像机的位置、方向和视野相对应的平截体计算出来的。 所有的用于改变平截体的位置和方向的UtilityCamera方法都会调用UtilityCamera的“-setPosition:lookAtPosition:" 方法,然后这个方法转而向摄像机的委托发送“(BOOL)camera:(UtilityCamera )aCamera willChangeEyePosition:(GLKVector3 )eyePositionPtrlookAtPosition:(GLKVector3 *)lookAtPositionPtr" 消息。这个委托会执行额外的处理以控制要发生什么。这个委托可以返回NO来阻止摄像机位置的变化。
注意
委托是 Cocoa Touch的一般模式。委托是提供了影响另一个对象行为的机会的对象。基本概念是两个对象协调解决一个问题。其中一个对象是非常综合的,并且适合在各种情况下重用。它会存储对于另一个对象的引用,即它的委托,并会在 关键时刻向它的委托发送消息。这个消息给予了这个委托一个执行额外处理或者控制要发生什么的机会。应该在委托中实现特定于应用的规则和逻辑,而不是修改或者子类化更通用的对象。
例子OpenGLES_Ch10_1会使用这个应用的OpenGLES_Ch10_1ViewController 实 例作为摄像机的委托。OpenGLES_Ch10_1ViewController 实现了“. camera:willChangeEyePosition:lookAtPosition:”方法来防止摄像机的位置低于在摄像机位置处的地形高度。 这个委托的更精密实现还能防止摄像机与其他模型碰撞,或者实现虚拟的重力以让摄像机滑下地形内的丘陵。 下面的UtilityCamera类的部分实现代码解释了这个类是怎么管理视平截体以及怎么与一个委托对象通信的。要获得完整实现,请访问: http://opengles.cosmicthump.com/ learning-opengl-es-sample-code/
///
// UtilityOpenGLCamera.m
//
//
#import "UtilityCamera.h"
#import "AGLKFrustum.h"
@interface UtilityCamera ()
{
AGLKFrustum frustum;
}
@property(assign, nonatomic, readwrite)
BOOL isInCallback;
@end
/////////////////////////////////////////////////////////////////
// Instances of this class encapsulate a viewing frustum and
// enable point-of-view animation using the metaphor of a
// camera.
@implementation UtilityCamera
@synthesize delegate = delegate_;
@synthesize isInCallback = isInCallback_;
/////////////////////////////////////////////////////////////////
// Initialize the receiver with a default frustum
- (id)init
{
self = [super init];
if(nil != self)
{
// Default 45 deg. field of view, square aspect ratio,
// near distance of 0.5, and far distance of 5000
frustum = AGLKFrustumMakeFrustumWithParameters(
GLKMathDegreesToRadians(45.0f),
1.0f,
0.5f,
5000.0f);
// Default eye at origin, look down neg. Z axis,
// Y axis is “up”
AGLKFrustumSetPositionAndDirection(
&frustum,
GLKVector3Make(0.0f, 0.0f, 0.0f),
GLKVector3Make(0.0f, 0.0f, -1.0f),
GLKVector3Make(0.0f, 1.0f, 0.0f));
}
return self;
}
/////////////////////////////////////////////////////////////////
// Set the receiver’s frustum shape
- (void)configurePerspectiveFieldOfViewRad:(GLfloat)angle
aspectRatio:(GLfloat)anAspectRatio
near:(GLfloat)nearLimit
far:(GLfloat)farLimit;
{
AGLKFrustumSetPerspective(
&frustum,
angle,
anAspectRatio,
nearLimit,
farLimit);
}
/////////////////////////////////////////////////////////////////
// Return the frustum to be used when culling objects that
// can't be seen by the camera at it's current position and
// orientation.
- (const AGLKFrustum *)frustumForCulling
{
return &frustum;
}
/////////////////////////////////////////////////////////////////
// Return the receiver’s “up” direction
- (GLKVector3)upUnitVector;
{
return frustum.yUnitVector;
}
/////////////////////////////////////////////////////////////////
// Return the receiver’s eye position
- (GLKVector3)position
{
return frustum.eyePosition;
}
/////////////////////////////////////////////////////////////////
// Return’s the receiver’s look-at position
- (GLKVector3)lookAtPosition
{
const GLKVector3 eyePosition = frustum.eyePosition;
const GLKVector3 lookAtPosition = GLKVector3Add(
eyePosition, frustum.zUnitVector);
return lookAtPosition;
}
/////////////////////////////////////////////////////////////////
// Set the receiver’s eye and look-at positions and give optional
// delegate object opportunity to revise or constrain the
// specified positions.
- (void)setPosition:(GLKVector3)aPosition
lookAtPosition:(GLKVector3)lookAtPosition;
{
if(!self.isInCallback)
{ // Prevent recursive call to -setPosition:lookAtPosition:
self.isInCallback = YES;
BOOL shouldMakeChange = YES;
if([self.delegate respondsToSelector:
@selector(camera:willChangeEyePosition:lookAtPosition:)])
{
shouldMakeChange =
[self.delegate camera:self
willChangeEyePosition:&aPosition
lookAtPosition:&lookAtPosition];
}
if(shouldMakeChange)
{
const GLKVector3 upUnitVector =
GLKVector3Make(0.0f, 1.0f, 0.0f); // Assume Y up
AGLKFrustumSetPositionAndDirection(
&frustum,
aPosition,
lookAtPosition,
upUnitVector);
}
self.isInCallback = NO;
}
}
- (void)setOrientation:(GLKMatrix4)aMatrix;
{
AGLKFrustumSetToMatchModelview(&frustum,
aMatrix);
}
/////////////////////////////////////////////////////////////////
// Move the receiver’s eye position in orbit around receiver’s
// look-at position. This method changes the receiver’s frustum
// viewing direction. This method indirectly calls
// -setPosition:lookAtPosition: giving the receiver’s optional
// delegate an opportunity to revise or constrain the resulting
// frustum positions. Positive angles produce counterclockwise
// orbit about the receiver’s “up” axis.
- (void)rotateAngleRadiansAboutY:(GLfloat)anAngleRadians;
{
GLKMatrix4 modelview = AGLKFrustumMakeModelview(&frustum);
GLKMatrix4 newmatrx = GLKMatrix4Rotate(
modelview, anAngleRadians, 0.0f, 1.0f, 0.0f);
AGLKFrustumSetToMatchModelview(&frustum, newmatrx);
}
/////////////////////////////////////////////////////////////////
// Move the receiver’s eye position in orbit around receiver’s
// look-at position. This method changes the receiver’s frustum
// viewing direction. This method indirectly calls
// -setPosition:lookAtPosition: giving the receiver’s optional
// delegate an opportunity to revise or constrain the resulting
// frustum positions. Positive angles produce counterclockwise
// orbit about the receiver’s “up” axis.
- (void)rotateAngleRadiansAboutX:(GLfloat)anAngleRadians;
{
GLKMatrix4 modelview = AGLKFrustumMakeModelview(&frustum);
GLKMatrix4 newmatrx = GLKMatrix4Rotate(
modelview, anAngleRadians, 1.0f, 0.0f, 0.0f);
AGLKFrustumSetToMatchModelview(&frustum, newmatrx);
}
/////////////////////////////////////////////////////////////////
// Move the receiver’s eye position and look-at position by the
// direction and distance of aVector. This method calls
// -setPosition:lookAtPosition: giving the receiver’s optional
// delegate an opportunity to revise or constrain the resulting
// frustum positions.
- (void)moveBy:(GLKVector3)aVector;
{
const GLKVector3 currentEyePosition = [self position];
const GLKVector3 currentLookAtPosition = [self lookAtPosition];
[self setPosition:GLKVector3Add(currentEyePosition, aVector)
lookAtPosition:GLKVector3Add(currentLookAtPosition, aVector)];
}