• Keine Ergebnisse gefunden

Vorlesung„Computergraphik I“S. Müller (4) Linien

N/A
N/A
Protected

Academic year: 2022

Aktie "Vorlesung„Computergraphik I“S. Müller (4) Linien"

Copied!
38
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

(4) Linien

Vorlesung

„Computergraphik I“

S. Müller

(2)

Linien

(3)

Wiederholung I

Gerade

Parameterdarstellung

Lineare Interpolation

Ebene

Parameterdarstellung

Normalform

Rasterisierung

v t u s A

X = + ⋅  + ⋅ 

= 0 +

⋅ +

⋅ +

x b y c z d

a

v t A

X = + ⋅ 

( t ) A

B t

X = ⋅ + 1 − ⋅

a

n

(4)

Pixel

Wir haben Ausgabegeräte mit endlicher, fester Auflösung und damit ein festes Raster

Bildpunkte werden „Pixel“ genannt für „picture element“

In Wirklichkeit ist ein Pixel kein fester Punkt, sondern eine Fläche

Rasterisierung:

Wir nähern z.B. eine Linie durch

„Flächen“ an

Problem der Unterabtastung (englisch: „aliasing“) führt zu

gezackten Strukturen (speziell bei schrägen Linien)

(5)

Implementierung

Bildspeicher ist ein Array von Werten mit einer definierten Startadresse im Speicher

Den Kern der

Computergraphik bildet eine putpixel-Funktion z.B. mit den Werten:

x, y (Position)

r, g, b (Farbe)

Je nachdem, wie dieses

Speicherarray organisiert ist, lässt sich die Speicheradresse bestimmen und die Werte für r, g und b setzen

$Start

Beispiel: Bildschirm löschen

Schleife über alle

Bildschirmpixel und setzen der Hintergrundfarbe

(6)

Beispiel: put_pixel in OpenGL

#include <GL/glut.h>

#define BREITE 500

#define HOEHE 500

void put_pixel(GLint x, GLint y) {

glBegin(GL_POINTS);

glVertex2i(x, y);

glEnd();

}

void display(void) {

glClear (GL_COLOR_BUFFER_BIT);

glColor3f (1.0, 0.0, 0.0);

put_pixel(50, 50);

glFlush ();

}

void init (void) {

glClearColor (1.0, 1.0, 1.0, 0.0);

glOrtho(0, BREITE, 0, HOEHE, -1, 1);

}

int main(int argc, char** argv) {

glutInit(&argc, argv);

glutInitDisplayMode (GLUT_SINGLE|GLUT_RGB);

glutInitWindowSize( BREITE, HOEHE);

glutInitWindowPosition (100, 100);

glutCreateWindow ("Points");

init ();

glutDisplayFunc(display);

glutMainLoop();

return 0;}

(7)

Beispiel: Bildschirm löschen

for (x = 0; x < BREITE; x ++)

for (y = 0; y < HOEHE; y++) {

put_pixel(x, y);

}

glFlush ();

pixel_clear.vcproj pixel_rechteck.vcproj

(8)

Beispiel Funktionen

void display(void) {

GLint x;

glClear (GL_COLOR_BUFFER_BIT);

for (x = 0; x < BREITE; x ++) {

glColor3f (0.0, 1.0, 0.0);

put_pixel(x, x);

}

glFlush ();

}

void display(void) {

GLint x;

glClear (GL_COLOR_BUFFER_BIT);

for (x = 0; x < BREITE; x ++) {

glColor3f (1.0, 0.0, 0.0);

put_pixel(x, sin(PI*2*x/BREITE)*

HOEHE/2+HOEHE/2);

}

glFlush ();

}

(9)

Linien I

void draw_line(GLint ax, GLint ay, GLint bx, GLint by)

{

GLint x, y;

float t;

for (t = 0.0; t < 1.0; t += 0.01) {

x = ax + t*(bx - ax);

y = ay + t*(by - ay);

put_pixel(x, y);

} }

Problem: wie ist Schrittweite entlang von t zu wählen?

Besser: x als Parameter pixelweise erhöhen

) ( B A t

A

X = + ⋅ −

) (

x x

x

t b a

a

x = + ⋅ −

)

(

y y

y

t b a

a

y = + ⋅ −

line_supernaiv.vcproj

(10)

Linien II

) ( B A t

A

X = + ⋅ −

A

B

xy

a

x

b

x

b

y

a

y

) (

x x

x

t b a

a

x = + ⋅ −

)

(

y y

y

t b a

a

y = + ⋅ −

x x

x

a b

a t x

= −

)

(

x

x x

y y

y

x a

a b

a a b

y ⋅ −

− + −

=

x

x a

b x=

y

y a

b y =

α

= tan

= ∆

= −

x y a

b

a m b

y y

α

)

(

x

y

m x a

a

y = + ⋅ −

x m a

m a

y =

y

− ⋅

x

+ ⋅ c

α

x m c

y = + ⋅

(11)

Linie zeichnen

void draw_line(GLint ax, GLint ay, GLint bx, GLint by) {

GLint x;

float m = (float)(by - ay)/(bx - ax);

float c = ay - m*ax;

for (x = ax; x < bx; x ++) put_pixel(x, m*x+c);

}

line_naiv.vcproj

Bemerkung: in C liefert 1 / 2 den Wert 0, da es sich um eine Division von Integerwerten handelt. Dagegen liefern 1.0 / 2 den Wert 0.5; ebenso liefert (float) 1 / 2 den Wert 0.5.

(12)

Bresenham-Algorithmus I

Das Zeichnen einer Linie ist ein sehr essentieller Befehl in Graphiksystemen. Er muss also äußerst effizient

implementiert werden

Wie kann man den letzten Algorithmus beschleunigen?

Teuer ist die Float-

Berechnung m*x+c, sowie die float-Division zur

Berechnung von m

Trick 1: inkrementell arbeiten

:

x y

0

= mx + c

c x

m

y

1

= ⋅ ( + 1 ) + m c

x m

y

1

= ⋅ + + m y

y

1

=

0

+ :

+ 1

x

(13)

Implementierung

void draw_line(GLint ax, GLint ay, GLint bx, GLint by) {

GLint x;

float y = ay;

float m = (float)(by - ay)/(bx - ax);

for (x = ax; x < bx; x ++) {

put_pixel(x, y);

y = y + m;

}

} Float-Operationen immer noch teuer.

(14)

Bresenham-Algorithmus

Wir beschränken uns auf Linien im 1. Oktant

Alle anderen lassen sich durch Vertauschen der x- und y-Koordinaten von A und B, bzw. durch negieren

darauf zurückführen.

Für Linien im ersten Oktant gelten:

Erhöht man x um Eins, dann kann sich y auch maximal um Eins erhöhen

1

(15)

Beispiele

Für jedes x wird also nur ein Pixel gezeichnet

„Gehe entlang der Linie von links nach rechts und gehe dabei ev. manchmal ein Pixel höher…“

Pseudocode

float y = ay;

for (x = ax; x < bx; x ++) {

put_pixel(x, y);

if („Bedingung“) y = y + 1;

}

(16)

Wie sieht „Bedingung“ aus?

Der Kandidat für das nächste Pixel ist

oder

Mit dem Mittelpunkt

Die Idee ist einfach: dieser Punkt wird (inkrementell) in die Funktion eingesetzt. Liegt der Punkt über der Geraden, wird das untere Pixel

gewählt, sonst das obere

) , 1 ( x + y

) 1 ,

1 ( x + y +

) 5 . 0 ,

1

( x + y +

„Midpoint-Algorithmus“

Dies ist nicht der originale

Bresenham-Algorithmus,

zeichnet aber die gleiche

Linie und ist etwas intuitiver

(17)

Implizite Darstellung

Die ursprüngliche Gleichung

lässt sich umformen in

Diese Darstellung f(x,y)=0 nennt sich „implizite Form“

)

(

x

x x

y y

y

x a

a b

a a b

y ⋅ −

− + −

=

) (

) (

) (

)

( b

x

a

x

a

y

b

x

a

x

b

y

a

y

x a

x

y ⋅ − = ⋅ − + − ⋅ −

0 )

( )

( − + ⋅ − + ⋅ − ⋅ =

a

y

b

y

y b

x

a

x

a

x

b

y

a

y

b

x

x

(18)

Midpoint-Algorithmus

Für einen Punkt (x, y) gilt in diesem Fall:

f(x,y) = 0: er liegt auf der Geraden

f(x,y) > 0: er liegt oberhalb der Geraden

f(x,y) < 0: er liegt unterhalb der Geraden

Pseudocode für „Bedingung“

if (f(x+1, y+0.5) < 0) y = y + 1;

(19)

Inkrementelles Vorgehen

Einsetzen der zwei möglichen Pixel liefert:

x y y

x x

x y

y

b y b a a b a b

a x y

x

f ( , ) = ⋅ ( − ) + ⋅ ( − ) + ⋅ − ⋅

) (

) , ( )

, 1

( x y f x y a

y

b

y

f + = + −

) (

) (

) , ( )

1 ,

1

( x y f x y a

y

b

y

b

x

a

x

f + + = + − + −

(20)

Pseudocode

d = f(ax + 1, ay + 0.5);

for (x = ax; x <= bx; x ++) {

put_pixel(x, y);

if (d < 0) {

y++;

d = d + (ay-by) + (bx-ax);

} else

d = d + (ay-by);

}

Schon besser. Inkrementell und fast an allen Stellen Integeroperationen. Das Problem ist d durch die Addition mit 0.5

(21)

Bestimmung des initialen d-Wertes

Was noch offen ist: wie bestimmt sich das initiale d?

d = f(ax + 1, ay + 0.5);

 Antwort: Einsetzen in die implizite Geradengleichung liefert:

x y y

x x

x y

y y

x

y x

b a

b a a

b a

b a

a

a a

f

⋅ +

⋅ +

+

⋅ +

=

= +

+

) (

) 5 . 0 (

) (

) 1 (

) 5 . 0 ,

1

(

(22)

Letzter Schritt

Wie werde ich die float-Multiplikation mit 0.5 los?

Antwort: gesucht ist nur das Vorzeichen, also ob der eingesetzte Punkt oberhalb der Linie (positiv) oder unterhalb der Linie (negativ) ist.

Statt setzt man

x y y

x x

x y

y y

x

y x

b a

b a a

b a

b a

a

a a

f

⋅ +

⋅ +

+

⋅ +

=

= +

+

) (

) 5 . 0 (

) (

) 1 (

) 5 . 0 ,

1 (

0 )

,

( x y =

f 2 ⋅ f ( x , y ) = 0

x y y

x x

x y

y y

x

y x

b a

b a a

b a

b a

a

a a

f

⋅ +

⋅ + +

⋅ +

=

= +

+

2 2

) (

) 1 2

( ) (

) 1 (

2

) 5 . 0 ,

1 (

2

(23)

Finaler Code

void draw_line(GLint ax, GLint ay, GLint bx, GLint by) {

GLint x = ax;

GLint y = ay;

int d = 2*(x+1)*(ay-by)+(2*y+1)*(bx-ax)+2*ax*by-2*ay*bx;

for (x = ax; x <= bx; x ++) {

put_pixel(x, y);

if (d < 0) {

y++;

d = d+2*(ay-by)+2*(bx-ax);

} else

d = d+2*(ay-by);

} }

Nur Integeroperationen !

line_midpoint.vcproj

(24)

C++ kompakt - Teil 3

(Dank an Markus Geimer, Matthias Biedermann)

(25)

Wiederholung

Vom Compiler automatisch erzeugte Methoden:

Default-Konstruktor

Kopier-Konstruktor

Destruktor

Zuweisungsoperator

Referenz

Alternativer Name für ein Objekt

Muss zwingend initialisiert werden

Notation: Datentyp&

Anwendung:

Call-by-reference

Überladen von Funktionen

Funktionen, die sich nur in Anzahl und Typen der

Parameter unterscheiden

Auswahl der “richtigen”

Variante anhand der realen Parameter beim Aufruf

Überladen von Operatoren

Definition der Standard- Operatoren für eigene Datentypen

Nicht möglich:

• Neue Operatoren definieren

• Funktionsweise für elem.

Typen ändern

(26)

Objektorientierte Programmierung

C++ basiert auf C, ermöglicht aber zusätzlich OO-Programierung

alle Konzepte wie Klassen, Kapselung, Vererbung usw.

jedoch optional (im Gegensatz zu Java!)

Syntax (Überblick):

Weiterführende Konzepte:

Mehrfachvererbung, private Vererbung

class Object {

public:

...

protected:

string m_name;

};

class Drawable : public Object {

public:

...

private:

Vector2D m_position;

};

(27)

Felder (Arrays)

Die Deklaration

int a[10];

definiert ein Feld a mit 10 Elementen vom Typ int . Auf die

einzelnen Elemente kann mit a[0] bis a[9] zugegriffen werden.

Auf diese Art lassen sich nur Arrays definieren, deren Größe zur Compilezeit bekannt ist (variable Größe später).

Mehrdimensionale Felder definiert man analog:

int a[10][20];

a[5][2] = 19;

Es gibt zur Laufzeit keine Möglichkeit, die Größe eines Arrays

festzustellen…

(28)

Exkurs: STL

Die Standard Template Library enthält viele effiziente Container, Algorithmen u.v.m., die für eigene Zwecke verwendet werden können

Beispiel: einfachere Felder

#include <vector>

vector<float> a; // default-Größe (meist 0) vector<float> b(10); // 10 Elemente

Verwendung wie bei herkömmlichen Arrays, zusätzlich z.B.:

Hinzufügen weiterer Elemente:

a.push_back(2.87f); // hinten

Abfragen der Größe:

(29)

Exkurs: STL (2)

Eigene Datentypen können ebenso verwendet werden, z.B.:

#include “Vector2D.h”

#include <vector>

#include <iostream>

using namespace std;

vector<Vector2D> points;

points.push_back(Vector2D(1, 5)); // anonyme Instanz points.push_back(Vector2D(-3, 0));

cout << points.size() << “ Punkte eingefügt.” << endl;

Weitere nützliche STL-Komponenten

Container wie map, list, stack

Algorithmen wie find(), sort(), min()

Zeichenketten string

(30)

Zeiger

Für einen beliebigen Datentyp type bezeichnet type* den Typ

“Zeiger auf type ”, d.h. eine Variable vom Typ type* kann die Adresse eines “Objektes” vom Typ type aufnehmen.

Wichtige Operatoren:

Adressoperator &

Dereferenzierungsoperator * (Inhaltsoperator)

(31)

Zeiger Beispiel

Beispiel:

int x = 1;

int y = 2;

int* ip; // “Zeiger auf int”

ip = &x; // ip enthält Adresse von x y = *ip; // y ist jetzt 1

*ip = 0; // x ist jetzt 0

ip = &y; // ip zeigt jetzt auf y

4711 1 (x)

4712 2 (y)

4713 - (*ip)

4711 1 (x)

4712 2 (y)

4713 4711 (*ip)

4711 1 (x)

4712 1 (y)

4713 4711 (*ip)

4711 0 (x)

4712 1 (y)

4713 4711 (*ip)

4711 0 (x)

4712 1 (y)

4713 4712 (*ip)

(32)

Zeiger II

Zeigern kann der Wert 0 zugewiesen werden (Null-Pointer). So ist prüfbar, ob ein Zeiger belegt ist oder nicht:

int x = 1;

int y = 2;

int* ip = 0;

if (ip) y = *ip; // Keine Zuweisung

ip = &x; // ip enthält Adresse von x if (ip) y = *ip; // y = 1

4711 1 (x)

4712 2 (y)

4713 0 (*ip)

4711 1 (x)

4712 2 (y)

4713 4711 (*ip)

4711 1 (x)

4712 1 (y)

4713 4711 (*ip)

(33)

Referenz vs. Zeiger

Referenz:

int a = 2;

doMagic( a);

void doMagic( int &x) { x = 5;

}

Referenz ist quasi ein konstanter Zeiger, der nicht verändert werden kann.

Zeiger

int a = 2;

doMagic( &a);

void doMagic( int *x) { *x = 5;

}

(34)

Dynamische Speicherverwaltung

Mit Hilfe von Zeigern kann man auch dynamische (herkömmliche) Arrays erzeugen (n sei eine Variable, welche die Größe enthält):

int* a = new int[n];

Es gibt in C++ keinen Garbage Collector, d.h. man muss sich selbst um die Freigabe des Speichers kümmern!

delete[] a;

Es gibt auch keinen Referenzzähler:

int* a = new int[10];

int* b = &a[0];

delete[] a;

int x = b[5]; // Fehler!!!

delete[] b; // Fehler!!!

(35)

Dynamische Speicherverwaltung (2)

Um ein Array von Objekten anzulegen, muss die entsprechende Klasse einen Default-Konstruktor (d.h. einen Konstruktor ohne Parameter)

bereitstellen.

Man kann auch einzelne Objekte anlegen und freigeben:

int* a = new int;

delete a;

Die Operatoren delete und delete[] sind auch für Null-Pointer definiert.

In diesem Fall wird der Aufruf ignoriert.

Wichtig:

new / delete und new[] / delete[] sind unterschiedliche Operatoren und dürfen nicht vermischt werden!

(36)

Dynamische Speicherverwaltung (3)

Anlegen eines zweidimensionalen Feldes (x * y):

int** feld;

feld = new (int*)[x];

for (int i = 0; i < x; ++i) feld[i] = new int[y];

Freigeben:

for (int i = 0; i < x; ++i) delete[] feld[i];

delete[] feld;

(37)

Dynamische Speicherverwaltung (4)

Beispiel mit STL-vector (dynamisch angelegt):

vector<int>* feld1D = new vector<int>(n); // 1D-Feld vector<vector<int> >* feld2D =

new vector<vector<int> >(x); // 2D-Feld for (int i = 0; i < x; ++i)

feld2D[i] = new vector<int>(y);

Freigeben:

for (int i = 0; i < x; ++i) delete[] feld[i];

delete[] feld;

Bei fast allen Compilern muss Leerzeichen dazwischen sein!

(38)

Wo ist der Fehler?

int* create() {

int feld[25];

for (int i = 0; i < 25; ++i) feld[i] = (2 * i) % 7;

return &feld[0];

}

An dieser Stelle wird die Adresse einer lokalen Variablen zurückgeliefert, die nach dem Verlassen der Funktion nicht mehr existiert!

Referenzen

ÄHNLICHE DOKUMENTE

Warum ist die Verwendung von Punktlichtquellen nur eine Vereinfachung und wel- che realen Effekte k¨ onnen damit nicht ad¨ aquat modelliert werden2. Erl¨ autern Sie die Bestandteile

Für Events und insbesondere mehr Treffpunkte wie ein Kino- programm für Jugendliche spricht sich Jannik Probst aus.. Lukas Bingger will insbesonde- re das Interesse am JGR

Ziel dieser Aufgabe ist es, beide Funktionen in eine generalisierte Initialisierungs- funktion zu ¨ uberf¨ uhren.. Dazu m¨ ussen wir sie lediglich um zwei Parameter erweitern, die

Aufgabe : eine generalisierte Funktion zur Initialisieren eines Zeichen-Arrays Parameter : drei Parameter: Anfangswert, Array, Gr¨ oße.. Ausgabe : die Inhalte der beiden

§  Dave Shreiner: The OpenGL Programming Guide:. The Official Guide to

§  Kleines Problem des Sutherland-Hodgeman-Algos: falls das ursprüngliche Polygon in mehrere Teile zerfällt beim Clipping, dann entsteht eine unschöne Polygonkante am Rand

Zeigen Sie, dass auch bei Verwendung der Fl¨ achenformel (auf Folie 11 des Kapitels “Baryzentrische Koordinaten”) wenigstens eine der 3 baryzentrischen Koordinaten eines Punktes

  Clipping = teilweise sichtbare Objekte (Linien / Polygone) müssen gegen Window / Viewport geclippt werden.   Resultat = maximales Teil-Objekt, das vollständig im