Skip to content

Instantly share code, notes, and snippets.

@bbbradsmith
Last active December 29, 2022 21:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbbradsmith/a65212dbbb917b5bc449995f6333de66 to your computer and use it in GitHub Desktop.
Save bbbradsmith/a65212dbbb917b5bc449995f6333de66 to your computer and use it in GitHub Desktop.
baricentric_perspective rendering
# A recreation of:
# https://commons.wikimedia.org/wiki/File:Perspective_correct_texture_mapping.jpg
#
# To give a simple code demonstration of perspective correction,
# and simple barycentric triangle coordinates.
#
# Note that this is a "naive" implementation,
# and the resulting floating point errors will produce some rough edges on the checkerboard texture.
# It would be more efficient and more numerically stable to rasterize the triangle instead,
# but this demonstrates a simple way to generate coordinates without the additional complexity of rasterization.
# A simple 2x anti-aliasing is added to make it look slightly nicer (see: scale).
#
# References:
# https://en.wikipedia.org/wiki/Texture_mapping#Affine_texture_mapping
# https://en.wikipedia.org/wiki/Barycentric_coordinate_system
import PIL.Image
import math
colbg = (180,179,210)
col0 = ( 0, 0, 0)
col1 = (255,255,255)
colodd = (255, 0, 0)
coleven = ( 0,255, 0)
edge = 0.05 # edge highlight width
check = 0.125 # checkerboard width (0 for red-green)
scale = 2 # simple anti-aliasing (1 = off)
img = PIL.Image.new("RGB",(800*scale,300*scale),colbg)
triangles = [
( 50, 50,1),(250, 50,1),( 50,250,1), # flat square
( 50,250,1),(250, 50,1),(250,250,1),
(350,100,1),(450,100,1),(300,250,1), # flat trapezoid
(300,250,1),(450,100,1),(500,250,1),
(600,100,2),(700,100,2),(550,250,1), # perspective square, already projected with Z intact
(550,250,1),(700,100,2),(750,250,1),
]
texture = [ # texture coordinates for even/odd triangles (u,v,1)
(0,0,1),(1,0,1),(0,1,1),
(0,1,1),(1,0,1),(1,1,1) ]
def vec_sum(a,b): # vector add
return tuple([a[i]+b[i] for i in range(len(a))])
def vec_sub(a,b): # vector subtract
return tuple([a[i]-b[i] for i in range(len(a))])
def vec_mul(v,m): # vector * scalar
return tuple([x * m for x in v])
def vec2_cross(a,b): # 2D cross product
return a[0] * b[1] - a[1] * b[0]
def triangle_area(t): # 2D area of x,y (ignores z)
u = vec_sub(t[1],t[0])
v = vec_sub(t[2],t[0])
return vec2_cross(u,v) / 2
def bary_lerp(v,w): # sum of 3 vectors * 3 weights
return vec_sum(vec_sum(
vec_mul(v[0],w[0]),
vec_mul(v[1],w[1])),
vec_mul(v[2],w[2]))
def blend(ca,cb): # 50% blend of 2 colours
return tuple([(ca[i]+cb[i])//2 for i in range(len(ca))])
for i in range(0,len(triangles),3):
t = triangles[i:i+3]
uv = texture[i%6:(i%6)+3]
coledge = coleven if (i % 6) == 0 else colodd # edge highlight colour even/odd triangles
x0 = min(x for (x,y,z) in t) * scale
x1 = max(x for (x,y,z) in t) * scale
y0 = min(y for (x,y,z) in t) * scale
y1 = max(y for (x,y,z) in t) * scale
a = triangle_area(t) # area of full triangle
for y in range(y0,y1+1):
for x in range(x0,x1+1):
# barycentric coordinates: area of edge + point triangle, divided by full area
tp = (x/scale,y/scale,1)
e0 = triangle_area([t[1],t[2],tp]) / a
e1 = triangle_area([t[2],t[0],tp]) / a
e2 = triangle_area([t[0],t[1],tp]) / a
if e0 < 0 or e1 < 0 or e2 < 0 or e0 > 1 or e1 > 1 or e2 > 1:
continue
# perspective correction
uv_z = [vec_mul(uv[j],1/t[j][2]) for j in range(3)] # divide uv/z before interpolation
l = bary_lerp(uv_z,(e0,e1,e2)) # linearly interpolate u/z, v/z, 1/z
(u,v) = (l[0]/l[2], l[1]/l[2]) # divide by interpolated 1/z
# checkerboard texture
if check != 0:
col = col0 if 0 == ((int(u/check) ^ int(v/check)) & 1) else col1
if e0 < edge or e1 < edge or e2 < edge: # edge highlight
col = blend(col,coledge)
else: # red-green UV visualization
col = (int(u*255),int(v*255),0)
img.putpixel((x,y),col)
img = img.resize((img.width//scale,img.height//scale),PIL.Image.Resampling.BILINEAR)
img.save("baricentric_perspective.png")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment