I haven't posted here in a while, but I thought some of you might find this interesting.
Real time raytracing (in progress)...
- Spheres only at this stage
- Shadows
- 1 level of reflection
- Diffuse, and specular lighting
It uses a funky optimisation called sub sampling to acheive (near) real time.
I'm getting 14.5 FPS on this PC.
What about you?
Code: Select all
'Realtime Ray Tracer v0.2
'by Voltage
'Started 21/3/2007
'Last update 2/5/2007
'Compiles with FreeBasic v0.16 (Tested on Win 32)
'DONE! - Cast a ray through each screen coord
'DONE! - Check for intersection with triangle
'DONE! - Find closest object that intersected
'DONE! - Draw appropriate diffuse colour
'DONE! - Basic Shadows (hard edges)
'DONE! - Spheres
'DONE! - Specular lighting
'DONE! - Improve the sub sampler
'DONE! - Interpolate between sub sample corners, instead of the quick and dirty rectangle in colour 0
'DONE! - Quicker sphere intersection test (2d bounding circle check)
'DONE! - Reflection
'DONE! - Clean up code for recursion and optimisation
'DONE! - Fix sub sample metric code to check if corners are all in or all out of shadow / ugly shadow bug
'*** STILL TO DO ***
'Adaptive subsampling - Instead of crappy 6x6 non adaptive
'Optimise for multiple processors
'Recursive reflections
'Texture maps
'Add Plane object
'Add Cylinder object
'Add CSG
Option Explicit
#define Vec_DotProduct(v1,v2) (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)
Type VectorType
x As Single
y As Single
z As Single
End Type
Type RayType
origin As VectorType
dir As VectorType
ShadowRay As Integer
CurrentObject As Integer
CurrentObjectType As Integer
ScreenX As Integer
ScreenY As Integer
End Type
Type IntersectionType
Dist As Single
Object As Integer
ObjectType As Integer
u As Single
v As Single
End Type
Type TriangleType
v1 As VectorType
v2 As VectorType
v3 As VectorType
Plane As RayType
R As UByte
G As UByte
B As UByte
End Type
Type SphereType
Centre As VectorType
Radius As Single
R As UByte
G As UByte
B As UByte
cx As Integer
cy As Integer
ProjectedRadiusSquared As Integer
End Type
Type SubSampleType
Colour As Integer
Object As Integer
Shadowed As Integer
End Type
Type RGBType
r As Integer
g As Integer
b As Integer
End Type
Type ColourCalc
Colour As RGBType
Shadowed As Integer
IP As VectorType
N As VectorType
End Type
Declare Function Vec_Normalize(v As VectorType) As VectorType
Declare Function FindClosestIntersection(Ray As RayType) As InterSectionType
Declare Function SphereIntersect(R As RayType, S As SphereType) As IntersectionType
Declare Function Vec_Subtract(v1 As VectorType, v2 As VectorType) As VectorType
Declare Sub SetupScene
Declare Function CalculateColour(Object As Integer, ObjectType As Integer, R As RayType, L As Single) As ColourCalc
Declare Function CornersAreSame As Integer
Declare Sub InterpolateSquare(sx,sy,sqSize As Single)
Declare Function InShadow(ShadowRay As RayType) As InterSectionType
Declare Function ReflectRay(V As RayType, IP As VectorType, N As VectorType) As RayType
Declare Function GetRayColour(MyRay As RayType) As SubSampleType
Const SCR_W=320
Const SCR_H=200
Const ViewDistance = 300
Const NUMSPHERES = 9 'The first sphere is the light source
Const EPSILON=0.000001
Const TRUE = -1
Const FALSE = 0
Const SP=2
Const SampleSize = 6
Const SPH = 2
Dim As RayType MyRay, MyReflectedRay
Dim As IntersectionType MyIntersection,MyIntersection2
Dim As ColourCalc Colour, Colour2
Dim Shared Spheres(1 To NUMSPHERES) As SphereType
Dim As Integer x,y,a,x1,y1,SubSampleCount,x2,y2
Dim Shared As VectorType LightSource
Dim Shared Corners(0 To 3) As SubSampleType
Dim Shared SubSamples(Int(SCR_W/SampleSize)+1,Int(SCR_H/SampleSize)+1) As SubSampleType
Dim BounceSphere As Single
Dim As Integer s,s1,Ticks
Dim Shared LastShadowCaster As Integer
Dim As Single StartTime,FPS
Dim Shared As Integer ErrorCutOff
Dim key$
Dim RayColour As SubSampleType
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
'************************** ************************************
'************************** MAIN CODE ************************************
'************************** ************************************
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
Screen 15,32,2
s=0:s1=1
ScreenSet s,s1
'Randomize Timer
Randomize -8
'Light Source
LightSource.x = 300
LightSource.y = -100
LightSource.z = -200
'View point
MyRay.origin.x=SCR_W/2
MyRay.origin.y=SCR_H/2 - 10
MyRay.origin.z=-ViewDistance
MyRay.ShadowRay = FALSE ' This ray is not checking InShadow
MyRay.CurrentObject=-1
SetupScene
ErrorCutOff = 40
StartTime=Timer
Do
BounceSphere=BounceSphere+.2
For a=1 To NUMSPHERES
If a<>1 Then Spheres(a).Centre.Y=140-Abs(Sin(BounceSphere+a)*90+a)
Spheres(a).cx=SCR_W/2+((Spheres(a).Centre.x-MyRay.Origin.x)*ViewDistance)/(Spheres(a).Centre.z+ViewDistance)
Spheres(a).cy=SCR_H/2-10+((Spheres(a).Centre.y-MyRay.Origin.y)*ViewDistance)/(Spheres(a).Centre.z+ViewDistance)
Spheres(a).ProjectedRadiusSquared=((Spheres(a).Radius)*ViewDistance*1.15)/(Spheres(a).Centre.z+ViewDistance)
Spheres(a).ProjectedRadiusSquared=Spheres(a).ProjectedRadiusSquared*Spheres(a).ProjectedRadiusSquared
Next a
'**********************************************************************
'**********************************************************************
'**********************************************************************
'******************************* RENDER *****************************
'**********************************************************************
'**********************************************************************
'**********************************************************************
SubSampleCount=0
x1=0
y1=0
For y=0 To SCR_H Step SampleSize
For x=0 To SCR_W Step SampleSize
MyRay.dir.x = x - MyRay.origin.x
MyRay.dir.y = y - MyRay.origin.y
MyRay.dir.z = - MyRay.origin.z
MyRay.dir = Vec_Normalize(MyRay.Dir)
MyRay.ScreenX = x
MyRay.ScreenY = y
MyRay.CurrentObject=0
SubSamples(x1,y1) = GetRayColour(MyRay)
PSet(x,y),SubSamples(x1,y1).Colour
x1+=1
Next x
y1+=1
x1=0
Next y
x1=0
y1=0
For y=0 To SCR_H Step SampleSize
For x=0 To SCR_W Step SampleSize
Corners(0)=SubSamples(x1,y1)
Corners(1)=SubSamples(x1+1,y1)
Corners(2)=SubSamples(x1,y1+1)
Corners(3)=SubSamples(x1+1,y1+1)
If CornersAreSame Then
SubSampleCount+=(SampleSize*SampleSize)-4
InterpolateSquare x,y,SampleSize
Else
MyRay.CurrentObject=0
For y2=0 To SampleSize-1
For x2=0 To SampleSize-1
'Setup Ray for each screen pixel
MyRay.dir.x = (x+x2) - MyRay.origin.x
MyRay.dir.y = (y+y2) - MyRay.origin.y
MyRay.dir.z = - MyRay.origin.z
MyRay.dir = Vec_Normalize(MyRay.Dir)
MyRay.ScreenX = x+x2
MyRay.ScreenY = y+y2
RayColour = GetRayColour(MyRay)
PSet (x2+x,y2+y),RayColour.Colour
Next x2
Next y2
End If
x1+=1
Next x
y1+=1
x1=0
Next y
Ticks+=1
FPS = Ticks/(Timer-StartTime)
key$=InKey$
If key$=Chr$(27) Then Exit Do
Color rgb(68,128,255)
Locate 28,1:? " Sub sample results"
Color rgb(255,255,255)
Print SCR_W*SCR_H;" -";SubSampleCount;" =";SCR_W*SCR_H-SubSampleCount
Color rgb(128,128,255)
Print " Total Skipped Calculated Rays"
Color rgb(212,212,255)
Locate 32,1:? " FPS:";FPS
Color rgb(255,212,212)
Locate 34,1:? " Image quality:";ErrorCutOff;" "
Color rgb(Rnd(1)*155+100,Rnd(1)*155+100,Rnd(1)*155+100)
Print " Press 1-5 to change quality"
Select Case key$
Case Is = "1"
ErrorCutOff = 0
Case Is = "2"
ErrorCutOff = 15
Case Is = "3"
ErrorCutOff = 40
Case Is = "4"
ErrorCutOff = 65
Case Is = "5"
ErrorCutOff = 130
End Select
Swap s,s1
ScreenSet s,s1
Loop
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
'************************** ************************************
'************************** FUNCTIONS AND SUBS ************************************
'************************** ************************************
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
'**************************************************************************************************************
Function Vec_Normalize(v As VectorType) As VectorType
Dim l As Single
l=1.0/Sqr(v.x*v.x+v.y*v.y+v.z*v.z)
v.x=v.x*l
v.y=v.y*l
v.z=v.z*l
Vec_Normalize = v
End Function
Function FindClosestIntersection(Ray As RayType) As InterSectionType
Dim Result As InterSectionType
Dim As Integer a,xd,yd
Dim Intersect As InterSectionType
'Set the default return values
Result.Dist=-1
Result.Object=-1
If Ray.CurrentObject=0 Then
For a=1 To NUMSPHERES
'Optimization - Check if squared distance from this ray to centre of sphere is >radius
xd = Spheres(a).cx - Ray.ScreenX
yd = Spheres(a).cy - Ray.ScreenY
If (xd*xd+yd*yd) > Spheres(a).ProjectedRadiusSquared Then
Intersect.Dist = -1
Else
Intersect = SphereIntersect(Ray, Spheres(a))
If Intersect.Dist>0 Then
If Result.Dist<>-1 Then
If Intersect.Dist < Result.Dist Then
Result.Dist = Intersect.Dist
Result.Object = a
Result.ObjectType = SPH
Result.u = Intersect.u
Result.v = Intersect.v
End If
Else
Result.Dist = Intersect.Dist
Result.Object = a
Result.ObjectType = SPH
Result.u = Intersect.u
Result.v = Intersect.v
End If
End If
End If
Next a
Else
For a=1 To NUMSPHERES
If a=Ray.CurrentObject Then
Intersect.Dist=-1
Else
Intersect = SphereIntersect(Ray, Spheres(a))
If Intersect.Dist>0 Then
If Result.Dist<>-1 Then
If Intersect.Dist < Result.Dist Then
Result.Dist = Intersect.Dist
Result.Object = a
Result.ObjectType = SPH
Result.u = Intersect.u
Result.v = Intersect.v
End If
Else
Result.Dist = Intersect.Dist
Result.Object = a
Result.ObjectType = SPH
Result.u = Intersect.u
Result.v = Intersect.v
End If
End If
End If
Next a
End If
FindClosestIntersection = Result
End Function
Function InShadow(ShadowRay As RayType) As InterSectionType
Dim Intersect As IntersectionType
Dim As Integer a,a1
Intersect.Dist = -1
If LastShadowCaster=0 Then
a1=2: 'Object 1 is the light source
Else
a1=LastShadowCaster
End If
For a=2 To NUMSPHERES
'The next check is so we don't check for intersections with the intersect point object
If a1<>ShadowRay.CurrentObject Then
Intersect = SphereIntersect(ShadowRay, Spheres(a1))
If Intersect.Dist>0 Then
Intersect.Dist = 1
InShadow = Intersect
Exit Function
End If
End If
a1=a1+1:If a1>NUMSPHERES Then a1=2: 'Object 1 is the light source
Next a
InShadow = Intersect
End Function
Function SphereIntersect(R As RayType, S As SphereType) As IntersectionType
Dim Result as IntersectionType
Dim As VectorType rayToSphereCentre
Dim As Single SquaredDistToCentre, ClosestApproach, HalfCord2
rayToSphereCentre.x = S.Centre.x-R.Origin.x
rayToSphereCentre.y = S.Centre.y-R.Origin.y
rayToSphereCentre.z = S.Centre.z-R.Origin.z
SquaredDistToCentre = Vec_DotProduct(rayToSphereCentre, rayToSphereCentre)
ClosestApproach = Vec_DotProduct(rayToSphereCentre, R.Dir)
If ClosestApproach<0 Then
Result.Dist=-1
SphereIntersect = Result
Exit Function
End If
HalfCord2 = (S.Radius * S.Radius) - SquaredDistToCentre + (ClosestApproach * ClosestApproach)
If HalfCord2 < 0 Then
Result.Dist=-1
SphereIntersect = Result
Exit Function
End If
Result.Dist = ClosestApproach - Sqr(Halfcord2)
SphereIntersect = Result
End Function
Function Vec_Subtract(v1 As VectorType, v2 As VectorType) As VectorType
Dim Result As VectorType
Result.x = v1.x - v2.x
Result.y = v1.y - v2.y
Result.z = v1.z - v2.z
Vec_Subtract = Result
End Function
Sub SetupScene
Dim a As Integer
'Now the spheres
If NUMSPHERES>0 Then
For a=1 To NUMSPHERES
Spheres(a).Centre.x = Int(Rnd(1)*(SCR_W-100))+50
Spheres(a).Centre.y = Int(Rnd(1)*SCR_H/2)+SCR_H/2
Spheres(a).Centre.z = Int(Rnd(1)*SCR_H)-SCR_H/2
Spheres(a).Radius = 35
Spheres(a).r=Int(Rnd(1)*200)+55
Spheres(a).g=Int(Rnd(1)*200)+55
Spheres(a).b=Int(Rnd(1)*200)+55
Next a
End If
'Make sphere 1 the light source
Spheres(1).Centre = LightSource
Spheres(1).Radius = 25
Spheres(1).r=255
Spheres(1).g=255
Spheres(1).b=255
End Sub
Function CalculateColour(Object As Integer, ObjectType As Integer, Ray As RayType, L As Single) As ColourCalc
Dim As Single DP,SpecRDT,Spec,Shade,SpecDOT
Dim As VectorType IL,SpecR,SpecV
Dim As IntersectionType AnIntersection
Dim As RayType ARay
Dim As Integer R,G,B
Dim Result As ColourCalc
'Get the intersection point
Result.IP.x=Ray.Origin.x + Ray.Dir.x * L
Result.IP.y=Ray.Origin.y + Ray.Dir.y * L
Result.IP.z=Ray.Origin.z + Ray.Dir.z * L
'Create a vector from the intersection point to the light source
IL = Vec_Normalize(Vec_Subtract(LightSource,Result.IP))
'Get the dot product of the normal and this new vector
Result.N = Vec_Normalize(Vec_Subtract(Result.IP,Spheres(Object).Centre))
DP = (Vec_DotProduct(IL, Result.N))+.2
If DP>0 Then
'Check if intersection point is in the shadow of another object
ARay.Origin = Result.IP
ARay.Dir = IL
ARay.CurrentObject = Object
ARay.CurrentObjectType = ObjectType
AnIntersection = InShadow(ARay)
'In shadow?
Result.Shadowed=1
If AnIntersection.Dist>0 Then
'Use these results to calc the light value
DP=DP*.25
Result.Shadowed = 0
End If
'Calculate Specular
Spec=0
SpecV.x = 0
SpecV.y = 0
SpecV.z = Sgn(ViewDistance)
SpecRDT = 2 * Vec_DotProduct( IL, Result.N )
SpecR.x = IL.x - SpecRDT * Result.N.x
SpecR.y = IL.y - SpecRDT * Result.N.y
SpecR.z = IL.z - SpecRDT * Result.N.z
SpecDOT = Vec_DotProduct(SpecV, SpecR)
If SpecDOT > 0.0 Then
Spec = SpecDOT ^ 20 * 0.55 * Result.Shadowed * 255
End If
r=Spheres(Object).r * dp + spec
g=Spheres(Object).g * dp + spec
b=Spheres(Object).b * dp + spec
If r>255 Then r = 255
If g>255 Then g = 255
If b>255 Then b = 255
Result.Colour.r = r
Result.Colour.g = g
Result.Colour.b = b
Else
Result.Colour.r = 0
Result.Colour.g = 0
Result.Colour.b = 0
End If
CalculateColour = Result
End Function
Function CornersAreSame As Integer
Dim As UByte r,g,b,r1,g1,b1,a
If Corners(0).Object=Corners(1).Object Then
If Corners(0).Object=Corners(2).Object Then
If Corners(0).Object=Corners(3).Object Then
If Corners(0).Shadowed<>Corners(1).Shadowed Or Corners(0).Shadowed<>Corners(2).Shadowed Or Corners(0).Shadowed<>Corners(3).Shadowed Then
CornersAreSame=FALSE
Exit Function
End If
r=(Corners(0).Colour Shr 16) And 255
g=(Corners(0).Colour Shr 8) And 255
b=Corners(0).Colour And 255
For a=1 To 3
r1=(Corners(a).Colour Shr 16) And 255
g1=(Corners(a).Colour Shr 8) And 255
b1=Corners(a).Colour And 255
If Abs(r1-r)>ErrorCutoff Then
CornersAreSame=FALSE
Exit Function
End If
If Abs(g1-g)>ErrorCutoff Then
CornersAreSame=FALSE
Exit Function
End If
If Abs(b1-b)>ErrorCutoff Then
CornersAreSame=FALSE
Exit Function
End If
Next a
CornersAreSame=TRUE
Exit Function
Else
CornersAreSame=FALSE
Exit Function
End If
Else
CornersAreSame=FALSE
Exit Function
End If
Else
CornersAreSame=FALSE
Exit Function
End If
End Function
Sub InterpolateSquare(sx,sy,sqSize As Single)
Dim c1 As RGBType
Dim As Single rs,gs,bs
Dim c(3) As RGBType
Dim As Integer ca,cb,car,cab,cag,cbr,cbg,cbb,x,y
For x=0 To 3
c(x).r = (Corners(x).Colour Shr 16) And 255
c(x).g = (Corners(x).Colour Shr 8) And 255
c(x).b = Corners(x).Colour And 255
Next x
y=0
rs=(c(1).r-c(0).r)/sqSize
gs=(c(1).g-c(0).g)/sqSize
bs=(c(1).b-c(0).b)/sqSize
For x=0 To sqSize
c1.r=c(0).r+x*rs
c1.g=c(0).g+x*gs
c1.b=c(0).b+x*bs
PSet (x+sx,y+sy),RGB(c1.r,c1.g,c1.b)
Next x
y=sqSize
rs=(c(3).r-c(2).r)/sqSize
gs=(c(3).g-c(2).g)/sqSize
bs=(c(3).b-c(2).b)/sqSize
For x=0 To sqSize
c1.r=c(2).r+x*rs
c1.g=c(2).g+x*gs
c1.b=c(2).b+x*bs
PSet (x+sx,y+sy),RGB(c1.r,c1.g,c1.b)
Next x
For x=0 To sqSize
ca=Point(x+sx,sy)
cb=Point(x+sx,sqSize+sy)
car=(ca Shr 16) And 255
cag=(ca Shr 8) And 255
cab=ca And 255
cbr=(cb Shr 16) And 255
cbg=(cb Shr 8) And 255
cbb=cb And 255
rs=(cbr-car)/sqSize
gs=(cbg-cag)/sqSize
bs=(cbb-cab)/sqSize
For y=1 To sqSize-1
c1.r=car+y*rs
c1.g=cag+y*gs
c1.b=cab+y*bs
PSet (x+sx,y+sy),RGB(c1.r,c1.g,c1.b)
Next y
Next x
End Sub
Function ReflectRay(V As RayType, IP As VectorType, N As VectorType) As RayType
Dim Result As RayType
Dim Dot As Single
Result.Origin = IP
Dot = 2 * Vec_DotProduct(V.Dir, N)
Result.Dir.x = V.Dir.x - Dot * N.x
Result.Dir.y = V.Dir.y - Dot * N.y
Result.Dir.z = V.Dir.z - Dot * N.z
Result.Dir = Vec_Normalize(Result.Dir)
ReflectRay = Result
End Function
Function GetRayColour(MyRay As RayType) As SubSampleType
Dim As RayType MyReflectedRay
Dim As IntersectionType MyIntersection,MyIntersection2
Dim As ColourCalc Colour, Colour2
Dim Result As SubSampleType
MyIntersection = FindClosestIntersection(MyRay)
If MyIntersection.Dist>0 Then
Colour = CalculateColour(MyIntersection.Object,MyIntersection.ObjectType,MyRay,MyIntersection.Dist)
MyReflectedRay = ReflectRay(MyRay, Colour.IP, Colour.N)
MyReflectedRay.CurrentObject = MyIntersection.Object
MyIntersection2 = FindClosestIntersection(MyReflectedRay)
If MyIntersection2.Dist>0 Then
Colour2 = CalculateColour(MyIntersection2.Object,MyIntersection2.ObjectType,MyReflectedRay,MyIntersection2.Dist)
Colour.Colour.r = Colour.Colour.r + Colour2.Colour.r/2:If Colour.Colour.r>255 Then Colour.Colour.r=255
Colour.Colour.g = Colour.Colour.g + Colour2.Colour.g/2:If Colour.Colour.g>255 Then Colour.Colour.g=255
Colour.Colour.b = Colour.Colour.b + Colour2.Colour.b/2:If Colour.Colour.b>255 Then Colour.Colour.b=255
Else
Colour.Colour.r = Colour.Colour.r+10:If Colour.Colour.r>255 Then Colour.Colour.r=255
Colour.Colour.g = Colour.Colour.g+10:If Colour.Colour.g>255 Then Colour.Colour.g=255
Colour.Colour.b = Colour.Colour.b+10:If Colour.Colour.b>255 Then Colour.Colour.b=255
End If
Result.Colour = RGB(Colour.Colour.r,Colour.Colour.g,Colour.Colour.b)
Result.Shadowed = Colour.Shadowed
Result.Object = MyIntersection.Object
Else
Result.Colour = RGB(20,20,20)
Result.Shadowed = FALSE
Result.Object = -1
End If
GetRayColour = Result
End Function