<b>3</b><b>)Renderer</b><b>:RayPickRenderer.java</b>
OpenGL渲染器,比較多的東西都在這裡面了。
public class RayPickRenderer implements Renderer {
private Context mContext;
private Cube cube;
int texture = -1;
public float mfAngleX = 0.0f;
public float mfAngleY = 0.0f;
public float gesDistance = 0.0f;
// 觀察者、中心和上方
private Vector3f mvEye = new Vector3f(0, 0, 7f), mvCenter = new Vector3f(0,
0, 0), mvUp = new Vector3f(0, 1, 0);
private OnSurfacePickedListener onSurfacePickedListener;
public RayPickRenderer(Context context) {
mContext = context;
cube = new Cube();
}
/**
* 逐幀渲染
*/
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 清除螢幕和深度緩存
gl.glLoadIdentity(); // 重置目前的模型觀察矩陣
// 緊接着設定模型視圖矩陣
setUpCamera(gl);
gl.glPushMatrix();
{
// 渲染物體
drawModel(gl);
}
gl.glPopMatrix();
// gl.glPushMatrix();
// {
// // 渲染射線
// PickFactory.getPickRay().draw(gl);
// }
// gl.glPopMatrix();
// 渲染選中的三角形
drawPickedTriangle(gl);
updatePick();
* 設定相機矩陣
*
* @param gl
private void setUpCamera(GL10 gl) {
// 設定模型視圖矩陣
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
// GLU.gluLookAt(gl, mfEyeX, mfEyeY, mfEyeZ, mfCenterX, mfCenterY,
// mfCenterZ, 0, 1, 0);//系統提供
Matrix4f.gluLookAt(mvEye, mvCenter, mvUp, AppConfig.gMatView);
gl.glLoadMatrixf(AppConfig.gMatView.asFloatBuffer());
// private Matrix4f matRotX = new Matrix4f();
// private Matrix4f matRotY = new Matrix4f();
private Matrix4f matRot = new Matrix4f();
private Vector3f point;
* 渲染模型
private void drawModel(GL10 gl) {
// 首先對模型進行變換
// -------使用系統函數進行變換
// gl.glRotatef(mfAngleX, 1, 0, 0);//繞X軸旋轉
// gl.glRotatef(mfAngleY, 0, 1, 0);//繞Y軸旋轉
// -------托管方式進行變換
// matRotX.setIdentity();
// matRotY.setIdentity();
// matRotX.rotX((float) (mfAngleX * Math.PI / 180));
// matRotY.rotY((float) (mfAngleY * Math.PI / 180));
// AppConfig.gMatModel.set(matRotX);
// AppConfig.gMatModel.mul(matRotY);
/* 以下方式完全按照手勢方向旋轉 */
matRot.setIdentity();
// 世界坐标系的向量點
point = new Vector3f(mfAngleX, mfAngleY, 0);
try {
// 轉換到模型内部的點,先要求逆
matInvertModel.set(AppConfig.gMatModel);
matInvertModel.invert();
matInvertModel.transform(point, point);
float d = Vector3f.distance(new Vector3f(), point);
// 再減少誤差
if (Math.abs(d - gesDistance) <= 1E-4) {
// 繞這個機關向量旋轉(由于誤差可能會産生縮放而使得模型消失不見)
matRot.glRotatef((float) (gesDistance * Math.PI / 180), point.x
/ d, point.y / d, point.z / d);
// 旋轉後在原基礎上再轉
if (0 != gesDistance) {
AppConfig.gMatModel.mul(matRot);
}
}
} catch (Exception e) {
// 由于四舍五入求逆矩陣失敗
gesDistance = 0;
gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer());
// 設定預設顔色
gl.glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0,
cube.getCoordinate(Cube.VERTEX_BUFFER));
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0,
cube.getCoordinate(Cube.TEXTURE_BUFFER));
gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 24, GL10.GL_UNSIGNED_BYTE,
cube.getIndices());
gl.glDisable(GL10.GL_TEXTURE_2D);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// 渲染坐标系
drawCoordinateSystem(gl);
private Vector3f transformedSphereCenter = new Vector3f();
private Ray transformedRay = new Ray();
private Matrix4f matInvertModel = new Matrix4f();
private Vector3f[] mpTriangle = { new Vector3f(), new Vector3f(),
new Vector3f() };
private FloatBuffer mBufPickedTriangle = IBufferFactory
.newFloatBuffer(3 * 3);
* 更新拾取事件
private void updatePick() {
if (!AppConfig.gbNeedPick) {
return;
AppConfig.gbNeedPick = false;
// 更新最新的拾取射線
PickFactory.update(AppConfig.gScreenX, AppConfig.gScreenY);
// 獲得最新的拾取射線
Ray ray = PickFactory.getPickRay();
// 首先把模型的綁定球通過模型矩陣,由模型局部空間變換到世界空間
AppConfig.gMatModel.transform(cube.getSphereCenter(),
transformedSphereCenter);
// 觸碰的立方體面的标記為無
cube.surface = -1;
// 首先檢測拾取射線是否與模型綁定球發生相交
// 這個檢測很快,可以快速排除不必要的精确相交檢測
if (ray.intersectSphere(transformedSphereCenter, cube.getSphereRadius())) {
// 如果射線與綁定球發生相交,那麼就需要進行精确的三角面級别的相交檢測
// 由于我們的模型渲染資料,均是在模型局部坐标系中
// 而拾取射線是在世界坐标系中
// 是以需要把射線轉換到模型坐标系中
// 這裡首先計算模型矩陣的逆矩陣
// 把射線變換到模型坐标系中,把結果存儲到transformedRay中
ray.transform(matInvertModel, transformedRay);
// 将射線與模型做精确相交檢測
if (cube.intersect(transformedRay, mpTriangle)) {
// 如果找到了相交的最近的三角形
AppConfig.gbTrianglePicked = true;
// 觸碰了哪一個面
Log.i("觸碰的立方體面", "=标記=" + cube.surface);
// 回調
if (null != onSurfacePickedListener) {
onSurfacePickedListener.onSurfacePicked(cube.surface);
// 填充資料到被選取三角形的渲染緩存中
mBufPickedTriangle.clear();
for (int i = 0; i < 3; i++) {
IBufferFactory
.fillBuffer(mBufPickedTriangle, mpTriangle[i]);
// Log.i("點" + i, mpTriangle[i].x + "\t" + mpTriangle[i].y
// + "\t" + mpTriangle[i].z);
mBufPickedTriangle.position(0);
} else {
AppConfig.gbTrianglePicked = false;
* 渲染選中的三角形
private void drawPickedTriangle(GL10 gl) {
if (!AppConfig.gbTrianglePicked) {
// 由于傳回的拾取三角形資料是出于模型坐标系中
// 是以需要經過模型變換,将它們變換到世界坐标系中進行渲染
// 設定模型變換矩陣
// 設定三角形顔色,alpha為0.7
gl.glColor4f(1.0f, 0.0f, 0.0f, 0.7f);
// 開啟Blend混合模式
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
// 禁用無關屬性,僅僅使用純色填充
gl.glDisable(GL10.GL_DEPTH_TEST);
// 開始綁定渲染頂點資料
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufPickedTriangle);
// 送出渲染
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
// 重置相關屬性
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDisable(GL10.GL_BLEND);
* 渲染坐标系
private void drawCoordinateSystem(GL10 gl) {
// 暫時禁用深度測試
// 設定點和線的寬度
gl.glLineWidth(2.0f);
// 僅僅啟用頂點資料
FloatBuffer fb = IBufferFactory.newFloatBuffer(3 * 2);
fb.put(new float[] { 0, 0, 0, 1.4f, 0, 0 });
fb.position(0);
// 渲染X軸
gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);// 設定紅色
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
gl.glDrawArrays(GL10.GL_LINES, 0, 2);
fb.clear();
fb.put(new float[] { 0, 0, 0, 0, 1.4f, 0 });
// 渲染Y軸
gl.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);// 設定綠色
fb.put(new float[] { 0, 0, 0, 0, 0, 1.4f });
// 渲染Z軸
gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);// 設定藍色
// 重置
gl.glLineWidth(1.0f);
* 建立繪圖表面時調用
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 全局性設定
gl.glEnable(GL10.GL_DITHER);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
// 設定清屏背景顔色
// gl.glClearColor(0, 0, 0, 0);
gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
// 設定着色模型為平滑着色
gl.glShadeModel(GL10.GL_SMOOTH);
// 啟用背面剪裁
gl.glEnable(GL10.GL_CULL_FACE);
gl.glCullFace(GL10.GL_BACK);
// 啟用深度測試
// 禁用光照和混合
gl.glDisable(GL10.GL_LIGHTING);
loadTexture(gl);
AppConfig.gMatModel.setIdentity();
* 當繪圖表面尺寸發生改變時調用
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 設定視口
gl.glViewport(0, 0, width, height);
AppConfig.gpViewport[0] = 0;
AppConfig.gpViewport[1] = 0;
AppConfig.gpViewport[2] = width;
AppConfig.gpViewport[3] = height;
// 設定投影矩陣
float ratio = (float) width / height;// 螢幕寬高比
gl.glMatrixMode(GL10.GL_PROJECTION);
// GLU.gluPerspective(gl, 45.0f, ratio, 1, 5000);系統提供
Matrix4f.gluPersective(45.0f, ratio, 1, 10, AppConfig.gMatProject);
gl.glLoadMatrixf(AppConfig.gMatProject.asFloatBuffer());
AppConfig.gMatProject.fillFloatArray(AppConfig.gpMatrixProjectArray);
// 每次修改完GL_PROJECTION後,最好将目前矩陣模型設定回GL_MODELVIEW
private void loadTexture(GL10 gl) {
// 啟用紋理映射
gl.glClearDepthf(1.0f);
// 允許2D貼圖,紋理
IntBuffer intBuffer = IntBuffer.allocate(1);
// 建立紋理
gl.glGenTextures(1, intBuffer);
texture = intBuffer.get();
// 設定要使用的紋理
gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);
// 打開二進制流
InputStream is = mContext.getResources().openRawResource(
R.drawable.snow_leopard);
Bitmap mBitmap = BitmapFactory.decodeStream(is);
// Log.i("寬度|高度", mBitmap.getWidth() + "|" + mBitmap.getHeight());
// 生成紋理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
// 線形濾波
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
public void setOnSurfacePickedListener(
OnSurfacePickedListener onSurfacePickedListener) {
this.onSurfacePickedListener = onSurfacePickedListener;
}
<b></b>
<b>4</b><b>)主Activity</b><b>:RayPickActivity</b>
繼承各面點選的監聽接口,提示了個Toast就OK了。很簡單,當時都沒注釋。
public class RayPickActivity extends Activity implements
OnSurfacePickedListener {
private GLSurfaceView mGLSurfaceView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLSurfaceView = new MyGLSurfaceView(this, this);
mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
setContentView(mGLSurfaceView);
mGLSurfaceView.requestFocus();
mGLSurfaceView.setFocusableInTouchMode(true);
protected void onResume() {
super.onResume();
mGLSurfaceView.onResume();
protected void onPause() {
super.onPause();
mGLSurfaceView.onPause();
private Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(RayPickActivity.this, "選中了" + msg.what + "面",
Toast.LENGTH_SHORT).show();
};
public void onSurfacePicked(int which) {
myHandler.sendEmptyMessage(which);
<b>5</b><b>)Matrix4f</b><b>内的glRotatef(…)</b><b>方法</b>
公式如下圖,其中其中C為cosθ,S為sinθ,A為機關化的旋轉軸。
<a target="_blank" href="http://blog.51cto.com/attachment/201202/095112297.jpg"></a>
但這是左手坐标系下的公式,之前有改過右手坐标系的但不能實作效果。其他幾個繞X、Y、Z軸的也是這樣。
這應該是因為OpenGL坐标系原點在左下角,而視窗坐标系的原點為右上角,程式在Y軸上做了修正的關系導緻的。這次可以高擡左手,大拇指向右、食指向下、彎曲中指90°,是不是各正軸都對應上了呢^^。
/**
* 繞任意機關軸旋轉任意角度的矩陣
*/
public final void glRotatef(float angle, float x, float y, float z) {
float sinAngle, cosAngle;
sinAngle = (float) Math.sin((double) angle);
cosAngle = (float) Math.cos((double) angle);
this.m00 = cosAngle + (1 - cosAngle) * x * x;
this.m01 = (1 - cosAngle) * x * y - sinAngle * z;
this.m02 = (1 - cosAngle) * x * z + sinAngle * y;
this.m03 = 0;
this.m10 = (1 - cosAngle) * x * y + sinAngle * z;
this.m11 = cosAngle + (1 - cosAngle) * y * y;
this.m12 = (1 - cosAngle) * y * z - sinAngle * x;
this.m13 = 0;
this.m20 = (1 - cosAngle) * x * z - sinAngle * y;
this.m21 = (1 - cosAngle) * y * z + sinAngle * x;
this.m22 = cosAngle + (1 - cosAngle) * z * z;
this.m23 = 0;
this.m30 = 0;
this.m31 = 0;
this.m32 = 0;
this.m33 = 1;
<b>三、後記</b>
小弟那時做這個,貌似是為了做個3D魔方啊。然後無聊可以在那轉啊轉啊的,不過願望很美好……總之,我沒做下去,忘記當時又忙活啥去了T^T。
現在也沒這個激情繼續做這個呢,希望這篇文檔能給那些有同樣想法的人給予一定的幫助吧,加油^^。
<a href="http://down.51cto.com/data/2359933" target="_blank">附件:http://down.51cto.com/data/2359933</a>
本文轉自winorlose2000 51CTO部落格,原文連結:http://blog.51cto.com/vaero/790637,如需轉載請自行聯系原作者