Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: OpenGL teksturointiongelma GL_QUADS:lla

Sivun loppuun

Mazzimo [03.08.2007 23:38:56]

#

Moro.

Eli oon tässä kokeilemassa tehdä 2d-engineä käyttäen OpenGL:ää rajapintana. Törmäsin pieneen ongelmaan kun yritin piirtää teksturoitua quad:ia ja hieman venyttää sitä. Tässäpä kuvaa ongelmasta: http://koti.mbnet.fi/masa_89/kuvia/text_ongelma.jpg

Eli näyttää siltä, että OpenGL piirtää quad:in kahtena kolmiona, mutta venyttää vain toisen kolmion tekstuuria, jättäen toisen normaaliksi. Tämä ei ole suinkaan tarkoituksena vaan haluaisin saada venytyksen "yhteneväiseksi", jotta kummankin kolmion tekstuurit venytetään sillä tavalla, että tekstuuri tulisi "yhtenäiseksi". Minkälaisia kikkoja teiltä quruilta? Olisin hyvin kiitollinen avusta.

Käytän kuvan lataukseen DevIL, kirjastoa. Se säätääkin jo jotain tekstuurin asetuksia. Itse en ole kuitenkaan vielä kovin tietoinen näiden asetusten merkityksestä. Pistän tähän hieman koodia, jolla nykyinen shotti on otettu.

OpenGL-asetus:

	// asetetaan OpenGL
	glViewport(0, 0, width, height);
	glEnable(GL_TEXTURE_2D);
	glMatrixMode(GL_PROJECTION);
	if( relative )	gluOrtho2D(0, 1.0, 1.0, 0);
	else			gluOrtho2D(0, width, height, 0);


	// asetetaan DevIL
	ilInit();
	ilEnable( IL_CONV_PAL );
	ilutEnable( ILUT_OPENGL_CONV );
	ilutRenderer( ILUT_OPENGL );

Kuvan lataus (ei liene tarpeellinen, mutta samaan pakettiin):

	GLuint text = 0;
	ILuint img = 0;

	ilGenImages(1, &img);
	ilBindImage(img);



	if( !ilLoadImage( "test.png" ) )
	{
		std::fstream file;
		file.open( "err.log", std::fstream::out );

		ILenum error = ilGetError ();
		if (error != IL_NO_ERROR) {
			do {
				file << iluErrorString(error);
			} while ((error = ilGetError ()));
		}
		return 1;
	}
	text = ilutGLBindTexImage();
	ilDeleteImages(1, &img);

Tässä DevIL:n latausfunktio:

GLuint ILAPIENTRY ilutGLBindTexImage()
{
	GLuint	TexID = 0, Target = GL_TEXTURE_2D;
	ILimage *Image;

	Image = ilGetCurImage();
	if (Image == NULL)
		return 0;

	if (ilutGetBoolean(ILUT_GL_AUTODETECT_TEXTURE_TARGET)) {
		if (HasCubemapHardware && Image->CubeFlags != 0)
			Target = ILGL_TEXTURE_CUBE_MAP;

	}

	glGenTextures(1, &TexID);
	glBindTexture(Target, TexID);

	if (Target == GL_TEXTURE_2D) {
		glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}
	else if (Target == ILGL_TEXTURE_CUBE_MAP) {
		glTexParameteri(Target, GL_TEXTURE_WRAP_S, ILGL_CLAMP_TO_EDGE);
		glTexParameteri(Target, GL_TEXTURE_WRAP_T, ILGL_CLAMP_TO_EDGE);
		glTexParameteri(Target, ILGL_TEXTURE_WRAP_R, ILGL_CLAMP_TO_EDGE);
	}
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
	glPixelStorei(GL_UNPACK_SWAP_BYTES, IL_FALSE);

	if (!ilutGLTexImage(0)) {
		glDeleteTextures(1, &TexID);
		return 0;
	}

	return TexID;
}

Ja tässä vielä koodi, jolla piirrän quad:in pääloopissa:

glClear(GL_COLOR_BUFFER_BIT);


		glBindTexture(GL_TEXTURE_2D, text);
		glBegin(GL_QUADS);
			glTexCoord2f (0.0f, 0.0f); glVertex2f(10,110);
			glTexCoord2f (0.0f, 1.0f); glVertex2f(10,10);
			glTexCoord2f (1.0f, 1.0f); glVertex2f(310,10);
			glTexCoord2f (1.0f, 0.0f); glVertex2f(110,110);
		glEnd();
glfwSwapBuffers();

Toivottavasti saamme ongelman ratkaistuksi. Jos tarvitsette lisää infoa, autan parhaani mukaan. :)

Kiitos.
EDIT: siis tälläistä haetaan http://koti.mbnet.fi/masa_89/kuvia/text_haluttu.jpg

eah [04.08.2007 16:32:14]

#

Quadit piirretään aina kahtena kolmiona ja kolmioille annetaan omat osansa tekstuurista, joten piirrettäessä kolmiot eivät automaattisesti osaa käyttää muita osia tekstuurista kun jotain _niille kuulumatonta_ quadin kulmaa "venytetään". Kolmiulotteiseen avaruuteen piirrettäessä voidaan käyttää ns. perspektiivikorjausta, joka päättelee verteksien "syvyyden" perusteella tekstuurin venyttämisen tarpeen, mutta ongelmaksi tässä muodostuu se, että nyt toimitaan vain 2d-tasossa.

Jos on tarvetta vain venyttää esim. jostain yksittäisestä quadista jotain yksittäistä kulmaa, yksi ratkaisu ongelmaan on se, että määrittelee quadin verteksit toisessa järjestyksessä siten, että venytettäväksi kulmaksi osuu sellainen verteksi, joka kuuluu quadin molemmille kolmioille. Tällainen "purkkaviritys" sopii kuitenkin vain joihinkin erikoistapauksiin, eikä tulos ole silloinkaan välttämättä ihan haluttu.

Ohjelmassasi huomaatkin varmaan eron, kun venytät oikean yläkulman verteksin sijasta oikean alakulman verteksiä, jota molemmat quadin kolmiot käyttävät. Jos taas välttämättä haluat venyttää oikeata yläkulmaa, vaihda verteksien järjestystä. Esimerkiksi vaihtamalla piirtojärjestykseksi

glTexCoord2f (0.0f, 1.0f); glVertex2f(10,10);
glTexCoord2f (1.0f, 1.0f); glVertex2f(310,10);
glTexCoord2f (1.0f, 0.0f); glVertex2f(110,110);
glTexCoord2f (0.0f, 0.0f); glVertex2f(10,110);

pitäisi(en juuri nyt pysty varmistamaan) quadin oikean yläkulman tulla yhteiseksi kolmioille ja venymisen näkyä molemmissa kolmioissa.

Kun mennään sellaisten erikoistapauksien ulkopuolelle, että ei ole kyse enää yksittäisistä quadeista tai kolmioista, edellinen menettely tulee väistämättä kohtuuttoman työlääksi ja vastaavanlaisia purkkavirityksiä pitää muutenkin välttää. Vaihtoehtoiset ratkaisukeinot (mitä tähän hätään keksin) menevät taas omalla tavallaan hieman monimutkaisemmiksi. Yksi tapa olisi esimerkiksi kirjoittaa piirtoa varten omat verteksi- ja fragment- ohjelmat/shaderit, jotka osaisivat huomioida venytyksen. Toinen tapa taas olisi piirtää kaikki quadit todellisuudessa kolmiulotteisina, toisin sanoen siten, että lopullinen quadi piirrettynä ruudulla on ikään kuin jonkun kolmiulotteisen quadin projektio ko. tasolla. Tällöin voidaan hyödyntää perspektiivikorjausta, joka syvyysparametrin avulla "venyttää" tekstuuria. Perspektiivikorjauksen saa OpenGL:ssä käyttöön kutsumalla funktiota "glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);", jos todellakin haluat kokeilla vastaavanlaista "niksiä".

Vaikeaksi menee, täytyy myöntää. Nerokkaammat valaiskoon minuakin, jos helpon ja hyvän ratkaisun tietävät. =P

Mazzimo [04.08.2007 17:31:56]

#

Kiitos vastauksesta. :)

Minusta tuntuu, että nuo shaderit saattavat ajaa asiansa paremmin, sillä tarkoitus olisi pystyä venyttämään vaikka kaikkia kulmia mihin suuntaan tahansa (kuvamanipulaatio). En ole kuitenkaan kovin hyvin perillä shadereistä, enkää osaa tehdä itse niitä. Tiedän kuitenkin mitä ne ovat ja miten ne suurin piirtein toimivat. Olisiko mitään linkkiä, jolla pääsisi helposti alkuun (saisi glsl:n tai cg:n integroitua ohjelmaan sekä shader-ohjelmoinnin perusteet)?

Mazzimo [05.08.2007 00:35:44]

#

Hmmmm... Tein vähän tutkimusta ja sain selville, että ratkaisu ongelmaan voisi olla q-koordinaatti (olen saanut selville, että opengl käyttää koordinaatteja s,t,r,q). Kaikki tiedot tästä mystisestä Q:sta otettaisiin vastaan kiitollisina! :)

Markus [05.08.2007 09:38:48]

#

Q-koordinaatti on sama kuin verteksin sijainnin W-koordinaatti. Se ei auta ongelmaasi.

Ongelmasi ratkeaa, kun piirrät nelikulmiosi yhden QUAD:n sijasta tiheänä ruudukkona. Mitä tiheamman ruudukosta teet, sitä pienemmäksi virhe tulee, mutta sitä enemmän resursseja sen piirtämiseen menee. Kokeile vaikka aluksi 10x10 ruudukolla ja tihennä siitä tarvittaessa.

Metabolix [05.08.2007 10:08:01]

#

Tein vähän tutkimusta ja uskaltaisin väittää, että q-koordinaatti ei ratkaise ongelmaasi. :) Empiiristen kokeiden (ja dokumentaation) mukaan q-koordinaatti vain skaalaa koordinaatteja niin, että lopullisiksi koordinaateiksi muissa suunnissa tulee s/q, t/q ja r/q. Siis samanlainen tapaus kuin verteksin w-koordinaatti. Tämän voisi päätellä siitäkin, että oletusarvo glTexCoord{123}:lla on 1. Muualtakaan en löytänyt tuohon kummempaa apua. Käytännössä haluaisit siis piirtää kolmion tekstuurin vääntyneenä, hankalampi homma. Perspektiivikikka on ainoa, joka näin äkkiseltä tulee mieleen. Sapluunapuskurin kanssa siitä saa jopa sinänsä toimivan ratkaisun. Laiskana en nyt jaksa oikeaa koodia kirjoittaa, kun ei tule ulkomuistista nimiä funktioille, mutta tällä ajatuksella:

// Piirto pois ja sapluunapiirto päälle
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glStencilMask(...); // yms. glStencil-juttuja, glClear...
// Piirretään tasossa (tekstuuri menee väärin)
glBegin(...) ... glEnd();
// Piirto päälle, sapluunapiirto pois
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilMask(0); // yms?
// Syvyystestaus pois ja sapluunatestaus päälle
glDisable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST); // Ja jotain tälle kaveriksi?
// Ja sitten piirretään venytettynä syvyyden avulla, pitää vain osua samaan kohti kuin edellisellä piirrolla

Ja näyttipä neliö vinolta, kun tunnin katsoi vain väänneltyjä versioita. O_o

Edit. Olinpa aika hidas. Pieninä palasina piirtämällä tietenkin pääsee liki toivottuun, kuten Markus sanoo, mutta suljin tuollaisen melkein-ratkaisun suoraan pois.

Markus [05.08.2007 10:29:33]

#

Metabolix kirjoitti:

Pieninä palasina piirtämällä tietenkin pääsee liki toivottuun, kuten Markus sanoo, mutta suljin tuollaisen melkein-ratkaisun suoraan pois.

Itse asiassa tuo palasina piirtäminen on täydellinen ratkaisu silloin, kun yhden palan koko on pienempi tai yhtä suuri kuin yhden pikselin koko.

Itse asiassa se on myös ainut käytännöllinen ratkaisu. Millä tahansa muulla tavalla (esim. perspektiivikorjauksen tekeminen shaderissa) saa kikkailla ihan tosissaan.

Metabolix [05.08.2007 11:23:47]

#

Markus kirjoitti:

Itse asiassa tuo palasina piirtäminen on täydellinen ratkaisu silloin, kun yhden palan koko on pienempi tai yhtä suuri kuin yhden pikselin koko.

Ja jokainen meistä varmasti haluaa piirtää satojatuhansia pieniä polygoneja, kun voisi selvitä parilla isolla? Luulisi, että siitä tulee jo pullonkaula ohjelmaan. Saman tien voisi väittää, että kannattaa käyttää raytraceria OpenGL:n sijaan, kun sillä tulee kuitenkin paljon aidompaa jälkeä. ;) Eikä esimerkiksi tuo sapluunatemppu mene lainkaan niin kikkailuksi kuin shaderit, vai miltä näyttää? (Hm, vai osuukohan silläkään ihan kohdalleen? :o)

Mazzimo [05.08.2007 11:37:59]

#

Hmmm... Kerta kaksi kovaa tekijää sanoo, ettei q-koordinaatti auta niin sitten se ei auta.. :) Lisäksi tuo Metabolixin perustelu on selvä jä järkevä. Tuo sapluunapuskurin käyttö näyttää tuossa esimerkissäsi melko suoraviivaiselta. Itse vierastan shadereita, joten pyrin välttämään niitä viimeiseen asti. En kuitenkaan ole oikein perillä tästä sapluunapuskurin käytöstä, joten pieni selvennys ei olisi pahitteeksi.. :) Yritän kuitenkin googlettaa jotain tietoa.

Markus [05.08.2007 14:23:42]

#

Ellen tajunnut ongelmaa aivan väärin, niin en usko, että sapluunapuskurista on tässä mitään hyötyä.

Käytä vaan tuota ruudukkoa. Esim. 10x10 ruudukossa on vain 100 QUAD:ia ja se vähentää virheen niin pieneksi, että ihmissilmä ei enää huomaa eroa.

Metabolix [05.08.2007 15:18:32]

#

Markus kirjoitti:

Ellen tajunnut ongelmaa aivan väärin, niin en usko, että sapluunapuskurista on tässä mitään hyötyä.

Tuosta nyt sai vähän sellaisen kuvan, ettet välttämättä lukenut koko juttua ajatuksella. Sapluunapuskuri ei ollut pääasia vaan väline piirron rajoittamiseksi tietylle alueelle silloin, kun syvyyspuskuria ei voi käyttää. Paitsi että 2D-moottorillahan tuota sapluunakikkailua ei edes tarvitsisi, jos syvyyspuskuria ei kuitenkaan käytetä.

Mutta joo, myönnetään, tulee tuosta ruudukostakin suhteellisen pienillä luvuilla jo siedettävää jälkeä... Kuitenkin 10x10 teki ristikkotekstuuristani vielä aika psykedeelistä aaltoa. x_x Mutta 20x20 ei enää aiheuttanut hurjaa päänsärkyä. Tuossa vain joutuu sitten tekemään algoritmin nelikulmion paloitteluun ja tekstuurin asetteluun, kun taas minun jutussani joutuisi säätämään lopulta vähän rankemmalla matematiikan alalla, projektiolaskuissa. Vaikka eihän se matematiikan hankaluus enää ohjelman ajon aikana paina, määrä ratkaisee. ;)

Mazzimo [05.08.2007 16:51:10]

#

Jooo-o. Kokeillaan sitten tuota ruudukkoa. Pitäisihän tuon OpenGL:n jaksaa vaikka vähän isompiakin määriä. Nyt pitäisi vielä kehitellä algoritmi... Olisiko mitään järkevää pseudoa avuksi?

Metabolix [05.08.2007 21:54:11]

#

Olkoon kulmio ABCD, jolloin siis kirjaimet merkitsevät kärkien paikkavektoreita. Jaetaan n×m ruutuun, jolloin tietty kulma P(i, j) lasketaan näin:
E(i) = A + i / n * (B - A) (piste sivulta AB)
F(j) = D + i / n * (C - D) (piste sivulta DC vastaavasti)
P(i, j) = E(i) + j / m * [F(j) - E(i)] (näiden väliseltä suoralta oikea kohta)
Muista sitten indeksien muunnos liukuluvuiksi oikeassa kohti. Sopivan laatikon saa luonnollisesti kulmista [P(i,j), P(i+1,j), P(i+1,j+1), P(i,j+1)], missä 0 <= i < n ja 0 <= j < m.

Mazzimo [05.08.2007 23:16:05]

#

Ahhh.. Loistavaa loistavaa. Ongelma selvisi. Todella paljon kiitoksia kummallekin!! Tässä kuvaa nykyisestä venytyksestä: http://koti.mbnet.fi/masa_89/kuvia/text_saavutettu.jpg

Ja sitten vielä jos jollekin tulee vielä tämänkaltaisia ongelmia niin lähdekoodi on tässä (se on vain testiversio, eikä sitä ole sen kummemmin optimoitu tai mitään. Koodista näkee vain idean.

#define GRID_X 10
#define GRID_Y 10
#define GRID_Xf 10.0f
#define GRID_Yf 10.0f


// a = vasen yläkulma
// b = oikea yläkulma
// c = oikea alakulma
// d = vasen alakulma
void drawQuad(const Vector& a, const Vector& b, const Vector& c, const Vector& d)
{
	Vector vrtx,e,f;

	const Vector ba = b - a;
	const Vector cd = c - d;

	glBegin( GL_QUADS );
	for( int y = 0 ; y < GRID_Y ; y++ )
	{
		for( int x = 0 ; x < GRID_X ; x++ )
		{
			e = a + ba * (x / GRID_Xf);
			f = d + cd * (x / GRID_Xf);
			vrtx = e + (f - e) * (y / GRID_Yf);
			glTexCoord2f(x / GRID_Xf, 1.0 - y / GRID_Yf);
			glVertex2f(vrtx.x, vrtx.y);

			e = a + ba * ((x+1) / GRID_Xf);
			f = d + cd * ((x+1) / GRID_Xf);
			vrtx = e + (f - e) * (y / GRID_Yf);
			glTexCoord2f((x+1) / GRID_Xf, 1.0 - y / GRID_Yf);
			glVertex2f(vrtx.x, vrtx.y);

			e = a + ba * ((x+1) / GRID_Xf);
			f = d + cd * ((x+1) / GRID_Xf);
			vrtx = e + (f - e) * ((y+1) / GRID_Yf);
			glTexCoord2f((x+1) / GRID_Xf, 1.0 - (y+1) / GRID_Yf);
			glVertex2f(vrtx.x, vrtx.y);

			e = a + ba * (x / GRID_Xf);
			f = d + cd * (x / GRID_Xf);
			vrtx = e + (f - e) * ((y+1) / GRID_Yf);
			glTexCoord2f(x / GRID_Xf, 1.0 - (y+1) / GRID_Yf);
			glVertex2f(vrtx.x, vrtx.y);
		}
	}
	glEnd();

}

Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta