#include "SoftwareRasteriser.h" #include #include #define MAX_SIDES 16 const int INSIDE_S = 0; const int LEFT_S = 1; const int RIGHT_S = 2; const int BOTTOM_S = 4; const int TOP_S = 8; const int FAR_S = 16; const int NEAR_S = 32; /* While less 'neat' than just doing a 'new', like in the tutorials, it's usually possible to render a bit quicker to use direct pointers to the drawing area that the OS gives you. For a bit of a speedup, you can uncomment the define below to switch to using this method. For those of you new to the preprocessor, here's a quick explanation: Preprocessor definitions like #define allow parts of a file to be selectively enabled or disabled at compile time. This is useful for hiding parts of the codebase on a per-platform basis: if you have support for linux and windows in your codebase, obviously the linux platform won't have the windows platform headers available, so compilation will fail. So instead you can hide away all the platform specific stuff: #if PLATFORM_WINDOWS DoSomeWindowsStuff(); #elif PLATFORM_LINUX DoSomeLinuxStuff(); #else #error Unsupported Platform Specified! #endif As in our usage, it also allows you to selectively compile in some different functionality without any 'run time' cost - if it's not enabled by the preprocessor, it won't make it to the compiler, so no assembly will be generated. Also, I've implemented the Resize method for you, in a manner that'll behave itself no matter which method you use. I kinda forgot to do that, so there was a chance you'd get exceptions if you resized to a bigger screen area. Sorry about that. */ //#define USE_OS_BUFFERS SoftwareRasteriser::SoftwareRasteriser(uint width, uint height) : Window(width, height) { currentTexture = NULL; currentDrawBuffer = 0; texSampleState = SAMPLE_NEAREST; blendSampleState = SAMPLE_BLEND_REPLACE; #ifndef USE_OS_BUFFERS // Hi! In the tutorials, it's mentioned that we need to form our front + back buffer like so: for (int i = 0; i < 2; ++i) { buffers[i] = new Colour[screenWidth * screenHeight]; } #else // This works, but we can actually save a memcopy by rendering directly into the memory the // windowing system gives us, which I've added to the Window class as the 'bufferData' pointers for (int i = 0; i < 2; ++i) { buffers[i] = (Colour *)bufferData[i]; } #endif depthBuffer = new unsigned short[screenWidth * screenHeight]; float zScale = (pow(2.0f, 16) - 1) * 0.5f; Vector3 halfScreen = Vector3((screenWidth - 1) * 0.5f, (screenHeight - 1) * 0.5f, zScale); portMatrix = Matrix4::Translation(halfScreen) * Matrix4::Scale(halfScreen); } SoftwareRasteriser::~SoftwareRasteriser(void) { #ifndef USE_OS_BUFFERS for (int i = 0; i < 2; ++i) { delete[] buffers[i]; } #endif delete[] depthBuffer; } void SoftwareRasteriser::Resize() { Window::Resize(); #ifndef USE_OS_BUFFERS for (int i = 0; i < 2; ++i) { delete[] buffers[i]; buffers[i] = new Colour[screenWidth * screenHeight]; } #else for (int i = 0; i < 2; ++i) { buffers[i] = (Colour *)bufferData[i]; } #endif delete[] depthBuffer; depthBuffer = new unsigned short[screenWidth * screenHeight]; float zScale = (pow(2.0f, 16) - 1) * 0.5f; Vector3 halfScreen = Vector3((screenWidth - 1) * 0.5f, (screenHeight - 1) * 0.5f, zScale); portMatrix = Matrix4::Translation(halfScreen) * Matrix4::Scale(halfScreen); } Colour *SoftwareRasteriser::GetCurrentBuffer() { return buffers[currentDrawBuffer]; } void SoftwareRasteriser::ClearBuffers() { Colour *buffer = GetCurrentBuffer(); unsigned int clearVal = 0xFF000000; unsigned int depthVal = ~0; for (uint y = 0; y < screenHeight; ++y) { for (uint x = 0; x < screenWidth; ++x) { buffer[(y * screenWidth) + x].c = clearVal; depthBuffer[(y * screenWidth) + x] = depthVal; } } } void SoftwareRasteriser::SwapBuffers() { PresentBuffer(buffers[currentDrawBuffer]); currentDrawBuffer = !currentDrawBuffer; } void SoftwareRasteriser::DrawObject(RenderObject *o) { currentTexture = o->GetTexure(); switch (o->GetMesh()->GetType()) { case PRIMITIVE_POINTS: RasterisePointsMesh(o); break; case PRIMITIVE_LINES: RasteriseLinesMesh(o); break; case PRIMITIVE_TRIANGLES: RasteriseTriMesh(o); break; case PRIMITIVE_TRIANGLES_STRIP: // RasteriseTriMeshStripPrimitives(o); break; case PRIMITIVE_TRIANGLES_FAN: // RasteriseTriMeshFanPrimitives(o); break; } } void SoftwareRasteriser::RasterisePointsMesh(RenderObject *o) { Matrix4 mvp = viewProjMatrix * o->GetModelMatrix(); for (uint i = 0; i < o->GetMesh()->numVertices; i++) { Vector4 vertexPos = mvp * o->GetMesh()->vertices[i]; vertexPos.SelfDivisionByW(); Vector4 screenPos = portMatrix * vertexPos; BlendPixel((uint)screenPos.x, (uint)screenPos.y, o->GetMesh()->colours[i]); } } void SoftwareRasteriser::RasteriseLinesMesh(RenderObject *o) { const int one = 1; const int two = 2; Matrix4 mvp = viewProjMatrix * o->GetModelMatrix(); for (uint i = 0; i < o->GetMesh()->numVertices; i = i + two) { Vector4 v0 = mvp * o->GetMesh()->vertices[i]; Vector4 v1 = mvp * o->GetMesh()->vertices[i + 1]; Colour c0 = o->GetMesh()->colours[0]; Colour c1 = o->GetMesh()->colours[1]; Vector3 t0 = Vector3(o->GetMesh()->textureCoords[i].x, o->GetMesh()->textureCoords[i].y, 1.0f); Vector3 t1 = Vector3(o->GetMesh()->textureCoords[i + 1].x, o->GetMesh()->textureCoords[i + 1].y, 1.0f); if (!RasteriseCohenLine(v0, v1, c0, c1, t0, t1)) { continue; } t0.z = 1.0f; t1.z = 1.0f; t0 /= v0.w; t1 /= v1.w; v0.SelfDivisionByW(); v1.SelfDivisionByW(); RasteriseLine(v0, v1, c0, c1, t0, t1); } } void SoftwareRasteriser::RasteriseLine(const Vector4 &vertA, const Vector4 &vertB, const Colour &colA, const Colour &colB, const Vector3 &texA, const Vector3 &texB) { Vector4 v0 = portMatrix * vertA; Vector4 v1 = portMatrix * vertB; Vector4 dir = v1 - v0; int xDir = (dir.x < 0.0f) ? -1 : 1; int yDir = (dir.y < 0.0f) ? -1 : 1; int x = (int)v0.x; int y = (int)v0.y; int* target = NULL; int* scan = NULL; int targetVal = 0; int scanVal = 0; float slope = 0.0f; int range = 0; if (abs(dir.y) > abs(dir.x)) { slope = dir.x / dir.y; range = (int)abs(dir.y); target = &x; scan = &y; targetVal = xDir; scanVal = yDir; } else { slope = dir.y / dir.x; range = (int)abs(dir.x); target = &y; scan = &x; targetVal = yDir; scanVal = xDir; } float absSlope = abs(slope); float error = 0.0f; const float reciprocalRange = 1.0f / range; for (int i = 0; i < range; i++) { const float t = i * reciprocalRange; const float zVal = v1.z * (i * reciprocalRange) + v0.z * (1.0 - (i * reciprocalRange)); Colour c; if (currentTexture == NULL) { c = colB * t + colA * (1.0f - t); } else { Vector3 subTex = texA * (i * reciprocalRange) + texB * (1.0f - (i * reciprocalRange)); subTex.x /= subTex.z; subTex.y /= subTex.z; c = currentTexture->ColourAtPoint((int)subTex.x, (int)subTex.y, 0); } if (DepthFunc((int)x, (int)y, zVal)) { BlendPixel(x, y, c); } error += absSlope; if (error > 0.5f) { error -= 1.0f; (*target) += targetVal; } (*scan) += scanVal; } } BoundingBox SoftwareRasteriser::CalculateBoxForTri(const Vector4 &a, const Vector4 &b, const Vector4 &c) { BoundingBox box; box.topLeft.x = a.x; box.topLeft.x = min(box.topLeft.x, b.x); box.topLeft.x = min(box.topLeft.x, c.x); box.topLeft.x = max(box.topLeft.x, 0.0f); box.topLeft.y = a.y; box.topLeft.y = min(box.topLeft.y, b.y); box.topLeft.y = min(box.topLeft.y, c.y); box.topLeft.y = max(box.topLeft.y, 0.0f); box.bottomRight.x = a.x; box.bottomRight.x = max(box.bottomRight.x, b.x); box.bottomRight.x = max(box.bottomRight.x, c.x); box.bottomRight.x = min(box.bottomRight.x, screenWidth); box.bottomRight.y = a.y; box.bottomRight.y = max(box.bottomRight.y, b.y); box.bottomRight.y = max(box.bottomRight.y, c.y); box.bottomRight.y = min(box.bottomRight.y, screenHeight); return box; } float SoftwareRasteriser::ScreenAreaOfTri(const Vector4 &a, const Vector4 &b, const Vector4 &c) { float area = ((a.x * b.y) + (b.x * c.y) + (c.x * a.y)) - ((b.x * a.y) + (c.x * b.y) + (a.x * c.y)); return area * 0.5f; } void SoftwareRasteriser::RasteriseTriMesh(RenderObject*o) { Matrix4 mvp = viewProjMatrix * o->GetModelMatrix(); for (uint i = 0; i < o->GetMesh()->numVertices; i += 3) { Vector4 v0 = mvp * o->GetMesh()->vertices[i]; Vector4 v1 = mvp * o->GetMesh()->vertices[i + 1]; Vector4 v2 = mvp * o->GetMesh()->vertices[i + 2]; Vector3 t0 = Vector3( o->GetMesh()->textureCoords[i].x, o->GetMesh()->textureCoords[i].y, 1.0f) / v0.w; Vector3 t1 = Vector3( o->GetMesh()->textureCoords[i + 1].x, o->GetMesh()->textureCoords[i + 1].y, 1.0f) / v1.w; Vector3 t2 = Vector3( o->GetMesh()->textureCoords[i + 2].x, o->GetMesh()->textureCoords[i + 2].y, 1.0f) / v2.w; v0.SelfDivisionByW(); v1.SelfDivisionByW(); v2.SelfDivisionByW(); RasteriseTri(v0, v1, v2, o->GetMesh()->colours[i], o->GetMesh()->colours[i + 1], o->GetMesh()->colours[i + 2], t0, t1, t2 ); } } //Fill in the Strip primitive void SoftwareRasteriser::RasteriseTriMeshStripPrimitives(RenderObject *o) { Matrix4 mvp = viewProjMatrix * o->GetModelMatrix(); Mesh *m = o->GetMesh(); const int one = 1; const int two = 2; for (uint i = 0; i < o->GetMesh()->numVertices - two; ++i) { Vector4 v0 = mvp * m->vertices[i]; Vector4 v1 = mvp * m->vertices[i + one]; Vector4 v2 = mvp * m->vertices[i + two]; RasteriseHodgmanTri(v0, v1, v2, m->colours[i], m->colours[i + one], m->colours[i + two], m->textureCoords[i], m->textureCoords[i + one], m->textureCoords[i + two]); } } //Fill Planet void SoftwareRasteriser::RasteriseTriMeshFanPrimitives(RenderObject *o) { const int one = 1; Matrix4 mvp = viewProjMatrix * o->GetModelMatrix(); Mesh *m = o->GetMesh(); Vector4 v0 = mvp * m->vertices[0]; for (uint i = 1; i < o->GetMesh()->numVertices - one; ++i) { Vector4 v1 = mvp * m->vertices[i]; Vector4 v2 = mvp * m->vertices[i + one]; RasteriseHodgmanTri(v0, v1, v2, m->colours[0], m->colours[i], m->colours[i + one], m->textureCoords[0], m->textureCoords[i], m->textureCoords[i + one]); } } void SoftwareRasteriser::RasteriseTri(const Vector4 &triA, const Vector4 &triB, const Vector4 &triC, const Colour &colA, const Colour &colB, const Colour &colC, const Vector3 &texA, const Vector3 &texB, const Vector3 &texC) { Vector4 v0 = portMatrix * triA; Vector4 v1 = portMatrix * triB; Vector4 v2 = portMatrix * triC; BoundingBox b = CalculateBoxForTri(v0, v1, v2); float triArea = abs(ScreenAreaOfTri(v0, v1, v2)); const float areaRecip = 1.0f / triArea; float subTriArea[3]; Vector4 screenPos(0, 0, 0, 1); for (float y = b.topLeft.y; y < b.bottomRight.y; ++y) { for (float x = b.topLeft.x; x < b.bottomRight.x; ++x) { screenPos.x = x; screenPos.y = y; subTriArea[0] = abs(ScreenAreaOfTri(v0, screenPos, v1)); subTriArea[1] = abs(ScreenAreaOfTri(v1, screenPos, v2)); subTriArea[2] = abs(ScreenAreaOfTri(v2, screenPos, v0)); float triSum = subTriArea[0] + subTriArea[1] + subTriArea[2]; if (triSum > (triArea + 1.0f)) { continue; } if (triSum < 1.0f) { continue; } //ShadePixel((int)x, (int)y, Colour::White); const float alpha = subTriArea[1] * areaRecip; const float beta = subTriArea[2] * areaRecip; const float gamma = subTriArea[0] * areaRecip; float zVal = (v0.z * alpha) + (v1.z * beta) + (v2.z * gamma); if (!DepthFunc((int)x, (int)y, zVal)) { continue; } Colour c = ((colA * alpha) + (colB * beta) + (colC * gamma)); BlendPixel((int)x, (int)y, c); //if only if (currentTexture) { Vector3 subTex = (texA * alpha) + (texB * beta) + (texC * gamma); subTex.x /= subTex.z; subTex.y /= subTex.z; if (texSampleState == SAMPLE_BILINEAR) { BlendPixel((int)x, (int)y, currentTexture->BilinearTexSample(subTex)); } else if (texSampleState == SAMPLE_MIPMAP_NEAREST) { float xAlpha, xBeta, xGamma, yAlpha, yBeta, yGamma; CalculateWeights(v0, v1, v2, screenPos + Vector4(1, 0, 0, 0), xAlpha, xBeta, xGamma); CalculateWeights(v0, v1, v2, screenPos + Vector4(0, 1, 0, 0), yAlpha, yBeta, yGamma); Vector3 xDerivs = (texA * xAlpha) + (texB * xBeta) + (texC * xGamma); Vector3 yDerivs = (texA * yAlpha) + (texB * yBeta) + (texC * yGamma); xDerivs.x /= xDerivs.z; xDerivs.y /= xDerivs.z; yDerivs.x /= yDerivs.z; yDerivs.y /= yDerivs.z; xDerivs = xDerivs - subTex; yDerivs = yDerivs - subTex; const float maxU = max(abs(xDerivs.x), abs(yDerivs.y)); const float maxV = max(abs(xDerivs.y), abs(yDerivs.y)); const float maxChange = abs(max(maxU, maxV)); const int lambda = (int)(abs(log(maxChange) / log(2.0))); BlendPixel((int)x, (int)y, currentTexture->NearestTexSample(subTex, lambda)); break; } else { BlendPixel((int)x, (int)y, currentTexture->NearestTexSample(subTex)); break; } } } } } inline bool SoftwareRasteriser::DepthFunc(int x, int y, float depthValue) { if (y < 0 || x < 0 || y >= screenHeight || x >= screenWidth) { return false; } int index = (y * screenWidth) + x; unsigned int castVal = (unsigned int)depthValue; if (castVal > depthBuffer[index]) return false; depthBuffer[index] = castVal; return true; } void SoftwareRasteriser::CalculateWeights(const Vector4 &v0, const Vector4 &v1, const Vector4 &v2, const Vector4 &p, float &alpha, float &beta, float &gamma) { const float triArea = ScreenAreaOfTri(v0, v1, v2); const float areaRecip = 1.0f / triArea; float subTriArea[3]; subTriArea[0] = abs(ScreenAreaOfTri(v0, p, v1)); subTriArea[1] = abs(ScreenAreaOfTri(v1, p, v2)); subTriArea[2] = abs(ScreenAreaOfTri(v2, p, v0)); alpha = subTriArea[1] * areaRecip; beta = subTriArea[2] * areaRecip; gamma = subTriArea[0] * areaRecip; } bool SoftwareRasteriser::RasteriseCohenLine(Vector4 &v0, Vector4 &v1, Colour &colA, Colour &colB, Vector3 &texA, Vector3 &texB) { const int max = 6; for (int i = 0; i < max; ++i) { int index = 1 << i; int outsidePixelA = (OutPixel(v0) & index); int outsidePixelB = (OutPixel(v1) & index); if (outsidePixelA && outsidePixelB) { return false; } if (!outsidePixelA && !outsidePixelB) { continue; } float position = RefineEdges(v0, v1, index); if (outsidePixelA) { texA = Vector3::Lerp(texA, texB, position); colA = Colour::Lerp(colA, colB, position); v0 = Vector4::Lerp(v0, v1, position); } else { texB = Vector3::Lerp(texA, texB, position); colB = Colour::Lerp(colA, colB, position); v1 = Vector4::Lerp(v0, v1, position); } } return true; } void SoftwareRasteriser::RasteriseHodgmanTri(Vector4 &triA, Vector4 &triB, Vector4 &triC, const Colour &colA, const Colour &colB, const Colour &colC, const Vector2 &texA, const Vector2 &texB, const Vector2 &texC) { Vector4 positionIn[MAX_SIDES]; Colour colourIn[MAX_SIDES]; Vector3 textureIn[MAX_SIDES]; Vector4 positionInOut[MAX_SIDES]; Colour colourOut[MAX_SIDES]; Vector3 textureOut[MAX_SIDES]; positionIn[0] = triA; positionIn[1] = triB; positionIn[2] = triC; colourIn[0] = colA; colourIn[1] = colB; colourIn[2] = colC; textureIn[0] = Vector3(texA.x, texA.y, 1); textureIn[1] = Vector3(texB.x, texB.y, 1); textureIn[2] = Vector3(texC.x, texC.y, 1); int inputSize = 3; const int max = 6; for (int i = 0; i <= max; i++) { int index = 1 << i; Vector4 previousPosition = positionIn[inputSize - 1]; Colour previousColour = colourIn[inputSize - 1]; Vector3 previousTexture = textureIn[inputSize - 1]; int outputSize = 0; for (int j = 0; j < inputSize; ++j) { int outsideA = OutPixel(positionIn[j]) & index; int outsideB = OutPixel(previousPosition) & index; if (outsideA ^ outsideB) { float clipRatio = RefineEdges(positionIn[j], previousPosition, index); colourOut[outputSize] = Colour::Lerp(colourIn[j], previousColour, clipRatio); textureOut[outputSize] = Vector3::Lerp(textureIn[j], previousTexture, clipRatio); positionInOut[outputSize] = Vector4::Lerp(positionIn[j], previousPosition, clipRatio); outputSize++; } if (!outsideA) { positionInOut[outputSize] = positionIn[j]; colourOut[outputSize] = colourIn[j]; textureOut[outputSize] = textureIn[j]; outputSize++; } previousPosition = positionIn[j]; previousColour = colourIn[j]; previousTexture = textureIn[j]; } for (int j = 0; j < outputSize; ++j) { positionIn[j] = positionInOut[j]; colourIn[j] = colourOut[j]; textureIn[j] = textureOut[j]; } inputSize = outputSize; } for (int i = 0; i < inputSize; ++i) { textureIn[i] = Vector3(textureIn[i].x, textureIn[i].y, 1.0f) / positionIn[i].w; positionIn[i].SelfDivisionByW(); } for (int i = 2; i < inputSize; ++i) { RasteriseTri(positionIn[0], positionIn[i - 1], positionIn[i], colourIn[0], colourIn[i - 1], colourIn[i], textureIn[0], textureIn[i - 1], textureIn[i]); } } float SoftwareRasteriser::RefineEdges(const Vector4 &v0, const Vector4 &v1, int edge) { float position = 0.0f; if (edge == LEFT_S) { position = (-v0.w - v0.x) / ((v1.x - v0.x) + v1.w - v0.w); } if (edge == RIGHT_S) { position = (v0.w - v0.x) / ((v1.x - v0.x) + v1.w - v0.w); } if (edge == BOTTOM_S) { position = (-v0.w - v0.y) / ((v1.y - v0.y) + v1.w - v0.w); } if (edge == TOP_S) { position = (v0.w - v0.y) / ((v1.y - v0.y) + v1.w - v0.w); } if (edge == NEAR_S) { position = (-v0.w - v0.z) / ((v1.z - v0.z) + v1.w - v0.w); } if (edge == FAR_S) { position = (v0.w - v0.z) / ((v1.z - v0.z) + v1.w - v0.w); } return min(1.0f, position); } int SoftwareRasteriser::OutPixel(const Vector4 &input) { int outsidePixel = INSIDE_S; if (input.x < -input.w) { outsidePixel |= LEFT_S; } else if (input.x > input.w) { outsidePixel |= RIGHT_S; } if (input.y < -input.w) { outsidePixel |= BOTTOM_S; } else if (input.y > input.w) { outsidePixel |= TOP_S; } if (input.z < -input.w) { outsidePixel |= NEAR_S; } else if (input.z > input.w) { outsidePixel |= FAR_S; } return outsidePixel; } void SoftwareRasteriser::RasteriseTriSpans(const Vector4 &triA, const Vector4 &triB, const Vector4 &triC, const Colour &colA, const Colour &colB, const Colour &colC, const Vector3 &texA, const Vector3 &texB, const Vector3 &t2texC) { Vector4 v0 = portMatrix * triA; Vector4 v1 = portMatrix * triB; Vector4 v2 = portMatrix * triC; float position0 = abs(v0.y - v1.y); float position1 = abs(v1.y - v2.y); float position2 = abs(v2.y - v0.y); if (position0 >= position1 && position0 >= position2) { RasteriseTriEdgeSpans(v0, v1, v2, v0); RasteriseTriEdgeSpans(v0, v1, v1, v2); } else if (position1 >= position0 && position1 >= position2) { RasteriseTriEdgeSpans(v1, v2, v2, v0); RasteriseTriEdgeSpans(v1, v2, v0, v1); } else { RasteriseTriEdgeSpans(v2, v0, v0, v1); RasteriseTriEdgeSpans(v2, v0, v1, v2); } } void SoftwareRasteriser::RasteriseTriEdgeSpans(const Vector4 &triA, const Vector4 &triB, const Vector4 &triC, const Vector4 &triD) { Vector4 EdgeX0 = triA; Vector4 EdgeX1 = triB; Vector4 EdgeY0 = triC; Vector4 EdgeY1 = triD; if (EdgeX1.y < EdgeX0.y) { EdgeX0 = triB; EdgeX1 = triA; } if (EdgeX1.y < EdgeY0.y) { EdgeY0 = triD; EdgeY1 = triC; } Vector4 longDiffEdge = EdgeX1 - EdgeX0; Vector4 shortDiffEdge = EdgeY1 - EdgeY0; float longStepEdge = longDiffEdge.x / longDiffEdge.y; float shortStepEdge = shortDiffEdge.x / shortDiffEdge.y; float startOffEdge = (EdgeY0.y - EdgeX0.y) / longDiffEdge.y; Vector4 startEdge = EdgeX0 + (longDiffEdge * startOffEdge); float endOffEdge = (startEdge.y - EdgeY0.y) / shortDiffEdge.y; Vector4 endEdge = EdgeY0 + (shortDiffEdge * endOffEdge); for (float y = EdgeY0.y; y < EdgeY1.y; ++y) { float minX = min(startEdge.x, endEdge.x); float maxX = max(startEdge.x, endEdge.x); for (float x = minX; x < maxX; ++x) BlendPixel((int)x, (int)y, Colour::White); startEdge.x += longStepEdge; endEdge.x += shortStepEdge; } }