Add perspective-correct texture mapping
This commit is contained in:
parent
370fb7bce0
commit
20402784f7
|
@ -14,9 +14,23 @@ struct Material
|
|||
ColorF32 kDiffuse;
|
||||
};
|
||||
|
||||
struct Texture
|
||||
{
|
||||
ColorU32 **texels;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
};
|
||||
|
||||
struct TextureCoord
|
||||
{
|
||||
float u;
|
||||
float v;
|
||||
};
|
||||
|
||||
struct Face
|
||||
{
|
||||
unsigned int vertIndex[3];
|
||||
unsigned int textureIndex[3];
|
||||
Vector normal;
|
||||
ColorF32 color;
|
||||
};
|
||||
|
@ -32,6 +46,7 @@ struct Mesh_LocalData
|
|||
{
|
||||
std::vector<Vertex> verts;
|
||||
std::vector<Face> faces;
|
||||
std::vector<TextureCoord> uvs;
|
||||
};
|
||||
|
||||
struct Mesh_TransformedData
|
||||
|
@ -73,7 +88,8 @@ void CullBackfaces(
|
|||
Point &camPosition);
|
||||
|
||||
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
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
|
||||
int LoadMesh(char *filename, Mesh &mesh);
|
||||
int LoadTexture(char *filename, Texture &texture);
|
||||
|
||||
|
||||
#define LOADER_H
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
static Mesh mesh;
|
||||
static Camera camera;
|
||||
static LightList lights;
|
||||
static Texture texture;
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
// The vertices of this face
|
||||
unsigned int vIndex0 = mesh.faces[f].vertIndex[0];
|
||||
unsigned int vIndex1 = mesh.faces[f].vertIndex[1];
|
||||
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);
|
||||
int yMin = (int)ceilf(box.yMin);
|
||||
int yMax = (int)ceilf(box.yMax) - 1;
|
||||
int xMin = (int)ceilf(box.xMin);
|
||||
int xMax = (int)ceilf(box.xMax) - 1;
|
||||
|
||||
|
||||
// Constants for this triangle used for barycentric calculations
|
||||
Vector v01 = v1.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 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 x = xMin; x <= xMax; ++x)
|
||||
{
|
||||
// Constant terms used for barycentric calculation
|
||||
Point p(x, y, 1.0f);
|
||||
|
||||
Vector v0P = p - v0.point;
|
||||
float dot0P01 = Vector::Dot(v0P, v01);
|
||||
float dot0P02 = Vector::Dot(v0P, v02);
|
||||
float denomInv = 1.0f / ((dot0101 * dot0202) - (dot0102 * dot0102));
|
||||
|
||||
|
||||
// Calculate the barycentric coordinate of this point
|
||||
float barycenter[3];
|
||||
barycenter[1] = (dot0202 * dot0P01 - dot0102 * dot0P02) * denomInv;
|
||||
barycenter[2] = (dot0101 * dot0P02 - dot0102 * dot0P01) * denomInv;
|
||||
barycenter[0] = 1.0f - barycenter[1] - barycenter[2];
|
||||
|
||||
|
||||
// Point is inside the triangle
|
||||
if ( (barycenter[0] >= 0.0f)
|
||||
&& (barycenter[1] >= 0.0f)
|
||||
&& (barycenter[2] >= 0.0f))
|
||||
&& (barycenter[1] >= 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)
|
||||
{
|
||||
totalColor =
|
||||
shading =
|
||||
(barycenter[0] * v0.color)
|
||||
+ (barycenter[1] * v1.color)
|
||||
+ (barycenter[2] * v2.color);
|
||||
}
|
||||
// Flat shading - base color on single face color
|
||||
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 =
|
||||
(barycenter[0] * v0.point.z)
|
||||
+ (barycenter[1] * v1.point.z)
|
||||
+ (barycenter[2] * v2.point.z);
|
||||
// Interpolate 1/z for the z-buffer
|
||||
float zInv =
|
||||
1.0f /
|
||||
((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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
118
src/loader.cpp
118
src/loader.cpp
|
@ -1,6 +1,8 @@
|
|||
#include "loader.h"
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
|
@ -8,6 +10,28 @@
|
|||
static char *GetLine(char *buffer, int maxLength, FILE *fp);
|
||||
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
|
||||
int LoadMesh(char *filename, Mesh &mesh)
|
||||
|
@ -32,31 +56,52 @@ int LoadMesh(char *filename, Mesh &mesh)
|
|||
{
|
||||
if (token[0] == 'v')
|
||||
{
|
||||
Vertex v;
|
||||
if (token[1] == ' ')
|
||||
{
|
||||
Vertex v;
|
||||
|
||||
sscanf(
|
||||
token, "%s %f %f %f",
|
||||
garbage,
|
||||
&v.point.x,
|
||||
&v.point.y,
|
||||
&v.point.z);
|
||||
sscanf(
|
||||
token, "%s %f %f %f",
|
||||
garbage,
|
||||
&v.point.x,
|
||||
&v.point.y,
|
||||
&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')
|
||||
{
|
||||
Face f;
|
||||
|
||||
sscanf(token, "%s %d %d %d",
|
||||
sscanf(token, "%s %d/%d %d/%d %d/%d",
|
||||
garbage,
|
||||
&f.vertIndex[0],
|
||||
&f.textureIndex[0],
|
||||
&f.vertIndex[1],
|
||||
&f.vertIndex[2]);
|
||||
&f.textureIndex[1],
|
||||
&f.vertIndex[2],
|
||||
&f.textureIndex[2]);
|
||||
|
||||
// Convert to 0-indexed
|
||||
f.vertIndex[0] -= 1;
|
||||
f.vertIndex[1] -= 1;
|
||||
f.vertIndex[2] -= 1;
|
||||
f.textureIndex[0] -= 1;
|
||||
f.textureIndex[1] -= 1;
|
||||
f.textureIndex[2] -= 1;
|
||||
|
||||
mesh.local.faces.push_back(f);
|
||||
}
|
||||
|
@ -66,9 +111,7 @@ int LoadMesh(char *filename, Mesh &mesh)
|
|||
|
||||
ComputeNormals(mesh);
|
||||
|
||||
printf("OBJ: %s\n", filename);
|
||||
printf("Verts: %lu\n", mesh.local.verts.size());
|
||||
printf("Faces: %lu\n", mesh.local.faces.size());
|
||||
printf("Mesh: %s [v: %lu f: %lu]\n", filename, mesh.local.verts.size(), mesh.local.faces.size());
|
||||
|
||||
mesh.transformed.verts.resize(mesh.local.verts.size());
|
||||
|
||||
|
@ -77,6 +120,53 @@ int LoadMesh(char *filename, Mesh &mesh)
|
|||
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)
|
||||
{
|
||||
while(true)
|
||||
|
@ -86,8 +176,6 @@ static char *GetLine(char *buffer, int maxLength, FILE *fp)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// for (length = strlen(buffer), index = 0; isspace(buffer[index]); ++index);
|
||||
|
||||
if (buffer[0] != 'v' && buffer[0] != 'f')
|
||||
{
|
||||
continue;
|
||||
|
|
Loading…
Reference in New Issue