From eeea920e45d2b3f40e83a609a05f52cdd29d102d Mon Sep 17 00:00:00 2001 From: IDunnoDev Date: Tue, 14 Dec 2021 13:50:09 +0000 Subject: [PATCH] Week12 [14/12] Added UVCheck texture file Added UV perspective Variables to the Vertex Class Added Texture Draw method to the Rasteriser --- Rasteriser.cpp | 145 +++++++++++++++++++++++++---------------- Vertex.cpp | 60 ++++++++++++++++- Vertex.h | 12 ++++ model_data/uvcheck.pcx | Bin 0 -> 19725 bytes 4 files changed, 161 insertions(+), 56 deletions(-) create mode 100644 model_data/uvcheck.pcx diff --git a/Rasteriser.cpp b/Rasteriser.cpp index 5e4bd32..b4ceeea 100644 --- a/Rasteriser.cpp +++ b/Rasteriser.cpp @@ -52,10 +52,10 @@ bool Rasteriser::Initialise() } } - //_lights.push_back(new AmbientLight(50, 50, 50)); - _lights.push_back(new AmbientLight(255, 255, 255)); - //_lights.push_back(new DirectionalLight(Vector3D(-1, 0, 0), 150, 150, 150)); - _lights.push_back(new PointLight(Vertex(0, 0, -10), 0, 1, 0.01f, 150, 0, 0)); + _lights.push_back(new AmbientLight(50, 50, 50)); + //_lights.push_back(new AmbientLight(255, 255, 255)); + _lights.push_back(new DirectionalLight(Vector3D(-1, 0, -1), 150, 150, 150)); + //_lights.push_back(new PointLight(Vertex(0, 0, -10), 0, 1, 0.01f, 150, 0, 0)); _cameras.push_back(Camera(0.0f, 0.0f, 0.0f, Vertex(0.0f, 0.0f, -50.0f))); _screenMinimized = false; @@ -132,7 +132,7 @@ float Rasteriser::Interpolate(int y, int x0, int x1, int y0, int y1) float Rasteriser::Lerp(float a, float b, float t) { - return a + (b - a) * t; + return (1 - t) * a + t * b; } float Rasteriser::Lerp(int a, int b, float t) @@ -140,8 +140,12 @@ float Rasteriser::Lerp(int a, int b, float t) return Lerp((float)a, (float)b, t); } +int toDraw = 1; +int drawn = 0; + void Rasteriser::Update(const Bitmap& bitmap) { + drawn = 0; if (bitmap.GetWidth() == 0 || bitmap.GetHeight() == 0) { _screenMinimized = true; @@ -153,7 +157,7 @@ void Rasteriser::Update(const Bitmap& bitmap) if (!_screenMinimized) { - int currentRot = (_rotation % 360); + int currentRot = 0;// 45; // (_rotation % 360); Vertex startPos = Vertex(0, 0, 20); int currentZOff = 0; int currentModelIndex = 0; @@ -569,7 +573,7 @@ void Rasteriser::FillPolygonGouraud(HDC hDc, vector& verts) float cBlue = Lerp(verts[0].GetB(), verts[2].GetB(), vDiff); temp.SetColor(BoundsCheck(0, 255, (int)cRed), BoundsCheck(0, 255, (int)cGreen), BoundsCheck(0, 255, (int)cBlue)); - if (verts[1].GetX() <= temp.GetX()) + if (verts[1].GetX() < temp.GetX()) { FillGouraudSideTriangle(hDc, verts[0], verts[1], temp); FillGouraudSideTriangle(hDc, verts[2], verts[1], temp); @@ -628,15 +632,26 @@ void Rasteriser::FillGouraudSideTriangle(HDC hDc, const Vertex& v1, const Vertex if (tempA.GetX() < tempB.GetX()) { - leftEndPoint = tempA.GetX() - 1.0f; + leftEndPoint = tempA.GetX(); rightEndPoint = tempB.GetX() + 1.0f; } else { - leftEndPoint = tempB.GetX() - 1.0f; + leftEndPoint = tempB.GetX(); rightEndPoint = tempA.GetX() + 1.0f; } + //float iRedA = Interpolate(tempA.GetY(), (float)v1.GetR(), (float)v2.GetR(), v1.GetY(), v2.GetY()); + //float iRedB = Interpolate(tempA.GetY(), (float)v1.GetR(), (float)v3.GetR(), v1.GetY(), v3.GetY()); + //float iGreenA = Interpolate(tempA.GetY(), (float)v1.GetG(), (float)v2.GetG(), v1.GetY(), v2.GetY()); + //float iGreenB = Interpolate(tempA.GetY(), (float)v1.GetG(), (float)v3.GetG(), v1.GetY(), v3.GetY()); + //float iBlueA = Interpolate(tempA.GetY(), (float)v1.GetB(), (float)v2.GetB(), v1.GetY(), v2.GetY()); + //float iBlueB = Interpolate(tempA.GetY(), (float)v1.GetB(), (float)v3.GetB(), v1.GetY(), v3.GetY()); + + // Interpolate the Edge Colors of the 3 Vertex, Lerping seems to give better results given the + // non precise nature of converting the floats into ints since it works on the start and end, and + // interpolates fixed between those values (regular interpolation would sometimes overflow and cause + // a value higher then 255 to be used. float iRedA = Lerp(v1.GetR(), v2.GetR(), (float)i / (float)dx1); float iRedB = Lerp(v1.GetR(), v3.GetR(), (float)i / (float)dx1); float iGreenA = Lerp(v1.GetG(), v2.GetG(), (float)i / (float)dx1); @@ -647,7 +662,7 @@ void Rasteriser::FillGouraudSideTriangle(HDC hDc, const Vertex& v1, const Vertex int xLength = (int)ceil(rightEndPoint) - (int)ceil(leftEndPoint); int ci = 0; - for (int xi = (int)ceil(leftEndPoint); xi <= (int)ceil(rightEndPoint); xi++) + for (int xi = (int)leftEndPoint; xi <= (int)rightEndPoint; xi++) { float ti = (float)ci / (float)xLength; float redTmp = Lerp(iRedA, iRedB, ti); @@ -690,22 +705,22 @@ void Rasteriser::FillGouraudSideTriangle(HDC hDc, const Vertex& v1, const Vertex { if (changed2) { - tempB.SetX((float)tempB.GetXInt() + (float)signx2); + tempB.SetX((float)tempB.GetX() + (float)signx2); } else { - tempB.SetY((float)tempB.GetYInt() + (float)signy2); + tempB.SetY((float)tempB.GetY() + (float)signy2); } e2 = e2 - 2 * dx2; } if (changed2) { - tempB.SetY((float)tempB.GetYInt() + (float)signy2); + tempB.SetY((float)tempB.GetY() + (float)signy2); } else { - tempB.SetX((float)tempB.GetXInt() + (float)signx2); + tempB.SetX((float)tempB.GetX() + (float)signx2); } e2 = e2 + 2 * dy2; } @@ -727,10 +742,18 @@ void Rasteriser::FillPolygonTextured(HDC hDc, Model& model, vector& vert { Vertex temp = Vertex(verts[0].GetX() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetX() - verts[0].GetX()), verts[1].GetY(), verts[1].GetZ()); temp.SetNormal(verts[1].GetNormal()); + + //model.GetUVCoord(verts[1].GetUVIndex()).GetU() + ((model.GetUVCoord(verts[1].GetUVIndex()).GetV() - model.GetUVCoord(verts[0].GetUVIndex()).GetV()) / (model.GetUVCoord(verts[2].GetUVIndex()).GetV() - model.GetUVCoord(verts[0].GetUVIndex()).GetV())) * (model.GetUVCoord(verts[2].GetUVIndex()).GetU() - model.GetUVCoord(verts[0].GetUVIndex()).GetU()); - float tempU = model.GetUVCoord(verts[0].GetUVIndex()).GetU() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (model.GetUVCoord(verts[2].GetUVIndex()).GetU() - model.GetUVCoord(verts[0].GetUVIndex()).GetU()); - float tempV = model.GetUVCoord(verts[0].GetUVIndex()).GetV() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (model.GetUVCoord(verts[2].GetUVIndex()).GetV() - model.GetUVCoord(verts[0].GetUVIndex()).GetV()); + float uc0 = model.GetUVCoord(verts[0].GetUVIndex()).GetU(); + float vc0 = model.GetUVCoord(verts[0].GetUVIndex()).GetV(); + float uc1 = model.GetUVCoord(verts[1].GetUVIndex()).GetU(); + float vc1 = model.GetUVCoord(verts[1].GetUVIndex()).GetV(); + float uc2 = model.GetUVCoord(verts[2].GetUVIndex()).GetU(); + float vc2 = model.GetUVCoord(verts[2].GetUVIndex()).GetV(); + float tempU = uc0 + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (uc2 - uc0); + float tempV = vc0 + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (vc2 - vc0); UVCoord tempCoords = UVCoord(tempU, tempV); float cRed = verts[0].GetR() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetR() - verts[0].GetR()); @@ -738,19 +761,20 @@ void Rasteriser::FillPolygonTextured(HDC hDc, Model& model, vector& vert float cBlue = verts[0].GetB() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetB() - verts[0].GetB()); temp.SetColor(BoundsCheck(0, 255, (int)cRed), BoundsCheck(0, 255, (int)cGreen), BoundsCheck(0, 255, (int)cBlue)); - if (verts[1].GetX() <= temp.GetX()) + if (drawn < toDraw + 100) { - temp.SetUVIndex(verts[2].GetUVIndex()); - FillTexturedSideTriangle(hDc, model, verts[0], verts[1], temp, model.GetUVCoord(verts[0].GetUVIndex()), model.GetUVCoord(verts[1].GetUVIndex()), tempCoords); - temp.SetUVIndex(verts[0].GetUVIndex()); - FillTexturedSideTriangle(hDc, model, verts[2], verts[1], temp, model.GetUVCoord(verts[2].GetUVIndex()), model.GetUVCoord(verts[1].GetUVIndex()), tempCoords); - } - else - { - temp.SetUVIndex(verts[2].GetUVIndex()); - FillTexturedSideTriangle(hDc, model, verts[0], temp, verts[1], model.GetUVCoord(verts[0].GetUVIndex()), tempCoords, model.GetUVCoord(verts[1].GetUVIndex())); - temp.SetUVIndex(verts[0].GetUVIndex()); - FillTexturedSideTriangle(hDc, model, verts[2], temp, verts[1], model.GetUVCoord(verts[2].GetUVIndex()), tempCoords, model.GetUVCoord(verts[1].GetUVIndex())); + if (verts[1].GetX() < temp.GetX()) + { + FillTexturedSideTriangle(hDc, model, verts[0], verts[1], temp, model.GetUVCoord(verts[0].GetUVIndex()), model.GetUVCoord(verts[1].GetUVIndex()), tempCoords); + FillTexturedSideTriangle(hDc, model, verts[2], verts[1], temp, model.GetUVCoord(verts[2].GetUVIndex()), model.GetUVCoord(verts[1].GetUVIndex()), tempCoords); + drawn++; + } + else + { + FillTexturedSideTriangle(hDc, model, verts[0], temp, verts[1], model.GetUVCoord(verts[0].GetUVIndex()), tempCoords, model.GetUVCoord(verts[1].GetUVIndex())); + FillTexturedSideTriangle(hDc, model, verts[2], temp, verts[1], model.GetUVCoord(verts[2].GetUVIndex()), tempCoords, model.GetUVCoord(verts[1].GetUVIndex())); + drawn++; + } } } } @@ -798,10 +822,6 @@ void Rasteriser::FillTexturedSideTriangle(HDC hDc, Model& model, const Vertex& v dx1 = dy1; dy1 = tempDx; - int tempDu = du1; - du1 = dv1; - dv1 = tempDu; - changed1 = true; } @@ -811,10 +831,6 @@ void Rasteriser::FillTexturedSideTriangle(HDC hDc, Model& model, const Vertex& v dx2 = dy2; dy2 = tempDx; - int tempDu = du2; - du2 = dv2; - dv2 = tempDu; - changed2 = true; } @@ -825,27 +841,42 @@ void Rasteriser::FillTexturedSideTriangle(HDC hDc, Model& model, const Vertex& v { float leftEndPoint; float rightEndPoint; - float leftUV; - float rightUV; + float leftU; + float rightU; + float leftV; + float rightV; if (tempA.GetX() < tempB.GetX()) { leftEndPoint = tempA.GetX() - 1.0f; rightEndPoint = tempB.GetX() + 1.0f; - leftUV = tempUVA.GetU(); - rightUV = tempUVB.GetU(); + leftU = tempUVA.GetU(); + rightU = tempUVB.GetU(); + leftV = tempUVA.GetV(); + rightV = tempUVB.GetV(); } else { leftEndPoint = tempB.GetX() - 1.0f; rightEndPoint = tempA.GetX() + 1.0f; - leftUV = tempUVB.GetU(); - rightUV = tempUVA.GetU(); + leftU = tempUVB.GetU(); + rightU = tempUVA.GetU(); + leftV = tempUVB.GetV(); + rightV = tempUVA.GetV(); } + float uy1 = Lerp(uv1.GetV(), uv2.GetV(), (float)i / (float)dx1); + float uy2 = Lerp(uv1.GetV(), uv3.GetV(), (float)i / (float)dx1); - float UCoordA = Interpolate(uv1.GetV() + i, uv1.GetU(), uv2.GetU(), uv1.GetV(), uv2.GetV()); - float UCoordB = Interpolate(uv1.GetV() + i, uv1.GetU(), uv3.GetU(), uv1.GetV(), uv3.GetV()); + //x0 + (y - y0) * ((x1 - x0) / (y1 - y0)) + //a + (b - a) * t + float UCoordA = Interpolate(tempA.GetY(), uv1.GetU(), uv2.GetU(), v1.GetY(), v2.GetY()); + float UCoordB = Interpolate(tempA.GetY(), uv1.GetU(), uv3.GetU(), v1.GetY(), v3.GetY()); + float VCoordA = Interpolate(tempA.GetY(), uv1.GetV(), uv2.GetV(), v1.GetY(), v2.GetY()); + float VCoordB = Interpolate(tempA.GetY(), uv1.GetV(), uv3.GetV(), v1.GetY(), v3.GetY()); + + float UCoord = (rightU - leftU) / (rightEndPoint - leftEndPoint); + float VCoord = (rightV - leftV) / (rightEndPoint - leftEndPoint); float iRedA = Lerp(v1.GetR(), v2.GetR(), (float)i / (float)dx1); float iRedB = Lerp(v1.GetR(), v3.GetR(), (float)i / (float)dx1); @@ -857,19 +888,23 @@ void Rasteriser::FillTexturedSideTriangle(HDC hDc, Model& model, const Vertex& v int xLength = (int)ceil(rightEndPoint) - (int)ceil(leftEndPoint); int ci = 0; - for (int xi = (int)ceil(leftEndPoint); xi <= (int)ceil(rightEndPoint); xi++) - { - float ti = (float)ci / (float)xLength; - float UCoordTmp = Lerp(leftUV, rightUV, ti); - float redTmp = Lerp(iRedA, iRedB, ti) / 100; - float greenTmp = Lerp(iGreenA, iGreenB, ti) / 100; - float blueTmp = Lerp(iBlueA, iBlueB, ti) / 100; + if (i < 10000) + { + for (int xi = (int)ceil(leftEndPoint); xi <= (int)ceil(rightEndPoint); xi++) + { + float ti = (float)ci / (float)xLength; + float UCoordTmp = Lerp(UCoordA, UCoordB, ti); + float VCoordTmp = Lerp(VCoordA, VCoordB, ti); + float redTmp = Lerp(iRedA, iRedB, ti) / 100; + float greenTmp = Lerp(iGreenA, iGreenB, ti) / 100; + float blueTmp = Lerp(iBlueA, iBlueB, ti) / 100; - COLORREF textureColor = model.GetTexture().GetTextureValue((int)UCoordTmp, (int)tempUVA.GetV()); - COLORREF currentColor = RGB(BoundsCheck(0, 255, (int)(GetRValue(textureColor) * redTmp)), BoundsCheck(0, 255, (int)(GetGValue(textureColor) * greenTmp)), BoundsCheck(0, 255, (int)(GetBValue(textureColor) * blueTmp))); + COLORREF textureColor = model.GetTexture().GetTextureValue((int)UCoordTmp, (int)VCoordTmp); + COLORREF currentColor = RGB(BoundsCheck(0, 255, (int)(GetRValue(textureColor) * redTmp)), BoundsCheck(0, 255, (int)(GetGValue(textureColor) * greenTmp)), BoundsCheck(0, 255, (int)(GetBValue(textureColor) * blueTmp))); - SetPixel(hDc, xi, tempA.GetYInt(), currentColor); - ci++; + SetPixel(hDc, xi, tempA.GetYInt(), currentColor); + ci++; + } } diff --git a/Vertex.cpp b/Vertex.cpp index 2c6fd37..23fc46a 100644 --- a/Vertex.cpp +++ b/Vertex.cpp @@ -11,6 +11,11 @@ Vertex::Vertex() _r = 0; _g = 0; _b = 0; + _uvIndex = -1; + _zOriginal = 0; + _uOverZ = 0; + _vOverZ = 0; + _zRecip = 0; } Vertex::Vertex(const float x, const float y, const float z) @@ -24,6 +29,11 @@ Vertex::Vertex(const float x, const float y, const float z) _r = 0; _g = 0; _b = 0; + _uvIndex = -1; + _zOriginal = 0; + _uOverZ = 0; + _vOverZ = 0; + _zRecip = 0; } Vertex::Vertex(const float x, const float y, const float z, const float w) @@ -37,6 +47,11 @@ Vertex::Vertex(const float x, const float y, const float z, const float w) _r = 0; _g = 0; _b = 0; + _uvIndex = -1; + _zOriginal = 0; + _uOverZ = 0; + _vOverZ = 0; + _zRecip = 0; } Vertex::Vertex(const Vertex & other) @@ -187,6 +202,44 @@ void Vertex::SetUVIndex(const int index) _uvIndex = index; } +float Vertex::GetZOriginal() const +{ + return _zOriginal; +} + +float Vertex::GetUOverZ() const +{ + return _uOverZ; +} + +void Vertex::SetUOverZ(const float value) +{ + _uOverZ = value / _zOriginal; +} + +float Vertex::GetVOverZ() const +{ + return _vOverZ; +} + +void Vertex::SetVOverZ(const float value) +{ + _vOverZ = value / _zOriginal; +} + +float Vertex::GetZRecip() const +{ + return _zRecip; +} + +void Vertex::UVCorrect(float u, float v) +{ + _zOriginal = _w; + _zRecip = 1 / _zOriginal; + SetUOverZ(u); + SetVOverZ(v); +} + int Vertex::GetXInt(bool forceRoundUp) const { if (forceRoundUp) @@ -236,7 +289,7 @@ int Vertex::GetWInt(bool forceRoundUp) const } void Vertex::Dehomogenize() -{ +{ _x = _x / _w; _y = _y / _w; _z = _z / _w; @@ -293,4 +346,9 @@ void Vertex::Copy(const Vertex& other) _b = other.GetB(); _uvIndex = other.GetUVIndex(); + + _zOriginal = other.GetZOriginal(); + _uOverZ = other.GetUOverZ(); + _vOverZ = other.GetVOverZ(); + _zRecip = other.GetZRecip(); } diff --git a/Vertex.h b/Vertex.h index 026ba5e..1ca154a 100644 --- a/Vertex.h +++ b/Vertex.h @@ -44,6 +44,14 @@ public: int GetUVIndex() const; void SetUVIndex(const int index); + float GetZOriginal() const; + float GetUOverZ() const; + void SetUOverZ(const float value); + float GetVOverZ() const; + void SetVOverZ(const float value); + float GetZRecip() const; + void UVCorrect(float u, float v); + // Accessors for returning the private x, y, z and w values as integeres instead of floats // the ceil function to round the number up by defaults but using providing a false param will // use the floor function instead to round the number down @@ -79,6 +87,10 @@ private: int _uvIndex; + float _uOverZ; + float _vOverZ; + float _zRecip; + void Copy(const Vertex& other); }; diff --git a/model_data/uvcheck.pcx b/model_data/uvcheck.pcx new file mode 100644 index 0000000000000000000000000000000000000000..77ec8ac5e4424e41e9de86def02678c05f0a2a59 GIT binary patch literal 19725 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 literal 0 HcmV?d00001