1
0
Fork 0

Add perspective-correct texture mapping

This commit is contained in:
Austin Morlan 2018-09-13 18:57:06 -07:00
parent 370fb7bce0
commit 20402784f7
Signed by: austin
GPG Key ID: FD6B27654AF5E348
5 changed files with 188 additions and 38 deletions

View File

@ -14,9 +14,23 @@ struct Material
ColorF32 kDiffuse; ColorF32 kDiffuse;
}; };
struct Texture
{
ColorU32 **texels;
unsigned int width;
unsigned int height;
};
struct TextureCoord
{
float u;
float v;
};
struct Face struct Face
{ {
unsigned int vertIndex[3]; unsigned int vertIndex[3];
unsigned int textureIndex[3];
Vector normal; Vector normal;
ColorF32 color; ColorF32 color;
}; };
@ -32,6 +46,7 @@ struct Mesh_LocalData
{ {
std::vector<Vertex> verts; std::vector<Vertex> verts;
std::vector<Face> faces; std::vector<Face> faces;
std::vector<TextureCoord> uvs;
}; };
struct Mesh_TransformedData struct Mesh_TransformedData
@ -73,7 +88,8 @@ void CullBackfaces(
Point &camPosition); Point &camPosition);
void RenderMesh( void RenderMesh(
Engine_Buffer &buffer, Mesh_TransformedData &mesh, bool smooth); Engine_Buffer &buffer, Mesh_TransformedData &mesh, bool smooth, Texture &texture,
std::vector<TextureCoord> &uvs);
#define GEOMETRY_H #define GEOMETRY_H

View File

@ -4,6 +4,7 @@
int LoadMesh(char *filename, Mesh &mesh); int LoadMesh(char *filename, Mesh &mesh);
int LoadTexture(char *filename, Texture &texture);
#define LOADER_H #define LOADER_H

View File

@ -15,6 +15,7 @@
static Mesh mesh; static Mesh mesh;
static Camera camera; static Camera camera;
static LightList lights; static LightList lights;
static Texture texture;
// PRIVATE PROTOTYPES // PRIVATE PROTOTYPES
@ -129,10 +130,12 @@ void Engine_Render(Engine_Buffer &buffer, uint32_t input)
for (size_t v = 0; v < mesh.transformed.verts.size(); ++v) for (size_t v = 0; v < mesh.transformed.verts.size(); ++v)
{ {
mesh.transformed.verts[v].point *= tView * tPersp * tScreen; mesh.transformed.verts[v].point *= tView * tPersp * tScreen;
mesh.transformed.verts[v].point /= mesh.transformed.verts[v].point.w; mesh.transformed.verts[v].point.x /= mesh.transformed.verts[v].point.w;
mesh.transformed.verts[v].point.y /= mesh.transformed.verts[v].point.w;
mesh.transformed.verts[v].point.z /= mesh.transformed.verts[v].point.w;
} }
RenderMesh(buffer, mesh.transformed, mesh.smooth); RenderMesh(buffer, mesh.transformed, mesh.smooth, texture, mesh.local.uvs);
} }
void Engine_Shutdown(void) void Engine_Shutdown(void)

View File

@ -66,25 +66,37 @@ void CullBackfaces(
} }
void RenderMesh(Engine_Buffer &buffer, Mesh_TransformedData &mesh, bool smooth) void RenderMesh(Engine_Buffer &buffer, Mesh_TransformedData &mesh, bool smooth, Texture &texture,
std::vector<TextureCoord> &uvs)
{ {
for(size_t f = 0; f < mesh.faces.size(); ++f) for(size_t f = 0; f < mesh.faces.size(); ++f)
{ {
// The vertices of this face
unsigned int vIndex0 = mesh.faces[f].vertIndex[0]; unsigned int vIndex0 = mesh.faces[f].vertIndex[0];
unsigned int vIndex1 = mesh.faces[f].vertIndex[1]; unsigned int vIndex1 = mesh.faces[f].vertIndex[1];
unsigned int vIndex2 = mesh.faces[f].vertIndex[2]; unsigned int vIndex2 = mesh.faces[f].vertIndex[2];
Vertex &v0 = mesh.verts[vIndex0];
Vertex &v1 = mesh.verts[vIndex1];
Vertex &v2 = mesh.verts[vIndex2];
Vertex v0 = mesh.verts[vIndex0];
Vertex v1 = mesh.verts[vIndex1];
Vertex v2 = mesh.verts[vIndex2];
// Bounding box used to for iterating over possible pixels of this triangle // The UVs of this face's vertices
unsigned int tIndex0 = mesh.faces[f].textureIndex[0];
unsigned int tIndex1 = mesh.faces[f].textureIndex[1];
unsigned int tIndex2 = mesh.faces[f].textureIndex[2];
TextureCoord &t0 = uvs[tIndex0];
TextureCoord &t1 = uvs[tIndex1];
TextureCoord &t2 = uvs[tIndex2];
// Bounding box for barycentric calculations (top-left fill convention)
BoundingBox box(v0.point, v1.point, v2.point); BoundingBox box(v0.point, v1.point, v2.point);
int yMin = (int)ceilf(box.yMin); int yMin = (int)ceilf(box.yMin);
int yMax = (int)ceilf(box.yMax) - 1; int yMax = (int)ceilf(box.yMax) - 1;
int xMin = (int)ceilf(box.xMin); int xMin = (int)ceilf(box.xMin);
int xMax = (int)ceilf(box.xMax) - 1; int xMax = (int)ceilf(box.xMax) - 1;
// Constants for this triangle used for barycentric calculations // Constants for this triangle used for barycentric calculations
Vector v01 = v1.point - v0.point; Vector v01 = v1.point - v0.point;
Vector v02 = v2.point - v0.point; Vector v02 = v2.point - v0.point;
@ -92,57 +104,87 @@ void RenderMesh(Engine_Buffer &buffer, Mesh_TransformedData &mesh, bool smooth)
float dot0102 = Vector::Dot(v01, v02); float dot0102 = Vector::Dot(v01, v02);
float dot0202 = Vector::Dot(v02, v02); float dot0202 = Vector::Dot(v02, v02);
// Iterate bounding box and determine if each point is in the triangle
// Iterate over the bounding box and determine if each point is in the triangle
for (int y = yMin; y <= yMax; ++y) for (int y = yMin; y <= yMax; ++y)
{ {
for (int x = xMin; x <= xMax; ++x) for (int x = xMin; x <= xMax; ++x)
{ {
// Constant terms used for barycentric calculation
Point p(x, y, 1.0f); Point p(x, y, 1.0f);
Vector v0P = p - v0.point; Vector v0P = p - v0.point;
float dot0P01 = Vector::Dot(v0P, v01); float dot0P01 = Vector::Dot(v0P, v01);
float dot0P02 = Vector::Dot(v0P, v02); float dot0P02 = Vector::Dot(v0P, v02);
float denomInv = 1.0f / ((dot0101 * dot0202) - (dot0102 * dot0102)); float denomInv = 1.0f / ((dot0101 * dot0202) - (dot0102 * dot0102));
// Calculate the barycentric coordinate of this point
float barycenter[3]; float barycenter[3];
barycenter[1] = (dot0202 * dot0P01 - dot0102 * dot0P02) * denomInv; barycenter[1] = (dot0202 * dot0P01 - dot0102 * dot0P02) * denomInv;
barycenter[2] = (dot0101 * dot0P02 - dot0102 * dot0P01) * denomInv; barycenter[2] = (dot0101 * dot0P02 - dot0102 * dot0P01) * denomInv;
barycenter[0] = 1.0f - barycenter[1] - barycenter[2]; barycenter[0] = 1.0f - barycenter[1] - barycenter[2];
// Point is inside the triangle // Point is inside the triangle
if ( (barycenter[0] >= 0.0f) if ( (barycenter[0] >= 0.0f)
&& (barycenter[1] >= 0.0f) && (barycenter[1] >= 0.0f)
&& (barycenter[2] >= 0.0f)) && (barycenter[2] >= 0.0f))
{ {
ColorF32 totalColor; // Constant terms used in the U and V interpolation
float a = barycenter[0] * v1.point.w * v2.point.w;
float b = barycenter[1] * v0.point.w * v2.point.w;
float c = barycenter[2] * v0.point.w * v1.point.w;
float abc = 1.0f / (a + b + c);
// Interpolate U and V
float u = ((a * t0.u) + (b * t1.u) + (c * t2.u)) * abc;
float v = ((a * t0.v) + (b * t1.v) + (c * t2.v)) * abc;
// Convert U and V to pixels in the texture image
unsigned int uPixel = (unsigned int)(u * texture.width);
unsigned int vPixel = (unsigned int)(v * texture.height);
ColorF32 shading;
// Gouraud shading - interpolate color based on vertices
if (smooth) if (smooth)
{ {
totalColor = shading =
(barycenter[0] * v0.color) (barycenter[0] * v0.color)
+ (barycenter[1] * v1.color) + (barycenter[1] * v1.color)
+ (barycenter[2] * v2.color); + (barycenter[2] * v2.color);
} }
// Flat shading - base color on single face color
else else
{ {
totalColor = mesh.faces[f].color; shading = mesh.faces[f].color;
} }
ColorU32 color = ColorF32::ConvertToU32(totalColor);
// Shade the texel with lighting calculations
ColorU32 texel = texture.texels[vPixel][uPixel];
texel.r *= shading.r;
texel.g *= shading.g;
texel.b *= shading.b;
float z = // Interpolate 1/z for the z-buffer
(barycenter[0] * v0.point.z) float zInv =
+ (barycenter[1] * v1.point.z) 1.0f /
+ (barycenter[2] * v2.point.z); ((barycenter[0] * v0.point.w)
+ (barycenter[1] * v1.point.w)
+ (barycenter[2] * v2.point.w));
float zInv = 1.0f / z;
// Draw the pixel if it's closer than what's in the z-buffer
int pixel = (y * buffer.width + x); int pixel = (y * buffer.width + x);
if (zInv > buffer.zbuffer[pixel]) if (zInv > buffer.zbuffer[pixel])
{ {
DrawPixel(buffer.buffer, buffer.width, color.u32, x, y); DrawPixel(buffer.buffer, buffer.width, texel.u32, x, y);
buffer.zbuffer[pixel] = zInv; buffer.zbuffer[pixel] = zInv;
} }

View File

@ -1,6 +1,8 @@
#include "loader.h" #include "loader.h"
#include <cctype> #include <cctype>
#include <cstdio> #include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <cstring> #include <cstring>
@ -8,6 +10,28 @@
static char *GetLine(char *buffer, int maxLength, FILE *fp); static char *GetLine(char *buffer, int maxLength, FILE *fp);
static void ComputeNormals(Mesh &mesh); static void ComputeNormals(Mesh &mesh);
#pragma pack(push, 1)
struct BMP_Header
{
uint16_t fileType;
uint32_t fileSize;
uint16_t reserved0;
uint16_t reserved1;
uint32_t bitmapOffset;
uint32_t size;
int32_t width;
int32_t height;
uint16_t planes;
uint16_t bitsPerPixel;
uint32_t compression;
uint32_t sizeOfBitmap;
int32_t horizRes;
int32_t vertRes;
uint32_t colorsUsed;
uint32_t colorsImportant;
};
#pragma pack(pop)
// PUBLIC FUNCTIONS // PUBLIC FUNCTIONS
int LoadMesh(char *filename, Mesh &mesh) int LoadMesh(char *filename, Mesh &mesh)
@ -31,6 +55,8 @@ int LoadMesh(char *filename, Mesh &mesh)
while (token != NULL) while (token != NULL)
{ {
if (token[0] == 'v') if (token[0] == 'v')
{
if (token[1] == ' ')
{ {
Vertex v; Vertex v;
@ -43,20 +69,39 @@ int LoadMesh(char *filename, Mesh &mesh)
mesh.local.verts.push_back(v); mesh.local.verts.push_back(v);
} }
else if (token[1] == 't')
{
TextureCoord textureCoord;
sscanf(
token, "%s %f %f",
garbage,
&textureCoord.u,
&textureCoord.v);
mesh.local.uvs.push_back(textureCoord);
}
}
else if (token[0] == 'f') else if (token[0] == 'f')
{ {
Face f; Face f;
sscanf(token, "%s %d %d %d", sscanf(token, "%s %d/%d %d/%d %d/%d",
garbage, garbage,
&f.vertIndex[0], &f.vertIndex[0],
&f.textureIndex[0],
&f.vertIndex[1], &f.vertIndex[1],
&f.vertIndex[2]); &f.textureIndex[1],
&f.vertIndex[2],
&f.textureIndex[2]);
// Convert to 0-indexed // Convert to 0-indexed
f.vertIndex[0] -= 1; f.vertIndex[0] -= 1;
f.vertIndex[1] -= 1; f.vertIndex[1] -= 1;
f.vertIndex[2] -= 1; f.vertIndex[2] -= 1;
f.textureIndex[0] -= 1;
f.textureIndex[1] -= 1;
f.textureIndex[2] -= 1;
mesh.local.faces.push_back(f); mesh.local.faces.push_back(f);
} }
@ -66,9 +111,7 @@ int LoadMesh(char *filename, Mesh &mesh)
ComputeNormals(mesh); ComputeNormals(mesh);
printf("OBJ: %s\n", filename); printf("Mesh: %s [v: %lu f: %lu]\n", filename, mesh.local.verts.size(), mesh.local.faces.size());
printf("Verts: %lu\n", mesh.local.verts.size());
printf("Faces: %lu\n", mesh.local.faces.size());
mesh.transformed.verts.resize(mesh.local.verts.size()); mesh.transformed.verts.resize(mesh.local.verts.size());
@ -77,6 +120,53 @@ int LoadMesh(char *filename, Mesh &mesh)
return 0; return 0;
} }
int LoadTexture(char *filename, Texture &texture)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL)
{
fprintf(stderr, "Could not open file: %s\n", filename);
return -1;
}
BMP_Header header = {};
fread((void*)&header, sizeof(BMP_Header), 1, fp);
fseek(fp, header.bitmapOffset, SEEK_SET);
texture.texels = (ColorU32**)malloc((size_t)header.height * sizeof(ColorU32*));
for (int row = 0; row < header.height; ++row)
{
texture.texels[row] = (ColorU32*)calloc(1, (size_t)header.width * sizeof(ColorU32));
}
// Padding is added to image to align to 4-byte boundaries
size_t paddingSize = header.width % 4;
uint8_t *padding = (uint8_t*)malloc(paddingSize * sizeof(*padding));
for (int y = 0; y < header.height; ++y)
{
for (int x = 0; x < header.width; ++x)
{
fread(&texture.texels[y][x].b, 1, 1, fp);
fread(&texture.texels[y][x].g, 1, 1, fp);
fread(&texture.texels[y][x].r, 1, 1, fp);
}
// Discard padding byte
if (paddingSize != 0)
{
fread(padding, paddingSize, 1, fp);
}
}
texture.width = (unsigned int)header.width;
texture.height = (unsigned int)header.height;
fclose(fp);
return 0;
}
static char *GetLine(char *buffer, int maxLength, FILE *fp) static char *GetLine(char *buffer, int maxLength, FILE *fp)
{ {
while(true) while(true)
@ -86,8 +176,6 @@ static char *GetLine(char *buffer, int maxLength, FILE *fp)
return NULL; return NULL;
} }
// for (length = strlen(buffer), index = 0; isspace(buffer[index]); ++index);
if (buffer[0] != 'v' && buffer[0] != 'f') if (buffer[0] != 'v' && buffer[0] != 'f')
{ {
continue; continue;