[3d/c++] software-rendered lighting probleempje

Pagina: 1
Acties:

  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
Hmm derde 3D topic al van * MisterData :) Ik ben bezig aan een port van de 3D engine van Peter Walser (www.idx3d.ch) en ben al een heel eind (dat wil zeggen: ik kan op dit moment met redelijke snelheid een getexture'de donut op m'n scherm krijgen, compleet met z-buffering en zelfs een op perlin-noise gebaseerde texture factory). Nu wil ik ook de lighting-features uit het origineel gaan implementeren.

Helaas stuit ik daar op een probleem. Ik ben namelijk zo slim geweest mijn CTriangle class als volgt te definieren:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CTriangle {
    friend class CRasterizer;
    friend class CRenderer;
    friend class CVertex;
    public:
        // constructors en andere zut stond hier

        CVertex _a;
        CVertex _b;
        CVertex _c;
    private:

                    // nog wat dingetjes
};


Het gaat me dus vooral om die CVertex'en. Nou en? zul je denken. Nou, het probleem is, als ik lighting wil doen moet ik in plaats van een aparte CVertex per triangle een CVertex* hebben, zodat twee triangles die naast elkaar liggen dezelfde CVertex hebben. Op die manier kan ik dan weer de object normal berekenen en op basis daarvan kan ik gaan belichten.

Goed, waarom verander ik niet gewoon die hele class (dus CVertex* _a inplaats van CVertex _a)? Omdat ik benieuwd ben of wat ik probeer te bereiken ook mogelijk is zonder dat ik een hele hoop regels ga omgooien.

Heeft er iemand ideeen over hoe ik lighting kan doen op basis van de class zoals 'ie nu is of denken jullie dat het omgooien van regels code slimmer is?

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Ik zou het omgooien...het is de klasieke manier om met Vertex* te werken. Je wil toch ook als je 1 vertex verplaatst dat alle triangles die die vertex delen ook veranderen?

  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
Zoijar schreef op 26 February 2003 @ 21:19:
Ik zou het omgooien...het is de klasieke manier om met Vertex* te werken. Je wil toch ook als je 1 vertex verplaatst dat alle triangles die die vertex delen ook veranderen?
Hmmjah..... en dan in de CObject class een lijstje met pointers naar vertices bijhouden om bij het destroyen van het object te weten welke vertices er gedelete moeten worden :?

leuk voor in de vakantie dit *D

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 22-08 01:56
In een 'realistische' mesh worden bijna alle vertices gedeeld, dus het komt gegarandeerd voor dat je dezelfde vertices in verschillende polygonen opneemt. Vanuit dat standpunt is het aantrekkelijk om een object per vertex te alloceren en pointers daarnaar in je polygoon op te nemen, ware het niet dat dat weer een rompslomp aan geheugen management met zich meebrengt en het qua efficientie (minder indirectie en gefragmenteerd geheugen) misschien niet eens zo'n slecht idee is om vertices direct als onderdeel van het object op te nemen.

De overtuigende reden waarom je toch wél met pointers wilt werken, als je 't mij vraagt, is dat je waarschijnlijk een heleboel eigenschappen (kleur, normaalvector, etcetera) aan je vertices wilt koppelen. De vertices worden daarmee te groot om praktisch in het object te stoppen en bovendien is het te erg handig om een eigenschap in 1 object te wijzigen, in plaats van alle polygonen met de betreffende vertex aan te moeten passen.

Ik zou dus zeggen: maak er mooie pointers (of references) van. Dat is veel flexibeler, omdat je vertices dan ook als unieke punten kunt beschouwen, en je veel makkelijker een mesh kunt manipuleren. Als je al je vertices in een array alloceert, valt de overhead door allocaties ook erg mee.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 22-08 01:56
MisterData schreef op 26 februari 2003 @ 21:22:
Hmmjah..... en dan in de CObject class een lijstje met pointers naar vertices bijhouden om bij het destroyen van het object te weten welke vertices er gedelete moeten worden :?
Afhankelijk van hoe dynamisch je model is (kunnen vertices dynamisch onstaan en verdwijnen?) kun je bijvoorbeeld voor een reference counted mechanisme kiezen, of je kunt de geheugenallocatie koppelen aan een beperkt component van je wereld. Als je bijvoorbeeld een model inlaadt, kan je voor dat model een hele array met alle vertices voor dat model alloceren, die je weer vrijgeeft wanneer je model opgeruimd wordt. Uiteraard kun je je vertices dan niet buiten dat model gebruiken, maar ik denk dat je dat toch nooit wilt. Geheugenmanagement is dan in ieder geval erg eenvoudig.

[ Voor 4% gewijzigd door Soultaker op 26-02-2003 21:27 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Ja idd, een actor heeft gewoon een vertex list (en eventueel nog andere stuctureren op dezelfde data zoals een edge list). geheugen management is geen probleem, twee mesh objects delen toch nooit vertices. Redundancy is the root of all evil :P Je wilt absoluut geen twee "dezelfde" vertices in apparte objecten.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-08 13:19

.oisyn

Moderator Devschuur®

Demotivational Speaker

De "traditionele" manier is om gewoon te werken met vertex en index buffers

De vertex buffer is gewoon een array van vertices van een mesh, en in de index buffer staan de indices van elke triangle. Je zit dan niet meer met een Triangle klasse, maar alles staat gewoon rechtstreeks in de Mesh. Vanuit OO oogpunt misschien minder netjes, maar het werkt wel beter ivm performance en memory management

zoiets dus zeg maar:
C++:
1
2
3
4
5
6
7
8
9
10
11
class Mesh
{
private:
    int numVertices;
    int numTriangles;
    Vertex * vertices;
    int (*faces)[3];

public:
    // ...
};


en met de STL kan het natuurlijk ook nog, dat scheelt je weer potentiele memory leaks enzo :)

[ Voor 9% gewijzigd door .oisyn op 27-02-2003 10:12 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 21-08 17:14
De kleinste wijziging is waarschijnlijk CVertex _abc[3], en CVertex& _a( ) { return _abc[0]; }

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
En het kan dus ook met references? Wat is het verschil eigenlijk precies (behalve dat een reference nooit NULL kan zijn)?

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-08 13:19

.oisyn

Moderator Devschuur®

Demotivational Speaker

een reference lijkt me geen goed plan, aangezien je die niet kunt her-assignen. Je kan in principe alleen een 'adres' toekennen bij construction

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
.oisyn schreef op 27 February 2003 @ 16:46:
een reference lijkt me geen goed plan, aangezien je die niet kunt her-assignen. Je kan in principe alleen een 'adres' toekennen bij construction
Aha :) Nee dan is het geen goede optie.....

  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
Nog een probleempje helaas :| Ik heb het geheel nu draaiend met een CVertex* per CTriangle, en ik heb maar gelijk de Phongshading geport. Nou krijg ik als ik een demo-progsel start het voglende plaatje:

Afbeeldingslocatie: http://dev.trag.nl/meuk/driedee.JPG

Kan me bijna niet voorstellen dat dit klopt eigenlijk :|

Dit is dus met een lichtbron met als direction (0.0f,0.0f, 5.0f).... nou denk ik niet dat jullie 1-2-3 weten waar dit aan ligt, maar mocht een van jullie dit probleem ook es zijn tegengekomen, laat het me dan weten :)

edit: nog even wat info: dit is dus gemaakt met een lightmap die vantevoren wordt opgebouwd. Aangezien ik die code 3x heb gechecked met het origineel ernaast kan het daar niet in zitten. Als ik uitzoom dan ziet het er wel weer goed uit:

Afbeeldingslocatie: http://dev.trag.nl/meuk/driedee2.JPG

zie ook de framerate die toch wel een stukje hoger ligt nu.... moet de rasterizer nog wat performanter maken

[ Voor 36% gewijzigd door MisterData op 01-03-2003 18:45 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-08 13:19

.oisyn

Moderator Devschuur®

Demotivational Speaker

Mja, als je er verder niets aan info bijgeeft weten wij natuurlijk ook niet of het zo goed is of niet.

Maar probeer de richting van de lichtbron eens te normalizeren?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
Wordt al gedaan, kijk maar (een stukje uit de CLight class):
C++:
1
2
3
4
void SetDirection(CVector& direction) {
    direction.Clone(v);
    v.Normalize();
}


Waarbij v een CVector in de CLight class is.

edit:
Extra informatie:


Voor het begin van de eerste render wordt de scene ge-rebuild. Dat betekent dat ieder object wordt gerebuild. Deze called eerst op iedere vertex ResetNeighbours, en dan looped ie door alle triangles heen om vervolgens op iedere vertex van die triangle AddNeighbour te doen met een pointer naar de triangle.

Als dat is gebeurd dan wordt RegenerateNormal eerst op alle triangles aangeroepen:

C++:
1
2
3
4
5
// _a, _b en _c zijn CVertex*
// n is ook een member van CTriangle
void CTriangle::RegenerateNormal() {
    n=CVector::GetNormal(_a->pos,_b->pos,_c->pos);
}


Vervolgens wordt er nog es een keertje RegenerateNormal op iedere vertex aangeroepen:

C++:
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
// _neighbours is een linked-list met alle CTriangle* naar neighbours
// n is een member van CVertex
void CVertex::RegenerateNormal() {  
    _neighbours.Restart();
    float nz = 0;
    float nx = 0;
    float ny = 0;

    CTriangle* tri;
    CVector wn;

    while(_neighbours.HasNext()) {
        tri = _neighbours.NextStack();

        wn = tri->GetWeightedNormal();
        nx += wn._x;
        ny += wn._y;
        nz += wn._z;
    }

    n = CVector(nx,ny,nz);
    n.Normalize();
}

// deze wordt gecalled vanuit CVertex::RegenerateNormal 
CVector CTriangle::GetWeightedNormal() {
    return CVector::VectorProduct(_a->pos,_b->pos,_c->pos); 
}



Als alle objecten zijn gerebuilt dan wordt er een lightmap opgebouwd. Hiervan post ik de twee belangrijkste methoden:

C++:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
void CLightmap::BuildSphereMap() {
    float fnx, fny, fnz;
    int pos;

    for (int ny=-128;ny<128;ny++) {
        fny=(float)ny/128;
        for (int nx=-128;nx<128;nx++) {
            pos=nx+128+((ny+128)<<8);
            fnx=(float)nx/128;
            fnz=(float)(1-sqrt(fnx*fnx+fny*fny));
            sphere[pos] = (fnz>0)?fnz:0;
        }
    }
}

void CLightmap::RebuildLightmap() {
    CVector l;
    float fnx, fny, phongfact, sheen, spread;
    CColor diffuse, specular;
    int cos, dr, dg, db, sr, sg, sb, pos;

    for(int ny=-128;ny<128;ny++) {
        fny = (float)ny/128;
        for(int nx=-128;nx<128;nx++) {
            pos = nx+128+((ny+128)<<8);
            fnx = (float)nx/128;
            sr = 0;
            sg = 0;
            sb = 0;
            dr = 0;
            dg = 0;
            db = 0;
        
            _scene->_lights.Restart();
            while(_scene->_lights.HasNext()) {
                CLight* li = _scene->_lights.NextStack();
                l = li->v;
                specular = li->specular;
                diffuse = li->diffuse;
                sheen = (float)li->highlightSheen/255.0f;
                spread = (float)li->highlightSpread/4096;
                spread = (spread<0.01f)?0.01f:spread;

                //cos=(int)(255*idx3d_Vector.angle(light[i].v,new idx3d_Vector(fnx,fny,sphere[pos])));
                cos = (int)(255*CVector::Angle(li->v,CVector(fnx, fny, sphere[pos])));
                cos = (cos>0)?cos:0;

                dr += (diffuse._red*cos)>>8; // >>8
                dg += (diffuse._green*cos)>>8; //>>8;
                db += (diffuse._blue*cos)>>8; //>>8;

                phongfact = sheen*(float)pow((float)cos/255.0f,1/spread);

                sr += (int)((float)specular._red*phongfact);
                sg += (int)((float)specular._green*phongfact);
                sb += (int)((float)specular._blue*phongfact);

                sr = sr>255?255:sr;
                sg = sg>255?255:sg;
                sb = sb>255?255:sb;

                dr = dr>255?255:dr;
                dg = dg>255?255:dg;
                db = db>255?255:db;             
            }

            this->diffuse[pos] = CColor(dr, dg, db);
            this->specular[pos] = CColor(sr, sg, sb);
        }
    }
}


Zoals je ziet is de lightmap 256*256 en dat betekent 65536 elementen in de arrays diffuse en specular. De rasterizer-code voor Phongshading volgt hieronder:

C++:
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
void CRasterizer::RenderLineP() {
    assert(_lightmap!=0);
    float gz;

    for(x=xL;x<xR;x++) {
        pos = x+offset;
        
        gz = _zbuffer->Get(x,y);
        
        if(z<gz) {
            lutID=((nx>>16)&255)+(((ny>>16)&255)<<8);
            _canvas->At(x,y,&bkgrd);
            c = CColor::Multiply(color,_lightmap->diffuse[lutID]);
            s = _lightmap->specular[lutID];
            s = CColor::Scale(s,reflectivity);
            CColor::Transparency(bkgrd,c,c);
            c = CColor::Add(c,s);
                                        
            _canvas->DrawTexel(x,y,c);
            _zbuffer->Put(x,y,z);
        }

        z+=dz;
        nx+=dnx;
        ny+=dny;
    }   
}



Hier nog even de code die zorgt voor interpolatie e.d. tijdens het tekenen van een triangle (in de rasterizer):

C++:
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
void CRasterizer::Render(CTriangle* tri) {
    RebuildReferences();

    // Draw a wireframe
    if (_mode==WIREFRAME) {
        DrawWireframe(tri);
        return;
    }

    CVertex* p1=tri->_a;
    CVertex* p2=tri->_b;
    CVertex* p3=tri->_c;
    CVertex* tempVertex;
        
    if (p1->_y>p2->_y) {
        tempVertex=p1;
        p1=p2;
        p2=tempVertex;
    }

    if (p2->_y>p3->_y) {
        tempVertex=p2;
        p2=p3;
        p3=tempVertex; 
    }
    if (p1->_y>p2->_y) {
        tempVertex=p1;
        p1=p2;
        p2=tempVertex;
    }
    
    if (p1->_y>=_height) {
        return;
    }
    if (p3->_y<0) {
        return;
    }
    if (p1->_y==p3->_y) {
        return;
    }
    
    if (_mode==FLAT) {
        //currentColor = CColor::Medium(tri->_a->_color,tri->_b->_color,tri->_c->_color);
        lutID = (int) (tri->n2._x*127+127)+((int)(tri->n2._y*127+127)<<8); // +127
        if(lutID>=65536) {
            lutID = 65535;
        }
        c = CColor::Multiply(color,_lightmap->diffuse[lutID]);
        s = CColor::Scale(_lightmap->specular[lutID],reflectivity);
        currentColor = CColor::Add(c,s);
    }

    
    x1=p1->_x<<8;
    x2=p2->_x<<8;
    x3=p3->_x<<8;
    y1=p1->_y;
    y2=p2->_y;
    y3=p3->_y;
    
    x4=x1+(x3-x1)*(y2-y1)/(y3-y1);
    x1<<=8; x2<<=8; x3<<=8; x4<<=8;

    z1 = p1->_z;
    z2 = p2->_z;
    z3 = p3->_z;
    nx1=p1->nx<<16;
    nx2=p2->nx<<16;
    nx3=p3->nx<<16;
    ny1=p1->ny<<16;
    ny2=p2->ny<<16;
    ny3=p3->ny<<16;

    tx1=p1->tx<<16;
    tx2=p2->tx<<16;
    tx3=p3->tx<<16;
    ty1=p1->ty<<16;
    ty2=p2->ty<<16;
    ty3=p3->ty<<16;
    
    dx=(x4-x2)>>16;
    if (dx==0) return;
    
    temp=256*(y2-y1)/(y3-y1);
    
    z4=z1+((z3-z1)/(float)pow(2,8))*temp; //    z4=z1+((z3-z1)>>8)*temp;
    nx4=nx1+((nx3-nx1)>>8)*temp;
    ny4=ny1+((ny3-ny1)>>8)*temp;
    tx4=tx1+((tx3-tx1)>>8)*temp;
    ty4=ty1+((ty3-ty1)>>8)*temp;

    dz=(z4-z2)/dx;
    dnx=(nx4-nx2)/dx;
    dny=(ny4-ny2)/dx;
    dtx=(tx4-tx2)/dx;
    dty=(ty4-ty2)/dx;


    if (dx<0)
    { 
        temp=x2; x2=x4; x4=temp; 
        z2=z4;
        tx2=tx4;
        ty2=ty4;
        nx2=nx4;
        ny2=ny4;
    }
    if (y2>=0)
    {
        dy=y2-y1;
        if (dy!=0)
        {
            dxL=(x2-x1)/dy;
            dxR=(x4-x1)/dy;
            dzBase=(z2-z1)/dy;
            dnxBase=(nx2-nx1)/dy;
            dnyBase=(ny2-ny1)/dy;
            dtxBase=(tx2-tx1)/dy;
            dtyBase=(ty2-ty1)/dy;
        }
                    
        xBase=x1;
        xMax=x1;
        zBase=z1;
        nxBase=nx1;
        nyBase=ny1;
        txBase=tx1;
        tyBase=ty1;
        
        if (y1<0)
        {
            xBase-=y1*dxL;
            xMax-=y1*dxR;
            zBase-=y1*dzBase;
            nxBase-=y1*dnxBase;
            nyBase-=y1*dnyBase;
            txBase-=y1*dtxBase;
            tyBase-=y1*dtyBase;
            y1=0;
        }
        
        y2=(y2<_height)?y2:_height;
        offset=y1*_width;
        for (y=y1;y<y2;y++) RenderLine();
    }
    
    if (y2<_height)
    {
        dy=y3-y2;
        if (dy!=0)
        {
            dxL=(x3-x2)/dy;
            dxR=(x3-x4)/dy;
            dzBase=(z3-z2)/dy;
            dnxBase=(nx3-nx2)/dy;
            dnyBase=(ny3-ny2)/dy;
            dtxBase=(tx3-tx2)/dy;
            dtyBase=(ty3-ty2)/dy;   
        }
                        
        xBase=x2;
        xMax=x4;
        zBase=z2;
        nxBase=nx2;
        nyBase=ny2;
        txBase=tx2;
        tyBase=ty2;
        
        if (y2<0)
        {
            xBase-=y2*dxL;
            xMax-=y2*dxR;
            zBase-=y2*dzBase;
            nxBase-=y2*dnxBase;
            nyBase-=y2*dnyBase;
            txBase-=y2*dtxBase;
            tyBase-=y2*dtyBase;
            y2=0;
        }
        
        y3=(y3<_height)?y3:_height;
        offset=y2*_width;
        

        for (y=y2;y<y3;y++) RenderLine();
    }
}

void CRasterizer::RenderLine() {
    xL=xBase>>16;
    xR=xMax>>16;
    z=zBase;
    nx=nxBase;
    ny=nyBase;
    tx=txBase;
    ty=tyBase;
    

    if (xL<0) {
        z-=xL*dz;
        nx-=xL*dnx;
        ny-=xL*dny;
        tx-=xL*dtx;
        ty-=xL*dty;
        xL=0;
    }

    xR = (xR<_width)?xR:_width;
    
    if (_mode==FLAT) RenderLineF();
    else if (_mode==TEXTURED) RenderLineT();
    else if (_mode==PHONG) RenderLineP();
    else if (_mode==(PHONG|TEXTURED)) RenderLinePT();

    
    offset+=_width;
    xBase+=dxL;
    xMax+=dxR;
    zBase+=dzBase;
    nxBase+=dnxBase;
    nyBase+=dnyBase;
    txBase+=dtxBase;
    tyBase+=dtyBase;    
}



Als het goed is dan valt je nu ook op dat ik gewoon z in plaats van 1/z interpoleer, (zie het vorige topic) dit moet ik dus ook nog veranderen.

Tis een hele lap code maar zo is het wel een stuk duidelijker denk ik :)

[ Voor 95% gewijzigd door MisterData op 02-03-2003 18:28 ]


  • hobbit_be
  • Registratie: November 2002
  • Laatst online: 04-07-2025
opengl gebruiken? :) sorry :) doet me denken aan mijn days waar ik me helaas niet veel meer van herinner om veel te helpen aan jouw optimized (lees: unreadable) code...

  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
hobbit_be schreef op 02 March 2003 @ 22:05:
opengl gebruiken? :) sorry :) doet me denken aan mijn days waar ik me helaas niet veel meer van herinner om veel te helpen aan jouw optimized (lees: unreadable) code...
Euhm.... ik ben JUIST niet OpenGL aan het gebruiken omdat ik _zelf_ wil weten hoe 3D graphics worden opgebouwd. Daarom port ik ook een software 3D engine |:( Maar er is niemand die een ideetje heeft over wat ik verkeerd doe?

  • MisterData
  • Registratie: September 2001
  • Laatst online: 21:49
Het is al opgelost. In CScene::Scale zat namelijk een foutje (hij schaalde de normalmatrix ook mee en dat moet nou juist niet) :) Toch bedankt!
Pagina: 1