Журнал LinuxFormat - перейти на главную

LXF167:Android

Материал из Linuxformat
Перейти к: навигация, поиск

Android » Программирование Работа с сенсорным экраном при помощи библиотек OpenGL

Содержание

Тек­сту­ры и OpenGL: На сен­сор­ном эк­ра­не

(thumbnail)
> Тут мы ос­та­но­ви­лись в про­шлой ста­тье (она есть на DVD). Те­перь до­ба­вим тек­сту­ры.
(thumbnail)
Наш эксперт Джуль­ет­та Кемп еще пом­нит вре­ме­на, ко­гда в фо­то­ап­па­ра­тах бы­ла на­стоя­щая плен­ка. Сей­час она де­ла­ет гораздо поболее сним­ков, чем то­гда.

OpenGL в Android по­зво­ля­ет соз­да­вать улуч­шен­ную гра­фи­ку и ис­поль­зо­вать гиб­кий от­кры­тый под­ход к трех­мер­ной гра­фи­ке. В пре­ды­ду­щей ста­тье мы ра­бо­та­ли с фи­гу­ра­ми, цве­та­ми, ка­ме­ра­ми и про­ек­ция­ми эк­ра­на и по­зна­ко­ми­лись с пе­ре­ме­щением фи­гу­ры; те­перь мы про­дол­жим ра­бо­ту с при­ме­ром и бу­дем пе­ре­ме­щать фи­гу­ру по эк­ра­ну в от­вет на ка­сания сен­сор­но­го эк­ра­на, а так­же по­зна­ко­мим­ся с уме­ренно слож­ной, но по­лез­ной тех­но­ло­ги­ей на­ло­жения тек­стур. Как и пре­ж­де, мы поль­зу­ем­ся OpenGL ES2.0, ко­то­рый под­дер­жи­ва­ет­ся в Android 2.2 и вы­ше. Помните, что код нель­зя бу­дет про­ве­рить в эму­ля­то­ре, так как он не ра­бо­та­ет с OpenGL 2, и для от­лад­ки вам по­тре­бу­ет­ся ре­аль­ное уст­рой­ст­во.


В раз­ви­тие пре­ды­ду­щей ста­тьи Джуль­ет­та Кемп пе­ре­хо­дит к тек­сту­рам и сен­сор­но­му эк­ра­ну для пе­ре­ме­щения фи­гур.

Пе­ре­ме­ще­ние и сен­сор­ный эк­ран

Пре­ж­де все­го крат­ко оз­на­ко­мим­ся с пе­ре­ме­ще­ни­ем (объ­ек­та по эк­ра­ну), не ка­са­ясь ра­бо­ты с сен­сор­ным эк­ра­ном. Это про­сто еще од­на опе­ра­ция с мат­ри­цей, translateM(). До­бавь­те сле­дую­щие две стро­ки по­сле по­во­ро­та в CISGLRenderer.onSurfaceCreated():

Matrix.setIdentityM(iMatrix, 0);

Matrix.translateM(iMatrix, 0, 0.2f, 0.2f, 0f);

Так­же по­на­до­бит­ся соз­дать пе­ре­мен­ную клас­са iMatrix и до­ба­вить в onDrawFrame() стро­ку

Matrix.multiplyMM(uMVPMatrix, 0, tMatrix, 0, uMVPMatrix, 0);

по­сле дру­гих опе­ра­ций ум­но­же­ния мат­риц.

Эти вы­зо­вы пе­ре­мес­тят куб на 0.2 ко­ор­ди­на­ты вле­во и на 0.2 ко­ор­ди­на­ты вверх по эк­ра­ну.

Помните, что по­ря­док мно­жи­те­лей при ум­но­жении мат­риц име­ет зна­чение! Под­роб­но­сти см. во врез­ке.

В ка­че­­ст­ве аль­тер­на­тив­но­го ва­ри­ан­та мож­но немно­го пе­ре­ме­щать куб в ка­ж­дом кад­ре, до­ба­вив сле­дую­щую стро­ку в onDrawFrame():

Matrix.translateM(iMatrix, 0, 0.001f, 0.001f, 0);

Ском­пи­ли­руй­те и за­пус­ти­те про­грам­му, и вы уви­ди­те, как куб мед­лен­но дви­жет­ся по эк­ра­ну.

Со­бы­тия сен­сор­но­го эк­ра­на

Дви­гать ку­би­ка­ми – де­ло хо­ро­шее, но ин­те­реснее за­ста­вить куб пе­ре­ме­щать­ся в кон­тек­сте со­бы­тия сен­сор­но­го эк­ра­на.

На­пи­шем код, ко­то­рый при дви­жении паль­цем по сен­сор­но­му эк­ра­ну будет пе­ре­ме­щать куб во­след. Что­бы про­ис­хо­дя­щее бы­ло яснее, сто­ит за­ком­мен­ти­ро­вать стро­ку с по­во­ро­том на по­сто­ян­ный угол в CISGLRenderer.onDrawFrame() (так что про­ис­хо­дит толь­ко пере­ме­щение) и стро­ку насчет пер­во­на­чаль­ного по­во­ро­та в CISGLRenderer.onSurfaceCreated() (что­бы объ­ект пе­ре­ме­щал­ся по обыч­ным, а не по­вер­ну­тым осям X/Y/Z, о чем сказано ранее).

Для об­ра­бот­ки со­бы­тий сен­сор­но­го эк­ра­на нуж­но соз­дать соб­ст­вен­ный класс SurfaceView, CISSurfaceView:

public class CISSurfaceView extends GLSurfaceView {

private CISGLRenderer renderer;

private float prevX = 0;

private float prevY = 0;

public CISSurfaceView(Context c) {

super(c);

setEGLContextClientVersion(2);

renderer = new CISGLRenderer(this.getContext());

setRenderer(renderer);

}

public boolean onTouchEvent(MotionEvent e) {

float x = e.getX();

float y = e.getY();

float height = getHeight();

float width = getWidth();

switch (e.getAction()) {

case MotionEvent.ACTION_MOVE:

float dx = (x - width/2) * -1;

float dy = (y - height/2) * -1;

renderer.diffX = (dx - prevX) / (width/2);

renderer.diffY = (dy - prevY) / (height/2);

prevX = dx;

prevY = dy;

}

return true;

}

}

Ме­тод кон­ст­рук­то­ра про­сто пе­ре­ме­ща­ет код из основ­но­го За­ня­тия в Пред­став­ление. Это про­из­во­дит­ся в ме­то­де onTouchEvent(). Здесь мы по­лу­ча­ем зна­чения X и Y из об­на­ру­жен­но­го со­бы­тия MotionEvent, за­тем вы­со­ту и ши­ри­ну эк­ра­на.

Сей­час нам ин­те­рес­но толь­ко дей­ст­вие ACTION_MOVE, оно ре­ги­ст­ри­ру­ет­ся при пе­ре­ме­щении паль­ца по эк­ра­ну. Это дей­ст­вие возника­ет по­втор­но (и очень час­то!) до тех пор, по­ка вы не сни­мете па­лец с эк­ра­на, так что этот ме­тод бу­дет вы­зы­вать­ся мно­го раз (в чем вы убе­ди­тесь, ес­ли до­ба­ви­те в него стро­ку для за­пи­си со­об­щения в лог).

dx и dy ис­поль­зу­ют­ся для пре­об­ра­зо­вания ко­ор­ди­нат сен­сор­но­го эк­ра­на X и Y в ко­ор­ди­на­ты сис­те­мы с точ­кой от­сче­та в цен­тре эк­ра­на. От­счет ко­ор­ди­нат сен­сор­но­го эк­ра­на ве­дет­ся из ле­во­го нижнего уг­ла, ось x ухо­дит вверх, а ось y – вле­во. В OpenGL ис­поль­зу­ет­ся сис­те­ма ко­ор­ди­нат с на­ча­лом от­сче­та в цен­тре эк­ра­на (ось x по-прежнему ухо­дит впра­во, а ось y – вверх). dx и dy со­дер­жат рас­стояние до точ­ки на­жа­тия от цен­тра эк­ра­на в пик­се­лях.

За­тем мы по­лу­ча­ем разницу ме­ж­ду пре­ды­ду­щи­ми ко­ор­ди­на­та­ми (prevX и prevY) и те­ку­щи­ми ко­ор­ди­на­та­ми (dx and dy) и пре­об­ра­зу­ем их из пик­се­лей в ко­ор­ди­на­ты OpenGL. В OpenGL по умол­чанию за 1 бе­рет­ся рас­стояние от цен­тра до ка­ж­до­го края – оче­вид­но, сен­сор­ный эк­ран име­ет (0.5 * вы­со­та) пик­се­лей от цен­тра до верхнего края и (0.5 * ши­ри­на) пик­се­лей от цен­тра до ка­ж­дой сто­ро­ны, по­это­му мы ис­поль­зу­ем эти зна­чения для генери­ро­вания ко­ор­ди­нат OpenGL.

На­конец, мы пе­ре­да­ем эти зна­чения рен­де­ре­ру. За­мените ста­тич­ную стро­ку translateM() в ме­то­де onDrawFrame() клас­са CISGLRenderer сле­дую­щей:

Matrix.translateM(iMatrix, 0, diffX, diffY, 0);

(так­же по­на­до­бит­ся объ­я­вить diffX и diffY как пуб­лич­ные пе­ре­мен­ные клас­са).

Ском­пи­ли­руй­те и за­пусти­те про­грам­му, и вы уви­ди­те, как куб пе­ре­ме­ща­ет­ся вслед за паль­цем.

Ис­прав­ля­ем недо­че­ты

Од­на­ко на са­мом де­ле он пе­ре­ме­ща­ет­ся не вслед за паль­цем, а по осям, силь­но на­по­ми­наю­щим ор­то­го­наль­ные. Это свя­за­но с взаи­мо­дей­ст­ви­ем с про­ек­ци­он­ной мат­ри­цей и мат­ри­цей про­ек­ции ка­ме­ры. Есть два спо­со­ба это ис­пра­вить. Один из них – из­менить на­прав­ление пе­ре­ме­щения:

Matrix.translateM(iMatrix, 0, -diffX, diffY, 0);

Однако луч­шим ре­шением бу­дет ис­поль­зо­вать дру­гую мат­ри­цу для пе­ре­ме­щения вслед за паль­цем и дом­но­жать на нее заранее. Врезка

Для этой цели мы поместим в наш код следующий дополнительный фрагмент:

private float[] tMatrix = new float[16];

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

[ ... ]

Matrix.setIdentityM(tMatrix, 0);

}

public void onDrawFrame(GL10 gl) {

Matrix.translateM(tMatrix, 0, diffX, diffY, 0);

Matrix.setIdentityM(uMVPMatrix, 0);

Matrix.multiplyMM(uMVPMatrix, 0, mMatrix, 0, uMVPMatrix, 0);

Matrix.multiplyMM(uMVPMatrix, 0, tMatrix, 0, uMVPMatrix, 0);

Matrix.multiplyMM(uMVPMatrix, 0, vMatrix, 0, uMVPMatrix, 0);

Matrix.multiplyMM(uMVPMatrix, 0, projMatrix, 0, uMVPMatrix, 0);

Matrix.multiplyMM(uMVPMatrix, 0, iMatrix, 0, uMVPMatrix, 0);

[ ... ]

}

Пе­ред вы­полнением с но­вой мат­ри­цей лю­бых дей­ст­вий важ­но так установить значения ее элементов, чтобы она сделалась единич­ной (в про­тив­ном слу­чае она бу­дет ну­ле­вой и останет­ся та­кой, что бы вы ни де­ла­ли).

За­тем мы ис­поль­зу­ем ее для пе­ре­да­чи зна­чений diffX и diffY и ум­но­жа­ем ее на унифи­ци­ро­ван­ную мат­ри­цу по­сле по­во­ро­та, но до ум­но­жения на про­ек­ци­он­ную мат­ри­цу/мат­ри­цу про­ек­ции ка­ме­ры.

Сно­ва ском­пи­ли­руй­те и за­пусти­те про­грам­му, и теперь куб дол­жен пе­ре­ме­щать­ся в нуж­ном на­прав­лении. О дальней­шем улуч­шении ко­да см. во врез­ке «Начало и окончание движения» на предыдущей странице.

Тек­сту­ры

По­ка что наш куб был цвет­ным, и это очень ми­ло, но од­на из пре­крас­ных воз­мож­но­стей OpenGL – воз­мож­ность на­кла­ды­вать на грани тек­сту­ры. Ра­зу­ме­ет­ся, это бо­лее гиб­кий ва­ри­ант, чем про­сто рас­крас­ка.

Что­бы на­чать ра­бо­ту с тек­сту­ра­ми, ко­то­рые мо­гут быть до­вольно слож­ны­ми, вернем­ся на­зад и на­ри­су­ем дву­мер­ную фи­гу­ру. Пер­вое, что нуж­но сде­лать – пе­ре­пи­сать вер­шин­ный и фраг­мент­ный шей­де­ры (в CISGLRenderer) так, что­бы в них ис­поль­зо­ва­лись тек­сту­ры вме­сто цве­та:

private final String vertexShaderCode =

“uniform mat4 uMVPMatrix; \n” +

“attribute vec4 vPosition; \n” +

“attribute vec2 aTexture; \n “ +

“varying vec2 vTexture; \n” +

“void main(){ \n” +

“ gl_Position = uMVPMatrix * vPosition; \n” +

“ vTexture = aTexture; \n” +

“} \n”;

private final String fragmentShaderCode =

“precision mediump float; \n” +

“uniform sampler2D u_texture; \n” +

“varying vec2 vTexture; \n” +

“void main(){ \n” +

“ gl_FragColor = texture2D(u_texture, vTexture); \n” +

“} \n”;

Этот код очень по­хож на пре­ды­ду­щий код шей­де­ра; мы про­сто уб­ра­ли опе­ра­ции с цве­том и до­ба­ви­ли опе­ра­ции с тек­сту­рой.

В вер­шин­ном шей­де­ре есть ат­ри­бут тек­сту­ры, из ко­то­ро­го соз­да­ет­ся пе­ре­мен­ная для пе­ре­да­чи фраг­мент­но­му шей­де­ру. За­тем во фраг­мент­ном шей­де­ре ис­поль­зу­ет­ся пе­ре­мен­ная sampler2D, ко­то­рая по­зво­ля­ет нам по­лу­чить об­ра­зец тек­сту­ры (“Uniform” оз­на­ча­ет, что пе­ре­мен­ная не из­ме­ня­ет­ся в те­чение од­но­го вы­зо­ва шей­де­ра, так что для ка­ж­до­го фраг­мен­та зна­чение оста­ет­ся прежним).

За­тем мы ис­поль­зу­ем ме­тод OpenGL texture2D для свя­зи пе­ре­мен­ной об­раз­ца тек­сту­ры с пе­ре­мен­ной vTexture, пе­ре­да­вае­мой из вер­шин­но­го шей­де­ра.

По­том до­ба­вим стро­ку в ме­тод onSurfaceCreated() для по­лу­чения ссыл­ки на пе­ре­мен­ную aTexture точ­но так же, как ранее с пе­ре­мен­ны­ми vPosition и aColour:

textureHandle = GLES20.glGetAttribLocation(program, “aTexture”);

На­конец, до­ба­вим в ме­тод onDrawFrame() стро­ку для ри­со­вания квад­ра­та Square с тек­сту­рой (мы на­пи­шем этот ме­тод че­рез ми­ну­ту):

square.loadTexture(context, R.drawable.test); square.draw(positionHandle, textureHandle, context, R.drawable.yurt);

За­меть­те, что для по­лу­чения пе­ре­мен­ной context мы до­ба­вили кон­ст­рук­тор в класс CISGLRenderer – про­смот­ри­те код на на­шем DVD. Это по­на­до­бит­ся для за­груз­ки би­то­вой кар­ты в ме­то­де loadTexture().

Вам так­же по­на­до­бит­ся би­то­вая кар­та, здесь это фо­то­гра­фия (юр­ты!), но вы мо­же­те за­менить ее на лю­бое дру­гое изо­бра­жение, со­хранив его в res/drawable.

Пе­рей­дем к Square.java и на­ше­му но­во­му ме­то­ду draw(). Для на­ча­ла нуж­но за­гру­зить тек­сту­ру:

public void draw(int positionHandle, int textureHandle, Context context, int bitmap_id) {

Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmap_id);

GLES20.glDeleteTextures(1, textures, 0);

GLES20.glGenTextures(1, textures, 0);

GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);

// Ус­та­нов­ка MIN_FILTER нуж­на, ко­гда не при­ме­ня­ют­ся mipmaps

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,

GLES20.GL_TEXTURE_MIN_FILTER,

GLES20.GL_LINEAR);

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();

[ ... ]

}

Сна­ча­ла нуж­но по­лу­чить би­то­вую кар­ту из ре­сур­сов при­ло­жения (уч­ти­те, что она долж­на быть квад­рат­ной, в про­тив­ном слу­чае она мо­жет вы­гля­деть стран­но. Так­же хо­ро­шо об­ра­ба­ты­ва­ют­ся тек­сту­ры, вы­со­та и ши­ри­на ко­то­рых яв­ля­ют­ся сте­пе­ня­ми двой­ки).

По­сле это­го мы за­да­ем тек­сту­ры. glDeleteTextures() уда­ля­ет все тек­сту­ры, уже на­хо­дя­щие­ся в пер­вом эле­мен­те мас­си­ва textures, что­бы ис­клю­чить все воз­мож­ные кон­флик­ты при об­ра­щении к нему. glGenTextures() генери­ру­ет имя тек­сту­ры (мы взя­ли все­го од­но, так как ис­поль­зу­ет­ся од­на тек­сту­ра) и со­хра­ня­ет его в мас­си­ве. glActiveTexture() ука­зы­ва­ет, ка­кой мо­дуль тек­сту­ры сде­лать ак­тив­ным, здесь ис­поль­зу­ет­ся ба­зо­вый мо­дуль тек­сту­ры (GL_TEXTURE0). На­конец, glBindTexture() свя­зы­ва­ет ука­зан­ную цель (GL_TEXTURE_2D) с пер­вым эле­мен­том на­ше­го мас­си­ва textures.

(thumbnail)
> Сле­ва — квад­рат и по­ря­док от­ри­сов­ки его вер­шин, об­ра­зую­щий два тре­уголь­ни­ка. Спра­ва — спо­со­бы от­ри­сов­ки тек­сту­ры и ре­зуль­та­ты.

В сле­дую­щей стро­ке за­да­ет­ся па­ра­метр MIN_FILTER для це­ли на­шей тек­сту­ры (GL_TEXTURE_2D). Этот па­ра­метр управ­ля­ет тем, как OpenGL вы­чис­ля­ет тек­сту­ру для пик­се­ля, когда этот пик­сель по­па­да­ет в ме­сто, вы­хо­дя­щее за пре­де­лы од­но­го эле­мен­та тек­сту­ры. Пред­ставь­те се­бе тек­сту­ру как ку­сок цел­ло­фа­на, по­кры­ваю­щий по­верх­ность; OpenGL сжи­ма­ет или рас­тя­ги­ва­ет его, что­бы он плот­но при­ле­гал к по­верх­но­сти, и умень­шаю­щая функ­ция – часть этот про­цес­са. Ес­ли вы не поль­зуе­тесь мно­же­ст­вен­ны­ми ото­бра­жения­ми (см. сле­дую­щий аб­зац), воз­мож­ные ва­ри­ан­ты – GL_NEAREST, при ко­то­ром ис­поль­зу­ет­ся эле­мент тек­сту­ры, бли­жай­ший к цен­тру об­ра­ба­ты­вае­мо­го пик­се­ля, или GL_LINEAR, при ко­то­ром ис­поль­зу­ет­ся средневзве­шен­ное зна­чение че­ты­рех бли­жай­ших эле­мен­тов тек­сту­ры. С GL_NEAREST изо­бра­жение обыч­но по­лу­ча­ет­ся чуть бо­лее чет­ким, но луч­ше по­экс­пе­ри­мен­ти­ро­вать и по­смот­реть, что да­ет луч­шие ре­зуль­та­ты в кон­крет­ном при­ло­жении.

Мно­же­ст­вен­ное ото­бра­жение [mipmapping] – это спо­соб пре­достав­ления несколь­ких вер­сий изо­бра­жения для раз­лич­ных мас­шта­бов. По су­ти это оз­на­ча­ет, что вме­сто генера­ции мень­шей вер­сии основ­но­го изо­бра­жения на ле­ту рен­де­рер мо­жет взять го­то­вое изо­бра­жение мень­ше­го раз­ме­ра с мень­шей де­та­ли­за­ци­ей из на­бо­ра ото­бра­жений и восполь­зо­вать­ся им.

Рен­де­ре­ры так­же мо­гут ин­тер­по­ли­ро­вать изо­бра­жения раз­но­го мас­шта­ба для по­лу­чения изо­бра­жений про­из­воль­но­го раз­ме­ра. Это по­зво­ля­ет проделывать мас­шта­би­ро­вание бо­лее эф­фек­тивно и заодно умень­шить ко­ли­че­­ст­во ар­те­фак­тов.

Ес­ли вы пи­ше­те иг­ры или дру­гие про­грам­мы, ин­тен­сив­но ис­поль­зую­щее про­цес­сор, обя­за­тель­но ис­поль­зуй­те мно­же­ст­вен­ное ото­бра­жение. Здесь мы ог­раничим­ся од­ной вер­си­ей од­но­го изо­бра­жения и оста­вим ра­бо­ту рен­де­ре­ру. Мож­но за­дать и дру­гие па­ра­мет­ры, но для дан­но­го ри­сун­ка они не нуж­ны. На­при­мер:

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);

GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);

Здесь за­дан па­ра­метр обо­ра­чи­вания для s- и t-ко­ор­ди­нат тек­сту­ры (они со­от­вет­ст­ву­ют x- и y-ко­ор­ди­на­там) для GL_REPEAT.

На­конец, с по­мо­щью вспо­мо­га­тель­но­го ме­то­да из GLUtils мы за­да­ем на­шу би­то­вую кар­ту в ка­че­­ст­ве тек­сту­ры и уда­ля­ем би­то­вую кар­ту, поскольку она нам боль­ше не нуж­на.

Еще нуж­но за­дать пе­ре­мен­ную squareTextureBuffer точ­но так же, как мы ранее за­да­вали бу­фер цве­та, за­тем до­ба­вить несколь­ко строк в draw(), и все будет го­то­во:

float verticesTexture[] = {

0.0f, 0.0f,

1.0f, 0.0f,

0.0f, 1.0f,

1.0f, 1.0f,

}

public Square() {

[ ... ]

squareTextureBuffer = ByteBuffer.allocateDirect(

verticesTexture.length * 4).order

(ByteOrder.nativeOrder()).asFloatBuffer();

squareTextureBuffer.put(verticesTexture).position(0);

}

public void draw(...) {

[ ... ]

// Уда­лить стро­ки __colourHandle__ за­ме­нив этим:

GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 0, squareTextureBuffer);

GLES20.glEnableVertexAttribArray(textureHandle);

[ ... ]

}

Об­ра­ти­те внимание, что ко­ор­ди­на­ты вер­шин тек­сту­ры – в диа­па­зоне от 0 до 1, так как ори­ги­нал тек­сту­ры на­хо­дит­ся в ле­вом нижнем уг­лу би­то­вой кар­ты, а не в цен­тре эк­ра­на (как в слу­чае с ок­ном OpenGL). Од­на­ко, ском­пи­ли­ро­вав и за­пустив про­грам­му, вы уви­ди­те, что изо­бра­жение вы­гля­дит не со­всем пра­виль­но.

Что­бы луч­ше по­нять, что про­ис­хо­дит, взгляните на схе­му на пре­ды­ду­щей странице. Ри­суя квад­рат, OpenGL про­хо­дит вер­ши­ны в том по­ряд­ке, в ко­то­ром они пе­ре­чис­ле­ны.

Но так как мы поль­зу­ем­ся ме­то­дом про­ри­сов­ки «с раз­де­лением на тре­угольники», то на са­мом де­ле он возь­мет пер­вые три ко­ор­ди­на­ты – (-0.5,-0.5) = ле­вый нижний угол, (0.5, -0.5) = пра­вый верхний угол, (-0.5, 0.5) = ле­вый верхний угол – и на­ри­су­ет пер­вый тре­угольник, за­тем возь­мет его по­след­нюю сто­ро­ну и ис­поль­зу­ет ее вер­ши­ны в ка­че­­ст­ве пер­вых двух вер­шин вто­ро­го тре­угольника, за­тем до­ба­вит к ним по­след­нюю ко­ор­ди­на­ту, (0.5, 0.5) = пра­вый верхний угол. Два тре­угольника об­ра­зу­ют квад­рат, и на этом про­цесс за­вер­шен, как по­ка­зы­ва­ет диа­грам­ма.

Восста­нав­ли­ва­ем по­ря­док

(thumbnail)
С тек­сту­ра­ми бы­ва­ет вся­кое. Ино­гда и не­же­ла­тель­ное.
Тек­сту­ра ри­су­ет­ся так же, вер­ши­на
(thumbnail)
Тек­сту­ры располагаются на всех гра­нях нашего ку­ба.
за вер­ши­ной. Од­на­ко тек­сту­ры OpenGL на­чи­на­ют­ся в ле­вом нижнем уг­лу, тогда как боль­шин­ст­во изо­бра­жений на ком­пь­ю­те­ре – в ле­вом верхнем.

По­это­му ось y пе­ре­во­ра­чи­ва­ет­ся (хо­тя ось x по-прежнему идет сле­ва на­пра­во), и это нуж­но учи­ты­вать при про­ри­сов­ке.

Та­ким об­ра­зом, что­бы по­ря­док сег­мен­тов был вер­ным, из­меним оче­ред­ность вер­шин. Они долж­ны ид­ти в на­прав­лении про­ри­сов­ки квад­ра­та и быть пе­ре­вер­ну­ты­ми (про­ри­со­вы­вать­ся свер­ху вниз, а не снизу вверх). Диа­грам­ма долж­на по­мочь вам пред­ста­вить это. На DVD вы най­де­те код для трех раз­лич­ных по­ряд­ков вер­шин и ре­зуль­та­ты его за­пуска.

Ес­ли у вас про­бле­мы с шей­де­ра­ми, с по­мо­щью GLES20 мож­но по­лу­чить неболь­шое ко­ли­че­­ст­во от­ла­доч­ной ин­фор­ма­ции из про­грам­мы OpenGL и шей­де­ров.

Ес­ли у вас еще бо­лее стран­ные ре­зуль­та­ты, кро­ме про­вер­ки по­ряд­ка вер­шин по­про­буй­те так­же из­менить раз­мер ис­ход­но­го изо­бра­жения с тек­сту­рой и/или за­дать па­ра­мет­ры GL_TEXTURE_WRAP, о ко­то­рых мы го­во­ри­ли ранее.

Трех­мер­ные тек­сту­ры

Итак, у нас есть квад­рат с тек­сту­рой; а как на­счет ку­ба с тек­сту­ра­ми? Мож­но про­сто рас­про­странить код для квад­ра­та на куб (будь­те внима­тель­ны с по­ряд­ком вер­шин!), но ес­ли вы по­про­буе­те ском­пи­ли­ро­вать и за­пустить его, то уви­ди­те, что тек­сту­ра поя­ви­лась толь­ко на пе­редней грани ку­ба (а ес­ли вы вер­ну­ли еще и код по­во­ро­та, то толь­ко на задней грани). Дру­гие грани нечет­кие.

Что­бы это ра­бо­та­ло пра­виль­но, нам, к со­жа­лению, при­дет­ся от­ка­зать­ся от ри­со­вания ку­ба тре­угольника­ми и ис­поль­зо­вать спи­сок вер­шин в по­ряд­ке про­ри­сов­ки (по че­ты­ре вер­ши­ны на грань). За­тем мы ука­зы­ва­ем че­ты­ре ко­ор­ди­на­ты уг­лов тек­сту­ры, один раз для ка­ж­дой грани. Весь код мож­но най­ти на DVD. Так­же нуж­но немно­го из­менить вы­зов glDrawElements(), из­менив ме­тод про­ри­сов­ки с GL_TRIANGLE_STRIP на GL_TRIANGLES.

В осталь­ном код поч­ти та­кой же, что и для квад­ра­та, но бу­фе­рам да­ны но­вые име­на (кро­ме то­го, би­то­вая кар­та за­да­ет­ся в кон­ст­рук­то­ре, а не в ме­то­де draw(); ка­кой спо­соб вы­брать, за­ви­сит от лич­ных пред­поч­тений и дру­гих воз­мож­ных дей­ст­­вий с ку­бом). Ском­пи­ли­руй­те и за­пусти­те про­грам­му, и вы уви­ди­те куб с тек­сту­ра­ми на всех гра­нях. Мо­же­те из­менить по­ря­док от­ри­сов­ки вер­шин тек­сту­ры, что­бы она рас­по­ла­га­лась пра­виль­но на всех гра­нях ку­ба; вам по­мо­жет чер­теж ку­ба с по­ряд­ком про­ри­сов­ки его вер­шин (Най­ти пра­виль­ный «путь на­верх» в ку­бе мо­жет быть несколько сложнее, чем в плоском квад­ра­те!). Дру­гой спо­соб про­ри­сов­ки изо­бра­жения на всех гра­нях ку­ба – CubeMap, но он вы­хо­дит за рам­ки этой ста­тьи. Рас­­крас­­ку граней мож­но вер­нуть, что­бы ее бы­ло вид­но вме­сте с тек­сту­ра­ми (про­сто восста­но­ви­те зна­чения vColour и aColour и до­бавь­те vColour к gl_FragColour).

Конеч­но, с OpenGL мож­но сде­лать на­мно­го боль­ше, и в Се­ти есть мас­са ре­сур­сов вам в по­мощь. Осо­бен­но ин­те­рес­но по­иг­рать с осве­щением; так­же сто­ит по­ду­мать о пе­ре­ме­щении точ­ки об­зо­ра по эк­ра­ну. |

Персональные инструменты
купить
подписаться
Яндекс.Метрика