| M | D | M | D | F | S | S |
|---|---|---|---|---|---|---|
| « Mrz | ||||||
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | |||
- Allgemein (5)
- Einfache 3D Welten (3)
- Erste Schritte (3)
- GU (4)
- Komplexe 3D Welten (1)
- 23.3.2010: Der Anstrich - aber mit Plan
- 4.3.2010: 3D Landschaft - optimiert
- 17.2.2010: Landschaft 3D mit GU
- 28.1.2010: Ein Dreieck - GU Test
- 20.1.2010: Homebrew - 3D
- 12.1.2010: GU - und nu ?
- 11.1.2010: Neustart...
- 7.1.2010: Bildschirm aufräumen
- 5.1.2010: Schwarz/Weiß war gestern
- 15.12.2009: Ein kleiner Schritt
PSP Allgemein
PSP Entwicklung
Der Anstrich - aber mit Plan
23.3.2010 von AnMaBaGiMa.
Die Drahtgitterlandschaften sind fertig gestellt - egal mit welcher Methode - und nun geht es daran auch dieser wieder etwas Farbe einzuhauchen. Dazu bedient man sich sogenannter Texturen. Das sind Bilder einer beliebigen Oberfläche die auf das virtuelle 3D Objekt gelegt werden. Dadurch erscheinen sie realer und plastischer, als wenn Sie einfach nur mit einer einfachen Farbe angestrichen wurden.
Texturen verwalten
Immer wieder , wenn wir 3D szenen auf der PSP darstellen wollen, werden wir die verschiedensten Texturen zum Einsatz bringen. Da macht es Sinn sich darüber Gedanken zu machen, wie wir diese am besten in unserer Homebrew verwalten. Die GU der PSP bringt hier “leider” keine Verwaltungsfunktionen mit. Darum werden wir hier eine sehr einfache Variante in Form einer Texterverwaltungsklasse selber erstellen. Ein paar Grundüberlegungen:
1. die Texturen sollen zunächst nur von PNG Bilddateien gelesen werden
2. ein und das selbe Bild soll als Textur nur einmal geladen werden
3. geladene Texturen sollen individuell wieder freigegeben werden können um Speicher zu sparen
4. die Texturen sollen ge-swizzled werden
5. die Klasse soll einfach erweiterbar sein, zBsp. um Laderoutinen für JPG oder andere Bildformate
Dies sind ein paar gute Rahmenbedingungen. Machen wir uns nun ans Werk. Wir erstellen eine neue Klasse mit dem Namen ClTextureMgr. Im Header definieren wir zunächst ein paar Beschränkungen, wie die maximale Größe einer Textur, die sich auf 512×512 Bildpunkte beschränkt (mehr kann die GU nicht verarbeiten). Zusätzlich eine Struktur, in der alle wichtigen Daten einer Textur die wir verwalten wollen abgelegt sind.
/*
* TextureMgr.h
*/
#ifndef TEXTUREMGR_H_
#define TEXTUREMGR_H_
// Maximale größe einer Textur und anzahl Texturen die verwaltet werden sollen
#define TEX_MAX_WIDTH (512)
#define TEX_MAX_HEIGHT (512)
#define MAX_TEX_COUNT 100
/*
* Definition einer Struktur die alle daten einer Textur enthält
* die hier verwaltet werden soll
*/
typedef struct Texture{
unsigned int id; //ID der Textur
char* name; //Name der Textur/Bilddatei
int type; //Typ der Textur - siehe GU_PSM* Enums
int width, height; //Breite und Höhe der Textur als Potenz von 2
int stride; //Pufferbreite für Zugriff auf Texturdaten als Potenz von 2
bool swizzled; //WAHR wenn Texturdaten ge-swizzled sind
void* data; //Zeiger auf die Bilddaten der Textur
}Texture;
Nun folgt die eigentliche Definition der Klasse. Sie wird als Singleton ausgeprägt und kann somit nicht direkt instanziiert werden. Dies vereinfacht die Verwaltungsaufgaben der Klasse. Es müssen also 2 statische Methoden zum holen und zum abräumen der Singleton-Instanz erzeugt werden. Alle weiteren Methoden werden dann Instanz basiert ausgeprägt. Die Definition sieht dann folgendermaßen aus.
class ClTextureMgr {
public:
/*
* Statische Methoden für den Singleton Zugriff
*/
static ClTextureMgr* getInstance();
static void freeInstance();
/*
* Laden einer Textur aus einer PNG Datei
*/
Texture* loadFromPNG(const char* filename);
/*
* Liefert die verwaltete Textur anhand der ID
*/
Texture* getTexture(unsigned int id);
/*
* Freigeben der Ressorcen für eine Textur
*/
void freeTexture(unsigned int id);
protected:
/*
* Texturdaten "swizzlen"
*/
void swizzle(unsigned char* out, unsigned char* in, unsigned int width, unsigned int height);
/*
* Konstruktor ist protected um direkte Instanziierung zu verhindern
*/
ClTextureMgr();
virtual ~ClTextureMgr();
/*
* es folgen die Member-Variablen zur Klasse
*/
private:
static ClTextureMgr* _instance; //die singleton Instanz
int managedTextures; //Anzahl verwalteter Texturen
Texture* textures; //Liste aller verwalteten Texturen
};
#endif /* TEXTUREMGR_H_ */
Kommen wir nun zur Implementierung der Klasse. Den Start bildet hier eine kleine - von der Klasse losgelöste - Hilfsroutine um von einer gegebenen Zahl die nächst höhere Zahl als Potenz von 2 zu bestimmen (das haben wir schonmal gesehen
). Daneben sind die statischen Methoden für die Verwendung der Klasse als Singleton implementiert.
/*
* TextureMgr.cpp
*/
//die notwendigen Includes
extern "C"{
#include <malloc.h>
#include <stdlib.h>
#include <png.h>
#include <psptypes.h>
#include <pspgu.h>
}
#include "TextureMgr.h"
/*
* eine einfache Hilfsfunktion. Lokal und statisch...
*/
static int getNextPower2(int width)
{
int b = width;
int n;
for (n = 0; b != 0; n++) b >>= 1;
b = 1 << n;
if (b == 2 * width) b >>= 1;
return b;
}
//Am Anfang ist die Singleton instanz nicht erzeugt
ClTextureMgr* ClTextureMgr::_instance = 0;
ClTextureMgr::ClTextureMgr() {
//der Konstruktor initialisiert die Datan
this->managedTextures = 0;
textures = (Texture*)malloc(MAX_TEX_COUNT*sizeof(Texture));
}
ClTextureMgr *ClTextureMgr::getInstance()
{
if (!_instance) {
_instance = new ClTextureMgr();
}
return _instance;
}
void ClTextureMgr::freeInstance(){
if (_instance){
delete(_instance);
}
}
Der nächste Schritt ist die Methode zum laden eines PNG Files als verwaltete Textur. Da bereits an vielen anderen Stellen das laden der PNG Datei beschrieben und auch als Code dargestellt wurde, und weil der original Code Teil des PSPSDK ist, werde ich diesen hier aussparen, wohl aber den für den Texturverwaltungsteil wichtigen Part zeigen.
Texture *ClTextureMgr::loadFromPNG(const char *filename){
//Befor wir das File laden, prüfen ob es schon eine Textur
//mit dem selben Namen gibt, wenn ja eben diese Textur zurück liefern
for (short t=0;t<managedTextures;t++){
if (textures[t].name){
if (strncmp(filename, textures[t].name, strlen(filename))==0){
return &textures[t];
}
}
}
//es ist wirklich eine neue Textur, also diese Laden und die Daten füllen
managedTextures++;
if (managedTextures >= MAX_TEX_COUNT) return NULL;
// get the pointer of the new texture data structure
Texture* newTex = &textures[managedTextures-1];
newTex->name = (char*)malloc(strlen(filename));
strcpy(newTex->name, filename);
/*********************************************************
* hier erfolgt nun das Laden des PNG Files und das Ablegen der Daten im
* Speicher für diese Textur, sollte hier ein Fehler passieren wird NULL zurück-
* gegeben.
*********************************************************/
//vorbereiten für die Rückgabe der Textur
newTex->id = managedTextures;
newTex->swizzled = false;
//veruche die Texturdaten zu swizzlen
//Speicher für die neuen Daten besorgen
void* swizzleData = malloc(sizeof(u32)*newTex->stride*newTex->height);
if(swizzleData){
swizzle((unsigned char*)swizzleData, (unsigned char*)newTex->data, newTex->stride*sizeof(u32), newTex->height);
newTex->swizzled = true;
//Speicher der originalen Daten freigeben
free(newTex->data);
newTex->data = swizzleData;
} else
newTex->swizzled = false;
return newTex;
}
Swizzle
Das nun schon oft erwähnte Wort bedeutet direkt übersetzt: Bauernfängerei
bzw. mit dem Zusatz “Stick” bedeutet es Rührstab. Letzteres trifft hier eher zu. Die Daten der Textur sind im Normalfall ja in einem Datenpuffer ordentlich in Reihenfolge der Bildpunkte abgelegt. Des Swizzle-Verfahren rührt diese Bildpunkte so “durcheinander” dass die Daten nicht mehr so abgelegt sind wie sie auf dem Bildschirm zu sehen sind, sondern optimal von der GU zugegriffen werden können. Das ganze bringt einen großen Geschwindigkeitsgewinn beim Arbeiten mit Texturen.
Eine leicht lesbare - nicht auf Performance getrimmte - Swizzle-Routine wird in unserer Textur-Verwaltungsklasse verwendet:
void ClTextureMgr::swizzle(unsigned char* out, unsigned char* in, unsigned int width, unsigned int height)
{
unsigned int i,j;
unsigned int rowblocks = (width / 16);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
unsigned int blockx = i / 16;
unsigned int blocky = j / 8;
unsigned int x = (i - blockx*16);
unsigned int y = (j - blocky*8);
unsigned int block_index = blockx + ((blocky) * rowblocks);
unsigned int block_address = block_index * 16 * 8;
out[block_address + x + y * 16] = in[i+j*width];
}
}
}
Abschließend und der Vollständigkeit halber die Methode um eine verwaltete Textur einer bestimmten ID zurückzuliefern, eine Textur freizugeben und den Klassendestruktor.
Texture *ClTextureMgr::getTexture(unsigned int id){
//Prüfen das die ID auch valide ist
if (id > managedTextures){
return NULL;
}
return &textures[id-1];
}
void ClTextureMgr::freeTexture(unsigned int id){
Texture* text = &textures[id-1];
if (text){
free(text->data);
free(text->name);
text->data = 0;
text->name = 0;
}
}
ClTextureMgr::~ClTextureMgr() {
//Allen reservierten Speicher freigeben
for (int t=0;t<managedTextures;t++){
if (textures[t].data) free(textures[t].data);
if (textures[t].name) free(textures[t].name);
}
if (managedTextures)
free(textures);
}
Geschrieben in GU | Drucken | Keine Kommentare »
3D Landschaft - optimiert
4.3.2010 von AnMaBaGiMa.
Unsere erste 3D Landschaft bestehend aus vielen kleinen Dreiecken ist nun fertig. Bei genauerer Betrachtung der Daten die wir für die Landschaft benötigen - also der Anzahl der Dreiecke die entstehen werden, wir schnell an Grenzen stoßen wenn wir größere Landschaftsflächen abbilden wollen. Eine dieser Grenzen ist die Anzahl der Dreiecke die im Hauptspeicher der PSP abgelegt werden können und zum anderen natürtliche die Anzahl der Dreiecke die in akzeptabler Geschwindigkeit von der GU prozessiert und gezeichnet werden können. Bevor wir unserer Landschaft “Leben” in Form von Bewegung einhauchen werden wir einen kurzen Exkurs in die Optimierung der Landschaft unternehmen.
Schlagworte die hier oft in diesem Zusammenhang aufgeführt werden sind SOAR oder LOD. Was dies ist und welche Methodik zur Optimierung ich gefunden habe (im Internet) stelle ich in diesem Bereich vor. Neben der theoretischen Abhandlung werde ich dort auch meine Ideen für die PSP Implementierung bereitstellen (zur Zeit noch in Arbeit).
Unabhängig davon können aber die sich an diese Überlegungen anschließenden nächsten Schritte in unserer Landschaft (Textur, Bewegung etc.) umgesetzt werden.
Geschrieben in Allgemein | Drucken | 1 Kommentar »
Landschaft 3D mit GU
17.2.2010 von AnMaBaGiMa.
Nun haben wir unser erstes Objekt im virtuellen Raum platziert. Nun können wir mit diesem Objekt einige Experimente machen um diverse Funktionen der GU an genau diesem auszuprobieren. Dies möchte ich hier aber etwas nach hinten schieben und allen Entdeckern überlassen. Denn unsere nächste Etappe soll sich mit der Landschaft die wir zunächst im Voxelspace betrachten konnten beschäftigen. Wie können wir diese über die GU im 3D Raum modellieren und betrachten ? Diese Frage wollen wir nun beantworten und an diesem reellen Beispiel nach und nach die einzelnen Möglichkeiten der GU ausprobieren und hoffentlich sinnvoll einzusetzen lernen.
Als Vorbereitung für diesen Teil legen wir zunächst ein neues Projekt mit dem Namen GuLandscape in Eclipse an. Die ersten Schritte sind nun ein Makefile in diesem Projekt zu erstellen, eine Applikationsklasse zu definieren und die main.cpp zu erstellen. Doch nochmal langsam der Reihe nach.
1. Das Makefile:
TARGET = GuLandscape OBJS = main.o GuLandscapeApp.o INCDIR = CFLAGS = -G0 -Wall -g CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti ASFLAGS = $(CFLAGS) BUILD_PRX = 1 LIBDIR = LDFLAGS = LIBS = -lstdc++ -lpsphbc -lpspgum -lpspgu -lpng -lz -lm EXTRA_TARGETS = EBOOT.PBP PSP_EBOOT_TITLE = Gu Landscape PSP_LARGE_MEM=1 PSP_FW_VERSION=500 PSPSDK=$(shell psp-config --pspsdk-path) include $(PSPSDK)/lib/build.mak
2. Die neue Applikationsklasse ClGuLandscapeApp:
*
* GuLandscapeApp.h
* Author: andreborrmann
*/
#ifndef GULANDSCAPEAPP_H_
#define GULANDSCAPEAPP_H_
#include <3dHomebrew.h>
class ClGuLandscapeApp : public Cl3dHomebrew {
public:
/*
* die Methoden zum erzeigen und aufräumen der Singleton Instanz der
* Applikationsklasse
*/
static ClGuLandscapeApp* getInstance();
static void releaseInstance();
/*
* Überladen der Initi-Routine
*/
bool init();
protected:
ClGuLandscapeApp();
virtual ~ClGuLandscapeApp();
/*
* Überladen der Render Methode
*/
void render();
static ClGuLandscapeApp* _instance;
};
#endif /* GULANDSCAPEAPP_H_ */
/*
* GuLandscapeApp.cpp
*/
extern “C”{
#include <pspgu.h>
}
#include “GuLandscapeApp.h”
ClGuLandscapeApp* ClGuLandscapeApp::_instance = 0;
ClGuLandscapeApp* ClGuLandscapeApp::getInstance(){
if (!_instance){
_instance = new ClGuLandscapeApp();
}
return _instance;
}
void ClGuLandscapeApp::releaseInstance(){
if (_instance){
delete(_instance);
_instance = 0;
}
}
bool ClGuLandscapeApp::init(){
//zuerst die Super-Methode
if (!Cl3dHomebrew::init()) return false;
//hier nun die eigene Initialisierung…
return true;
}
void ClGuLandscapeApp::render() {
}
ClGuLandscapeApp::ClGuLandscapeApp() {
}
ClGuLandscapeApp::~ClGuLandscapeApp() {
}
3. Die main.cpp
/*
* main.cpp
*/
extern “C”{
#include <pspkernel.h>
}
#include “GuLandscapeApp.h”
PSP_MODULE_INFO(”GU Landscape”, 0, 1, 1);
PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER);
PSP_HEAP_SIZE_KB(-1024);
int main(int argc, char* argv[])
{
ClGuLandscapeApp* myHomebrew = ClGuLandscapeApp::getInstance();
if (myHomebrew->init()){
myHomebrew->run();
myHomebrew->exit();
}
ClGuLandscapeApp::releaseInstance();
sceKernelExitGame();
return 0;
}
Das war nun das Rahmenwerk für unsere neue Homebrew. Nun schauen wir uns den Teil an indem wir aus einer in einer Höhenkarte abgelegten Landschaft eine 3 dimensionale virtuelle Welt erschaffen. Ähnlich wie beim Ansatz im Voxelspace gehen wir dabei davon aus dass die X und Y Werte des Bildes die Ausdehnung der Landschaft in Breite und Tiefe und der Farbwert die Höhe beschreiben. Um unsere neue Homebrew möglichst übersichlich zu halten, werden wir die Umwandlung des 3D Terrains aus einer Höhenkarte und die Darstellung desselben in einer eigenen Klasse kapseln. Dadurch bleibt die Applikationsklasse klein und erweiterbar für weitere Funktionen die wir hier nach und nach - quasi Modular - Hinzufügen können.
Das Terrain
Für die Abbildung des Terrains legen wir eine neue Klasse mit dem Namen ClGuTerrain an. Diese Klasse wird nicht von einer anderen Abgeleitet. Die Klasse soll folgendermaßen aufgebaut sein:
Im Konstruktor übergeben wir den Dateinamen (.png) der Höhenkarte die maximal 512×512 Bildpunkte groß sein darf. Hier werden die einzelnen Bildpunkte aus der Datei extrahiert und in einem Array mit ihren Eigenschaften abgelegt.
In einem zweiten Schritt werden aus diesen Punkten die von der GU verarbeitbaren Vertices erstellt und zu einer Vertexliste zusammengesetzt. Dabei werden wir nicht wie in unserem ersten Beispiel ein enzelnes Dreieck erstellen, sondern die Landschaft in viele viele Dreiecke aufteilen. Dabei wird ein (quadratischer) Bereich der virtuellen Landschaft der aus 4 benachbarten Punkten besteht durch genau 2 Dreiecke dargestellt - im Detail zeigen das die Bilder die etwas weiter unten zu sehen sind.
Die Definition der neuen Klasse:
/*
* GuTerrain.h Die Klasse kapselt die Beschreibung und Darstellung eines 3D Terrain
* basierend auf einer Hightmap PNG Datei
*/
#ifndef GUTERRAIN_H_
#define GUTERRAIN_H_
/*
* Struktur zur Definition eines Punktes im Terrain
*/
typedef struct TerrainPoint{
int x, y; // Koordinaten
int height; // Höhe des Punktes
int color; // Farbe des Punktes
}TerrainPoint;
/*
* Struktur zur Definition eines GU Vertexes
*/
typedef struct Vertex {
int color;
float x, y, z;
}Vertex;
class ClGuTerrain {
public:
/*
* Der Konstruktor erhält den Filenamen der Höhenkarte
* aud dessen Basis das 3D Terrain erzeugt
*/
ClGuTerrain(const char* heightMapFile);
virtual ~ClGuTerrain();
/*
* Render Methode für das Terrain
*/
void render();
protected:
int width, height; //Größe des Terrains
TerrainPoint* terrainPoints;
Vertex* mesh; //Das Vertex-Netz des Terrains
int vertexCount;
/*
* Generierung der GU Vertices für das Terrain
*/
void generateMesh();
/*
* Laderoutine die ein PNG lädt und die Bilddaten zurückliefert
* der benötigte speicher wird in der Methode reserviert
*/
bool loadPNG(const char* fileName, void** data);
/*
* Diverse Daten sollten der Einfachheit halber immer in größen einer Potenz von 2 abgelegt
* sein. Diese Methode liefert von einer Zahl die nächstgrößere potenz von 2 - also 2 hoch n
*/
int getPowerOf2(int value);
};
#endif /* GUTERRAIN_H_ */
Die Implementierung dieser Klasse zeige ich nun Schritt für Schritt. Die Methode zum laden des PNG Files bedarf nun keiner großen Eklärungen mehr, da wir das bereits bei der Voxellandscape benutzt haben. Der Konstruktor ruft zunächst diese Methode auf um das Bild zu laden und extrahiert die Bilddaten dann in die Liste der Terrain Punkte.
/*
* Die notwendigen Header Files
*/
extern “C”{
#include <stdio.h>
#include <png.h>
#include <malloc.h>
#include <psptypes.h>
#include <pspgum.h>
#include <pspgu.h>
}
#include “GuTerrain.h”
ClGuTerrain::ClGuTerrain(const char *heightMapFile){
//Laden des Höhenkartenbildes und die Punkte extrahieren
int* altitude;
terrainPoints = 0; if (loadPNG(heightMapFile, (void**)&altitude)){
// Nun erzeugen wir die Punktinformationen welche die Landschaft aufspannen
// diese werden später in ein GU 3d “Drahtgitternetz” (mesh) umgewandelt.
this->terrainPoints = (TerrainPoint*)malloc(width*height*sizeof(TerrainPoint));
for (int x=0;x<width;x++){
for (int y=0;y<height;y++){
terrainPoints[x+width*y].x = -width/2 + x; //das Terrain soll im virtuellen Raum zentral Platziert sein
terrainPoints[x+width*y].y = -height/2 + y;
terrainPoints[x+width*y].height = 255 & altitude[x+width*y];
terrainPoints[x+width*y].color = altitude[x+width*y];
}
}
// den Speicher des Bildes freigeben, da er nun nicht mehr gebraucht wird.
free(altitude);
// nun die Punkte in GU Vertices konvertieren
generateMesh();
}
}
Der nächste Schritt wird nun die Generierung der sogenannten Mesh werden, also des “Drahtgitter” Modells wie man es vielleicht aus 3D Modellierungsprogrammen kennt. Im Grunde besteht dieses Drahtgitter wie schon erwähnt aus Dreiecken. Nun wird jedes Dreieck durch 3 Punkte definiert. Das bedeutet, dass wir bei einer Höhenkarte von 4×4 Punkten bereits 3×3 Quadrate von jeweils 2 Dreiecken mit jeweils 3 Punkten, also 18×3 - 54 Punkte für die Darstellung brauchen. Es gibt hier eine Optimierungsmöglichkeit die uns sowohl Speicherplatz beim Erzeugen der Mesh spart als auch Performancevorteile bringt. Es kommt ein sogenannter Dreiecksstreifen (TriangleStripe) zum Einsatz. Dabei werden die Punkte so angeordnet, dass die ersten 3 Punkte ein vollständiges Dreieck definieren. Für jedes weitere Dreieck wird dann nur noch ein neuer Punkt gebraucht der dann mit den beiden zuletzt aufgeführten Punkten wieder ein Dreieck bildet.
Eine kleine Illustration:
Es ist schön zu sehen dass für die 3 Dreiecke T1 bis T3 nun anstelle von 9 Punkten nur 5 benötigt werden. Um einen fortlaufenden Dreiecksstreifen zu erhalten müssen wir beim Wechsel in eine neue Zeile “unsichtbare” Dreiecke hinzufügen. Das ist in dem folgenden Bild mit den blauen Linien veranschaulicht. Die Zahlen repräsentieren die Vertexpunkte.

Dieses Verfahren werden wir nun bei der Erstellung der Vertexliste (Mesh) anwenden, auch wenn dieses offensichtlich noch einiges Optimierungspotential hat (da Punkte mehrfach/doppelt erzeugt werden):
void ClGuTerrain::generateMesh() {
// Erzeugen der Vertices für ein TriangleStripe
// Dies wird von links nach rechts aufgebaut Zeile für Zeile
// Das bedeutet, dass wir jeweils am Ende ein ganz ganz flaches (unsichtbares) Dreieck
// platzieren um den Wechsel in eine neue Zeile ohne Fehler zu ermöglichen.
// zuerst den Speicherbedarf für die Vertexe bestimmen und allokieren
mesh=0;
this->vertexCount = 2*(width+2)*height;
mesh = (Vertex*)malloc(this->vertexCount*sizeof(Vertex));
int vertex = 0;
float scale = 0.5f;
for (int y=0;y< height-1; y++){
for (int x=0;x < width; x++){
// Die Vertices werden in der Zeile immer Paarweise erzeugt.
mesh[vertex].x = terrainPoints[x+y*width].x*scale;
mesh[vertex].z = terrainPoints[x+y*width].y*scale;
mesh[vertex].y = (terrainPoints[x+y*width].height / 10.0f)- 20.0f;
mesh[vertex].color = terrainPoints[x+y*width].color;
vertex++;
mesh[vertex].x = terrainPoints[x+(y+1)*width].x*scale;
mesh[vertex].z = terrainPoints[x+(y+1)*width].y*scale;
mesh[vertex].y = (terrainPoints[x+(y+1)*width].height / 10.0f)- 20.0f;
mesh[vertex].color = terrainPoints[x+(y+1)*width].color;
vertex++;
// sobald wir am rechten Rand angekommen sind müssen wir das “unsichtbare”
// Dreieck erzeugt. Dazu wird der letzte Vertex und der 2. der ersten Zeile nochmal angehängt
// das ganze soll aber nicht in der letzten Zeile passieren…
if (x == (width-1) && y < (height-2)){
mesh[vertex].x = mesh[vertex-1].x;
mesh[vertex].y = mesh[vertex-1].y;
mesh[vertex].z = mesh[vertex-1].z;
mesh[vertex].color = mesh[vertex-1].color;
vertex++;
mesh[vertex].x = terrainPoints[(y+1)*width].x*scale;
mesh[vertex].z = terrainPoints[(y+1)*width].y*scale;
mesh[vertex].y = (terrainPoints[(y+1)*width].height / 10.0f)- 20.0f;
mesh[vertex].color = terrainPoints[(y+1)*width].color;
vertex++;
}
}
}
// Damit wir wissen wie viele Punkte unser Mesh wirklich hat merken wir uns hier die Anzahl
this->vertexCount = vertex;
}
Der nun noch offene Teil befasst sich mit der Darstellung des Terrain. Dazu wird die render-Methode ausgeprägt. Die Darstellung der Dreiecke erfolgt mit der nun schon bekannten Funktion sceGumDrawArray. Dabei ist zu beachten, dass diese nur maximal 65535 Vertices verarbeiten kann. Darum müssen wir unser gesammtes Drahtgitter - sollte es aus mehr Punkten bestehen - in mehreren Häppchen übergeben. Der Einfachheithalber wählen wir hier Pakete von maximal 65000 Punkten.
void ClGuTerrain::render(){
// Rendern des Terrains. Dabei nur bis zu 65k Vertices darstellen
// andernfalls kann es zu Abstürzen kommen, da die GU nur bis zu 65535 Vertices
// auf einmal rendern kann
int renderVertices;
sceGumMatrixMode(GU_VIEW);
sceGumLoadIdentity();
sceGumMatrixMode(GU_MODEL);
sceGumLoadIdentity();
sceGuFrontFace(GU_CCW);
renderVertices = vertexCount;
Vertex* drawMesh = mesh;
while (renderVertices>65000){
sceGumDrawArray(GU_TRIANGLE_STRIP, GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D, 65000, 0, drawMesh);
renderVertices-=65000;
drawMesh+=65000;
}
if (renderVertices > 0){
sceGumDrawArray(GU_TRIANGLE_STRIP, GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D, renderVertices, 0, drawMesh);
}
}
Abschließend müssen noch der Destruktor und die Hilfsmethode mit “Leben” gefüllt werden. Da diese keiner weiteren Erklärung bedürfen (glaube ich) hier das Coding:
int ClGuTerrain::getPowerOf2(int value){
short c;
int temp;
temp = value;
c=0;
while ((temp >>= 1) > 0) c++;
temp = 1 << c+1;
if (temp >> 1 == value){
temp = value;
}
return temp;
}
ClGuTerrain::~ClGuTerrain() {
if (terrainPoints)
free(terrainPoints);
if (mesh)
free(mesh);
}
Die nächsten Schritte werden nun das Terrain in unserer Landscape Homebrew zur Verfügung stellen, es erzeugen und rendern. Dazu sind nur noch kleine Anpassungen notwendig. Zuerst muss das Makefile erweitert werden, so dass die Terrain-Klassen-Objektdatei erzeugt wird:
OBJS = main.o GuLandscapeApp.o GuTerrain.o
In der LandscapeApp-Klasse fügen wir in der Header Datei die neue Klassenheaderdatei hinzu und definieren ein Attribut das eine Referenz auf die Terrainklasse enthalten kann:
#include “GuTerrain.h”
class ClGuLandscapeApp : public Cl3dHomebrew {
……
protected:
ClGuTerrain* terrain;
Die Implementierung der Applikationsklasse wird nun noch in der Init, der Render und Exit Methode angepasst:
bool ClGuLandscapeApp::init(){
if (!Cl3dHomebrew::init()) return false;
terrain = new ClGuTerrain(”altitude.png”);
if (!terrain) return false;
}
void ClGuLandscapeApp::render() {
if (terrain) terrain->render();
}
void ClGuLandscapeApp::exit(){
if (terrain)
delete(terrain);
}
Das Ergebnis
Wenn alles glatt geht, dann sollte das Ergebnis auf dem PSP Screen so aus sehen:

Geschrieben in Komplexe 3D Welten | Drucken | Keine Kommentare »
Ein Dreieck - GU Test
28.1.2010 von AnMaBaGiMa.
Die GU Bibliothek zur einfachen Erstellung unser ersten 3D Homebrew ist fertig und nun wollen wir natürlich einen ersten kleinen Test durchführen um - zunächsteinmal nur ein Dreieck - im virtuellen Raum auf den realen PSP Bildschirm zu zaubern.
Die in der PspHbc Bibliothek enthaltenen Header Files entpackt ihr am besten in euer PSPSDK Verzeichnis C:\pspsdk\psp\sdk\include das Lib-File kommt dann in das Verzeichnis C:\pspsdk\psp\sdk\lib dadurch wird die Nutzung in eigenen Projekten stark vereinfacht.
Nun aber los. Wie schon bei unserer ersten “normalen” Homebrew beginnt alles mit dem erstellen einer neuen Klasse. Diese wird nun von der 3DHomebrew-Basisklasse abgeleitet. Unsere Beispiel Homebrew soll den Namen HbcGuSample tragen. Die Klasse heißt dann treffender Weise: ClHbcGuSampleApp
/*
* HbcGuSampleApp.h
* Beispiel Implementierung der GU Homebrew Basisklasse
*/
#ifndef HBCGUSAMPLEAPP_H_
#define HBCGUSAMPLEAPP_H_
#include <3dHomebrew.h>
class ClHbcGuSampleApp : public Cl3dHomebrew {
public:
static ClHbcGuSampleApp* getInstance();
static void releaseInstance();
// Redefiniren der Render Methode um sie mit eigender Logik zu füllen
void render();protected: ClHbcGuSampleApp();
virtual ~ClHbcGuSampleApp();
static ClHbcGuSampleApp* _instance;
};
#endif /* HBCGUSAMPLEAPP_H_ */
Nachdem die neue Klasse nun definiert ist, können wir diese implementieren. Dabei ist der erste Teil der Implementierung recht einfach - glaube ich - da wir nur eine singleton Instanz erzeugen und diese zurück liefern. Die GU initializeirung bleibt unverändert.
/*
* HbcGuSampleApp.cpp
*
*/
extern “C”{
#include<pspgu.h>
#include<pspgum.h>
}
#include “HbcGuSampleApp.h”
ClHbcGuSampleApp* ClHbcGuSampleApp::_instance = 0;
// liefert singleton Instaz
ClHbcGuSampleApp *ClHbcGuSampleApp::getInstance(){
if(!_instance){
_instance = new ClHbcGuSampleApp();
}
return _instance;
}
//baut die singleton Instanz wieder ab und gibt Resourcen frei
void ClHbcGuSampleApp::releaseInstance(){
if (_instance) {
delete(_instance);
_instance = 0;
}
}
Der für die Klasse generierte Constructor und Destructor bleiben unberührt bzw. leer. Der nun “spannende” Teil befindet sich in der render Methode. Hier wollen wir unser erstes Objekt im virtuellen Raum definieren.
Wichtig:Definitionen von Objekten, Bewegungen und Objektrelationen im 3 dimensionalen Raum nutzen Vektoren und Matrizen. Wer also keinerlei Grundkenntnisse auf diesem Gebiet mitbringt sollte sich diese aneignen befor er hier weiter macht. Wikipedia ist eine gute Addresse um sich diesbezüglich aufzuschlauen. Oftmals helfen einem bei der Veranschaulichung auch ein paar Bleistifte (als Vektoren) und ein Tisch aus.
Das Dreieck
Das Dreick wird durch 3 Punkte im virtuellen Raum beschrieben. Diese Punkte erhalten zusätzlich zu ihrer Position noch einen Farbwert, so dass die Fläche die von dem Dreieck umspannt wird entsprechend ausgefüllt wird. Dabei verwenden wir hier für jeden Punkt einen anderen Farbwert um einen netten Farbverlauf zu erzeugen.
void ClHbcGuSampleApp::render(){
// Definition einer Struktur die einen 3D-Punkt beschreibt
typedef struct Vertex {
int color;
float x, y, z;
}Vertex;
//Speicher für 3 Punkte reservieren
//dieser Speicher ist dabei nur temporär und nur innerhalb der aktuellen GU Liste gültig
Vertex* triangle = (Vertex*)sceGuGetMemory(sizeof(Vertex)*3);
//Daten für die Punkte
triangle[0].x = -4.0f;
triangle[0].y = -2.0f;
triangle[0].z = -10.0f;
triangle[0].color = 0xff0000ff;triangle[1].x = 0.0f;
triangle[1].y = 2.0f;
triangle[1].z = -10.0f;
triangle[1].color = 0xffff0000;triangle[2].x = 4.0f;
triangle[2].y = -2.0f;
triangle[2].z = -10.0f;
triangle[2].color = 0xff00ff00;
Als nächsten Schritt wollen wir dieses Dreieck auch zeichnen. Zuerst leeren wir den Bildschirm mit einer von schwarz verschiedenen Farbe (schwarz macht’s die Basisklasse). Dadurch kann mann schneller erkennen ob hier was “gezeichnet” wird oder nicht, da ggfl. schwarz auf schwarz gezeichnete Objekte nicht sichtbar wären…
Nachdem wir den Bildschirm nochmals geleert haben müssen wir die Projektion der GU festlegen. Dafür legen wir für den aktuellen View und für das Objektmodell eine Einheitsmatrix fest. Dies bedeutet, dass weder unsere Kamera, noch unser Objekt im Raum manipuliert werden. Sie werden so dargestellt wie wir sie im Raum platziert haben. Mit der Model-Matrix wären wir z.Bsp. in der Lage das Objekt im Raum zu bewegen oder zu rotieren, ohne die ursprüngliche Definition der Punkte zu verändern. Doch das beleuchten wir zu einem späteren Zeitpunkt.
Doch nun zum Code:
//den Bildschirm mit einer eigenen Farbe leeren, anders als in 3DHomebrew sceGuClearColor(0xff442222); sceGuClear(GU_COLOR_BUFFER_BIT);// Festlegen der View und Model Matrix als Einheitsmatrix sceGumMatrixMode(GU_VIEW); sceGumLoadIdentity(); sceGumMatrixMode(GU_MODEL); sceGumLoadIdentity();//damit die Farbwerte beim Füllen des Dreiecks verlaufen “smooth” (weiche/fortlaufende) Schattierung aktivieren sceGuShadeModel(GU_SMOOTH); //Das Dreieck kommt ganz ohne Texturen aus…also deaktivieren sceGuDisable(GU_TEXTURE_2D); // hier wird das Dreieck dann tatsächlich auf den Bildschirm gebracht, oder // besser in den Zeichenpuffer der beim nächsten wechsel zwischen Display- und Zeichenpuffer // sichtbar wird sceGumDrawArray(GU_TRIANGLES, GU_TRANSFORM_3D | GU_VERTEX_32BITF | GU_COLOR_8888, 3, 0, triangle); }
Ganz zum Schluss - bevor wir dieses Beispiel nun erstellen können müssen wir in unserem Makefile noch einige Bibliotheken aufnehmen, damit wir die GU Funktionen auch nutzen können. Im Detail sieht dieses dann folgendermaßen aus:
TARGET = hbcGuSample OBJS = main.o HbcGuSampleApp.o INCDIR = CFLAGS = -G0 -Wall -g CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti ASFLAGS = $(CFLAGS) BUILD_PRX = 1 LIBDIR = LDFLAGS = LIBS = -lstdc++ -lpsphbc -lpspgum -lpspgu -lpng -lz -lm EXTRA_TARGETS = EBOOT.PBP PSP_EBOOT_TITLE = HBC Gu Sample PSP_LARGE_MEM=1 PSP_FW_VERSION=500 PSPSDK=$(shell psp-config --pspsdk-path) include $(PSPSDK)/lib/build.mak
Wenn nun die Erstellung dieses Beispiels glatt gelaufen ist, dann sollte das ganze auf der PSP so aussehen:

Geschrieben in GU | Drucken | Keine Kommentare »
Homebrew - 3D
20.1.2010 von AnMaBaGiMa.
Die ersten Grundlagen für die GU habe ich zusammengestellt und die Initialisierung ist klar. Ähnlich wie am Anfang werde ich nun eine Homebrew-Basisklasse bauen die die Initialisierung der GU durchführt und von der die eigentliche Applikations-/Spielklasse abgeleitet werden kann.
Da die Grundlegende Homebrew Einrichtung (mit den Callbacks etc) bereits in einer Klasse zusammengefasst ist, werden wir diese einfach ableiten und eine neue Klasse Cl3dHomebrew erstellen:
#include “Homebrew.h”
class Cl3dHomebrew : public ClHomebrew
{
protected:
Cl3dHomebrew();
virtual ~Cl3dHomebrew();
};
Die Initialiserung wird ebenfalls in der init-Methode stattfinden die wir nun neu ausprägen.
class Cl3dHomwbrew : public ClHomebrew
{
public:
/*
* Die initialisierung der GU Homebrew.
*/
bool init();
/*
* Hier werden alle Aufräumarbeiten vor’m verlassen der
* Homebrew platziert.
*/
void exit();
Die Implementierung der Klasse und der Initialisierung greift dabei die Überlegungen auf dieser Seite auf. Darum werde ich den gesammten Quellcode nicht nochmals hier rein stellen, sondern nur Ausschnittsweise andeuten. Zusätzlich notwendige Includes sind die pspgu.h und die pspgum.h:
extern “C” {
#include <pspgu.h>
#include <pspgum.h>
}
#include “3dHomebrew.h”
static unsigned int __attribute__((aligned(16))) list[262144];
bool Cl3dHomebrew::init(){
//zuerst die Superklassen Methode rufen
if (!ClHomebrew::init()) return false;
//nun die eigene Initialisierung der GU
sceGuInit();
…….
//abschließen der GU Liste führt zur sofortigen Abarbeitung
//in diesem Falle, da wir die Liste mit GU_DIRECT gestartet haben
sceGuFinish();
sceGuSync(0,0);
//Aktiviere die Anzeige der GU auf dem realen Bildschirm
sceGuDisplay(GU_TRUE);
return true;
}
void Cl3dHomebrew::exit(){
//GU beenden
sceGuTerm();
//Superklassen Methode am Schluss
ClHomebrew::exit();
}
Nun ist der Punkt erreicht, an dem wir unsere erste GU Homebrew beginnen können. Um den Einstieg nun möglichst einfach zu gestalten, habe ich die bis hier beschriebenen Basisklassen und Vorbereitungen mal in einer Bibliothek zusammen gefasst. Zusammen mit den Header-Files könnt Ihr diese in Euer PSPSDK einbinden und schon loslegen, ohne die ganzen Quellcodes hier abschreiben zu müssen.
Die Bibliothek PspHbc bietet dabei die Basis-Homebrewklasse, eine 2D Grafik-Basisklasse, so wie sie bei der Voxellandschaft zum Einsatz kam, und die nun neue 3D-GU Basisklasse.
Wie Ihr diese Bibliothek für Eure eigenen Projekte einsetzt um eigene Homebrews zu schreiben zeige ich am Beispiel einer kleinen 3D Homebrew in Kürze…
Die Dokumentation der Header-Files in dem Paket sind auf English - aber da hier alles auf deutsch erklärt wurde, sollte das kein Problem darstellen.
Geschrieben in GU | Drucken | Keine Kommentare »
GU - und nu ?
12.1.2010 von AnMaBaGiMa.
Die GU - das ist die “Graphic Unit” der PSP. Ich habe keine wirkliche Auflösung dieses Kürzels gefunden, aber im Grunde kann man sich die GU wie einen Teil der Grafikkarte eines PC’s vorstellen. Dieser Teil ist in der Lage 3D Grafiken zu berechnen und darzustellen.
Der Vorteil der GU ist, dass diese eine sehr leistungsstarke Einheit ist, die quasi parallel zur CPU arbeitet und einen optimierten Datenbus zur Grafikausgabe und zu bestimmten Hauptspeicherbereichen hat. Wer sich schon mit verschieden 3D Spielen auf dem PC auseinander gesetzt hat weiß vielleicht, dass Hardwarebeschleunigung in einer Grafikkarte bei der Berechnung der Grafik gegenüber der reinen CPU enorme Vorteile und Performancegewinn bietet und uns dadurch auch auf der PSP vermutlich einige Türen öffnen wird.
Der “Haken” an der ganzen Sache ? Nun - wir müssen uns wieder durch ein wenig staubige Theorie quälen um zu verstehen, was wir machen müssen um mit der GU zu arbeiten und erste Resultate zu erzielen….
Damit dieser Teil der ersten Betrachtung der GU und weitere Erkenntnisse nicht in der Blog-Geschichte untergehen, platziere ich das ganze auf einer separaten Seite…
Geschrieben in GU | Drucken | 1 Kommentar »
Neustart…
11.1.2010 von AnMaBaGiMa.
Nun bin ich schon so weit gekommen. Die Ergebnisse sind auch schon ganz passabel, aber können doch mit dem aktuellen Anspruch an 3D Spiele nicht mithalten. Die Berechnung der Voxellandschaft ist sehr CPU lastig und kann die vielen Vorteile der in der PSP verbauten Grafikhardware nicht nutzen.
Ernüchternd macht sich etwas Enttäuschung breit - habe ich mir doch so viel vorgenommen…
Die Recherche in einigen Foren bringt mich dann zu dem Entschluss die Voxel in ihrem Space liegen zu lassen und sich mal ein wenig mit den 3D Grafikmöglichkeiten der PSP zu beschäftigen, denn diese Hardware ist ja - wie wir von vielen Spielen wissen - in der Lage sehr schöne Welten zum Vorschein zu bringen. Da wollen wir doch mal schauen, ob da auch was für unser Projekt dabei ist.
Es macht sich ein wenig vorsichtiger Enthusiasmus breit
Geschrieben in Allgemein | Drucken | Keine Kommentare »
Bildschirm aufräumen
7.1.2010 von AnMaBaGiMa.
Bisher haben wir immer wieder unsere Zeichenfläche mit der Landschaft voll”gemalt”. Das sah schonmal ganz gut aus, jedoch fällt uns auf, dass in dem Falle das am Horizont hohe Berge zu sehen waren und danach wieder flache die hohen einfach “stehen” bleiben. Das liegt daran, dass wir den Bildschirm nicht aufräumen - also leeren - bevor wir neu zeichnen.
Wenn wir dies nun einfach so einbauen, werden wir schnell feststellen, dass dies zum Teil zum Flackern kommt. Das sieht sehr unschön aus. Um das zu verhindern gibt es einen recht einfachen Trick:
Backbuffer
Oder wie auch immer man es nennen wil. Man verwendet quasi zunächst eine unsichtbare Zeichenfläche.Diese wird geleert und das neue Bild dargestellt/berechnet. Anschließend wird der fertige Bildschirm sichtbar gemacht. Nun wird wieder im “verborgenen” gezeichnet. Das führt dazu dass wir nun 2 Datenbereiche haben in die wir unser Spiel zeichnen und sagen der PSP am Ende nur noch wo der fertige Bildschirm liegt.
Zuerst definieren wir einen “flip” parameter vom Typ bool der immer anzeigt welcher Puffer denn nun der ist, in den wir zeichnen. Dann brauchen wir noch eine Methode die uns immer die Adresse auf den aktuellen Zeichenpuffer liefert, diese Adresse werden wir dann auch an die Render-Methode übergeben. Zum Schluss muss nur noch der Mainthread angepasst werden, so dass die Darstellung zwischen den 2 Puffern gewechselt wird.
Doch nun erstmal zu den neuen Definitionen in der Homebrew Basisklasse:
class ClHomebrew {
….
protected:
/*
* in welchen Puffer soll gezeichnet werden ?
*/
bool pg_drawframe;
/*
* ermitteln des adresse des zeichen Puffers
*/
unsigned char* pgGetVramAddr();
Die Implementierung für diese Methode sieht dann so aus:
unsigned char *ClHomebrew::pgGetVramAddr(){
if (pg_drawframe)
return VRAM_TOP + FRAMESIZE;
else
return VRAM_TOP;
}
Zusätzlich müssen wir den neuen Zeichenpuffer auch beim setzen der Bildpunkte berücksichtigen. Darum sieht die Methode setPixel nun wie folgt aus:
void ClHomebrew::setPixel(int x, int y, int color){
//Zeiger auf das zu zeichnende Pixel berechnen
unsigned char* screen = pgGetVramAddr()+x*PIXEL_SIZE+y*LINE_SIZE*PIXEL_SIZE;
//Farbe setzen
u32* s;
s = (u32*)screen;
*s = color;
}
Im nächsten Schritt überarbeiten wir nun den Mainthread unserer Voxelklasse. Dort holen wir uns erstmal die Adresse des Zeichenpuffers und setzen dort alle Daten auf 0 (also schwarz). Danach wird die Voxellandschaft gezeichnet und dann wird der Puffer dargestellt. Am Schluss folgt das setzen des Flags, dass wir nun in den 2. Puffer zeichnen wollen. Ggfl. müssen wir folgende Includes in unsere Voxelklasse mit aufnehmen:<stdlib.h> und <pspdisplay.h>
void ClPspVoxel::mainthread(){
unsigned char* drawBuffer;
drawBuffer = pgGetVramAddr();
//leer räumen des Bildschirms
memset(drawBuffer, 0, FRAMESIZE);
//Zeichnen
render();
//nun teilen wir der PSP mit, welches unser Zeichenpuffer ist der nun gezeigt werden soll
sceDisplaySetFrameBuf(drawBuffer, LINE_SIZE, PSP_DISPLAY_PIXEL_FORMAT_8888, PSP_DISPLAY_SETBUF_IMMEDIATE );
//Flip des schalters welcher Puffer gefüllt wird
pg_drawframe = pg_drawframe?false:true;
//nach jedem Zeichnungsvorgang darauf warten dass die PSP das Bild
//komplett ausgegeben hat
sceDisplayWaitVblankStart();
}
Geschrieben in Einfache 3D Welten | Drucken | Keine Kommentare »
Schwarz/Weiß war gestern
5.1.2010 von AnMaBaGiMa.
Heute ist Farbe angesagt. Darum werden wir nun ein paar Farbtöpfe auf unserer Landschaft “vergießen”. Zusätzlich wollen wir die Bewegungen über der Landschaft durch den PSP Analogstick beinflussen, verhindern, dass wir uns in der Landschaft durchgraben müssen und dann noch ein paar kleine Erweiterungen einbauen, die den Gesammteindruck verbessern…
Doch immer eins nach dem anderen.
Erstmal die Farbe
Für die Farbe auf der Landschaft brauchen wir eine Textur die die Landschaft farblich darstellt, da die Höhenkarte ja nur Grau-Werte - eben für die Höhe enthält. Ich verwende für die Höhenkarte eine passende Textur die von den Dimensionen die selben Maße hat (512×512) und wie folgt aussieht:

Die Textur müssen wir natürlich auch laden um diese benutzen zu können. Dazu werden wir die Methode, welche uns eine PNG Datei bereits in den Höhenkartenspeicher lädt erweitern und den Speicherplatz dynamisch zuweisen anstelle ein festes array zu definieren.
Die erweiterungen in der Klassendefinition:
class ClPspVoxel : public Cl2dHomebrew {
….
protected:
….
//neuer Parameter zur Aufnahme der gelesenen Daten
bool loadMap(const char* filename, unsigned int* data);
….
//Höhenkarte nun als Zeiger für dynamische Speicher Reservierung
//unsigned int mapData[512][512]; //Höhenwerte der Karte
unsigned int* mapData;
unsigned int* texture;
Die Anpassungen in der Implentierung der Lademethode sehen dann wie folgt aus:
/*
* Laden der Bilddaten aus PNG-File in Datenpuffer
* dazu wird die Vorlage aus der SDK graphics.c genutzt
* der Einfachheit halber gehen wir von Daten 512×512 punkten aus
*/
bool ClPspVoxel::loadMap(const char* filename, unsigned int* data) {
….
//Zeile nun Punkt für Punkt durchgehen und die Farbe in den Puffer übertragen
for (x = 0; x < width; x++) {
u32 color = line[x];
//neuer Zugriff auf den Datenpuffer in linearer Form ->zeile*maxBreite + spalte
//mapData[y][x] = color;
data[y*512 + x] = color;
}
Für die Anpassung der Initialisierungsmethode bei der wir nun den Speicher für die Höhenkarte und die Texture reservieren müssen wir noch das zusätzliche Include malloc.h einbinden.Der Code hierfür sieht dann so aus:
bool ClPspVoxel::init(){
…..
//Speicher für die Höhenkarte
mapData = (unsigned int*)malloc(sizeof(unsigned int)*512*512);
//Höhenkarte laden
loadMap(”altitude.png”, mapData);
//speicher für die Textur
texture = (unsigned int*)malloc(sizeof(unsigned int)*512*512);
loadMap(”landscape.png”, texture);
Um den dynamisch reservierten Speicher wieder freizugeben, wenn er nicht mehr gebraucht wird - das ist ganz wichtig! - platzieren wir folgende zeilen in den Klassendestruktor:
free(mapData); free(texture);
Um die Textur nun bei der Darstellung der Landschaft zu berücksichtigen müssen wir nur geringe Anpassungen in der render Methode vornehmen um auch die neue Art auf die Daten zuzugreifen zu berücksichtigen:
void ClPspVoxel::render() {
….
//Höhe des Voxel
altitude = mapData[voxelY*512 + voxelX]; //mapData[voxelY][voxelX];
//die Farbe dieses Voxel aus der textur lesen
unsigned int color = texture[voxelY*512 + voxelX];
……
// Punkt im Bildschirm
//setPixel(pixelX, pixelY, altitude);
setPixel(pixelX, pixelY, color);
Das Ergebnis sieht schon ganz ordentlich aus:

Nachdem wir die Landschaft mit ewas Farbe aufgepeppt haben, wollen wir die Bewegungen auf der PSP mit dem Analogstick kontrollieren. Um das zu erreichen brauchen wir in der Implementierungsklasse ersteinmal ein neues Include dass uns die Notwendigen Funktionen bereitstellt: pspctrl.h.
Nun können wir an das Ende der Bildbereichnung die Abfrage des PSP Pads legen:
void ClPspVoxel::render() {
….
//nach dem rendern fragen wir das pad ab.
//wurde der Analog-Stick bewegt ?
SceCtrlData pad;
sceCtrlPeekBufferPositive(&pad, 1);
//die ausrichtung des analog sticks ist
//0 ganz links, 255 ganz rechts
//0 ganz oben und 255 ganz unten.
//für eine Bewegung brauchen wir -x bis +x für links rechts
//und -y bis + y
short moveX, moveY;
moveX = pad.Lx - 127;
moveY = 127 - pad.Ly;
nbsp;
//da wir uns nicht im vollen “Auschlag” des Stix bewegen wollen, sondern
//maximal um 4 Pixel pro durchlauf verringern wir die Werte entsprechend
moveX >>= 5;
moveY >>= 5;
posY+=moveY;
posX+=moveX;
}
Schweben
Nun wollen wir noch über unsere Landschaft “schweben”. Dazu müssen wir die render Methode nur geringfügig anpassen. Bis jetzt ist die Höhe des Betrachters immer fest vorgegeben gewesen. Nun wird diese aber aus der Höhe des Punktes berechnet über dem sich der Betrachter gerade befindet.Zusätzlich bauen wir nach dem zeichnen der Bildpunkte in einer Spalte noch eine Sicherheitsabfrage ein, die verhindert, dass der Strahl weiter verfolgt wird, wenn der Bildschirm schon bis oben hin voll-”gemalt” wurde…
void ClPspVoxel::render() {
…..
rayZ = 81 + (mapData[(posY&511)*512 + (posX&511)] & 255); //384;
….
}while (rayZ < currentAltitude);
}
// wenn der Bildschirm am oberen Rand erreicht ist, brauchen
//wir den strahl nicht weiter verfolgen…
if (pixelY <= 1) break;
Viel Spaß bei den ersten “Flugversuchen”
Geschrieben in Einfache 3D Welten | Drucken | 1 Kommentar »
Ein kleiner Schritt
15.12.2009 von AnMaBaGiMa.
Wie eine einfache Voxellandschaft berechnet wird, habe ich nun kurz in der entsprechenden Seite ausgeführt. Doch wie bekommen wir das jetzt in die PSP ?
Bevor wieder etwas Code und Theorie sehr staubig daherkommen, sollen die folgenden Bilder den Appetit etwas anregen…

Die Implementierung
Nun aber los. Wir wollen das ganze in unsere Homebrew einbauen. Als Startpunkt nutzen wir unsere Feuer-Homebrew-Klasse. Die Dateien dieser Klasse kopieren wir im ersten Schritt und legen Sie unter dem Namen PspVoxel ab. Über die Refactoring-funktion in Eclipse benennen wir nun die Klasse von ClPspFire in ClPspVoxel um. Nun können alle Feuer spezifischen Definitionen gelöscht werden. Für unsere Landschaft brauchen wir folgende Attribute:
protected:
/*
* Datendeklarationen
*/
struct rayDelta {
long rayDeltaX, rayDeltaY;
} ;
rayDelta preDelta[480];
short posX; // Position X des Betrachters
short posY; // Position Y des Betrachters
unsigned int mapData[512][512]; //Höhenwerte der Karte
long mapSize; //breite der Karte
Im Bereich der Methoden werden wir nun eine neue Definieren. Diese soll es ermöglichen eine PNG Grafikdatei zu laden und als Höhenkarte zu verwenden. Der Einfachheithalber gehen wir zunächst einfach mal davon aus, dass dieses Bild immer 512×512 Bildpunkte hat.
protected: /* * laden eines PNG files als Höhenkarte */ bool loadMap(const char* filename);
Nun müssen nur noch die einzelnen Methoden implementiert werden. Zunächst die Initialisierung. Hier finden ein paar Vorberechnungen statt, dann initialisieren wir den Standpunkt des Betrachters auf der Höhenkarte und laden selbige in unseren Kartenpuffer. Der Vollständigkeithalber sind auch die Header-Dateien aufgeführt die wir benötigen als auch der Initialisierungsteil den wir von der Seite Voxellandscape kennen.
/*
* PspVoxel.cpp
*/
#include “PspVoxel.h”
extern “C”{
#include <stdlib.h>
#include <pspdisplay.h>
#include <math.h>
#include <png.h>
}
bool ClPspVoxel::init(){
short x;
if (!ClHomebrew::init()) return false;
//in der Initialisierung einmal Hallo Voxel schreiben….
pspDebugScreenPrintf(”Hallo Voxel!”);
long rayDeltaX, rayDeltaY;
float hx;
// Initiale Startposition in der Mitte der Karte
mapSize = 512;
posX = mapSize >> 1;
posY = mapSize >> 1;
for (x=0;x;lt;480;x++){
hx = x - 240;
rayDeltaX = ((hx / sqrtf(hx*hx + 400*400))*1024);
rayDeltaY = (400 / sqrtf(hx*hx + 400*400)*1024);
preDelta[x].rayDeltaX = rayDeltaX;
preDelta[x].rayDeltaY = rayDeltaY;
}
// Höhenkarte von Datei laden
loadMap(”altitude.png”);
return true;
}
Als nächstes bauen wir die Methode zum Rendern der Landschaft. Diese kann 1:1 aus der Seite Voxellandscape entnommen werden und soll darum hier nicht nochmal aufgeführt werden. Im nächsten Schritt kommen die Anpassungen an dem Haupt-Thread dran:
void ClPspVoxel::mainthread(){
//zeichne Voxelspace
render();
//ein wenig Bewegung. Nach jedem “rendern” einen Schritt nach vorn laufen..
posY++;
//nach jedem Zeichnungsvorgang darauf warten dass die PSP das Bild
//komplett ausgegeben hat
sceDisplayWaitVblankStart();
}
Nun kommt der für diese Homebrew spannenste Teil - so glaube ich. Das laden einer PNG Grafik als Höhenkarte. Das PSP-SDK liefert hierzu eine gute Vorlage. In der Datei graphics.c ist die Implementierung zu finden. Wir werden diese jedoch nicht direkt aus dieser Datei nutzen sondern den Teil den wir brauchen mit kleineren Anpassungen in unsere Klasse implementieren. Warum machen wir das ? Die Graphics.c bietet weitaus mehr Funtkionen als wir brauchen und initialisiert für unseren aktuellen Bedarf etwas zu viel. Aber mit ein wenig copy+paste sieht die Laderoutine dann so aus:
/*
* Laden einer Höhenkarte aus PNG-File
* dazu wird die Vorlage aus der SDK graphics.c genutzt
*/
bool ClPspVoxel::loadMap(const char* filename) {
png_structp png_ptr;
png_infop info_ptr;
unsigned int sig_read = 0;
png_uint_32 width, height;
int bit_depth, color_type, interlace_type, x, y;
u32* line;
FILE *fp;
//öffnen der Datei
if ((fp = fopen(filename, “rb”)) == NULL) return false;
//lesen des PNG FileHeader
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL) {
fclose(fp);
return false;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
fclose(fp);
png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
return false;
}
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, sig_read);
png_read_info(png_ptr, info_ptr);
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, int_p_NULL, int_p_NULL);
//zunächst sollte das Bild nicht größer als 512×512 Punkte sein…
if (width > 512 || height > 512) {
fclose(fp);
png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
return false;
}
png_set_strip_16(png_ptr);
png_set_packing(png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8 ) png_set_gray_1_2_4_to_8(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr);
png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
//speicherplatz zum lesen einer Zeile vom PNG file
line = (u32*) malloc(width * 4);
if (!line) {
fclose(fp);
png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
return false;
}
//PNG Zeile für Zeile lesen
for (y = 0; y < height; y++) {
png_read_row(png_ptr, (u8*) line, png_bytep_NULL);
//Zeile nun Punkt für Punkt durchgehen und die Farbe in den Puffer übertragen
for (x = 0; x < width; x++) {
u32 color = line[x];
mapData[y][x] = color;
}
}
free(line);
png_read_end(png_ptr, info_ptr);
png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
fclose(fp);
return true;
}
Nun haben wir im Grunde alles beisammen. Der letzte Schritt bevor wir diese Homebrew erstellen und testen können ist eine kleine Anpassung in der main.cpp, in der wir die Feuer-Klasse gegen die Voxelklasse austauschen, und in unserem Makefile. Durch die Nutzung der PNG und der Mathematischen Funtkionen brauchen wir 3 neue Bibliotheken die wir mit einbeinden müssen. Dies ist die png und z Bibliothek für die PNG Dateien und die math Bibliothek für die Funktionen wie sqrtf.
Das Makefile sieht damit wie folgt aus:
TARGET = pspdemo OBJS = main.o PspVoxel.o Homebrew.o ../common/callbacks.o INCDIR = CFLAGS = -G0 -Wall -g CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti ASFLAGS = $(CFLAGS) BUILD_PRX = 1 LIBDIR = LDFLAGS = LIBS= -lstdc++ -lpng -lz -lm EXTRA_TARGETS = EBOOT.PBP PSP_EBOOT_TITLE = PSP Demo PSP_LARGE_MEM=1 PSP_FW_VERSION=500 PSPSDK=$(shell psp-config –pspsdk-path) include $(PSPSDK)/lib/build.mak
Zwei weitere ggfl. Hilfreiche Hinweise:
Da wir nun durch das Laden einer Grafikdatei einen sehr großen Speicherbereich belegen werden, müssen wir das der PSP auch mitteilen. Dafür müssen wir zum einen im Makefile den Eintrag PSP_LARGE_MEM=1 platzieren - was in dem Auszug oben schon zu sehen ist. Zuätzlich muss in der main.cpp die Speicher Heap-Größe die genutzt werden soll auf einen “ungültigen” Wert gesetzt werden, um einen möglichst großen Block zugewiesen zu bekommen. Die Code Zeile sieht dann am Beginn der main.cpp so aus:
PSP_MODULE_INFO(”PSP Voxel”, 0, 1, 1); PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER); PSP_HEAP_SIZE_KB(-1024);// maximale Heap Größe nutzen
Und nun viel Erfolg beim testen und den ersten “Flugversuchen” über die Landschaft. Das ist ein kleiner Schritt für die PSP aber ein durchaus gewaltiger für den Entwickler ![]()
Geschrieben in Einfache 3D Welten | Drucken | Keine Kommentare »