OpenGL의 기본적인 기능들을 이용하여 태양계를 구성
- Viewport, Projection, Viewing transform
- Texture 2D mapping
- Transformation matrix
- ModelView, Projection mode
Viewport Transform
void gl.glViewport(int x, int y, int width, int height)
- x, y : Viewport 사각형의 왼쪽 아래 코너의 좌표, default는 (0, 0)
- width, height : Viewport 영역의 폭과 높이
Projection Transform
void GLU.gluPerspective(GL10 gl, float fovy, float aspect, float zNear, float zFar)
- fovy : 수직 방향의 field of view (단위 : degree)
- aspect : Clipping window의 가로:세로 비율 (가로/세로)
- zNear : Near plane까지의 거리 (>0)
- zFar : Far plane 까지의 거리 (>0)
Viewing Transform
void GLU.glLookat(GL10 gl, float eyeX, float eyeY, float eyeZ,
float centerX, float centerY, float centerZ,
float upX, float upY, float upZ)
- eyeX, eyeY, eyeZ : 카메라(눈)의 위치
- centerX, centerY, centerZ : 카메라가 바라보는 점의 위치
- upX, upY, upZ : 카메라 위쪽의 방향 (Up vector)
Transformation Matrix
- Modelview matrix M
- gl.glMatrixMode(GL10.GL_MODELVIEW); 로 활성화
- 좌표계에서 물체의 변환, 시점의 이동 등 반영
- Projection matrix P
- gl.glMatrixMode(GL10.GL_PROJECTION); 로 활성화
- 카메라의 projection 특성 반영
- 예) 평형 투사, 원근 투사 등
- gl.glLoadIdentity()
- 현재 활성화된 행렬을 단위 행렬로 초기화
- 변환에 관련된 명령 수행 시
- 현재 활성화된 행렬(M 또는 P)에 명령이 의미하는 변환이 곱해짐
- 현재 활성화된 행렬이 M이고 gl.glTranslate(행렬 T 생성) 수행 시 M <- MT로 바뀜 (post-multiplication)
- 따라서 실제 적용되는 변환의 역순으로 GL 명령들을 수행
- 3차원 점 X에 대해서는 최종적으로 PMX와 같이 변환되어 viewport 상의 최종적인 좌표 생성
- void gl.glLoadMatrixf(float[] m, int offset)
- 배열 m을 이용한 변환 행렬 설정
- 변환 matrix를 배열 m의 matrix로 초기화
- void gl.glMultMatrixf(float[] m, int offset)
- 배열 m을 이용한 변환 행렬 설정
- 변환 matrix에 배열 m에 따른 matrix의 post-multiplication
gl.glMatrixMode(gl.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glMultMatrix(T, 0);
gl.glMultMatrix(R, 0);
gl.glMatrixMode(gl.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glMultMatrix(R, 0);
gl.glMultMatrix(T, 0);
- void gl.glTranslatef(float x, float y, float z);
- 평행이동에 해당하는 matrix를 현재 matrix에 곱함
- x, y, z : 평행 이동량
- void gl.glRotatef(float angle, float x, float y, float z);
- 회전이동에 해당하는 matrix를 현재 matrix에 곱함
- angle : 회전각
- x, y, z : 회전 중심축
- void gl.glScalef(float x, float y, float z);
- 크기변화에 해당하는 matrix를 현재 matrix에 곱함
- x, y, z : 각 축 방향 Scaling 배수
Manifest
항상 landscape 모드로 유지하도록 설정
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.solarsystem_obj_demo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
그림 파일을 <Project path>/app/src/main/res/drawable에 저장합니다.
Layout attribute로 android:background="@drawable/background"
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="@drawable/background">
<Button
android:id="@+id/btnTextureOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:onClick="texture_on_off"
android:text="Texture"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnRotationOnOff" />
<Button
android:id="@+id/btnRotationOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:onClick="rotation_on_off"
android:text="Rotation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Main Activity
Touch event를 위한 변수들 및 OpenGL을 사용하기 위해 GLSurfaceView, Renderer(SolarSystemRenderer) 선언, 상단바 제거 및 Renderer 지정
public class MainActivity extends AppCompatActivity {
private final double TOUCH_SCALE_FACTOR = 0.1;
private double preAzim;
private double preElev;
private GLSurfaceView surfaceView;
private SolarSystemRenderer solarSystem = new SolarSystemRenderer(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
ActionBar actionBar = getSupportActionBar();
actionBar.hide();
setContentView(R.layout.activity_main);
surfaceView = new GLSurfaceView(this);
surfaceView.setEGLConfigChooser( 8, 8, 8, 8, 16, 0 );
surfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
surfaceView.setRenderer(solarSystem);
surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
surfaceView.setZOrderOnTop(true);
addContentView(surfaceView, new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
}
Touch를 하여 화면 조작을 위한 event 지정
x, y 방향으로의 변화량을 통해 태양계를 바라보는 카메라의 위도, 경도를 변경
0~360˚로 범위 제한
텍스쳐 및 행성 자전, 공전 토글 버튼 함수 정의
@Override
public boolean onTouchEvent(MotionEvent e) {
double azim = e.getX();
double elev = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
double dAzim = azim - preAzim;
double dElev = elev - preElev;
solarSystem.azim += dAzim * TOUCH_SCALE_FACTOR;
solarSystem.elev += dElev * TOUCH_SCALE_FACTOR;
if(solarSystem.azim > 360.0f) {
solarSystem.azim -= 360.0f;
}
if(solarSystem.azim < 0.0f) {
solarSystem.azim += 360.0f;
}
if(solarSystem.elev > 360.0f) {
solarSystem.elev -= 360.0f;
}
if(solarSystem.elev < 0.0f) {
solarSystem.elev += 360.0f;
}
surfaceView.requestRender();
}
preAzim = azim;
preElev = elev;
return true;
}
public void texture_on_off(View view){
solarSystem.texture_on_off = !solarSystem.texture_on_off;
}
public void rotation_on_off(View view){
solarSystem.rot_flag = !solarSystem.rot_flag;
}
}
SolarSystemRenderer.java
태양계를 그려주기 위해 GLSurfaceView.Renderer를 Implementing하는 class 생성
행성들, 조명, 재질, 회전 및 카메라를 위한 전역 변수들 선언
public class SolarSystemRenderer implements GLSurfaceView.Renderer {
private Context context;
/* For obj(planet) & texture */
private ObjStructure[] planet = new ObjStructure[10];
public int[] texture_id = new int[]{ // 태양, 수성, 금성, 지구, 달, 화성, 목성, 토성, 천왕성, 해왕성
R.drawable.sun,R.drawable.mercury,R.drawable.venus,
R.drawable.earth,R.drawable.moon,R.drawable.mars,
R.drawable.jupiter, R.drawable.saturn, R.drawable.uranus,
R.drawable.neptune};
public float scaler = .5f; // 태양 크기 결정
public float[] scalefactor = new float[]{ // 태양으로부터 상대적 크기 결정
scaler, scaler*0.1f, scaler*0.2f, // 태양, 수성, 금성
scaler*0.25f, scaler*0.08f, scaler*0.18f, // 지구, 달, 화성
scaler*0.5f, scaler*0.4f,scaler*0.3f,scaler*0.3f}; // 목성, 토성, 천왕성, 해왕성
/* For rotation */
public boolean rot_flag = true;
private float rot_sun = 360.0f;
private float angle_mercury = 1.0f;
private float angle_venus = 2.6f;
private float angle_earth = 2.0f;
private float angle_moon = 1.4f;
private float angle_mars = 1.3f;
private float angle_jupiter = 2.5f;
private float angle_saturn = 2.2f;
private float angle_uranus = 1.5f;
private float angle_neptune = 2.7f;
/* For camera setting */
private double distance;
public volatile double elev;
public volatile double azim;
private float[] cam_eye = new float[3];
private float[] cam_center = new float[3];
private float[] cam_up = new float[3];
private float[] cam_vpn = new float[3];
private float[] cam_x_axis = new float[3];
private float[] uv_py = new float[3];
private float[] uv_ny = new float[3];
/* For texture on, off */
public boolean texture_on_off = false;
카메라의 up vector를 계산해주기 위해 vector의 외적(calcCross), 정규화(vNorm), up vector를 계산(calcUpVector)해주는 함수 선언
public SolarSystemRenderer(Context context) {
this.context = context;
}
// vector의 외적
private void calcCross(float[] vector1, float[] vector2, float[] cp_vector) {
cp_vector[0] = vector1[1] * vector2[2] - vector1[2] * vector2[1];
cp_vector[1] = vector1[2] * vector2[0] - vector1[0] * vector2[2];
cp_vector[2] = vector1[0] * vector2[1] - vector1[1] * vector2[0];
}
// 정규화
private void vNorm(float[] vector) {
float scale = (float) Math.sqrt(Math.pow((double) vector[0], 2) + Math.pow((double) vector[1], 2) + Math.pow((double) vector[2], 2));
vector[0] = vector[0] / scale;
vector[1] = vector[1] / scale;
vector[2] = vector[2] / scale;
}
// up vector 계산
private void calcUpVector() {
double r_elev = elev * Math.PI / 180.0;
double r_azim = azim * Math.PI / 180.0;
cam_eye[0] = (float) distance * (float) Math.sin(r_elev) * (float) Math.sin(r_azim);
cam_eye[1] = (float) distance * (float) Math.cos(r_elev);
cam_eye[2] = (float) distance * (float) Math.sin(r_elev) * (float) Math.cos(r_azim);
cam_vpn[0] = cam_eye[0] - cam_center[0];
cam_vpn[1] = cam_eye[1] - cam_center[1];
cam_vpn[2] = cam_eye[2] - cam_center[2];
vNorm(cam_vpn);
if (elev >= 0 && elev < 180) {
calcCross(uv_py, cam_vpn, cam_x_axis);
}
else {
calcCross(uv_ny, cam_vpn, cam_x_axis);
}
calcCross(cam_vpn, cam_x_axis, cam_up);
vNorm(cam_up);
}
Camera Up Vector 계산
World 좌표계와 카메라 좌표계의 관계를 이용하여 카메라의 up vector(y_c 방향) 계산
다음 순서로 계산
- z_c (View-plane Normal) X y축 unit vector = x_c
- x_c X z_c = y_c
- y_c의 unit vector = up vector
GLRenderer를 통해 surface가 생성될 때(onSurfaceCreated) 변수 초기화
// GLRenderer를 통해 surface가 생성될 때(onSurfaceCreated) 변수 초기화
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glHint(gl.GL_PERSPECTIVE_CORRECTION_HINT, gl.GL_FASTEST);
gl.glEnable(gl.GL_DEPTH_TEST);
gl.glEnable(gl.GL_CULL_FACE);
gl.glCullFace(gl.GL_BACK);
distance = 10.0;
elev = 90.0;
azim = 0.0;
uv_py[0] = 0.0f;
uv_py[1] = 1.0f;
uv_py[2] = 0.0f;
uv_ny[0] = 0.0f;
uv_ny[1] = -1.0f;
uv_ny[2] = 0.0f;
cam_center[0] = 0.0f;
cam_center[1] = 0.0f;
cam_center[2] = 0.0f;
calcUpVector();
for(int i=0; i<10;i ++){
ObjParser objParser = new ObjParser(context); // obj 파일 Parser 생성
try {
objParser.parse(R.raw.planet); // obj 파일 parsing
} catch (IOException e) {
}
int group = objParser.getObjectIds().size(); // 몇 개의 obj 파일이 있는지 확인
int[] texture = new int[group];
texture[0] = texture_id[i]; // texture 파일 설정
planet[i] = new ObjStructure(objParser, gl, this.context, texture); // objstructure 생성
}
gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE);
gl.glEnable(GL10.GL_DEPTH_TEST);
}
화면의 해상도가 변경될 때(onSurfaceChanged) perspective와 viewport 지정
// 화면의 해상도가 변경될 때(onSurfaceChanged) perspective와 viewport 지정
public void onSurfaceChanged(GL10 gl, int width, int height) {
float zNear = 0.1f;
float zFar = 1000f;
float fovy = 45.0f;
float aspect = (float) width / (float) height;
/**
* Projection matrix P
* 카메라의 projection 특성 반영
* 예) 평형 투사, 원근 투사 등
*/
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity(); // 현재 활성화된 행렬을 단위 행렬로 초기화
/**
* fovy: 수직 방향의 field of view (단위: degree)
* aspect: Clipping window의 가로:세로 비율(가로/세로)
* zNear: Near plane 까지의 거리 (>0)
* zFar: Far plane 까지의 거리 (>0)
*/
GLU.gluPerspective(gl, fovy, aspect, zNear, zFar);
gl.glViewport(0, 0, width, height);
// x,y: Viewport 사각형의 왼쪽 아래 코너의 좌표
// width, height: Viewport 영역의 폭과 높이
}
직접 행성들을 그릴 때(onDrawFrame) color 및 depth buffer 초기화
카메라 위치 조정(gluLookAt)
gl.glPushMatrix(), glPopMatrix()를 이용하여 각 행성 별 공전, 자전이 개별적으로 가능하도록 설정
// 직접 행성들을 그릴 때(onDrawFrame) color 및 depth buffer 초기화
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glClearDepthf(1.0f);
/**
* ModelView matirx M
* 좌표계에서 물체의 변환, 시점의 이동 등 반영
*/
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity(); // 현재 활성화된 행렬을 단위 행렬로 초기화
calcUpVector();
/**
* gluLookAt 카메라 위치 지정
* eyeX, eyeY, eyeZ: 카메라(눈)의 위치
* centerX, centerY, centerZ: 카메라가 바라보는 점의 위치
* upX, upY, upZ: 카메라 위쪽의 방향(Up vector)
*/
GLU.gluLookAt(gl, cam_eye[0], cam_eye[1], cam_eye[2], cam_center[0], cam_center[1], cam_center[2], cam_up[0], cam_up[1], cam_up[2]);
if(texture_on_off){
gl.glEnable(GL10.GL_TEXTURE_2D);
}else{
gl.glDisable(GL10.GL_TEXTURE_2D);
}
/**
* gl.glPushMatrix(), gl.glPopMatrix()를 이용하여
* 각 행성별 공전, 자전이 개별적으로 가능하도록 설정
*/
gl.glPushMatrix();
gl.glRotatef(rot_sun, 0.0f, 1.0f, 0.0f); // 태양의 자전
// draw Sun
gl.glColor4f(1.0f,0.0f,0.0f,1.0f);
planet[0].setScale(scalefactor[0]);
planet[0].draw(gl);
gl.glPopMatrix();
/**
* gl.glRotatef(float angle, float x, float y, float z)
* 회전이동에 해당하는 matrix를 현재 matrix에 곱함
* angle: 회전각
* x, y, z: 회전 중심축
*/
/**
* gl.glTranslatef(float x, float y, float z)
* 평행이동에 해당하는 matrix를 현재 matrix에 곱함
* x, y, z: 평행 이동량
*/
gl.glPushMatrix();
gl.glRotatef(angle_mercury, 0.0f, 1.0f, 0.0f); // 수성의 자전
gl.glTranslatef(1.3f, 0.0f, 0.0f);
// draw Mercury
gl.glColor4f(0.0f, 1.0f, 1.0f, 1.0f);
planet[1].setScale(scalefactor[1]);
planet[1].draw(gl);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glRotatef(angle_venus, 0.0f, 1.0f, 0.0f); // 금성의 자전
gl.glTranslatef(1.6f, 0.0f, 0.0f);
// draw Venus
gl.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
planet[2].setScale(scalefactor[2]);
planet[2].draw(gl);
gl.glPopMatrix();
gl.glPushMatrix(); // 태양 기준의 좌표계를 스택에 푸시하여 저장
gl.glPushMatrix(); // 지구 기준의 좌표계를 스택에 푸시하여 저장
gl.glRotatef(angle_earth, 0.0f, 1.0f, 0.0f); // 지구의 자전
gl.glTranslatef(2.0f, 0.0f, 0.0f);
// draw Earth
gl.glColor4f(0.0f,0.0f,1.0f,1.0f);
planet[3].setScale(scalefactor[3]);
planet[3].draw(gl);
gl.glPushMatrix();
gl.glRotatef(angle_moon, 0.0f, 1.0f, 0.0f); // 달의 자전
gl.glTranslatef(0.5f, 0.0f, 0.0f);
// draw Moon
gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
planet[4].setScale(scalefactor[4]);
planet[4].draw(gl);
gl.glPopMatrix();
gl.glPopMatrix();
gl.glPushMatrix();
gl.glRotatef(angle_mars, 0.0f, 1.0f, 0.0f); // 화성의 자전
gl.glTranslatef(3.0f, 0.0f, 0.0f);
// draw Moon
gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
planet[5].setScale(scalefactor[5]);
planet[5].draw(gl);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glRotatef(angle_jupiter, 0.0f, 1.0f, 0.0f); // 목성의 자전
gl.glTranslatef(4.0f, 0.0f, 0.0f);
// draw Moon
gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
planet[6].setScale(scalefactor[6]);
planet[6].draw(gl);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glRotatef(angle_saturn, 0.0f, 1.0f, 0.0f); // 토성의 자전
gl.glTranslatef(5.0f, 0.0f, 0.0f);
// draw Moon
gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
planet[7].setScale(scalefactor[7]);
planet[7].draw(gl);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glRotatef(angle_uranus, 0.0f, 1.0f, 0.0f); // 천왕성의 자전
gl.glTranslatef(6.0f, 0.0f, 0.0f);
// draw Moon
gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
planet[8].setScale(scalefactor[8]);
planet[8].draw(gl);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glRotatef(angle_neptune, 0.0f, 1.0f, 0.0f); // 해왕성의 자전
gl.glTranslatef(7.0f, 0.0f, 0.0f);
// draw Moon
gl.glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
planet[9].setScale(scalefactor[9]);
planet[9].draw(gl);
gl.glPopMatrix();
float orbital_mercury = 1.0f;
float orbital_venus = 2.6f;
float orbital_moon = 1.4f;
float orbital_earth = 2.0f;
float orbital_mars = 1.3f;
float orbital_juptier = 2.5f;
float orbital_saturn = 2.2f;
float orbital_uranus = 1.5f;
float orbital_neptune = 2.7f;
if(rot_flag) {
rot_sun -= 0.2f;
angle_mercury += orbital_mercury;
angle_venus += orbital_venus;
angle_earth += orbital_earth;
angle_moon += orbital_moon;
angle_mars += orbital_mars;
angle_jupiter += orbital_juptier;
angle_saturn += orbital_saturn;
angle_uranus += orbital_uranus;
angle_neptune += orbital_neptune;
if (angle_mercury >= 360.0f) {
angle_mercury -= 360.0f;
}
if (angle_venus >= 360.0f) {
angle_venus -= 360.0f;
}
if (angle_earth >= 360.0f) {
angle_earth -= 360.0f;
}
if (angle_moon >= 360.0f) {
angle_moon -= 360.0f;
}
if (angle_mars >= 360.0f) {
angle_mars -= 360.0f;
}
if (angle_jupiter >= 360.0f) {
angle_jupiter -= 360.0f;
}
if (angle_saturn >= 360.0f) {
angle_saturn -= 360.0f;
}
if (angle_uranus >= 360.0f) {
angle_uranus -= 360.0f;
}
if (angle_neptune >= 360.0f) {
angle_neptune -= 360.0f;
}
}
gl.glFlush();
}
}
'DEVLOG > Android' 카테고리의 다른 글
#2 - 안드로이드 스튜디오(Android Studio) 설치하기 (0) | 2019.04.29 |
---|---|
#1 - 안드로이드 소개 (0) | 2019.04.29 |
댓글