Geometrie/Stínování

Stínování

editovat

Pod pojmem stínování rozumíme vykreslování barevných objektů různými odstíny barev. Stínování pomáhá člověku ve vnímání křivosti a zaoblení ploch a snaží se dosáhnout co nejvěrnějšího vzhledu prostorových objektů. Samotnou barvu bodu lze vypočítat na základě znalosti směru paprsku, normál plochy, umístění zdrojů světla a dalších faktorů.

Existuje několik neznámějších algoritmů stínování

  • konstantní stínování
  • Gouraudovo stínování
  • Phongovo stínování

Konstantní stínování

Nejjednodušší a zároveň velmi rychlá metoda, jak vystínovat prostorový objekt. Převážně se používá pro zobrazování rovinných ploch, nebo - v případě potřeby velmi rychlého vystínování - obecných ploch aproximovaných rovinnými záplatami.

Samotný algoritmus počítá s tím, že každá plocha má pouze jednu normálu. Podle normály je vypočítána barva, která je následně přiřazena všem pixelům dané plochy.

Nevýhodou tohoto stínování je zjevná vlastnost, že celá plocha má naprosto stejnou barvu. Navíc pak není do světelného modelu zahrnuto zrcadlové osvětlení (odlesky na zobrazovaném objektu, které mohou mít jinou barvu než jaké má objekt).

Pro tvorbu mnohostěnů je však toto stínovaní ve většině případů postačující.

U obecnějších těles ale konstantní stínování spíše kazí celkový dojem objektu, protože spíše ukazuje, že celý jeho povrch je aproximován skupinou plošek.

Gouraudovo stínování

Tato metoda byla navržena pro objekty, které jsou tvořeny množinou rovinných ploch a byla poprvé uvedena H. Gouraudem.

Její princip spočívá v tom, že pokud budeme znát barevné odstíny všech vrcholů ploch tohoto objektu (tj. normál, ze kterých se pak dá barva dopočítat), pak lze tyto vrcholy propojit a barvy jednotlivých pixelů této úsečky pak vypočítat interpolací. Ve chvíli, kdy máme obarvené všechny hrany, které ohraničují danou plochu, pak stačí vybrat jednu z os, po které budeme tuto plochu procházet a lineární interpolací zjistíme barvu pixelů, které se nacházejí na úsečce ležící mezi dvěma body na hranách plochy. Tím jsme celou plochu pokryli barvou.

Gouraudova metoda zajišťuje plynulé stínování křivých povrchů tak, že aproximace povrchů ploškami není zřetelná.

Přesto ani tento způsob stínování neposkytuje zcela věrný obraz reálných objektů - interpolace samotného odstínu barvy totiž nemůže způsobit místní zvýšení jasu na plošce, která nahrazuje oblinu kolmou na dopadající světelný paprsek, stejně jako nemůže kvalitně vytvořit odlesky způsobené odraženým světlem. Dá se říci, že tato metoda zahlazuje barevné rozdíly u místních nerovností povrchu.

Phongovo stínování

Tato metoda je určena ke spojitému stínování těles, jejichž povrch je tvořen množinou rovinných ploch. Jejím autorem je Bui-Tuong Phong.

Pro daný výpočet je třeba, stejně jako u Gouraudova stínování, nejprve určit normálové vektory ve vrcholech stínované plochy. Zde však nejsou vypočteny barevné odstíny v těchto vrcholech, ale jsou použity k interpolaci normálových vektorů v ostatním bodech plošky. Současně s rozkladem plochy na pixely jsou vypočítávány normály ve vnitřních bodech plochy a pomocí nich je ze světelného modelu určen odstín barvy každého pixelu.

Oproti Gouraudovu stínování jsou zřetelné barevné odlesky světel na plochách.

Tento způsob zajišťuje hladké vystínování ploch s korektně vykreslenými odlesky od světel

Algoritmizace

editovat

Algoritmus konstantního stínování:

  1. Vypočteme normálové vektory pro všechny plošky ze kterých je objekt složen
  2. Pro každou plošku určíme odchylku normálového vektoru a vektoru paprsků světelného zdroje. Čím více je ploška ke světelnému zdroji přivrácena, tím více ji světelný zdroj osvětluje
  3. Podle odchylky přiřadíme každé plošce jednu barvu se kterou bude vyplněna

Tento algoritmus je vhodný pro rychlé zobrazování objektů (např. pro náhled při tvorbě objektů), ale nehodí se pro vytvoření efektivně vystínovaného a vyhlazeného objektu.

Algoritmus Gouraudova stínování:

  • Vypočteme normálové vektory pro všechny plošky ze kterých je objekt složený
  • Pro každý vrchol spočítáme normálový vektor v tomto vrcholu jako průměr normálových vektorů plošek, které se v tomto vrcholu stýkají
  • Určíme odchylku normálových vektorů ve vrcholech a vektoru paprsků světelného zdroje
  • Podle této odchylky přiřadíme bodům ve vrcholech plošek barvu
  • Nyní provedeme interpolaci barvy pro body jednotlivých plošek takto:
    • Nejprve provedu interpolaci barvy po hranách   a  . Dostanu barvu v bodech   a  
    • Nyní provedu interpolaci barvy na hraně   a dostanu výslednou barvu v bodě  

Tento algoritmus je pomalejší než konstantní stínování, ale umožňuje dobře zobrazit i hladké objekty. Vzhledem k tomu, že je v dnešní době již dobře zpracován a není příliš složitý, používá se dnes jako nejčastější metoda stínování. Bohužel si nedokáže korektně poradit s odlesky světla.

Algoritmus Phongova stínování:

  • Provedu výpočet normál pro všechny plošky, ze kterých je objekt složen a stejně jako v Gouraudově stínování výpočet normál ve vrcholech
  • Podobně jako jsme v Gouraudově stínování prováděli interpolaci barev pro jednotlivé body plošky, nyní provádíme interpolaci normály pro jednotlivé body plošky
  • Jakmile mám pro všechny body na plošce určeny normály, určím odchylky těchto normál od vektoru světelných paprsků
  • Podle těchto odchylek přiřadím každému bodu jeho příslušnou barvu

Tento algoritmus je početně náročnější a také nejpomalejší. Dosahuje však nejlepšího zobrazení daného objektu.

Kód v jazyce C#

editovat

Pomocné objekty a metody

editovat

InterpolVals

  • struktura pro uchování dat o vektorech a jejich vlastnostech.

Face

  • třída uchovávající informaci o jednom trojúhelníku.

LinearInterpolator

  • třída provádějící lineární interpolaci seznamu hodnot přes plochu trojúhelníku.

metody:

  • void Init(InterpolVals[] triple) // Inicializuje interpolátor.Vypočítá potřebné přírustky atd.
  • static InterpolVals[] SortByY(InterpolVals[] tripl) // Setřídí tři vektory podle y-nové souřadnice
  • static void Draw(SetPixelDelegate setPixel, object tag) //Vykreslit trojúhelník. Bod se nekreslí přímo, ale pro každý získaný bod je vyvolán delegát

ShadingRenderSystem

editovat

třída starající se o samotné stínovaní zmíněnými metodami

Pomocná metoda:

Vypočíta barvu bodu podle jeho polohy, normály a polohy světel (a vlastností povrchu). Phongův osvětlovací model.

/// <param name="pos">Polohový vektor bodu (v prostoru), ve kterém se má určit barva.</param>
/// <param name="normal">Normálový vektor (plochy) v bodě <c>pos</c>.</param>
/// <param name="materialDifuse">Barva povrchu.</param>
/// <param name="specular">Intenzita zrcadlové složky.</param>
/// <returns>Barvu v zadaném bodě.</returns>
Color PhongLit(Vector pos, Vector normal, Color materialDifuse, double specular)
{
	double jas=0;//Jas aktualniho svetla
	double[] celkovyJas=new double[3];//RGB, soucet pres vsechny svetla
	celkovyJas.Initialize();//Vynulovat
	double scalar;
	Vector litDir;//Vektor, urcujici smer ze svetla do zadaneho bodu
	Vector reflxDir;//Vektor odrazeneho paprsku
	foreach (Light l in Scene.Lights)//Pro vsechny svetla
	{
		if (l.IsOff) continue;
		litDir= (pos - l.Position*transform);
		litDir.Normalize();
		//litdir je smer paprsku ze svetla (jednotkovy vektor) dopadajici na pozici vertexu
		scalar=litDir*normal;//Skalarni soucin vektoru svetla a normaly
		if (scalar<0) jas-=scalar;//Difuzni slozka je primo umerna cosinu odchylky L a N
		else jas=0;//Odvracena face
		reflxDir= normal * (2*scalar) - litDir;
		//Vektor symetricky podle normaly povrch. Odrazeny paprsek
		scalar=-reflxDir.Z;
		//Skalarni soucin odrazeneho paprsku s vektorem pohledu, ktery je [0,0,1]
		//Vektor pohledu je [0,0,1], protoze se cela scena transformuje podle polohy
		//kamery a prumetna je rovina XY
		if (scalar<0) jas+=Math.Pow(-scalar, specularExp)*specular;
		//Intenzita zrcadlove slozky roste exponencionalne
		celkovyJas[0]+=jas* (l.Color.R + materialDifuse.R) * 0.5;
		//Pricte prumer barvy svetla a povrchu vynasobeneho
		celkovyJas[1]+=jas* (l.Color.G + materialDifuse.G)* 0.5;
		//intenzitou k celkovemu souctu
		celkovyJas[2]+=jas* (l.Color.B + materialDifuse.B)* 0.5;
	}
	for (int f=0; f<3; f++)
	{
		if (celkovyJas[f]>255) celkovyJas[f]=255;//Ohlida preteceni
	}
	return Color.FromArgb(
		(int)celkovyJas[0], (int)celkovyJas[1], (int)celkovyJas[2]
	);
}

Vlastní algoritmy

editovat

Konstantní stínování

Vykreslí jednu Face.

/// <param name="g">Kam se má kreslit.</param>
/// <param name="v2d">Vektory plošky transformované a promítnuté.
// Jsou to souřadnice na obr. Z je původní po transformaci.</param>
/// <param name="tPos">Vektory plošky transformované (ale ještě ne promítnuté).</param>
/// <param name="tNormals">Transformované normálové vektory.</param>
/// <param name="face">Původní ploška.</param>
void DrawTripleConst(Graphics g, Vector[] v2d, Vector[] tPos, Vector[] tNormals, Face face)
{
	mat=face.Material;
	Vector normala=(tNormals[0] + tNormals[1] + tNormals[2]) * 0.333;
	//Vezme se prumerna normala vsech tri normal torjuhelnicku.
	Vector stred=(tPos[0] + tPos[1] + tPos[2]) * 0.333;
	//Stejne tak se vezme stred plosky
	Color c=PhongLit(stred, normala, face.Material.Difuse, face.Material.SpecularDouble);
	//Vypocita se barva
	Brush br=new SolidBrush(c);
	g.FillPolygon(br, new Point[] { (Point)v2d[0], (Point)v2d[1], (Point)v2d[2]});
	br.Dispose();
}

Gouraudovo stínování

Vykreslí jeden bod.

/// <param name="x">Souřadnice na obrazovce.</param>
/// <param name="y">Souřadnice na obrazovce.</param>
/// <param name="actualVals">Aktuální hodnoty interpolovaných hodnot.</param>
/// <param name="tag">Pomocná proměnná (nepoužívá se).</param>
void GouraudSetPixel(double x, double y, InterpolVals actualVals, object tag)
{
	int r=(int)actualVals.Vals[0];//Prevede RGB z double na inty
	int g=(int)actualVals.Vals[1];
	int b=(int)actualVals.Vals[2];
	if (r<0) r=0;if (r>255) r=255;//Osetreni preteceni
	if (g<0) g=0;if (g>255) g=255;
	if (b<0) b=0;if (b>255) b=255;
	int ix=(int)x;
	int iy=(int)y;
	if (ix>=0 && iy>=0 && ix<buffers[1-front].Width && iy<buffers[1-front].Height)
	//Mimo obrazovku
	buffers[1-front].SetPixel(ix,iy, Color.FromArgb(r,g,b));
	//Vykresli bod do back-bufferu
}

Vykreslí jednu Face.

/// <param name="g">Kam se má kreslit.</param>
/// <param name="v2d">Vektory plošky transformované a promítnuté.
// Jsou to souřadnice na obr. Z je původní po transformaci.</param>
/// <param name="tPos">Vektory plošky transformované (ale ještě ne promítnuté).</param>
/// <param name="tNormals">Transformované normálové vektory.</param>
/// <param name="face">Původní ploška.</param>
void DrawTripleGouraud(Graphics g, Vector[] v2d, Vector[] tPos, Vector[] tNormals, Face face)
{
	mat=face.Material;
	double[] colors;//Barva ve vrcholu plosky (face).
	Color c;
	InterpolVals[] vals=new InterpolVals[3];
	for (int i=0; i<3; i++)//Pro vsechny tri vrcholy
	{
		c=PhongLit(tPos[i],tNormals[i],face.Material.Difuse,face.Material.SpecularDouble);
		//Vypocte se svetlo
		colors=new double[3];
		colors[0]=c.R;
		colors[1]=c.G;
		colors[2]=c.B;
		vals[i]=new InterpolVals(v2d[i], colors);//Interpoluji se tri cisla (R,G,B)
	}
	LinearInterpolator.Init(vals);
	LinearInterpolator.Draw(new SetPixelDelegate(GouraudSetPixel), g);
}

Phongovo stínování

Vykreslí jeden bod.

/// <param name="x">Souřadnice na obrazovce.</param>
/// <param name="y">Souřadnice na obrazovce.</param>
/// <param name="actualVals">Aktuální hodnoty interpolovaných hodnot.</param>
/// <param name="tag">Pomocná proměnná (nepoužívá se).</param>
void PhongSetPixel(double x, double y, InterpolVals actualVals, object tag)
{
	Vector pos=new Vector(actualVals.Vals[0], actualVals.Vals[1] , actualVals.Vals[2]);
	//Bod, ve kterem se ma vypocita barva
	Vector nrm=new Vector(actualVals.Vals[3], actualVals.Vals[4] , actualVals.Vals[5]);
	//Normala v tomto bode
	Color c=PhongLit(pos, nrm, mat.Difuse, mat.SpecularDouble);//Vypocita svetlo
	int ix=(int)x;
	int iy=(int)y;
	if (ix>=0 && iy>=0 && ix<buffers[1-front].Width && iy<buffers[1-front].Height)
	buffers[1-front].SetPixel(ix,iy, c);
}

Vykreslí jednu Face.

/// <param name="g">Kam se má kreslit.</param>
/// <param name="v2d">Vektory plošky transformované a promítnuté.
// Jsou to souřadnice na obr. Z je původní po transformaci.</param>
/// <param name="tPos">Vektory plošky transformované (ale ještě ne promítnuté).</param>
/// <param name="tNormals">Transformované normálové vektory.</param>
/// <param name="face">Původní ploška.</param>
void DrawTriplePhong(Graphics g, Vector[] v2d, Vector[] tPos, Vector[] tNormals, Face face)
{
	mat=face.Material;//PhongSetPixel potrebuje material, tak se pradava pres glob.
	// promennou (slo by to i pres Tag, ale tohe je rychlejsi
	InterpolVals[] vals=new InterpolVals[3];
	for (int i=0; i<3; i++)
	{
		//Interpoluje se 6 cisel. Souradnice trojuhelnicku a souradnice normaly.
		vals[i]=new InterpolVals(v2d[i],
			new double[] {
				tPos[i].X, tPos[i].Y, tPos[i].Z,
				tNormals[i].X, tNormals[i].Y, tNormals[i].Z
			}
		);
	}
	LinearInterpolator.Init(vals);
	LinearInterpolator.Draw(new SetPixelDelegate(PhongSetPixel), g);
}


Autoři

editovat

Tento text vypracovali studenti Univerzity Palackého v Olomouci katedry Matematické informatiky jako součást zápočtového úkolu do předmětu Počítačová geometrie.