Add perspective-correct texture mapping
This commit is contained in:
parent
370fb7bce0
commit
20402784f7
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
118
src/loader.cpp
118
src/loader.cpp
|
@ -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)
|
||||||
|
@ -32,31 +56,52 @@ int LoadMesh(char *filename, Mesh &mesh)
|
||||||
{
|
{
|
||||||
if (token[0] == 'v')
|
if (token[0] == 'v')
|
||||||
{
|
{
|
||||||
Vertex v;
|
if (token[1] == ' ')
|
||||||
|
{
|
||||||
|
Vertex v;
|
||||||
|
|
||||||
sscanf(
|
sscanf(
|
||||||
token, "%s %f %f %f",
|
token, "%s %f %f %f",
|
||||||
garbage,
|
garbage,
|
||||||
&v.point.x,
|
&v.point.x,
|
||||||
&v.point.y,
|
&v.point.y,
|
||||||
&v.point.z);
|
&v.point.z);
|
||||||
|
|
||||||
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;
|
||||||
|
|
Loading…
Reference in New Issue