mikrocontroller.net

Forum: PC-Programmierung Durchschnitt von 16-bit Farbwerten berechnen


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mein Grafikprogramm auf einem µC verwendet aufgrund der RAM- und 
LCD-Limitierung 16-bit Farben, d.h. der Farbwert für einen Pixel wird 
wie folgt gebildet:
uint16_t color_code;
color_code = (r & 0x1f) << 11;
color_code |= (g & 0x3f) << 5;
color_code |= (b & 0x1f) << 0;

Das Programm summiert die Farbwerte für jeden Pixel einzeln auf:
buffer[i] += color_code;

Das geht so lange gut, bis ein Überlauf der 16-bit Werte stattfindet. 
Nun stelle ich mir die Frage, wie ich trotzdem noch den Durchschnitt 
aller Farben in 16-bit untergebracht bekomme?

Meine erste Idee war, die Summe durch die Anzahl der Summanden zu 
teilen:
buffer[i] /= samples
das endet allerdings in ständig wechselnden Farben.

Ein weiterer Versuch war, R, G und B zu extrahieren, zu teilen und dann 
wieder zusammen zu bauen:
uint8_t r = buffer[i] >> 11;
uint8_t g = (buffer[i] >> 5) & 0x1f;
uint8_t b = buffer[i] & 0x1f;

r /= samples;
g /= samples;
b /= samples;

uint16_t color_code;

color_code = (r & 0x1f) << 11;
color_code |= (g & 0x3f) << 5;
color_code |= (b & 0x1f) << 0;

buffer[i] = color_code;

Hat allerdings den gleichen Effekt, dass ständig die Farben wechseln.

Hat jemand noch eine Idee?

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Das Programm summiert die Farbwerte für jeden Pixel einzeln auf:
> buffer[i] += color_code;
> Das geht so lange gut, bis ein Überlauf der 16-bit Werte stattfindet.

Oder ein Überlauf einer der drei Komponenten. Immerhin ist dein 
16-Bit-Wert ja nicht ein einzelner Wert mit 16 Bit, sondern ein Wert mit 
5 Bit, einer mit 6 Bit und dann wieder einer mit 5 Bit. Jeder einzelne 
davon kann natürlich überlaufen. Wenn z.B. dein blau-Anteil überläuft, 
läuft er ins grün rein.

> Meine erste Idee war, die Summe durch die Anzahl der Summanden zu
> teilen:
> buffer[i] /= samplesdas endet allerdings in ständig wechselnden Farben.

Ja, wegen der Überläufe der einzelnen Farbkomponenten.

> Ein weiterer Versuch war, R, G und B zu extrahieren, zu teilen und dann
> wieder zusammen zu bauen:

Du musst sie natürlich für das Summieren extrahieren und nicht erst 
für's Teilen. An der Stelle ist es schon zu spät, da sind die Daten 
bereits kaputt, weshalb das kein Wunder ist:

> Hat allerdings den gleichen Effekt, dass ständig die Farben wechseln.

Autor: georg (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> den Durchschnitt
> aller Farben

Was in aller Welt verstehst du denn unter dem Durchschnitt von Farben? 
Für so etwas gibt es doch garkeine Definition. Aber wenn du es besser 
weisst: was ist der Durchschnitt von Blau und Grün?

Max M. schrieb:
> Hat allerdings den gleichen Effekt, dass ständig die Farben wechseln

Meiner Meinung nach kann auch nichts anderes herauskommen. Vielleicht 
nach Goethes Farbenlehre, die kenne ich nicht so genau.

Georg

Autor: ~Mercedes~ (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max meinte:

> Hat jemand noch eine Idee?

Geil!
Ein Grafikprogramm auf nem Mikroprozessor!
Wozu braucht man sowas?

Wozu braucht man dann den Mittelwert aller Farben?
Farben, die gerade angezeigt werden?
Oder alle Farben, die das LCD kann?

mfg

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Du musst sie natürlich für das Summieren extrahieren und nicht erst
> für's Teilen.

Wie stelle ich da denn sicher, dass es nicht überläuft bzw. das nach 
einem Überlauf die Farben konsistent sind?

Seltsamerweise klappt es in der Version am PC, da speichere ich jeden 
RGB-Wert in einem 32-bit Integer und teile sie dann durch die Anzahl der 
Summanden:
buffer[i * 3 + 0] += color.r;
buffer[i * 3 + 1] += color.g;
buffer[i * 3 + 2] += color.b;
...
int r = buffer[i * 3 + 0] / samples;
int g = buffer[i * 3 + 1] / samples;
int b = buffer[i * 3 + 2] / samples;
setPixel(x,y,r,g,b)

Am PC kann ich über 16 Millionen 8-bit Werte addieren, auf einem µC 
leider nicht, da der RAM sehr begrenzt ist.

georg schrieb:
> Was in aller Welt verstehst du denn unter dem Durchschnitt von Farben?

Naja, wenn ich z.B. verschiedene Blautöne aufaddiere möchte ich einen 
Blauton, der alle zu gleichen Teilen repräsentiert

georg schrieb:
> was ist der Durchschnitt von Blau und Grün?

"Aqua" (https://www.w3schools.com/colors/colors_picker.asp)

: Bearbeitet durch User
Autor: c-hater (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:

> Am PC kann ich über 16 Millionen 8-bit Werte addieren, auf einem µC
> leider nicht, da der RAM sehr begrenzt ist.

So ein Unsinn. Um 16 Millionen 8Bit Werte aufzuaddieren, braucht man 
genau nur 4 Bytes RAM. Wären also für drei Farbkanäle insgesamt 12 
Bytes. So viel RAM hat fast jeder µC zu bieten.

Und wenn nicht: deine RGB565-Daten sind ja nichtmal 8Bit-Daten, sondern 
halt nur 2x5Bit und einmal 6Bit-Daten. Dementsprechend braucht man zum 
Aufaddieren insgesamt im Minimum sogar nur 11 Bytes.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
c-hater schrieb:
> Wären also für drei Farbkanäle insgesamt 12
> Bytes. So viel RAM hat fast jeder µC zu bieten.

Dann kann ich unglaubliche 40 x 40 Pixel darstellen.

Autor: Lothar M. (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Das Programm summiert die Farbwerte für jeden Pixel einzeln
> auf:buffer[i] += color_code;
Diese Summenbildung ist unsinnig, weil ja rot 2048 mal stärker gewichtet 
ist als blau, da kann man keine Summe bilden

> die Summe durch die Anzahl der Summanden zu teilen:
> buffer[i] /= samplesdas
> endet allerdings in ständig wechselnden Farben.
Das ist das Resultat der Summenbildung: mal angenommen, du hast 2048 
Pixel. 1 einziger davon ist "voll rot" und der Rest ist komplett 
"schwarz". Dann teilst du letztlich die Zahl 0xf800 durch 2048 und 
erhältst als "Mittelwert mit 0x001F einzigen "voll blauen" Pixel.
Oder wenn du ein Bild halb voll mit "ziemlich dunklen" grünen Pixeln 
(nur das LSB gesetzt) hast, dann ist die Summe 0x8000 und nach der 
"Mittelwertdivision" kommt ein "halbheller" blauer Pixel heraus.
Nimm einfach mal einen Rechner und kaspere ein paar solcher Werte 
durch...

> Hat jemand noch eine Idee?
Das Aufsummieren der zusammengefügten und unterschiedlich 
gewichteten Werte einem Puffer geht also schon schief. Du musst die 
einzelnen Farben in 3 getrennte Puffer für R, G und B summieren und 
dann aus diesen Einzelsummen den Mittelwert pro Farbe ausrechnen.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> du hast 2048
> Pixel.

Ich berechne nicht den Durchschnitt über alle Pixel sondern für jeden 
Pixel einzeln.

Lothar M. schrieb:
> Du musst die
> einzelnen Farben in 3 getrennte Puffer für R, G und B summieren

Okay, das resultiert leider darin, dass ich dann mehr RAM benötige.

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Lothar M. schrieb:
>> du hast 2048
>> Pixel.
>
> Ich berechne nicht den Durchschnitt über alle Pixel sondern für jeden
> Pixel einzeln.

Hä? Wie bildest du die Summe aus nur einem Wert?

> Lothar M. schrieb:
>> Du musst die
>> einzelnen Farben in 3 getrennte Puffer für R, G und B summieren
>
> Okay, das resultiert leider darin, dass ich dann mehr RAM benötige.

Erzähl doch mal, was du eigentlich machen willst. Irgendwie drehen wir 
uns hier im Kreis.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Hä? Wie bildest du die Summe aus nur einem Wert?

Eventuell hab ich Lothar falsch verstanden.
Jeder Pixel erhält verschiedene Farbwerte über die Berechnung, diese 
sollen für jeden Pixel einzeln ausgewertet werden.

Rolf M. schrieb:
> Erzähl doch mal, was du eigentlich machen willst.

Aus einer Vielzahl an Berechnung ergeben sich unterschiedliche 
Farbinformationen für jeden Pixel. Ich würde gerne den Durchschnitt 
dieser Farben für jeden Pixel separat berechnen.
Das Konstrukt sieht am PC so aus:
while(..){
 i = 0
 for(x)
  for(y)
   color = getPixelColor(x,y);
   buffer[i * 3 + 0] += color.r
   buffer[i * 3 + 1] += color.b
   buffer[i * 3 + 2] += color.g
   i++;
}

Auf dem µC wandle ich die drei einzelnen 8-bit RGB Werte in einen 16-bit 
Wert um und sende es an das Display. Statt drei einzelnen Buffern gibt 
es da nur einen 16-bit Buffer.

Autor: Achim S. (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:
> Ich würde gerne den Durchschnitt
> dieser Farben für jeden Pixel separat berechnen.

Die Durchschnittsberechnung der drei Farbkomponenten wäre:

(color.r+color.g+color.b)/3

Damit würdest du die Einzelfarben in eine Helligkeitsinformation 
umrechnen.

Was du machst ist nicht der Durchschnitt, sondern du versuchst wohl 
einen RGB-Wert zusammenzubauen. Wobei der gezeigte Code eher so 
aussieht, als wäre jeder RGB-Wert 24 Bit groß (nicht die 16 Bit, die du 
schreibst).

Max M. schrieb:
> Dann kann ich unglaubliche 40 x 40 Pixel darstellen.

Das klingt als versuchst du einen "Videospeicher" in einem µC 
vorzuhalten, der nicht genug RAM für diesen Videospeicher hat. Das ist 
kein Problem mit einer Durchschnittsbildung, sondern ein Fehler im 
Konzept.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Achim S. schrieb:
> Wobei der gezeigte Code eher so
> aussieht, als wäre jeder RGB-Wert 24 Bit groß (nicht die 16 Bit, die du
> schreibst).

Ich verwende in der Berechnung der Einfachheit halber 8-bit für jeden 
Farbkanal, die ich dann auf 16-bit für das Display transformiere.

Achim S. schrieb:
> Das ist
> kein Problem mit einer Durchschnittsbildung, sondern ein Fehler im
> Konzept.

Da hast du recht. Ich hatte auch zuerst vor, mir die Pixeldaten vom 
Display selber wieder zu holen, allerdings hat das Display keinen 
zusätzlichen MISO Ausgang. Ich müsste daher wahrscheinlich MISO und MOSI 
an eine Leitung löten. Da ich gerade noch im Bastelstadium bin ist mir 
das zu unflexibel.

Kann das Problem des Überlaufens überhaupt verhindert werden?

Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> Kann das Problem des Überlaufens überhaupt verhindert werden?

Es gibt kein Problem des Überlaufens wenn du
- die einzelnen Farbwerte getrennt aufaddierst und nicht vesuchst, das 
3-Tupel der RGB-Werte auf einmal zu addieren.
- die Ergebnisvariable groß genug ist. Wie weiter oben schon geschrieben 
wurde reicht für jede Farbe ein 32 Bit-Wert (also insgesamt 12 Byte) Von 
jedem einzelnen Pixel kann bei jeder Einzelfarbe maximal 255 dazu 
addiert werden (8 Bit). Ein 32 Bit Wert hat also Platz für 2^24 = 16 
Megapixel.

Autor: Theor (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hm. Vielleicht nochmal von vorne - wobei das eigentlich auch schon 
gefragt wurde. Ich wiederhole aber mal die Frage, weil Du ihre 
Bedeutung, meinem Eindruck nach, unterschätzt.

Ich habe den Eindruck, dass Du einen Durchschnitt von Farbwerten 
ausrechnen willst, aber nicht darüber im klaren bist, was visuell das 
Ergebnis ist. So laufen die Antworten drauf hinaus, die Anwendung eines 
Mittelwertes an sich nicht wirklich in Frage zu stellen, sondern 
arithmetische bzw. binäre Darstellungsprobleme zu lösen.

Ob das aber zielführend ist, wissen wir nicht. Weil wir nicht wissen, 
welche Information die Farbwerte repräsentieren und welche Information 
hinzugefügt, entfernt oder in ihrer Repräsentation, mit welchem Ziel, 
geändert werden soll.

Also: Was willst Du erreichen? Warum wähltest Du den mathematischen 
Durchschnitt als Ansatz dafür?

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Theor schrieb:
> Also: Was willst Du erreichen? Warum wähltest Du den mathematischen
> Durchschnitt als Ansatz dafür?

Es handelt sich um eine Bildberechnung nach dem Monte-Carlo Prinzip 
(Pathtracing).

Ich hab es jetzt mal genauso wie am PC gemacht, der buffer besteht aus 
32-bit Werten.

Die Ausgabe:
  uint16_t i = 0;
  for(uint8_t y = 0; y < H; y++) {
    for(uint8_t x = 0; x < W; x++) {
      const uint8_t r = buffer[i * 3 + 0] / samples;
      const uint8_t g = buffer[i * 3 + 1] / samples;
      const uint8_t b = buffer[i * 3 + 2] / samples;
      
      uint16_t color_code;
      color_code = (r & 0x1f) << 11;
      color_code |= (g & 0x3f) << 5;
      color_code |= (b & 0x1f) << 0;
      
      drawPixel(x + (128 - W) / 2, y + (128 - H) / 2, color_code);
      i++;
    }
  }

In der Berechnung:
uint16_t i = 0;
for(x)
for(y)
buffer[i * 3 + 0] += color.r;
buffer[i * 3 + 1] += color.g;
buffer[i * 3 + 2] += color.b;
i++

Am PC funktioniert das einwandfrei. Auf dem µC wechseln immer noch 
ständig die Farben!
Kann das am Compiler für den µC liegen? Mir ist klar, dass der 
Assembler-Code anders ist, aber er sollte das gleiche tun wie am PC.

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Am PC hast du für jedes einzelne Pixel 3 (wegen der Farben) 
32Bit-Buffer. Für die "mittlere Farbe" eines Bildes würden insgesamt 3 
32Bit-Buffer reichen.

In deiner Berechnung mittelst du imho gar nichts, weil die Additionen 
jeweils auf unterschiedliche Stellen des Buffers durchgeführt werden 
(der Index wird nach jeder Addition der drei Farben erhöht). Oder rufst 
du diesen Pseudocode mehrfach nacheinander für verschiedene Bilder auf?

Also auch von mir noch mal die Frage: was willst du eigentlich machen.

Zur Auswahl stehen aus meiner Sicht gerade
- die "mittlere Farbe" von mehreren Pixeln eines Bildes berechnen? (das 
ließe sich trivial durchführen)
- die Farbe pixelindividuell über mehrere Bilder mitteln? Dann brauchst 
du ausreichen Speicher oder wiederholte Zugriffsmöglichkeit auf die 
Einzelbilder.

Autor: ECL (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Erzwinge auf dem PC einen unit_16t Buffer und schau v was passiert.
Du musst einen 32 Bit Buffer auch auf dem uc nehmen!

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Achim S. schrieb:
> die Farbe pixelindividuell über mehrere Bilder mitteln? Dann brauchst
> du ausreichen Speicher oder wiederholte Zugriffsmöglichkeit auf die
> Einzelbilder.

Das will ich!

Achim S. schrieb:
> In deiner Berechnung mittelst du imho gar nichts

Okay, ich hab vergessen zu erwähnen, dass
samples
 für die Anzahl der bisher berechneten Bilder steht.
Ist das nicht der Mittelwert für jede Farbe einzeln (also nicht über RGB 
sondern individuell für R, G und B)?
      const uint8_t r = buffer[i * 3 + 0] / samples;
      const uint8_t g = buffer[i * 3 + 1] / samples;
      const uint8_t b = buffer[i * 3 + 2] / samples;

ECL schrieb:
> Du musst einen 32 Bit Buffer auch auf dem uc nehmen!
uint32_t buffer[W * H * 3];

Achim S. schrieb:
> Oder rufst
> du diesen Pseudocode mehrfach nacheinander für verschiedene Bilder auf?

Jap

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Na prima, endlich hab ich kapiert worum es dir geht ;-)


entweder braucht der µC genügend Speicher, um den Buffer abzulegen 
(Anzahl_Pixel*3*(x+1)Bytes). x hängt davon ab, wie viele Bilder du 
mitteln willst. Z.B. x=1 für bis zu 256 Bilder)

Wenn der Speicher des µC dafür nicht reicht, dann könntest du höchstens 
die Reihenfolge deiner Schleifen umdrehen: wenn du als innerste Schleife 
die verschiedenen Bilder durchlaufen lässt, dann kannst du den 
Mittelwert pixelweise berechnen, du kannst ihn nur nicht für alle Pixel 
gleichzeitig speichern. Voraussetzung dafür wäre allerdings, dass du auf 
die Bilder wahlfrei zugreifen kannst (also nicht immer nur jeweils ein 
Bild vorhanden ist und das "verschwindet" sobald das Folgebild kommt).

Mehr als die beiden Möglichkeiten sehe ich nicht. (Na ja, wenn du die 
Farbinformation gleich vor der Mittelung reduzierst, kommst du 
vielleicht mit ein ganz klein wenig weniger Speicher aus. Aber das macht 
den Kohl nicht fett.)

Autor: Vlad T. (vlad_tepesch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
was sind denn deine Inputbilder?
sind die auch im Format auch 5,6 und 5 bit?

Max M. schrieb:
> uint16_t color_code;
>       color_code = (r & 0x1f) << 11;
>       color_code |= (g & 0x3f) << 5;
>       color_code |= (b & 0x1f) << 0;


kommt mir komisch vor.
Wenn die obere Frage mit 'ja' zu beantworten ist, sollte das '&' 
überflüssig sein, da geteilt durch die Anzahl nix größeres drin stehen 
kann.

sind die Inputfarbe 8 bit wäre die korrekte Berechnung (vorausgesetzt 
deine Reihenfolgen stimmen:
       color_code  = (r >> 3) << 11;
       color_code |= (g >> 2) << 5;
       color_code |= (b >> 3) << 0;

Wo ich mir gerade nicht vollständig sicher bin, ist, ob bei dem Shiften 
die 8bit-Variablen autormatisch auf Integerbreite (in deinem Fall 
wahrscheinlich 16 bit) erweitert werden. Ich würde vorsichtshalner nen 
cast ranschreiben, damit es unabhängig vom System in den richtigen Typ 
konvertiert wird.

Autor: Max M. (maxmicr)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Bitte erklärt mir den Unterschied zwischen meinem PC-Programm und dem 
auf dem µC! Ich verstehs nicht.

Mein Code am *PC*:
#include <stdint.h>
#include <stdio.h>
#include "renderer.h"
#include "image.h"
#include "Structs/Sphere.h"
#include "Structs/camera.h"

struct Sphere spheres[] = {
        { .center = {.x = 0.0f, .y = -0.0f, .z = 3.0f}, .color = {.r = 255, .g = 255, .b = 255}, .radius = 4.0f, .isEmitter = true },
        { .center = {.x = 0.0f, .y = 8.0f, .z = 3.0f}, .color = {.r = 255, .g = 255, .b = 255}, .radius = 4.0f, .isEmitter = false },
        { .center = {.x = 0.0f, .y = -8.0f, .z = 3.0f}, .color = {.r = 255, .g = 255, .b = 255}, .radius = 4.0f, .isEmitter = false }
};

int buffer[W * H * 3];

const struct camera c = {.pos = {.x = 0.0, .y = .25f, .z = 50.25}};

void render() {
    int samples = 1;
    while (samples < SAMPLES) {
        int i = 0;
        for (int y = 0; y < H; y++) {
            for (int x = 0; x < W; x++) {
                const struct Ray r = generateRay(c, x, y, W, H, samples);
                const struct RGB color = trace(r, 0);
                buffer[i * 3 + 0] += color.r;
                buffer[i * 3 + 1] += color.g;
                buffer[i * 3 + 2] += color.b;
                i++;
            }
        }
        samples++;
    }
    drawbmp("test.bmp",W,H,buffer,samples);
}

/* Ausschnitt drawbmp() */
int i = 0;
for (y = 0; y <HEIGHT; y++)
{
  for (x = 0; x <= WIDTH - 1; x++)
  {

    int r = buffer[i * 3 + 0] / samples;
    int g = buffer[i * 3 + 1] / samples;
    int b = buffer[i * 3 + 2] / samples;

    fprintf(outfile, "%c", b);
    fprintf(outfile, "%c", g);
    fprintf(outfile, "%c", r);
    i++;
  }
}

struct RGB trace(const struct Ray ry, int tdepth) {
    if(tdepth == TRACEDEPTH) return (struct RGB){.r = 0, .g = 0, .b = 0};

    float hitDistance  = 1e20f;
    struct Sphere hitObject = {};
    for(int i = 0; i < (sizeof(spheres) / sizeof(spheres[0])); i++) {
        float dist = intersectSphere(spheres[i], ry);
        if(dist != -1.0 && dist < hitDistance) {
            hitDistance = dist;
            hitObject = spheres[i];
        }
    }

    if(hitDistance == 1e20f) return (struct RGB){.r = 0, .g = 0, .b = 0};
    if(hitObject.isEmitter) return hitObject.color;

    const struct Point hitPoint = add(ry.origin, mult(ry.dir, hitDistance * 0.998));
    const struct Point nrml = sphereNormal(hitObject, hitPoint);
    struct Point rnd = diffuse();

    const struct Ray reflectionRay = (struct Ray){ .origin = hitPoint, .dir = norm(rnd) };

    struct RGB returnColor = trace(reflectionRay, tdepth + 1);
    int r = hitObject.color.r * returnColor.r;
    int g = hitObject.color.g * returnColor.g;
    int b = hitObject.color.b * returnColor.b;

    r /= 255.0f;
    g /= 255.0f;
    b /= 255.0f;

    return (struct RGB){ .r = r, .g = g, .b = b};
}

Am *µC*:
#include "stdio.h"
#include "renderer.h"
#include "Structs/Sphere.h"
#include "Structs/camera.h"

struct Sphere spheres[] = {
        { .center = {.x = 0.0f, .y = -0.0f, .z = 3.0f}, .color = {.r = 255, .g = 255, .b = 255}, .radius = 4.0f, .isEmitter = true },
        { .center = {.x = 0.0f, .y = 8.0f, .z = 3.0f}, .color = {.r = 255, .g = 255, .b = 255}, .radius = 4.0f, .isEmitter = false },
        { .center = {.x = 0.0f, .y = -8.0f, .z = 3.0f}, .color = {.r = 255, .g = 255, .b = 255}, .radius = 4.0f, .isEmitter = false }
};

uint16_t buffer[W * H * 3];
uint8_t samples = 0;
const struct camera c = {.pos = {.x = 0.0, .y = .25f, .z = 50.25f}};

void display() {
  uint16_t x,y,i = 0;
  for (y = 0; y < H; y++)
  {
      for (x = 0; x < W; x++)
      {
          const uint16_t r = buffer[i * 3 + 0] / samples;
          const uint16_t g = buffer[i * 3 + 1] / samples;
          const uint16_t b = buffer[i * 3 + 2] / samples;

          uint16_t color_code;
          color_code  = (r >> 3) << 11;
          color_code |= (g >> 2) << 5;
          color_code |= (b >> 3) << 0;
          
          drawPixel(x + (128 - W) / 2, y + (128 - H) / 2, color_code);
          i++;
      }
  }
}

void render() {
    samples++;
    uint16_t i = 0;
    for (int y = 0; y < H; y++) {
        for (int x = 0; x < W; x++) {
            const struct Ray r = generateRay(c, x, y, W, H, samples);
            const struct RGB color = trace(r, 0);
            buffer[i * 3 + 0] += color.r;
            buffer[i * 3 + 1] += color.g;
            buffer[i * 3 + 2] += color.b;
            i++;
        }
    }
}

struct RGB trace(const struct Ray ry, int tdepth) {
    if(tdepth == TRACEDEPTH) return (struct RGB){.r = 0, .g = 0, .b = 0};

    float hitDistance  = 1e20f;
    struct Sphere hitObject;
    for(int i = 0; i < (sizeof(spheres) / sizeof(spheres[0])); i++) {
        float dist = intersectSphere(spheres[i], ry);
        if(dist != -1.0 && dist < hitDistance) {
            hitDistance = dist;
            hitObject = spheres[i];
        }
    }

    if(hitDistance == 1e20f) return (struct RGB){.r = 0, .g = 0, .b = 0};
    if(hitObject.isEmitter) return hitObject.color;

    const struct Point hitPoint = add(ry.origin, mult(ry.dir, hitDistance * 0.998));
    const struct Point nrml = sphereNormal(hitObject, hitPoint);
    struct Point rnd = diffuse();

    const struct Ray reflectionRay = (struct Ray){ .origin = hitPoint, .dir = norm(rnd) };

    struct RGB returnColor = trace(reflectionRay, tdepth + 1);
    int r = hitObject.color.r * returnColor.r;
    int g = hitObject.color.g * returnColor.g;
    int b = hitObject.color.b * returnColor.b;

    r /= 255.0f;
    g /= 255.0f;
    b /= 255.0f;

    return (struct RGB){ .r = r, .g = g, .b = b};
}

Wo kommt das Rot und Grün her?

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Fang doch mal mit dem an, was ECL vorgeschlagen hat:

ECL schrieb:
> Erzwinge auf dem PC einen unit_16t Buffer und schau v was passiert.
> Du musst einen 32 Bit Buffer auch auf dem uc nehmen!

Derzeit hast du noch unterschiedliche Datenbreiten von Buffer auf PC und 
µC.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Achim S. schrieb:
> Derzeit hast du noch unterschiedliche Datenbreiten von Buffer auf PC und
> µC.

So? Funktioniert leider auch nicht, selbes Verhalten.
uint32_t buffer[W * H * 3];
uint8_t samples = 0;

void display() {
  uint16_t x,y,i = 0;
  for (y = 0; y < H; y++)
  {
      for (x = 0; x < W; x++)
      {
          const uint16_t r = buffer[i * 3 + 0] / samples;
          const uint16_t g = buffer[i * 3 + 1] / samples;
          const uint16_t b = buffer[i * 3 + 2] / samples;

          uint16_t color_code;
          color_code  = (r >> 3) << 11;
          color_code |= (g >> 2) << 5;
          color_code |= (b >> 3) << 0;
          
          drawPixel(x + (128 - W) / 2, y + (128 - H) / 2, color_code);
          i++;
      }
  }
}

Ich hab die Dateien aus meinem CLion-Projekt 1:1 kopiert und musste 
außer dem bisher gezeigten Code nichts verändern, nur ein M_PI 
nachdefinieren, da das nicht in den IAR-Bibs enthalten war.

Trigonometrische Funktionen sind ein entscheidender Bestandteil des 
Verfahrens, kann es da eventuell zu Problemen kommen, der STM hat ja 
keine FPU?

: Bearbeitet durch User
Autor: zyxw (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
int ist auf dem PC 32bit
samples ist auf dem Controller 8bit, reicht das?
Außerdem sollte es wegen der Division nie null sein.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
zyxw schrieb:
> int ist auf dem PC 32bit

Deswegen uint32_t auf dem µC.

zyxw schrieb:
> samples ist auf dem Controller 8bit, reicht das?

Ja, ein sample entspricht einem berechneten Bild.

zyxw schrieb:
> Außerdem sollte es wegen der Division nie null sein.

Ist es nicht, render() wird vor display() aufgerufen.

: Bearbeitet durch User
Autor: Achim S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> So? Funktioniert leider auch nicht, selbes Verhalten.

Ja, so.

Dann würde ich mal mit dem nächsten offensichtlichen Unterschied 
weitermachen (oder kurz darüber nachdenken, ob er die Ursache sein 
kann). Der Index i, mit dem du auf dem Buffer zugreifst, ist auf dem PC 
32 Bit breit, auf dem µC nur 16 Bit. Reicht uint16_t aus? (oder anders 
gefragt: wie groß ist W*H?)

Wenn das auch nichts hilft, dann muss im schlimmsten Fall mal klassische 
Debugarbeit gemacht werden. Also z.B. bei bestimmten Pixelpositionen 
jeweils die Zwischenergebnisse des Buffers anschauen und ergründen, ab 
wann etwas "komisches" geschieht.

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Achim S. schrieb:
> Reicht uint16_t aus? (oder anders
> gefragt: wie groß ist W*H?)

W und H = 30

Achim S. schrieb:
> Wenn das auch nichts hilft, dann muss im schlimmsten Fall mal klassische
> Debugarbeit gemacht werden.

Ich hab eigentlich die Software zuerst am PC entwickelt damit ich gerade 
nicht mehr debuggen muss :(

: Bearbeitet durch User
Autor: c-hater (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:

> W und H = 30

D.h.: 900Pixel. á 12Byte für die "Farbsumme" sind das knapp 11kByte für 
den Summenpuffer.

Mein Gott, das kann schon ein µC wie etwa der ATmega1284P beherbergen...

Du weißt echt nicht, was du tust, stimmt's?

Autor: Achim S. (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Max M. schrieb:
> W und H = 30

Die weiter oben angehängten Bilder waren also noch ein "früherer Stand"? 
Sie bestehen jedenfalls aus mer als 30x30 Pixeln.

Die geringe Größe hast du ausgewählt, weil du berechnet hast, dass der 
Buffer damit grade noch in den Speicher passt? Für den Fall, dass du 
dich dabei verrechnet hast: bleiben die Artefakte gleich, wenn du 
nochmal einen Faktor 2 oder 4 runtergehst?

Max M. schrieb:
> Ich hab eigentlich die Software zuerst am PC entwickelt damit ich gerade
> nicht mehr debuggen muss :(

Manchmal läuft es im Leben halt anders als geplant. Wenn der µC/die 
Entwicklungsumgebung halbwegs vernünftige Debugmöglichkeiten bieten ist 
es aber auch keine allzu große Sache, die Werteentwicklung im Buffer bei 
einer Pixelkorrdinate nachzuvollziehen.

Autor: Frank E. (Firma: Q3) (qualidat)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn man ernsthaft mit Farben "rechnen" will, dann ist das 
RGB-Farbmodell so ziemlich das Ungeeignetste überhaupt.

In so einem Fall verwendet man HSV/HSL oder LAB, d.h. man trennt die 
Farbe in in Helligkeit, Farbton und Sättigung auf und kann so eine ganze 
Reihe sinnvoller Berechnungen machen ...

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Max M. schrieb:
> zyxw schrieb:
>> int ist auf dem PC 32bit
>
> Deswegen uint32_t auf dem µC.

Das ist die halbe Miete, denn auch mit uint32_t et al. wird nur bis zu 
int promotet. Beispiel mit int = 16 Bit (AVR):
#include <stdint.h>
#include <stdio.h>

uint32_t mul (uint16_t a, uint16_t b)
{
    return a * b;
}

int main (void)
{
    uint16_t a = 300, b = 400;
    printf ("// sizeof (int) = %d\n", (int) sizeof (int));
    printf ("// Ausgabe: %" PRIu16 " * %" PRIu16 " = %" PRIu32 "\n",
             a, b, mul (a, b));
}

// sizeof (int) = 2
// Ausgabe: 300 * 400 = 54464

Das Ergebnis stimmt also nur modulo 65536, denn die 16->32 Erweiterung 
in mul() geschieht nach der Multiplikation.  Um 120000 zu erhalten, 
genügt zum Beispiel
return (uint32_t) a * b;

Ohne den ganzen Thread mit seinen Rants und Ausfällen gelesen zu haben 
ist das allgemeine Vorgehen:

1) Die Farbwerte liegen in einer gepackten Darstellung vor.  Zunächst 
werden also die einzelnen Komponenten entpackt bzw. extrakiert, um 
sinnvoll rechnen zu können.

2) Für alle (Farb-)Koordinaten werden unabhängig voneinander die 
Mittelwerte berechnet:  Zunächst werden alle Werte addiert, dann wird 
durch die Anzahl der Werte dividiert, evtl. mit Rundung.  Anschaulich 
berechnet man den Schwerpunkt von Punkten (Farbdarstellungen) im 
Koordinatenraum.

3) Die erhaltenen Werte / Koordinaten werden ins gewünschte Farbformat 
gepackt.

Wie bereits weiter oben angemerkt, ist das Ergebnis im Sinne seiner 
Darstellung als Farbe abhängig vom verwendeten Farbraum, in Deinem Falle 
wohl srgb mit 5:6:5 für r:g:b.  Die erhaltenen Farben werden andere 
sein, wenn Du einen anderen Farbraum wie Lab, HSV oder CMYK verwendest.

Wenn das Resultat also nicht Deinen Erwartungen entspricht, kann das an 
der Wahl des Farbraumes liegen, aber auch daran, dass die (menschliche) 
Wahrnehmung nicht linear ist sondern nach Weber-Fechner eher 
logarithmisch.

Die Rundung in 2) kann etwa so erfolgen, hier für n × Rot:

r_sum := r1 + r2 + ... + rn
r_mean := (r_sum + r_sum/2) / n

Autor: Max M. (maxmicr)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johann L. schrieb:
> Das ist die halbe Miete, denn auch mit uint32_t et al. wird nur bis zu
> int promotet.

Und wie komme ich dann auf echte 32-bit?

Johann L. schrieb:
> Beispiel mit int = 16 Bit (AVR)

AVR ist eine 8-Bit Architektur, ist da ein M3 nicht etwas anderes mit 
seinen 32-Bit?

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.